Home Assistant Setup Instructions - tagyoureit/nodejs-poolController GitHub Wiki

Home Assistant Setup Instructions

These instructions are provided courtesy of @sonapsent

Summary

The following shows how nodejs-poolController can be integrated into Home Assistant. This allows everything smart home related to be controlled from a central platform and provide additional local automation for a swimming pool. After setup it is possiable to create notifcations, view historical data/graphs, or even connect to voice services like Google or Alexa within Home Assistant.

Disclaimer: Myself and others involved in nodejs-poolController are not responsiable for damage to equipment, thermonuclear war, or other harm to personnel. Controlling industrial equipment is serious so please monitor the system and thoroughly test your setup.

Example Equipment

  • Controller: [e.g. Nixie Single Body (nodejs-PC v7.2)]
  • 30,000 Gallon In-Ground Pool
  • Pump manufacturer and model: [e.g. V-Green 165 VS Motor / Stealth Pump, relay control]
  • Filter: [Pressure Gauge]
  • Temperature Sensors: [ 10k thermistors for water and air]
  • Chemical controller: [e.g. REMChem, Atlas EZO ORP and PH]
  • Heater(s): [e.g. Heatpump, relay control]
  • Chlorinator: [e.g. Aquarite 900, RS485]

Requirements / Recommendations

The instructions below assume that both nodejs-PoolController and Home Assistant are setup and running independently. Please ensure that pool devices are working within the web based dashPanel before continuing.

nodejs-poolController:

  • Enable MQTT on dashPanel
  • Optional: Enable MQTT on REM (for pressure sensor)

Home Assistant Add-Ons:

  • MQTT Broker
  • NodeRed
  • Visual Studio Code (or other various text editors)
  • Optional: HACS

Home Assistant - configuration.yaml

The following sections can be added to the appropriate sections in your HA configuration.yaml file depending on what equipment you have. Pay attention to the rootTopic text for your setup as the default will be different depending on controller setup. In the example below this has been simplified to "pool" for state_topic. The use of an application like MQTT Explorer is valuable for troubleshooting and monitoring messages in real time.

After the configuration file is saved it is nessary to restart HA for new entities to be created or changes to take place.

configuration>server controls>check configuration>restart server

input_number:
  pool_tempurature_set_point:
    name: Pool Tempurature Set Point
    min: 70
    max: 90
    step: 1
    icon: mdi:target
    unit_of_measurement: "°F"
  pool_chlorine_set_point:
    name: Pool Chlorine Set Point
    min: 0
    max: 100
    step: 10
    icon: mdi:target
    unit_of_measurement: "%"
sensor:

#Pool Controller Sensors
  - platform: mqtt
    name: Pool Water Temp
    state_topic: "pool/state/temps/bodies/1/pool/temp"
    unit_of_measurement: '°F'
    value_template: "{{ value_json.temp | round(1) }}"
    expire_after: 6000

  - platform: mqtt
    name: Pool Air Temp
    state_topic: "pool/state/temps/air"
    unit_of_measurement: '°F'
    value_template: "{{ value_json.temp | round(1) }}"
    expire_after: 6000

  - platform: mqtt
    name: Pool Pump Flow
    icon: mdi:pump
    state_topic: "pool/state/pumps/50/v-green165/flow"
    value_template: "{{ value_json.flow }}"

  - platform: mqtt
    name: Pool Heater Status
    icon: mdi:fire
    state_topic: "pool/state/temps/bodies/1/pool/heatStatus"
    value_template: "{{ value_json.heatStatus.desc }}"

  - platform: mqtt
    name: Pool Chlorinator Status
    icon: mdi:check-circle-outline
    state_topic: "pool/state/chlorinators/1/aquarite/status"
    value_template: "{{ value_json.status.desc }}"

  - platform: mqtt
    name: Pool Chlorinator Target Output
    icon: mdi:chemical-weapon
    state_topic: "pool/state/chlorinators/1/aquarite/targetOutput"
    value_template: "{{ value_json.targetOutput }}"
    unit_of_measurement: '%'

  - platform: mqtt
    name: Pool Salt Level
    icon: mdi:shaker-outline
    state_topic: "pool/state/chlorinators/1/aquarite/saltLevel"
    value_template: "{{ value_json.saltLevel }}"
    unit_of_measurement: ppm

  - platform: mqtt
    name: Pool PH Level
    icon: mdi:beaker-question
    state_topic: "pool/state/chemControllers/50/remchem/pHLevel"
    value_template: "{{ value_json.pHLevel | round(2) }}"
    unit_of_measurement: ' '
    expire_after: 6000

  - platform: mqtt
    name: Pool ORP Level
    icon: mdi:beaker-question-outline
    state_topic: "pool/state/chemControllers/50/remchem/orpLevel"
    value_template: "{{ value_json.orpLevel | round(0) }}"
    unit_of_measurement: ' '
    expire_after: 6000

  - platform: mqtt
    name: Pool Saturation Index
    icon: mdi:water-check-outline
    state_topic: "pool/state/chemControllers/50/remchem/saturationIndex"
    value_template: "{{ value_json.saturationIndex | round(2)}}"
    unit_of_measurement: ' '

  - platform: mqtt
    name: Pool Flow Alarm
    icon: mdi:pipe
    state_topic: "pool/state/chemControllers/50/remchem/alarms/flow"
    value_template: "{{ value_json.alarms.flow }}"

  - platform: mqtt
    name: Pool Filter Pressure
    icon: mdi:air-filter
    state_topic: "pool/state/pressures/filter"
    value_template: "{{ value_json | round(1) }}"
    unit_of_measurement: psi
    expire_after: 6000

switch:

#Pool Controller Sensors
  - platform: mqtt
    name: Pool
    icon: mdi:pool
    state_topic: "pool/state/circuits/6/pool"
    value_template: "{{ value_json.isOn }}"
    command_topic: "pool/state/circuits/toggleState"
    payload_on: '{"id": 6}'
    payload_off: '{"id": 6}'
    state_on: "on"
    state_off: "off"
    optimistic: false
    retain: false
    qos: 0

  - platform: mqtt
    name: Pool Pump Speed 1
    icon: mdi:pump
    state_topic: "pool/state/circuits/2/pumpsp1"
    value_template: "{{ value_json.isOn }}"
    command_topic: "pool/state/circuits/toggleState"
    payload_on: '{"id": 2}'
    payload_off: '{"id": 2}'
    state_on: "on"
    state_off: "off"
    optimistic: false
    retain: false
    qos: 0

  - platform: mqtt
    name: Pool Pump Speed 2
    icon: mdi:pump
    state_topic: "pool/state/circuits/3/pumpsp2"
    value_template: "{{ value_json.isOn }}"
    command_topic: "pool/state/circuits/toggleState"
    payload_on: '{"id": 3}'
    payload_off: '{"id": 3}'
    state_on: "on"
    state_off: "off"
    optimistic: false
    retain: false
    qos: 0

  - platform: mqtt
    name: Pool Pump Speed 3
    icon: mdi:pump
    state_topic: "pool/state/circuits/4/pumpsp3"
    value_template: "{{ value_json.isOn }}"
    command_topic: "pool/state/circuits/toggleState"
    payload_on: '{"id": 4}'
    payload_off: '{"id": 4}'
    state_on: "on"
    state_off: "off"
    optimistic: false
    retain: false
    qos: 0

  - platform: mqtt
    name: Pool Pump Speed 4
    icon: mdi:pump
    state_topic: "pool/state/circuits/5/pumpsp4"
    value_template: "{{ value_json.isOn }}"
    command_topic: "pool/state/circuits/toggleState"
    payload_on: '{"id": 5}'
    payload_off: '{"id": 5}'
    state_on: "on"
    state_off: "off"
    optimistic: false
    retain: false
    qos: 0

  - platform: mqtt
    name: Pool Heater Mode
    icon: mdi:fire
    state_topic: "pool/state/temps/bodies/1/pool/heatMode"
    value_template: "{{ value_json.heatMode.desc }}"
    state_on: "Heater"
    state_off: "Off"
    command_topic: "pool/state/body/heatMode"
    payload_on: '{"id":1,"mode":"heater"}'
    payload_off: '{"id":1,"mode":"off"}'
    optimistic: false
    retain: false
    qos: 0
climate:
  - platform: mqtt
    name: "Pool Heater 2"
    min_temp: 40
    max_temp: 104
    modes:
      - "off"
      - "heat"
    current_temperature_topic: "pool/state/temps/bodies/1/pool/temp"
    value_template: "{{ value_json.temp }}"
    mode_state_topic: "pool/state/temps/bodies/1/pool/heatMode"
    mode_state_template: >-
      {% if value_json.heatMode.val == 0 %}
        off
      {% elif value_json.heatMode.val == 1 %}
        off
      {% elif value_json.heatMode.val == 2 %}
        heat
      {% endif %}
    mode_command_topic: "pool/state/body/heatMode"
    mode_command_template: >-
      {% set values = { 'heat':'{"heatMode":2, "id":1}', 'off':'{"heatMode":1, "id":1}'} %}    
      {{ values[value] if value in values.keys() else '{"heatMode":1, "id":1}' }}
    action_topic: "pool/state/temps/bodies/1/pool/heatStatus"
    action_template: >-
      {% if value_json.heatStatus.val == 0 %}
        off
      {% elif value_json.heatStatus.val == 1 %}
        heating
      {% endif %}
    temperature_command_topic: pool/setpoint #REQUIRES AN AUTOMATION - SEE BELOW
    temperature_state_topic: "pool/state/temps/bodies/1/pool/setPoint"
    temperature_state_template: "{{ value_json.setPoint }}"
Automation in HA Config for syncing setpoint temperature:
- alias: "pool setpoint converter"
  trigger:
    platform: mqtt
    topic: pool/setpoint
  action:
    service: mqtt.publish
    data_template:
      topic: pool/state/body/setPoint # <-- from https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations-in-2.0#mqtt
      payload: '{"id":1,"setPoint":{{trigger.payload | int}}}' # <-- id sets body/circuit

## Automation Example 1 - Node-Red

You will need the following or similar automations to keep the temperature and/or chlorinator set points synchronized between nodejs-poolController and Home Assistant. You will need to remap references to your specific HA and MQTT servers in node-red after importing the code below.

**Temperature Set Point**
![Alt text](https://user-images.githubusercontent.com/24758410/126051167-2f34806a-27d4-415a-9759-9cc04c914991.png "Temp")

[{"id":"4319aa0f.2cbff4","type":"server-state-changed","z":"f34355e5.bd58d8","name":"Tempurature Set Point","server":"38c4ea68.0b9556","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.pool_tempurature_set_point","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":180,"y":2160,"wires":"57f184f1.f9c97c"},{"id":"57f184f1.f9c97c","type":"function","z":"f34355e5.bd58d8","name":"Data","func":"msg.payload = {"id": 1, "setPoint": msg.payload}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":2160,"wires":"9dfef7f5.e73638"},{"id":"9dfef7f5.e73638","type":"mqtt out","z":"f34355e5.bd58d8","name":"","topic":"pool/state/body/setPoint","qos":"0","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"f5279b01.b328d8","x":570,"y":2160,"wires":[]},{"id":"846063cc.a4605","type":"mqtt in","z":"f34355e5.bd58d8","name":"","topic":"pool/state/temps/bodies/1/pool/setPoint","qos":"0","datatype":"auto","broker":"f5279b01.b328d8","nl":false,"rap":true,"rh":0,"x":230,"y":2220,"wires":"3741aa31.3d9186"},{"id":"ab1bb7f0.961018","type":"api-call-service","z":"f34355e5.bd58d8","name":"Set Tempurature Target","server":"38c4ea68.0b9556","version":3,"debugenabled":false,"service_domain":"input_number","service":"set_value","entityId":"input_number.pool_tempurature_set_point","data":"{"value":"{{payload}}"}","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":890,"y":2220,"wires":},{"id":"3741aa31.3d9186","type":"json","z":"f34355e5.bd58d8","name":"","property":"payload","action":"","pretty":false,"x":510,"y":2220,"wires":"ed09e2e2.ba582"},{"id":"ed09e2e2.ba582","type":"split","z":"f34355e5.bd58d8","name":"","splt":"\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":670,"y":2220,"wires":"ab1bb7f0.961018"},{"id":"38c4ea68.0b9556","type":"server","name":"Home Assistant","version":1,"legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true},{"id":"f5279b01.b328d8","type":"mqtt-broker","name":"HASSIO - MQTT","broker":"localhost","port":"1883","clientid":"","usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""}]


**Chlorinator Set Point**
![Alt text](https://user-images.githubusercontent.com/24758410/126051165-9bad0fff-bde7-4562-96c7-bbdf09f0b94a.png "Chorinator")

[{"id":"1c3c85e8.30330a","type":"server-state-changed","z":"f34355e5.bd58d8","name":"Chlorine Set Point","server":"38c4ea68.0b9556","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.pool_chlorine_set_point","entityidfiltertype":"exact","outputinitially":false,"state_type":"num","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":170,"y":2280,"wires":"670c0c22.2c1e94"},{"id":"670c0c22.2c1e94","type":"function","z":"f34355e5.bd58d8","name":"Data","func":"msg.payload = {"id": 1, "poolSetpoint": msg.payload}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":2280,"wires":"3e2b068a.f93a8a"},{"id":"3e2b068a.f93a8a","type":"mqtt out","z":"f34355e5.bd58d8","name":"","topic":"pool/state/chlorinator","qos":"0","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"f5279b01.b328d8","x":560,"y":2280,"wires":[]},{"id":"49f2a840.fdddb8","type":"mqtt in","z":"f34355e5.bd58d8","name":"","topic":"pool/state/chlorinators/1/aquarite/poolSetpoint","qos":"0","datatype":"auto","broker":"f5279b01.b328d8","nl":false,"rap":true,"rh":0,"x":250,"y":2340,"wires":"dfa3118d.6dceb"},{"id":"dda4d920.bb2138","type":"api-call-service","z":"f34355e5.bd58d8","name":"Set Chlorine Target","server":"38c4ea68.0b9556","version":3,"debugenabled":false,"service_domain":"input_number","service":"set_value","entityId":"input_number.pool_chlorine_set_point","data":"{"value":"{{payload}}"}","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":870,"y":2340,"wires":},{"id":"dfa3118d.6dceb","type":"json","z":"f34355e5.bd58d8","name":"","property":"payload","action":"","pretty":false,"x":510,"y":2340,"wires":"f0cde6ae.d4e118"},{"id":"f0cde6ae.d4e118","type":"split","z":"f34355e5.bd58d8","name":"","splt":"\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":670,"y":2340,"wires":"dda4d920.bb2138"},{"id":"38c4ea68.0b9556","type":"server","name":"Home Assistant","version":1,"legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true},{"id":"f5279b01.b328d8","type":"mqtt-broker","name":"HASSIO - MQTT","broker":"localhost","port":"1883","clientid":"","usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""}]


At this point you can make any automation you can dream up. If you need any ideas to start with, here is a list of some of mine.
* Logic to toggle pool "on" state and select pump speeds while keeping them sync'd with nodejs-poolController
* Schedule for the pool pump
* Avoid using electricity during peak hours
* Turn the heat pump on only for days that pool is likely to be used
* Notification if communication drops from the nodejs-poolController (ex: monitor the pressure value for "unavailable", as the MQTT value is setup to expire after 10 minutes)

## Automation Example 2 - Home Assistant YAML

Of course another way of doing automations in HA is to simply using yaml. The following two automations should keep the chorinator set point synchronized in both directions.

Credits to [@truvec](https://github.com/truvec)

automation:

  • id: '808456' alias: "Set SWG slider" trigger: platform: mqtt topic: "pool/state/chlorinators/1/aquarite/poolSetpoint" action: service: input_number.set_value target: entity_id: input_number.pool_chlorine_set_point data: value: "{{ trigger.payload_json.poolSetpoint }}"

  • id: '808457' alias: "SWG slider moved" trigger: platform: state entity_id: input_number.pool_chlorine_set_point action: service: mqtt.publish data: topic: "pool/state/chlorinator" retain: true payload: "{"id":1,"poolSetpoint":{{ states('input_number.pool_chlorine_set_point')|round(0)}}}"


## Lovelace Dashboard

Here is a card that I use to display relevant pool information. This will need to be edited for your use case as it contains sensors not created or explained in this writeup. There are also some custom lovelace plugins that I use below that can be installed manually or by using HACS.

- custom:mini-graph-card
- custom:multiple-entity-row

![Alt text](https://user-images.githubusercontent.com/24758410/126051168-7ff35333-6a2d-4532-a57a-8a07464e677a.png "Pool Card")

type: vertical-stack cards:

  • type: custom:mini-graph-card entities:
    • entity: sensor.pool_water_temp name: Pool
    • entity: sensor.pool_air_temp name: Enclosure
    • aggregate_func: min color: gray entity: binary_sensor.night show_legend: false show_line: false show_points: false y_axis: secondary hours_to_show: 24 points_per_hour: 2 name: Pool Tempuratures show: labels: true decimals: 1 state_map:
    • label: Night value: 'off'
    • label: Day value: 'on' type: ''
  • type: entities title: Pool Control show_header_toggle: false entities:
    • entity: switch.pool
    • entity: input_select.pool_pump_speed
    • type: custom:multiple-entity-row entity: sensor.pool_filter_pressure name: Pump/Filter secondary_info: last-changed show_state: false entities:
      • entity: sensor.pool_filter_pressure name: Pressure
      • entity: sensor.iotawatt_input_9 name: Power
    • entity: input_number.pool_tempurature_set_point
    • type: custom:multiple-entity-row entity: sensor.pool_heater_status name: Pool Heater secondary_info: last-changed show_state: false entities:
      • entity: sensor.pool_heater_status name: Status
      • entity: sensor.iotawatt_input_8 name: Power
      • entity: switch.pool_heater_mode name: Enable toggle: true
    • entity: input_number.pool_chlorine_set_point
    • type: custom:multiple-entity-row entity: sensor.pool_salt_level name: Chlorine secondary_info: last-changed show_state: false entities:
      • entity: sensor.pool_salt_level name: Salt
      • entity: sensor.pool_chlorinator_target_output name: Duty
      • entity: input_boolean.make_chlorine name: Enable toggle: true
    • type: custom:multiple-entity-row entity: sensor.pool_orp_level name: Chemical Levels secondary_info: last-changed show_state: false entities:
      • entity: sensor.pool_orp_level name: ORP
      • entity: sensor.pool_ph_level name: PH
      • entity: sensor.pool_saturation_index name: Sat-Index