HA controlling z-wave thermostats connected to ISY994i

Hi,

Here is my existing setup: I have a mix of Insteon and Z-Wave devices, all attached to an ISY994i. Plus other devices such as a DSC alarm. I recently purchased two z-wave thermostats (CT101): one in my garage to control the heater and one in my basement to control my gas fireplace. The ISY detects the thermostats just fine but one of the problems with the thermostats is that they only report in Fahrenheit, and I need Celcius/Metric, being outside the United States.

HA does not yet have direct support for thermostats attached to ISY. So, for now, I decided to have HA control my thermostats through the ISY REST API. Thanks to DetroitEE and this post, I was able to figure it out. Note that I do not have a Cool option as they are only heaters. The configuration could be easily modified accordingly if needed. Also note that I could not use the GREP option with -P as it wouldn’t work in HASS.io, so I decided to use AWK instead (not great, but it works). Figuring out the right options to use to correctly read the values passed on by the REST API was the biggest challenge. I use HASSio (latest build 0.59.2 as I’m writing this).

Here is what it currently looks like in HA:

I promised I would share my configuration, so here it is. I’m using a split configuration with import file to make it easier.

In configuration.yaml:

homeassistant:
  customize: !include customize.yaml
sensor: !include sensors.yaml
input_number: !include input_numbers.yaml
input_select: !include input_selects.yaml
rest_command: !include rest_commands.yaml
group: !include groups.yaml
automation: !include automations.yaml

In sensors.yaml:

# ==============  Garage thermostat sensors  ==============
  - platform: command_line
    name: "Garage Temp"
    command: /usr/bin/curl --user username:password "http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_1/ST" 2>&1 | awk -F"\"" '{print $8}'
    value_template: '{{ (((value_json/10-32)/1.8)/5) | round(1)*5 }}'
    unit_of_measurement: "°C"
    scan_interval: 5
  - platform: command_line
    name: Garage Heat Setpoint
    command: /usr/bin/curl --user username:password "http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_1/CLISPH" 2>&1 | awk -F"\"" '{print $8}'
    value_template: '{{ (((value_json-32)/1.8)/5) | round(1)*5 }}'
    unit_of_measurement: "°C"
    scan_interval: 10
  - platform: command_line
    name: Garage Thermostat Mode
    command: /usr/bin/curl --user username:password "http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_1/CLIMD" 2>&1 | awk -F"\"" '{print $10}'
    scan_interval: 10
  - platform: command_line
    name: Garage Thermostat State
    command: /usr/bin/curl --user username:password "http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_1/CLIHCS" 2>&1 | awk -F"\"" '{print $10}'
    scan_interval: 5

  # ==============  Basement thermostat sensors  ==============
  - platform: command_line
    name: "Basement Temp"
    command: /usr/bin/curl --user username:password "http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_2/ST" 2>&1 | awk -F"\"" '{print $8}'
    value_template: '{{ (((value_json/10-32)/1.8)/5) | round(1)*5 }}'
    unit_of_measurement: "°C"
    scan_interval: 5
  - platform: command_line
    name: Basement Heat Setpoint
    command: /usr/bin/curl --user username:password "http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_2/CLISPH" 2>&1 | awk -F"\"" '{print $8}'
    value_template: '{{ (((value_json-32)/1.8)/5) | round(1)*5 }}'
    unit_of_measurement: "°C"
    scan_interval: 10
  - platform: command_line
    name: Basement Thermostat Mode
    command: /usr/bin/curl --user username:password "http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_2/CLIMD" 2>&1 | awk -F"\"" '{print $10}'
    scan_interval: 10
  - platform: command_line
    name: Basement Thermostat State
    command: /usr/bin/curl --user username:password "http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_2/CLIHCS" 2>&1 | awk -F"\"" '{print $10}'
    scan_interval: 5

In input_numbers.yaml:

 garage_heat_setpoint:
    name: Garage Heat Setpoint
    min: 10
    max: 24
    step: 0.5
    mode: slider
    icon: mdi:thermometer
    unit_of_measurement: °C
  basement_heat_setpoint:
    name: Basement Heat Setpoint
    min: 10
    max: 24
    step: 0.5
    mode: slider
    icon: mdi:thermometer
    unit_of_measurement: °C

In input_selects.yaml:

  garage_thermostat_mode:
    name: Thermostat Mode
    options:
      - Heat
      - "Off"
    icon: mdi:gauge
  basement_thermostat_mode:
    name: Thermostat Mode
    options:
      - Heat
      - "Off"
    icon: mdi:gauge

In rest_commands.yaml:

  # ===================  Garage Thermostat  ===================
  garage_thermostat_mode_off:
    url: http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_1/cmd/CLIMD/0
    username: !secret isy_username
    password: !secret http_password
  garage_thermostat_mode_heat:
    url: http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_1/cmd/CLIMD/1
    username: !secret isy_username
    password: !secret http_password
  garage_thermostat_heat_setpoint:
    url: 'http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_1/cmd/CLISPH/{{ (states.input_number.garage_heat_setpoint.state | float * 1.8 + 32) | round(0) }}'
    username: !secret isy_username
    password: !secret http_password
  # ===================  Basement Thermostat  ===================
  basement_thermostat_mode_off:
    url: http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_2/cmd/CLIMD/0
    username: !secret isy_username
    password: !secret http_password
  basement_thermostat_mode_heat:
    url: http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_2/cmd/CLIMD/1
    username: !secret isy_username
    password: !secret http_password
  basement_thermostat_heat_setpoint:
    url: 'http://MyIPAddress/rest/nodes/ZW_NODEADDRESS_2/cmd/CLISPH/{{ (states.input_number.basement_heat_setpoint.state | float * 1.8 + 32) | round(0) }}'
    username: !secret isy_username
    password: !secret http_password

In groups.yaml:

  garage_thermostat:
    name: 'Garage Heater'
    icon: mdi:thermometer
    entities:
      - sensor.garage_temp      
      - input_select.garage_thermostat_mode
      - sensor.garage_thermostat_state
      - sensor.garage_heat_setpoint
      - input_number.garage_heat_setpoint
      - sensor.garage_humidity_sensor
  basement_thermostat:
    name: 'Basement Fireplace'
    icon: mdi:thermometer
    entities:
      - sensor.basement_temp      
      - input_select.basement_thermostat_mode
      - sensor.basement_thermostat_state
      - sensor.basement_heat_setpoint
      - input_number.basement_heat_setpoint 

In customize.yaml

sensor.garage_temp:
  friendly_name: 'Current Temperature'
  icon: mdi:thermometer
sensor.garage_thermostat_mode:
  friendly_name: 'Mode'
  icon: mdi:thermometer-lines
input_select.garage_thermostat_mode:
  friendly_name: 'Mode'
  icon: mdi:thermometer-lines
sensor.garage_thermostat_state:
  friendly_name: 'State'
  icon: mdi:eye
sensor.garage_heat_setpoint:
  friendly_name: 'Existing Heat Target'
  icon: mdi:bullseye
input_number.garage_heat_setpoint:
  friendly_name: 'Heat Setpoint'
  icon: mdi:gesture-tap
sensor.garage_humidity_sensor:
  friendly_name: 'Humidity Sensor'
  icon: mdi:water-percent
sensor.house_humidity_sensor:
  friendly_name: 'House Humidity Sensor'
  icon: mdi:water-percent
sensor.basement_temp:
  friendly_name: 'Current Temperature'
  icon: mdi:thermometer
sensor.basement_thermostat_mode:
  friendly_name: 'Mode'
  icon: mdi:thermometer-lines
input_select.basement_thermostat_mode:
  friendly_name: 'Mode'
  icon: mdi:thermometer-lines
sensor.basement_thermostat_state:
  friendly_name: 'State'
  icon: mdi:eye
sensor.basement_heat_setpoint:
  friendly_name: 'Existing Heat Target'
  icon: mdi:bullseye
input_number.basement_heat_setpoint:
  friendly_name: 'Heat Setpoint'
  icon: mdi:gesture-tap

In automations.yaml (formatting is a little weird because of the HA Automation Editor):

 - id: Set_Garage_Thermostat_Mode_to_Heat
  alias: Set Garage Thermostat Mode to Heat
  trigger:
  - entity_id: input_select.garage_thermostat_mode
    platform: state
    to: "Heat"  
  action:
    service: rest_command.garage_thermostat_mode_heat
- id: Set_Garage_Thermostat_Mode_to_Off
  alias: Set Garage Thermostat Mode to Off 
  trigger:
  - entity_id: input_select.garage_thermostat_mode
    platform: state
    to: "Off"
  action:
    service: rest_command.garage_thermostat_mode_off
- id: Garage_Heat_Setpoint
  alias: Garage Heat Setpoint Slider
  trigger:
  - entity_id: input_number.garage_heat_setpoint
    platform: state
  action:
    service: rest_command.garage_thermostat_heat_setpoint
- id: Set_Basement_Thermostat_Mode_to_Heat
  alias: Set Basement Thermostat Mode to Heat
  trigger:
  - entity_id: input_select.basement_thermostat_mode
    platform: state
    to: "Heat"  
  action:
    service: rest_command.basement_thermostat_mode_heat
- id: Set_Basement_Thermostat_Mode_to_Off
  alias: Set Basement Thermostat Mode to Off 
  trigger:
  - entity_id: input_select.basement_thermostat_mode
    platform: state
    to: "Off"
  action:
    service: rest_command.basement_thermostat_mode_off
- id: Basement_Heat_Setpoint
  alias: Basement Heat Setpoint Slider
  trigger:
  - entity_id: input_number.basement_heat_setpoint
    platform: state
  action:
    service: rest_command.basement_thermostat_heat_setpoint

I hope this is useful to someone. Note that this would work exactly the same for Insteon thermostats, but you may have to play with the numbers (divide or multiply by 2, as reported elsewhere). For now the configuration above works well for what I need.

Please let me know if you have any questions.

Cheers,

1 Like

In order for a manual change to the thermostats to be reflected in the input slider of the interface, I recently added the following automations to my setup. It was bugging me that both were not synchronized. Everything is now bidirectional.

- id: Garage_Heat_Setpoint_Adjust
  alias: Garage Heat Setpoint Slider Adjust
  trigger:
  - entity_id: sensor.garage_heat_setpoint
    platform: state
  action:
    service: input_number.set_value
    data_template:
        entity_id: input_number.garage_heat_setpoint
        value: '{{ states.sensor.garage_heat_setpoint.state }}'
- id: Basement_Heat_Setpoint_Adjust
  alias: Basement Heat Setpoint Slider Adjust
  trigger:
  - entity_id: sensor.basement_heat_setpoint
    platform: state
  action:
    service: input_number.set_value
    data_template:
        entity_id: input_number.basement_heat_setpoint
        value: '{{ states.sensor.basement_heat_setpoint.state }}'

and:

- id: Garage_Thermostat_Mode_Adjust
  alias: Garage Thermostat Mode Adjust
  trigger:
  - entity_id: sensor.garage_thermostat_mode
    platform: state
  action:
    service: input_select.set_value
    data_template:
        entity_id: input_select.garage_thermostat_mode
        value: '{{ states.sensor.garage_thermostat_mode.state }}'
- id: Basement_Thermostat_Mode_Adjust
  alias: Basement Thermostat Mode Adjust
  trigger:
  - entity_id: sensor.basement_thermostat_mode
    platform: state
  action:
    service: input_select.set_value
    data_template:
        entity_id: input_select.basement_thermostat_mode
        value: '{{ states.sensor.basement_thermostat_mode.state }}'

I added this “automation.yaml” code to re-initialize the input sliders to their existing heat targets upon reboot. Before, the sliders would stay at 10 instead of the existing heat targets:

- id: heating_update_temperature_at_start
  alias: 'THERMOSTAT - Heating Update Sliders to Existing Targets At Start'
  trigger:
    platform: homeassistant
    event: start
  action:
    - service: input_number.set_value
      data_template:
        entity_id: input_number.garage_heat_setpoint
        value: '{{ states.sensor.garage_heat_setpoint.state }}'
    - service: input_number.set_value
      data_template:
        entity_id: input_number.basement_heat_setpoint
        value: '{{ states.sensor.basement_heat_setpoint.state }}'

I’ve been trying to get this to work for a couple of days, unsuccessfully. With 2>&1 | awk -F""" ‘{print $8}’, copied directly and exactly from your post, i only get config errors. Is this formatted correctly in your above rest_commands?

Is this an antiquated method that you have had to replace with something else since the original post? I’m running Hass.io v 0.68.1, so as far as I can tell, it should work. If I leave only the URL and nothing else, I at least get something other than “unknown” for the value, but it ends up being the whole XML, as expected.

Everything is formatted correctly. Ensure you use single quotes correctly. When I copy the text from your post to Notepad, I don’t get the correct quotes, I have to retype them. When I copy the text from my post to Notepad, they are fine. Also check your escape character \.

What exact error message do you get?

When I paste them exactly as displayed in this post, I get the following config error. I’m no programmer, but when looking at it, it looks like there is one too many " marks after the “” in it, but I’ve tried every combination I can think of and none of it works.

Configuration invalid
Error loading /config/configuration.yaml: while parsing a block mapping
in “/config/configuration.yaml”, line 94, column 5
expected , but found ‘{’
in “/config/configuration.yaml”, line 96, column 122

My config looks like this:

Any guidance you can give is appreciated.

Okay, so as soon as I posted that screenshot, I realized I had started the “command:” line with ’ and in your examples, you omit that. Fixed it, and now the config errors are gone, but still, I don’t get any values displayed for the sensors.

Start by pasting that entire command without the ’ at each end into SSH command line, and see what it returns.

Just did, and it returns nothing but a couple of empty lines. When I change {print $8} to some other numbers, I am able to get some portions of the XML (none of the ones I need, of course) but $8 always returns nothing, and $10 returns nothing.

This is the XML i get back when I put the URL into my browser. Is it possible that my XML returns are formatted differently from others’ due to a different ISY SW version? When I tried other numbers for print, the data returned looked like portions of the header, which doesn’t show in the browser, as you can see here.
image

Sorry, I don’t know much about using awk and print, as I am using grep since I am not running hass.io.

Understood. I appreciate the attempt. Hopefully Madelinot will be able to chime back in.

Not sure what else I can contribute. Like I said, please ensure you have the correct quotes and escape character. It’s easy to get the wrong quotes. Like this in your code:

command: /usr/bin/curl --user user:pass "http://0.0.0.0/rest/nodes/ZW_NODEADDRESS_1/ST" 2>&1 | awk -F"\"" '{print $8}'

“awk” scans the input file returned by the command for lines that match ". This line works fine for me…

BTW, I’m using version 5.0.12A on my ISY994 controller; I’m not sure if that makes a difference or not.

Alright, I managed to get this all working about a week ago. Posting about it here in case someone else runs into this issue (although, there doesn’t appear to be much demand).

My problem, it turns out, was two-fold. I ended up deleting the AWK command altogether, to try and decipher the issue, and that’s when I realized the XML that was being returned was just a 404 message. ultimately, i discovered that my ISY was returning this file not found message because I did not include %20 (in place of the spaces) in the device node address for my thermostat. I could have sworn that DetroitEE said that was not required, but for me, it apparently was.

Once I fixed that issue, it became obvious that {print $8} was not working for me, and returned the wrong values or segments of the XML. Turns out, {print $10} ended up being the right segment for me, for all entities. I can only assume that this is resulting from the difference in ISY firmware versions I am running (4.6.2 official) vs Madelinot (5.11-something beta). There must be some differences in the XML format between our two versions.

Anyway, just figured I’d explain my findings for anyone else that may run into this, if anyone. Start by deleting everything after the REST url, and see what XML comes back under entities. I had to highlight the text to see the entirety of the information, then figure out which segment to target {print} from there. Hope this helps someone else out, eventually. Thanks to you guys for trying to help me out along the way.

Sorry for any confusion; what I was trying to say in my original post is to enter the node address with the spaces into your browser, then use the re-formatted version with the %20 for your YAML files. Glad you got it figured out.

It was probably just me reading it wrong. I poured other this and your original post so many times trying to make things work, that I probably wasn’t even reading the right words anymore. But yeah, it’s working, finally. On to new problems, like Hadashboard RSS feeds.

I’m glad you figured it out…

Regards,