Skip to main content

Software Implementation

The gustometer interface is based on Home Assistant, which is a "free and open-source software for home automation, designed to be an Internet of things ecosystem-independent integration platform and smart home hub for controlling smart home devices, with a focus on local control and privacy". This created a building block that allowed for a clean interface with the frontend, and controller interfacing already handled by this software.

Home Assistant Dependencies

Frontend Interface

Welcome Dashboard

Welcome Dashboard code

Code
/welcome.yaml
    type: vertical-stack
cards:
- type: custom:mushroom-title-card
title: Welcome, {{ user }}
subtitle: ''
- type: markdown
content: >
Welcome to the gustometer interface. This has been built using open source
software called [Home Assistant](https://www.home-assistant.io/) <font
color='#03a9f4'><ha-icon icon=mdi:home-assistant ></ha-icon></font></a>.
Please read and access the documentation below. System backups and config
files can be found on github through the website found
[here](https://brockcooper.au) to run this device. If there are any issues
that can't be troubledshooeted using the provided documenetion one of the
following contributers can be contacted below to help assist in
rectifiying problems.

<a href='https://drive.google.com/drive/my-drive'><img src =
'/local/gustometer/gustometer.png' alt='Google Drive' align='middle'
width='455' height='215.6'></img></a>


This device was built by the Faculty of Medicine and Health Engineering
Department in 2024.


**Contributers:**
- [Brock Cooper](https://www.brockcooper.au/)
- [Stuart Kirkwood](https://www.linkedin.com/in/stuartkirkwood/)

The system as been setup to integrate with PsycoPy allowing the
particpants to select between 2 different flavoured drink options which
will trigger the system to pump using a long lived access token with the
python home assistant API.


**Disclaimer:** *This device is not a medical certified and their may be
bugs in the system that could potentially cause issues in the trials.
Please use at your own risk and understand how the system works before
operation. This system was designed for the intention of research
applications building a relatively budget system that has the ability to
experiement with a variety of different viscosity fluids allowing each
pump module to be calibrated for the intended fluid.*


Please report any system bugs or issues to the contributers above.


Press the proceed button below after reading this and it will
automatically take you through to the gustometer interface control
dashboard.
title: Overview
theme: Mushroom
- type: custom:mushroom-template-card
primary: Gustometer Documentation
secondary: ''
icon: mdi:file-document
tap_action:
action: url
url_path: >-
http://homeassistant.local:8123/local/gustometer/Documentation for End
User.pdf
icon_color: blue
- type: custom:mushroom-template-card
primary: PsychoPy Files
secondary: ''
icon: mdi:folder-zip
tap_action:
action: url
url_path: http://homeassistant.local:8123/local/gustometer/PsychoPy.zip
icon_color: blue
- type: custom:mushroom-template-card
primary: Proceed to Gustometer Interface
secondary: ''
icon: mdi:arrow-right-circle
tap_action:
action: navigate
navigation_path: /dashboard-gustometer
icon_color: green

Gustometer Dashboard

Pump Calibration Page

Gustometer Interface code

Code
/gustometer.yaml
    title: Gustometer
panel: true
views:
- title: Gustometer
panel: true
type: panel
badges: []
cards:
- type: vertical-stack
cards:
- type: picture-elements
image: /local/gustometer/Gustometer_Top_View.png
elements:
- type: custom:button-card
color_type: card
size: 40%
display: flex
entity: select.wled_preset
name: E-STOP
state:
- value: E-STOP
color: red
icon: mdi:alert
styles:
card:
- animation: blink 2s ease infinite
- operator: default
color: green
icon: mdi:shield-check
style:
align-items: center
background: transparent
border-radius: 15px
padding: 1px
top: 72%
left: 49.75%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
width: 18.2%
height: 19.3%
tap_action:
action: null
- type: custom:button-card
entity: binary_sensor.pump_1_tank_water_level
name: ' '
size: 500%
display: flex
grid:
- position: relative
state:
- value: 'off'
color: red
icon: mdi:water-off
styles:
card:
- background-color: transparent
- animation: blink 2s ease infinite
- '--mdc-ripple-press-opacity': 0
text:
- color: red
- value: 'on'
color: DodgerBlue
icon: mdi:water
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
icon: mdi:water-sync
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 50%
left: 12.6%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 5.2%
height: 6.2%
- type: custom:button-card
entity: cover.pump_1_motor
name: ' '
size: 1000%
display: flex
icon: mdi:fan
color: red
state:
- value: closed
color: orange
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: open
color: DodgerBlue
styles:
icon:
- animation: rotating 2s linear infinite
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 31%
left: 12.6%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 9.2%
height: 8.2%
tap_action:
entity: button.pump_1_dispense
confirmation:
text: Are you sure you want to dispense?
action: toggle
double_tap_action:
action: navigate
navigation_path: /dashboard-gustometer/pump-1
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_1
- type: custom:button-card
entity: binary_sensor.pump_2_tank_water_level
name: ' '
size: 500%
display: flex
grid:
- position: relative
state:
- value: 'off'
color: red
icon: mdi:water-off
styles:
card:
- background-color: transparent
- animation: blink 2s ease infinite
- '--mdc-ripple-press-opacity': 0
text:
- color: red
- value: 'on'
color: DodgerBlue
icon: mdi:water
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
icon: mdi:water-sync
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 50%
left: 27.5%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 5.2%
height: 6.2%
- type: custom:button-card
entity: cover.pump_2_motor
name: ' '
size: 1000%
display: flex
icon: mdi:fan
color: red
state:
- value: closed
color: orange
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: open
color: DodgerBlue
styles:
icon:
- animation: rotating 2s linear infinite
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 31%
left: 27.5%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 9.2%
height: 8.2%
tap_action:
entity: button.pump_2_dispense
confirmation:
text: Are you sure you want to dispense?
action: toggle
double_tap_action:
action: navigate
navigation_path: /dashboard-gustometer/pump-2
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_2
- type: custom:button-card
entity: binary_sensor.pump_3_tank_water_level
name: ' '
size: 500%
display: flex
grid:
- position: relative
state:
- value: 'off'
color: red
icon: mdi:water-off
styles:
card:
- background-color: transparent
- animation: blink 2s ease infinite
- '--mdc-ripple-press-opacity': 0
text:
- color: red
- value: 'on'
color: DodgerBlue
icon: mdi:water
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
icon: mdi:water-sync
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 50%
left: 42.4%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 5.2%
height: 6.2%
- type: custom:button-card
entity: cover.pump_3_motor
name: ' '
size: 1000%
display: flex
icon: mdi:fan
color: red
state:
- value: closed
color: orange
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: open
color: DodgerBlue
styles:
icon:
- animation: rotating 2s linear infinite
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 31%
left: 42.4%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 9.2%
height: 8.2%
tap_action:
entity: button.pump_3_dispense
confirmation:
text: Are you sure you want to dispense?
action: toggle
double_tap_action:
action: navigate
navigation_path: /dashboard-gustometer/pump-3
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_3
- type: custom:button-card
entity: binary_sensor.pump_4_tank_water_level
name: ' '
size: 500%
display: flex
grid:
- position: relative
state:
- value: 'off'
color: red
icon: mdi:water-off
styles:
card:
- background-color: transparent
- animation: blink 2s ease infinite
- '--mdc-ripple-press-opacity': 0
text:
- color: red
- value: 'on'
color: DodgerBlue
icon: mdi:water
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
icon: mdi:water-sync
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 50%
left: 57.2%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 5.2%
height: 6.2%
- type: custom:button-card
entity: cover.pump_4_motor
name: ' '
size: 1000%
display: flex
icon: mdi:fan
color: red
state:
- value: closed
color: orange
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: open
color: DodgerBlue
styles:
icon:
- animation: rotating 2s linear infinite
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 31%
left: 57.2%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 9.2%
height: 8.2%
tap_action:
entity: button.pump_4_dispense
confirmation:
text: Are you sure you want to dispense?
action: toggle
double_tap_action:
action: navigate
navigation_path: /dashboard-gustometer/pump-4
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_4
- type: custom:button-card
entity: binary_sensor.pump_5_tank_water_level
name: ' '
size: 500%
display: flex
grid:
- position: relative
state:
- value: 'off'
color: red
icon: mdi:water-off
styles:
card:
- background-color: transparent
- animation: blink 2s ease infinite
- '--mdc-ripple-press-opacity': 0
text:
- color: red
- value: 'on'
color: DodgerBlue
icon: mdi:water
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
icon: mdi:water-sync
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 50%
left: 72%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 5.2%
height: 6.2%
- type: custom:button-card
entity: cover.pump_5_motor
name: ' '
size: 1000%
display: flex
icon: mdi:fan
color: red
state:
- value: closed
color: orange
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: open
color: DodgerBlue
styles:
icon:
- animation: rotating 2s linear infinite
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 31%
left: 72%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 9.2%
height: 8.2%
tap_action:
entity: button.pump_5_dispense
confirmation:
text: Are you sure you want to dispense?
action: toggle
double_tap_action:
action: navigate
navigation_path: /dashboard-gustometer/pump-5
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_5
- type: custom:button-card
entity: binary_sensor.pump_6_tank_water_level
name: ' '
size: 500%
display: flex
grid:
- position: relative
state:
- value: 'off'
color: red
icon: mdi:water-off
styles:
card:
- background-color: transparent
- animation: blink 2s ease infinite
- '--mdc-ripple-press-opacity': 0
text:
- color: red
- value: 'on'
color: DodgerBlue
icon: mdi:water
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
icon: mdi:water-sync
styles:
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 50%
left: 86.8%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 5.2%
height: 6.2%
- type: custom:button-card
entity: cover.pump_6_motor
name: ' '
size: 1000%
display: flex
icon: mdi:fan
color: red
state:
- value: closed
color: orange
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: open
color: DodgerBlue
styles:
icon:
- animation: rotating 2s linear infinite
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- value: unavailable
color: rgba(148, 0, 211, 1)
styles:
icon:
- animation: null
card:
- background-color: transparent
- '--mdc-ripple-press-opacity': 0
- animation: blink 5s ease infinite
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 31%
left: 86.8%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
display: flex
width: 9.2%
height: 8.2%
tap_action:
entity: button.pump_6_dispense
confirmation:
text: Are you sure you want to dispense?
action: toggle
double_tap_action:
action: navigate
navigation_path: /dashboard-gustometer/pump-6
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_6
- type: custom:mushroom-number-card
entity: input_number.slider8
size: 1%
display: flex
icon: mdi:speedometer
display_mode: ''
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 64%
left: 22%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
width: 36.2%
height: 3.2%
- type: custom:mushroom-number-card
entity: input_number.slider7
size: 40%
display: flex
align-items: center
icon: mdi:speedometer
display_mode: ''
style:
align-items: center
background: transparent
border-radius: 25px
padding: 1px
top: 64%
left: 77.5%
'--paper-item-icon-color': black
'--mdc-icon-size': 60%
width: 36.2%
height: 3.2%
- type: custom:restriction-card
size: 40%
display: flex
locked_icon: mdi:lock
unlocked_icon: mdi:lock-open-check
duration: 30
restrictions:
confirm: null
pin:
code: 4202
text: Please Enter 4202 to Unlock?
block: null
exemptions: null
card:
type: button
entity: button.pump_all_dispense
icon: mdi:fan
name: Pump All
style:
align-items: center
border-radius: 25px
padding: 1px
'--restriction-regular-lock-color': orange
'--restriction-lock-icon-size': 20px
'--restriction-lock-row-margin-left': 50px
'--restriction-success-lock-color': green
'--restriction-lock-opacity': 1
'--paper-item-icon-color': DodgerBlue
'--mdc-icon-size': 20%
top: 91%
left: 49.75%
width: 18.2%
height: 19.3%
- type: custom:restriction-card
size: 40%
display: flex
locked_icon: mdi:lock
unlocked_icon: mdi:lock-open-check
duration: 30
restrictions:
confirm: null
pin:
code: 4202
text: >-
You are going to shutdown the system, please enter 4202
and then press the button to confirm shutdown. Please
allow upto 2 mins for the system to fully shutdown before
remving power.
block: null
exemptions: null
card:
type: button
tap_action:
action: call-service
service: automation.trigger
service_data:
entity_id: automation.system_shutdown
icon: mdi:power
name: System Shutdown
style:
align-items: center
border-radius: 25px
padding: 1px
'--restriction-regular-lock-color': orange
'--restriction-lock-icon-size': 20px
'--restriction-lock-row-margin-left': 50px
'--restriction-success-lock-color': green
'--restriction-lock-opacity': 1
'--paper-item-icon-color': Yellow
'--mdc-icon-size': 20%
top: 91%
left: 12.75%
width: 18.2%
height: 19.3%
- type: custom:button-card
entity: sensor.pump_1_dispensed_amount
name: Pump 1
show_icon: false
display: flex
size: 40%
color: White
style:
align-items: center
background: transparent
border-radius: 15px
padding: 1px
top: 9%
left: 12.6%
width: 11.2%
height: 10.2%
tap_action:
action: null
double_tap_action:
action: null
hold_action:
action: null
- type: custom:button-card
entity: sensor.pump_2_dispensed_amount
name: Pump 2
show_icon: false
display: flex
size: 40%
color: White
style:
align-items: center
background: transparent
border-radius: 15px
padding: 1px
top: 9%
left: 27.5%
width: 11.2%
height: 10.2%
tap_action:
action: null
double_tap_action:
action: null
hold_action:
action: null
- type: custom:button-card
entity: sensor.pump_3_dispensed_amount
name: Pump 3
show_icon: false
display: flex
size: 40%
color: White
style:
align-items: center
background: transparent
border-radius: 15px
padding: 1px
top: 9%
left: 42.4%
width: 11.2%
height: 10.2%
tap_action:
action: null
double_tap_action:
action: null
hold_action:
action: null
- type: custom:button-card
entity: sensor.pump_2_dispensed_amount
name: Pump 4
show_icon: false
display: flex
size: 40%
color: White
style:
align-items: center
background: transparent
border-radius: 15px
padding: 1px
top: 9%
left: 57.2%
width: 11.2%
height: 10.2%
tap_action:
action: null
double_tap_action:
action: null
hold_action:
action: null
- type: custom:button-card
entity: sensor.pump_5_dispensed_amount
name: Pump 5
show_icon: false
display: flex
size: 40%
color: White
style:
align-items: center
background: transparent
border-radius: 15px
padding: 1px
top: 9%
left: 72%
width: 11.2%
height: 10.2%
tap_action:
action: null
double_tap_action:
action: null
hold_action:
action: null
- type: custom:button-card
entity: sensor.pump_6_dispensed_amount
name: Pump 6
show_icon: false
display: flex
size: 40%
color: White
style:
align-items: center
background: transparent
border-radius: 15px
padding: 1px
top: 9%
left: 86.8%
width: 11.2%
height: 10.2%
tap_action:
action: null
double_tap_action:
action: null
hold_action:
action: null
- title: Pump 1
path: pump-1
subview: true
badges: []
cards:
- type: vertical-stack
cards:
- type: custom:mushroom-entity-card
entity: button.pump_1_dispense
icon: mdi:pump
fill_container: false
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg
data:
skip_condition: true
double_tap_action:
action: toggle
tap_action:
confirmation:
text: Are you sure you want to dispense?
action: toggle
- type: custom:mushroom-number-card
entity: input_number.slider1
icon: mdi:speedometer
display_mode: ''
- type: entities
entities:
- entity: number.pump_1_dispense_amount_ml
name: Dispense Amount (ml)
- entity: sensor.pump_1_dispensed_amount
header:
type: custom:mushroom-template-card
entities: []
image: none
primary: Pump 1 Tank
secondary: |-
{% set state=states(entity) %}
{% if state=='on' %}
Full
{% elif state=='off' %}
Refill
{% else %}
{% endif %}
icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
mdi:water
{% elif state=='off' %}
mdi:water-off
{% else %}
{% endif %}
icon_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
blue
{% elif state=='off' %}
red
{% else %}
{% endif %}
fill_container: false
badge_icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
{% elif state=='off' %}
mdi:exclamation-thick
{% else %}
{% endif %}
badge_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
green
{% elif state=='off' %}
yellow
{% else %}
{% endif %}
tap_action:
action: more-info
hold_action:
action: none
double_tap_action:
action: none
layout: vertical
entity: binary_sensor.pump_1_tank_water_level
- type: entities
entities:
- entity: number.pump_1_dispensed_amount_ml
name: Dispensed Amount
title: Pump 1 Calibration
footer:
type: custom:mushroom-template-card
entities: []
entity: none
image: none
primary: Pump 1 Update Calibration Value (HOLD TO PROCEED)
secondary: >
The calibration value for Pump 1 is: {{
states('number.pump_1_calibration_value') }}
icon: mdi:update
icon_color: red
tap_action:
action: none
hold_action:
confirmation:
text: >-
Are you sure you want to update calibration value, this
cannot be undone?
action: call-service
service: automation.trigger
target:
entity_id: automation.pump_1_update_calibration_value
data:
skip_condition: true
- title: Pump 2
path: pump-2
subview: true
badges: []
cards:
- type: vertical-stack
cards:
- type: custom:mushroom-entity-card
icon: mdi:pump
fill_container: false
tap_action:
confirmation:
text: Are you sure you want to dispense?
action: toggle
entity: button.pump_2_dispense
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_2
data:
skip_condition: true
- type: custom:mushroom-number-card
entity: input_number.slider2
icon: mdi:speedometer
display_mode: ''
- type: entities
entities:
- entity: number.pump_2_dispense_amount_ml
name: Dispense Amount (ml)
- entity: sensor.pump_2_dispensed_amount
header:
type: custom:mushroom-template-card
entities: []
image: none
primary: Pump 2 Tank
secondary: |-
{% set state=states(entity) %}
{% if state=='on' %}
Full
{% elif state=='off' %}
Refill
{% else %}
{% endif %}
icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
mdi:water
{% elif state=='off' %}
mdi:water-off
{% else %}
{% endif %}
icon_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
blue
{% elif state=='off' %}
red
{% else %}
{% endif %}
fill_container: false
badge_icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
{% elif state=='off' %}
mdi:exclamation-thick
{% else %}
{% endif %}
badge_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
green
{% elif state=='off' %}
yellow
{% else %}
{% endif %}
tap_action:
action: more-info
hold_action:
action: none
double_tap_action:
action: none
layout: vertical
entity: binary_sensor.pump_2_tank_water_level
- type: entities
entities:
- entity: number.pump_2_dispensed_amount_ml
name: Dispensed Amount
title: Pump 2 Calibration
footer:
type: custom:mushroom-template-card
entities: []
entity: none
image: none
primary: Pump 2 Update Calibration Value (HOLD TO PROCEED)
secondary: >
The calibration value for Pump 2 is: {{
states('number.pump_2_calibration_value') }}
icon: mdi:update
icon_color: red
tap_action:
action: none
hold_action:
confirmation:
text: >-
Are you sure you want to update calibration value, this
cannot be undone?
action: call-service
service: automation.trigger
target:
entity_id: automation.pump_2_update_calibration_value
data:
skip_condition: true
- title: Pump 3
path: pump-3
subview: true
badges: []
cards:
- type: vertical-stack
cards:
- type: custom:mushroom-entity-card
entity: button.pump_3_dispense
icon: mdi:pump
fill_container: false
tap_action:
confirmation:
text: Are you sure you want to dispense?
action: toggle
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_3
data:
skip_condition: true
- type: custom:mushroom-number-card
entity: input_number.slider3
icon: mdi:speedometer
display_mode: ''
- type: entities
entities:
- entity: number.pump_3_dispense_amount_ml
name: Dispense Amount (ml)
- entity: sensor.pump_3_dispensed_amount
header:
type: custom:mushroom-template-card
entities: []
image: none
primary: Pump 3 Tank
secondary: |-
{% set state=states(entity) %}
{% if state=='on' %}
Full
{% elif state=='off' %}
Refill
{% else %}
{% endif %}
icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
mdi:water
{% elif state=='off' %}
mdi:water-off
{% else %}
{% endif %}
icon_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
blue
{% elif state=='off' %}
red
{% else %}
{% endif %}
fill_container: false
badge_icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
{% elif state=='off' %}
mdi:exclamation-thick
{% else %}
{% endif %}
badge_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
green
{% elif state=='off' %}
yellow
{% else %}
{% endif %}
tap_action:
action: more-info
hold_action:
action: none
double_tap_action:
action: none
layout: vertical
entity: binary_sensor.pump_3_tank_water_level
- type: entities
entities:
- entity: number.pump_3_dispensed_amount_ml
name: Dispensed Amount
title: Pump 3 Calibration
footer:
type: custom:mushroom-template-card
entities: []
entity: none
image: none
primary: Pump 3 Update Calibration Value (HOLD TO PROCEED)
secondary: >
The calibration value for Pump 3 is: {{
states('number.pump_3_calibration_value') }}
icon: mdi:update
icon_color: red
tap_action:
action: none
hold_action:
confirmation:
text: >-
Are you sure you want to update calibration value, this
cannot be undone?
action: call-service
service: automation.trigger
target:
entity_id: automation.pump_3_update_calibration_value
data:
skip_condition: true
- title: Pump 4
path: pump-4
subview: true
badges: []
cards:
- type: vertical-stack
cards:
- type: custom:mushroom-entity-card
entity: button.pump_4_dispense
icon: mdi:pump
fill_container: false
tap_action:
confirmation:
text: Are you sure you want to dispense?
action: toggle
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_4
data:
skip_condition: true
- type: custom:mushroom-number-card
entity: input_number.slider4
icon: mdi:speedometer
display_mode: ''
- type: entities
entities:
- entity: number.pump_4_dispense_amount_ml
name: Dispense Amount (ml)
- entity: sensor.pump_4_dispensed_amount
header:
type: custom:mushroom-template-card
entities: []
image: none
primary: Pump 4 Tank
secondary: |-
{% set state=states(entity) %}
{% if state=='on' %}
Full
{% elif state=='off' %}
Refill
{% else %}
{% endif %}
icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
mdi:water
{% elif state=='off' %}
mdi:water-off
{% else %}
{% endif %}
icon_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
blue
{% elif state=='off' %}
red
{% else %}
{% endif %}
fill_container: false
badge_icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
{% elif state=='off' %}
mdi:exclamation-thick
{% else %}
{% endif %}
badge_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
green
{% elif state=='off' %}
yellow
{% else %}
{% endif %}
tap_action:
action: more-info
hold_action:
action: none
double_tap_action:
action: none
layout: vertical
entity: binary_sensor.pump_4_tank_water_level
- type: entities
entities:
- entity: number.pump_4_dispensed_amount_ml
name: Dispensed Amount
title: Pump 4 Calibration
footer:
type: custom:mushroom-template-card
entities: []
entity: none
image: none
primary: Pump 4 Update Calibration Value (HOLD TO PROCEED)
secondary: >
The calibration value for Pump 4 is: {{
states('number.pump_4_calibration_value') }}
icon: mdi:update
icon_color: red
tap_action:
action: none
hold_action:
confirmation:
text: >-
Are you sure you want to update calibration value, this
cannot be undone?
action: call-service
service: automation.trigger
target:
entity_id: automation.pump_4_update_calibration_value
data:
skip_condition: true
- title: Pump 5
path: pump-5
subview: true
badges: []
cards:
- type: vertical-stack
cards:
- type: custom:mushroom-entity-card
entity: button.pump_5_dispense
icon: mdi:pump
fill_container: false
tap_action:
confirmation:
text: Are you sure you want to dispense?
action: toggle
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_5
data:
skip_condition: true
- type: custom:mushroom-number-card
entity: input_number.slider5
icon: mdi:speedometer
display_mode: ''
- type: entities
entities:
- entity: number.pump_5_dispense_amount_ml
name: Dispense Amount (ml)
- entity: sensor.pump_5_dispensed_amount
header:
type: custom:mushroom-template-card
entities: []
image: none
primary: Pump 5 Tank
secondary: |-
{% set state=states(entity) %}
{% if state=='on' %}
Full
{% elif state=='off' %}
Refill
{% else %}
{% endif %}
icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
mdi:water
{% elif state=='off' %}
mdi:water-off
{% else %}
{% endif %}
icon_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
blue
{% elif state=='off' %}
red
{% else %}
{% endif %}
fill_container: false
badge_icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
{% elif state=='off' %}
mdi:exclamation-thick
{% else %}
{% endif %}
badge_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
green
{% elif state=='off' %}
yellow
{% else %}
{% endif %}
tap_action:
action: more-info
hold_action:
action: none
double_tap_action:
action: none
layout: vertical
entity: binary_sensor.pump_5_tank_water_level
- type: entities
entities:
- entity: number.pump_5_dispensed_amount_ml
name: Dispensed Amount
title: Pump 5 Calibration
footer:
type: custom:mushroom-template-card
entities: []
entity: none
image: none
primary: Pump 5 Update Calibration Value (HOLD TO PROCEED)
secondary: >
The calibration value for Pump 5 is: {{
states('number.pump_5_calibration_value') }}
icon: mdi:update
icon_color: red
tap_action:
action: none
hold_action:
confirmation:
text: >-
Are you sure you want to update calibration value, this
cannot be undone?
action: call-service
service: automation.trigger
target:
entity_id: automation.pump_5_update_calibration_value
data:
skip_condition: true
- title: Pump 6
path: pump-6
subview: true
badges: []
cards:
- type: vertical-stack
cards:
- type: custom:mushroom-entity-card
entity: button.pump_6_dispense
icon: mdi:pump
fill_container: false
tap_action:
confirmation:
text: Are you sure you want to dispense?
action: toggle
hold_action:
action: call-service
service: automation.trigger
target:
entity_id: automation.easter_egg_6
data:
skip_condition: true
- type: custom:mushroom-number-card
entity: input_number.slider6
icon: mdi:speedometer
display_mode: ''
- type: entities
entities:
- entity: number.pump_6_dispense_amount_ml
name: Dispense Amount (ml)
- entity: sensor.pump_6_dispensed_amount
header:
type: custom:mushroom-template-card
entities: []
image: none
primary: Pump 6 Tank
secondary: |-
{% set state=states(entity) %}
{% if state=='on' %}
Full
{% elif state=='off' %}
Refill
{% else %}
{% endif %}
icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
mdi:water
{% elif state=='off' %}
mdi:water-off
{% else %}
{% endif %}
icon_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
blue
{% elif state=='off' %}
red
{% else %}
{% endif %}
fill_container: false
badge_icon: |-
{% set state=states(entity) %}
{% if state=='on' %}
{% elif state=='off' %}
mdi:exclamation-thick
{% else %}
{% endif %}
badge_color: |-
{% set state=states(entity) %}
{% if state=='on' %}
green
{% elif state=='off' %}
yellow
{% else %}
{% endif %}
tap_action:
action: more-info
hold_action:
action: none
double_tap_action:
action: none
layout: vertical
entity: binary_sensor.pump_6_tank_water_level
- type: entities
entities:
- entity: number.pump_6_dispensed_amount_ml
name: Dispensed Amount
title: Pump 6 Calibration
footer:
type: custom:mushroom-template-card
entities: []
entity: none
image: none
primary: Pump 6 Update Calibration Value (HOLD TO PROCEED)
secondary: >
The calibration value for Pump 6 is: {{
states('number.pump_6_calibration_value') }}
icon: mdi:update
icon_color: red
tap_action:
action: none
hold_action:
confirmation:
text: >-
Are you sure you want to update calibration value, this
cannot be undone?
action: call-service
service: automation.trigger
target:
entity_id: automation.pump_6_update_calibration_value
data:
skip_condition: true

ESPHome

To allow the system to be modular and easily expandible allowing for as many pumps that are desired. Each module has it's own custom PCB that is using an ESP32 running it's own custom firmware using ESPHome. The modules interface with 2 sensors being a magnetic encoder and a M2EE Liquid Level Sensor. Along with an output to plug directly into a BIGTREETECH S42C V1.1 stepper motor.

ESP32 code

Code
/homeassistant/esphome/pump-1.yaml
    ################## Information ###################

# Author: Brock Cooper
# Date: 22/09/2023

# To adjust speed in home assistant a helper needs to be added as a slider or input field
# Automation to updated helper in dashboard
# alias: motor speed
# description: ""
# trigger:
# - platform: state
# entity_id:
# - input_number.motor_speed
# condition: []
# action:
# - service: esphome.stepper_control_stepper
# data_template:
# speed: "{{ trigger.to_state.state | float}}"

# OR
# add to manual_automations.yaml

# - id: pump_1_speed
# alias: Pump-1 (Speed-rev/s)
# description: Call Service to Set Pump-1 Speed
# trigger:
# - platform: state
# entity_id:
# - input_number.slider1
# condition: []
# action:
# - service: esphome.pump_1_control_stepper
# data_template:
# speed: "{{ trigger.to_state.state | float}}"


# NOTE:
# 1080 deg = 3ml
# dispense = (1080/3*dose_ammount)


################## Code Begin ###################
esphome:
name: pump-1
friendly_name: Pump_1

esp32:
board: esp32dev
framework:
type: arduino

# Set Stepping mode (200/(stepping mode)
globals:
- id: stepping_per_rev
type: int
restore_value: no
initial_value: '400'
- id: cumulative_angle
type: float
restore_value: yes
initial_value: '0.0'
- id: last_angle
type: float
restore_value: yes
initial_value: '0.0'
- id: initialized
type: bool
restore_value: no
initial_value: 'false'

# Enable logging
logger:

# Enable Home Assistant API
api:
encryption:
key: "mkCfzHp24A+Rf4W8bHz7O2ELP+SD9rJonLtSoMbiZcQ="

services:
# Define two services in the Home Assistant API
# Variable "position"
- service: pos_stepper
variables:
target: int
then:
- stepper.set_target:
id: nema17
target: !lambda 'return target;'
- sensor.template.publish:
id: position
state: !lambda 'return target;'

# Variable "speed"
- service: control_stepper
variables:
speed: float
then:
- logger.log: "Speed updated"
- stepper.set_speed:
id: nema17
speed: !lambda |-
return speed * id(stepping_per_rev);

ota:
password: "cef64505f2594bcecdbcf8cbac8e963b"

wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password

# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Pump-1 Fallback Hotspot"
password: "tl00OYDfspOA"

captive_portal:

stepper:
- platform: a4988
id: nema17
step_pin: GPIO18
dir_pin: GPIO19
max_speed: 800 steps/s # was 1000

# Optional:
sleep_pin: GPIO2
acceleration: inf
deceleration: inf


# Example configuration entry for ESP32
i2c:
sda: GPIO21
scl: GPIO22
scan: true
id: bus_a

as5600:
id: my_as5600
dir_pin: GPIO32

# Publish a sensor with the real motor position
sensor:
- platform: as5600
id: magnetic_encoder
update_interval: 5.0s
gain:
name: Gain
magnitude:
name: Magnitude
status:
name: Status

- platform: as5600
id: my_as5600_sensor
update_interval: 0.15s # Faster internal update
internal: true
filters:
- lambda: |-
float current_angle = x * as5600::RAW_TO_DEGREES * id(my_as5600).get_range_scale();

// Initialize last_angle on the first run
if (!id(initialized)) {
id(last_angle) = current_angle;
id(initialized) = true;
}

float angle_diff = current_angle - id(last_angle);
if (angle_diff > 180.0) {
angle_diff -= 360.0;
} else if (angle_diff < -180.0) {
angle_diff += 360.0;
}

id(cumulative_angle) += angle_diff;
id(last_angle) = current_angle;

// Return the updated cumulative angle
return id(cumulative_angle);

- platform: template
name: "Dispensed Amount"
id: actual_dispensed_amount
unit_of_measurement: "ml"
accuracy_decimals: 1
icon: mdi:cup-water
update_interval: 1.0s
lambda: |-
return id(cumulative_angle)/id(p_cal).state * -1;

- platform: template
name: "Position Motor Angle"
internal: true
icon: mdi:rotate-right
unit_of_measurement: "°"
id: position
update_interval: 1s
lambda: |-
float angle = id(position).state * 360 / id(stepping_per_rev);
ESP_LOGD("motor", "Position: %.2f°", angle);
return angle;

binary_sensor:
- platform: gpio
pin: GPIO26
name: "Tank Water Level"
id: tank_water_level
filters:
- invert:
- delayed_on: 1500ms
- delayed_off: 1500ms
# mqtt:
# broker: "192.168.10.1"
# username: "Workshop"
# password: "W504Shop"

# With the COVER object, you can use 3 buttons: up, down, and stop
# Motor is set to 1/16 microstepping therefore 200 steps in the 1.8 deg stepper motor * 16 = 3200 puleses in a single revolution
cover:
- platform: template
name: "Motor"
id: contols # was grifo
device_class: damper
open_action:
- stepper.set_target:
id: nema17
target: !lambda |-
return id(nema17).target_position + id(inc_pos_slider).state * id(stepping_per_rev)/360 * id(p_cal).state;
# target: 10000
- sensor.template.publish:
id: position
state: !lambda return id(nema17).target_position;
close_action:
- stepper.set_target:
id: nema17
target: !lambda |-
return id(nema17).target_position - id(inc_pos_slider).state * id(stepping_per_rev)/360 * id(p_cal).state;
# target: 0
- sensor.template.publish:
id: position
state: !lambda return id(nema17).target_position;
stop_action:
- stepper.set_target:
id: nema17
target: !lambda |-
id(cumulative_angle) = 0.0;
id(last_angle) = 0.0;
id(initialized) = 0.0;
return id(nema17).current_position;
- sensor.template.publish:
id: position
state: !lambda return id(nema17).current_position;
optimistic: true
assumed_state: true

number:
- platform: template
name: "Dispense Amount (ml)"
id: "inc_pos_slider"
step: 0.1
min_value: 0
max_value: 50
mode: auto
optimistic: true
restore_value: true

- platform: template
name: "Calibration Value"
id: "p_cal"
step: 0.1
min_value: 0
max_value: 1000
mode: auto
optimistic: true
restore_value: true

- platform: template
name: "Dispensed Amount (ml)"
id: "dispensed_amount"
initial_value: 0
step: 0.1
min_value: 0
max_value: 50
mode: auto
optimistic: true
# restore_value: true

WLED - Pump Modules Status Indicator

To display the status of each module without having to look at the interface the status of each module can be seen from WS2812 RGB led strip that changes colour and led animation patterns depending on the state of each of the pump modules. This is using the open source WLED Project which has a built-in home assistant integration allowing it to seamlessly integrate with home assistant that allows status to be changed depending on each module status.