Trane XR524 with control of the Schedule-Run/Hold/ESM modes

I installed two Trane XR524 Model TZEMT524AA21MA. It’s great. I like that it can run a schedule on it’s own without any automation. But I realized, without further control, I could set the temp from HA, but if the schedule was running, it only would set a ‘temporary override’. So at the next schedule interval it would change again. I wanted to be able to set Hold like I could from the device itself.

First, I watched the Zwave log when I changed the modes on the device and it emitted config parameter value changes to param id’s 76 and 77. Open Zwave has these set to something else. So I modified the zwcfg*.xml under both thermostat nodes to:

				<Value type="list" genre="config" instance="1" index="76" label="Run Schedule" units="" read_only="false" write_only="false" verify_changes="false" poll_intensity="0" min="0" max="1" vindex="0" size="1">
					<Help>0=hold 1=run</Help>
					<Item label="Hold" value="0" />
					<Item label="Run" value="1" />
				<Value type="list" genre="config" instance="1" index="77" label="ESM Mode" units="" read_only="false" write_only="false" verify_changes="false" poll_intensity="0" min="0" max="2" vindex="0" size="1">
					<Help>0 = esm off, 2 esm on</Help>
					<Item label="Off" value="0" />
					<Item label="On" value="2" />

Now I could turn off the schedule from my phone. This left me with no feedback however of what the value actually was on the device. While OpenZwave logged the information, config parameter changes aren’t something that fires an event in HA.
With a LOT of help from the community, I ended up with a node-red flow that will track and modify these states with just a few input booleans setup.

I added a couple carefully named input booleans to my config. My thermostats are nodes 3 and 4.

    name: ds esm
    name: ds hold
    name: up sem
    name: up hold

[{"id":"1ba1a15b.b3001f","type":"server-state-changed","z":"b5543901.4c5968","name":"esm or hold toggle toggled","server":"d812fdda.9bbad","entityidfilter":"input_boolean.xc_(esm|hold)_\\d+","entityidfiltertype":"regex","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"","halt_if_compare":"is","outputs":1,"x":110,"y":80,"wires":[["145b7ed2.fac491"]]},{"id":"145b7ed2.fac491","type":"function","z":"b5543901.4c5968","name":"convert to svc params","func":"var regExp =/xc_(esm|hold)_(\\d+)/\nvar results = regExp.exec(;\nvar paramId = 0;\nvar theVal = \"\";\nvar nodeid = results[2];\nif (results[1]==\"esm\") {\n    paramId=77;\n    if (msg.payload==\"on\") {\n        theVal=\"On\";\n    } else {\n        theVal=\"Off\";\n    }\n}else {\n  if (results[1]==\"hold\") {\n    paramId=76;\n      if (msg.payload==\"on\") {\n        theVal=\"Hold\";\n    } else {\n        theVal=\"Run\";\n    }\n} else {\n    return null;\n}\n}\nmsg =  { \n    payload: {\n        domain: \"zwave\", \n        service: \"set_config_parameter\",\n        data: {\n            node_id: nodeid,\n            parameter: paramId,\n            value: theVal\n        }\n    }\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":460,"y":80,"wires":[["954469f6.871698"]]},{"id":"3fd81dd.7113ae2","type":"catch","z":"b5543901.4c5968","name":"","scope":null,"x":100,"y":40,"wires":[[]]},{"id":"954469f6.871698","type":"api-call-service","z":"b5543901.4c5968","name":"call it!","server":"d812fdda.9bbad","service_domain":"","service":"","data":"","render_data":false,"mergecontext":"","output_location":"payload","output_location_type":"msg","x":650,"y":120,"wires":[[]]},{"id":"511b76b3.5583e8","type":"tail","z":"b5543901.4c5968","name":"ozwlog tail","filetype":"text","split":true,"filename":"/config/OZW_Log.txt","x":60,"y":160,"wires":[["320d0fb8.4b689"]]},{"id":"320d0fb8.4b689","type":"switch","z":"b5543901.4c5968","name":"Only Config Reports","property":"payload","propertyType":"msg","rules":[{"t":"regex","v":"Info, Node\\d{3}, Received Configuration report","vt":"str","case":true}],"checkall":"true","repair":false,"outputs":1,"x":260,"y":160,"wires":[["26863c8b.fa61d4"]]},{"id":"26863c8b.fa61d4","type":"function","z":"b5543901.4c5968","name":"mkServiceCall","func":"var regExp =/Info, Node0*(\\d+), Received Configuration report: Parameter=(\\d+), Value=(\\d+)/\nvar results = regExp.exec(msg.payload);\nvar nodeId = results[1]; \nvar parmIndex= results[2]; \nvar parmValue= results[3];\nvar swtype=\"\";\nvar action=\"\";\nif (parmIndex==\"76\") {\n    swtype=\"hold\";\n    if (parmValue==\"0\") {\n            action=\"turn_on\";\n        } else {\n            action=\"turn_off\"\n        }\n} else {\n    if (parmIndex==\"77\"){\n        swtype=\"esm\";\n        if (parmValue==\"0\") {\n            action=\"turn_off\";\n        } else {\n            action=\"turn_on\"\n        }\n    }else {\n        return null;\n    }\n}\n\nvar entid=\"input_boolean.xc_\" + swtype + \"_\" + nodeId;\nmsg =  { \n    payload: {\n        domain: \"input_boolean\", \n        service: action,\n        data: {\n            entity_id: entid\n        }\n    }\n};\nreturn msg;","outputs":1,"noerr":0,"x":480,"y":160,"wires":[["954469f6.871698"]]},{"id":"d812fdda.9bbad","type":"server","z":"","name":"Home Assistant"}]

Next steps:

  • Should I submit a change to the openzwave configuration?
  • Discover the other config parameters true value/indexes. No clue how I’d do this without a spare thermostat and zwave controller to play with.
  • Combine the thermostat setpoints, and some template sensors to pretty up my dashboard.
  • …?

Dude this is great information – THANK YOU!

I’m currently testing a z-wave deployment at a resort in Destin, FL and have deployed these thermostats in the rooms. If you’re able to find additional parameters, like configuring scheduling and thermostat preferences, that would be awesome!!!

I’m in the process of moving my HA install from my raspberry pi to a docker container my server.

This should let me, I hope anyway: Shutdown HASSIO, Open up a openzwave control panel or pyopenzwave tool of some sort and just go thru each config item (1…254) until I can rule it out or make sense of it.

Now I just gotta learn how to query the device in a more adhoc manor.

Damn you Hometoast, I lost 4 hours of my life due to you! :slight_smile: I saw the work you did and I had to integrate this into my setup. I do not run Node-Red, so in order to work around that shortcoming I ended up running a script to tail the openzwave log and update a binary sensor via MQTT.

The money shot, this is the dual thermostat card, I wish we could get ESM and Run Scedule added to this:


  icon: mdi:leaf
  icon: mdi:calendar


  - platform: mqtt
    name: HVAC Run Schedule
    state_topic: "hvac/run_schedule"
    payload_on: "1"
    payload_off: "0"

  - platform: mqtt
    name: HVAC Energy Saver Mode
    state_topic: "hvac/esm"
    payload_on: "2"
    payload_off: "0"

  - platform: template
        value_template: "{{ is_state('binary_sensor.hvac_run_schedule', 'on') }}"
        friendly_name: 'Run Schedule'
          service: zwave.set_config_parameter
            node_id: 16
            parameter: 76
            value: "Run"
          service: zwave.set_config_parameter
            node_id: 16
            parameter: 76
            value: "Hold"

  - platform: template
        friendly_name: 'Energy Saving Mode'
        value_template: "{{ is_state('binary_sensor.hvac_energy_saver_mode', 'on') }}"
          service: zwave.set_config_parameter
            node_id: 16
            parameter: 77
            value: "On"
          service: zwave.set_config_parameter
            node_id: 16
            parameter: 77
            value: "Off"

ozw-to-mqtt script:

while read line; do
 if [[ $line =~ "Node016, Received Configuration report: Parameter=76, Value=" ]];
 then echo "$line" | sed 's/^.*Value=//' | mosquitto_pub -r -h localhost -u <USERNAME> -P <PASSWORD> -t hvac/run_schedule -l;
 fi ;
 if [[ $line =~ "Node016, Received Configuration report: Parameter=77, Value=" ]];
 then echo "$line" | sed 's/^.*Value=//' | mosquitto_pub -r -h localhost -u <USERNAME> -P <PASSWORD> -t hvac/esm -l;
 fi ;
 done  < <(tail -f /home/homeassistant/.homeassistant/OZW_Log.txt)

If you choose to go down this path, obviously you need to have a MQTT server running, I am using Mosquitto as the server and the publishing client. Second, be sure to change “Node016” to your Thermostat’s node ID, as well as the username and password.

This script tails the openzwave log, looks for the specific “Configuration Report” strings, cleans up the string, and sends it to the MQTT server. The “-r” value of mosquitto_pub sets the retain flag, so that after a restart of HASS the switch state should remain the same as it was prior to the restart.

The last thing I need to figure out is how to start the script in the background at system boot. I’ll follow up with that later.

Again, thanks Hometoast!

Edit: Here is how I made the script kick off at system startup on my Ubuntu 18.04 installation:

I placed my script file here:


Change ownership and mark it executable:

chown homeassistant:homeassistant /home/homeassistant/.homeassistant/ozw-to-mqtt
chmod +x /home/homeassistant/.homeassistant/ozw-to-mqtt

Create a systemd service file:

vi /etc/systemd/system/[email protected]

Add the following to the file:

Description=Openzwave log to MQTT
[email protected]



Enable the service:

systemctl enable [email protected]
systemctl daemon-reload

If you have any questions feel free to ask.


Great stuff! Thanks for sharing; I like how yours was handled with external scripts, but gets the exact same result.