Zont to HomeKit Gateway
Gateway between Microline Zont Thermostat (https://zont-online.ru) and Apple Homekit.
It allows you to:
- translate temperature sensors readings
- update and control you thermostat:
- read temperature
- set temperature
- set mode
- Use Siri to control your Zont
Modes mapping:
HomeKit Heating <-> ZONT Comfort
HomeKit Cooling <-> ZONT Economy
HomeKit Auto <-> ZONT Schedule
HomeKit Off <-> ZONT Anti-freezing
Requirements:
- node-red-contrib-homekit
- node-red-contrib-jsonpath
Brief manual:
- install node-red
- install requirements
- import flow
- configure auth settings (login & password) for "make DEVICES request" and "make UPDATE request" nodes
- configure your thermometers - create HomeKit Temperature Sensors and attach them to the Switch. Use the names same as in your ZONT profile.
- ask me if it's not working :]
TODO
- Fix device discovery. Now only one ZONT H-1V is supported.
- Boiler Alert support. Needs to support boiler error alerts.
[{"id":"7c7cc80f.76d888","type":"tab","label":"Zont GW"},{"id":"1761d462.769304","type":"http request","z":"7c7cc80f.76d888","name":"make DEVICES request","method":"POST","ret":"obj","url":"https://zont-online.ru/api/devices","tls":"","x":181.5,"y":344,"wires":[["a897aa83.0bcd6","fddc8c2.fa6f67","7bd9fd85.63f1d4","336a79a.9230786"]]},{"id":"495cf5db.61050c","type":"inject","z":"7c7cc80f.76d888","name":"Get periodical ZONT Update","topic":"","payload":"{\"load_io\": true}","payloadType":"json","repeat":"60","crontab":"","once":true,"x":208.5,"y":169,"wires":[["b5c29fb9.87983"]]},{"id":"b5c29fb9.87983","type":"function","z":"7c7cc80f.76d888","name":"Add ZONT header","func":"msg.headers = {\n \"Content-type\" : \"application/json\",\n \"X-ZONT-Client\" : \"[email protected]\"\n }\n\nreturn msg;\n","outputs":1,"noerr":0,"x":161.5,"y":251,"wires":[["1761d462.769304"]]},{"id":"4d308236.72aa84","type":"homekit-service","z":"7c7cc80f.76d888","accessory":"d89c95f5.b65cf8","name":"room","serviceName":"TemperatureSensor","x":1485.5,"y":173,"wires":[[]]},{"id":"a897aa83.0bcd6","type":"jsonpath","z":"7c7cc80f.76d888","expression":"$.devices[?(@.device_type.code=='T100')].thermometers[?(@.is_assigned_to_slot==true && @.last_state=='ok')]","split":true,"name":"Gather temperature sensors from ZONT","x":600.5,"y":272,"wires":[["45c44f4a.d9ac38"]]},{"id":"343744eb.740a4c","type":"switch","z":"7c7cc80f.76d888","name":"Choose right sensor","property":"payload.Name","propertyType":"msg","rules":[{"t":"eq","v":"room","vt":"str"},{"t":"eq","v":"Котел подача","vt":"str"},{"t":"eq","v":"Котел обратка","vt":"str"},{"t":"eq","v":"ТП подача","vt":"str"},{"t":"eq","v":"ТП обратка","vt":"str"},{"t":"eq","v":"Улица","vt":"str"}],"checkall":"false","outputs":6,"x":1206.5,"y":276,"wires":[["4d308236.72aa84"],["9f6e2b6.bb8bc58"],["2dc40a06.e50dbe"],["805024f4.4dc69"],["50763fa1.d66f28"],["5b80df79.4f77"]]},{"id":"45c44f4a.d9ac38","type":"function","z":"7c7cc80f.76d888","name":"Create update JSON","func":"msg.payload = {\n \"Name\": msg.payload.name,\n \"CurrentTemperature\": msg.payload.last_value,\n \"StatusActive\": true,\n}\nreturn msg","outputs":1,"noerr":0,"x":924,"y":274,"wires":[["343744eb.740a4c"]]},{"id":"9f6e2b6.bb8bc58","type":"homekit-service","z":"7c7cc80f.76d888","accessory":"d89c95f5.b65cf8","name":"Котел подача","serviceName":"TemperatureSensor","x":1518,"y":225,"wires":[[]]},{"id":"2dc40a06.e50dbe","type":"homekit-service","z":"7c7cc80f.76d888","accessory":"d89c95f5.b65cf8","name":"Котел обратка","serviceName":"TemperatureSensor","x":1517,"y":273,"wires":[[]]},{"id":"805024f4.4dc69","type":"homekit-service","z":"7c7cc80f.76d888","accessory":"d89c95f5.b65cf8","name":"ТП подача","serviceName":"TemperatureSensor","x":1509,"y":326,"wires":[[]]},{"id":"50763fa1.d66f28","type":"homekit-service","z":"7c7cc80f.76d888","accessory":"d89c95f5.b65cf8","name":"ТП обратка","serviceName":"TemperatureSensor","x":1511,"y":385,"wires":[[]]},{"id":"5b80df79.4f77","type":"homekit-service","z":"7c7cc80f.76d888","accessory":"d89c95f5.b65cf8","name":"Улица","serviceName":"TemperatureSensor","x":1500,"y":440,"wires":[[]]},{"id":"fddc8c2.fa6f67","type":"jsonpath","z":"7c7cc80f.76d888","expression":"$.devices[?(@.device_type.code=='T100')].thermostat_mode","split":true,"name":"Gather thermostat mode","x":579,"y":453,"wires":[["12b76895.7f7b5f"]]},{"id":"7bd9fd85.63f1d4","type":"jsonpath","z":"7c7cc80f.76d888","expression":"$.devices[?(@.device_type.code=='T100')].thermostat_mode_temps","split":true,"name":"Gather thermostat temps","x":577,"y":512,"wires":[["8ad82210.0f4a48"]]},{"id":"12b76895.7f7b5f","type":"function","z":"7c7cc80f.76d888","name":"Update thermostat with the current mode","func":"switch(msg.payload) {\n case \"comfort\":\n flow.set('thermostat_mode', msg.payload)\n msg.payload = {\n \"CurrentHeatingCoolingState\": 1,\n \"TargetHeatingCoolingState\": 1,\n }\n break;\n case \"econom\":\n flow.set('thermostat_mode', msg.payload)\n msg.payload = {\n \"CurrentHeatingCoolingState\": 2,\n \"TargetHeatingCoolingState\": 2,\n }\n break;\n case \"idle\":\n flow.set('thermostat_mode', msg.payload)\n msg.payload = {\n \"CurrentHeatingCoolingState\": 0,\n \"TargetHeatingCoolingState\": 0,\n }\n break;\n case \"schedule\":\n flow.set('thermostat_mode', msg.payload)\n msg.payload = {\n \"CurrentHeatingCoolingState\": 3,\n \"TargetHeatingCoolingState\": 3,\n }\n break;\n}\n\nreturn msg","outputs":1,"noerr":0,"x":899.5554809570312,"y":454,"wires":[["dccf9d02.c47d08"]]},{"id":"8ad82210.0f4a48","type":"function","z":"7c7cc80f.76d888","name":"Update thermostat teperature","func":"// save thermostat temperatures\nflow.set('thermostat_temperatures', msg.payload); \n\nvar thermostat_mode = flow.get('thermostat_mode');\n\nif (typeof thermostat_mode !== 'undefined') {\n \n msg.payload = {\n \"CurrentTemperature\": msg.payload[thermostat_mode],\n \"TargetTemperature\": msg.payload[thermostat_mode],\n };\n\n return msg; \n}\n","outputs":1,"noerr":0,"x":862,"y":510.888916015625,"wires":[["dccf9d02.c47d08"]]},{"id":"ddd821ad.69ea1","type":"function","z":"7c7cc80f.76d888","name":"Prepare update","func":"// check if HomeKit is initialized\nvar HomeKitInitialized = flow.get('HomeKitInitialized') || false;\nif (HomeKitInitialized === false) {\n // catch first message from Thermostat and supress it, not updating ZONT\n flow.set('HomeKitInitialized', true);\n return null;\n \n}\n\n// preserve parameters which came from Thermostat to do instantaneous updates \nmsg.data = {};\n\nreply_json = {};\n\nreply_json.device_id = flow.get('deviceid');\n\n// gather thermostat mode from flow context\nvar thermostat_mode = flow.get('thermostat_mode');\n\n// check if we have updated thermostat mode came from HomeKit\nif (typeof msg.payload.TargetHeatingCoolingState !== 'undefined') {\n\n msg.data.CurrentHeatingCoolingState = msg.payload.TargetHeatingCoolingState; \n\n // now we have updated mode\n switch(msg.payload.TargetHeatingCoolingState) {\n case 0:\n thermostat_mode = 'idle';\n \n break;\n case 1:\n thermostat_mode = 'comfort';\n break;\n case 2:\n thermostat_mode = 'econom';\n break;\n case 3:\n thermostat_mode = 'schedule';\n break;\n }\n\n // update thermostat mode in flow context\n flow.set('thermostat_mode', thermostat_mode);\n\n reply_json.thermostat_mode = thermostat_mode;\n \n msg.payload = reply_json;\n return msg;\n \n}\n\n// check if we have updated temperature\nif (typeof msg.payload.TargetTemperature !== 'undefined') {\n \n\n if (thermostat_mode != 'schedule') {\n \n // we should not update a temperature in schedule mode\n reply_json.thermostat_mode_temps = {};\n reply_json.thermostat_mode_temps[thermostat_mode] = msg.payload.TargetTemperature;\n \n msg.data.CurrentTemperature = msg.payload.TargetTemperature; \n\n msg.payload = reply_json;\n return msg;\n \n }\n}\n\nreturn null;\n","outputs":"1","noerr":0,"x":559,"y":735.7777709960938,"wires":[["ee51c829.b17808"]]},{"id":"ee51c829.b17808","type":"function","z":"7c7cc80f.76d888","name":"Add ZONT header","func":"msg.headers = {\n \"Content-type\" : \"application/json\",\n \"X-ZONT-Client\" : \"[email protected]\"\n }\n\nreturn msg;\n","outputs":1,"noerr":0,"x":790,"y":734,"wires":[["d3052c22.a1491"]]},{"id":"d3052c22.a1491","type":"http request","z":"7c7cc80f.76d888","name":"make UPDATE request","method":"POST","ret":"obj","url":"https://zont-online.ru/api/update_device","tls":"","x":1015,"y":735,"wires":[["a1037d30.9a7d3"]]},{"id":"336a79a.9230786","type":"jsonpath","z":"7c7cc80f.76d888","expression":"$.devices[?(@.device_type.code=='T100')].id","split":true,"name":"Gather device id for ZONT","x":561,"y":175,"wires":[["22fe7853.6d27e8"]]},{"id":"22fe7853.6d27e8","type":"function","z":"7c7cc80f.76d888","name":"save device id into context","func":"flow.set('deviceid', msg.payload);\n","outputs":1,"noerr":0,"x":863.5,"y":164,"wires":[[]]},{"id":"dccf9d02.c47d08","type":"join","z":"7c7cc80f.76d888","name":"","mode":"custom","build":"merged","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","timeout":"1","count":"","x":1188.5,"y":512,"wires":[["f2912f77.e1acc8"]]},{"id":"44e322da.9da44c","type":"join","z":"7c7cc80f.76d888","name":"","mode":"custom","build":"merged","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","timeout":"1","count":"","x":1808.5,"y":608,"wires":[["ddd821ad.69ea1"]]},{"id":"acfc65cd.2d7588","type":"function","z":"7c7cc80f.76d888","name":"check HomeKit Initialization","func":"// We don't want to delay messages after HomeKit initialization,\n// thus setting up msg.complete flag\n\n// check if HomeKit is initialized\nvar HomeKitInitialized = flow.get('HomeKitInitialized') || false;\nif (HomeKitInitialized === true) {\n msg.complete = true;\n}\nreturn msg;","outputs":1,"noerr":0,"x":1571.5,"y":611,"wires":[["44e322da.9da44c"]]},{"id":"a1037d30.9a7d3","type":"function","z":"7c7cc80f.76d888","name":"update thermostat","func":"if (msg.payload[\"ok\"] === true) {\n // updating was successful, updating thermostat\n msg.payload = msg.data;\n \n // if we've updated thermostat mode we need to tune HomeKit thermostat temperature as well\n if (typeof msg.data.CurrentHeatingCoolingState !== 'undefined') {\n \n switch(msg.data.CurrentHeatingCoolingState) {\n case 0:\n msg.payload.CurrentTemperature = flow.get('thermostat_temperatures').idle;\n break;\n case 1:\n msg.payload.CurrentTemperature = flow.get('thermostat_temperatures').comfort;\n break;\n case 2:\n msg.payload.CurrentTemperature = flow.get('thermostat_temperatures').econom;\n break;\n }\n \n }\n \n msg.payload.TargetTemperature = msg.payload.CurrentTemperature;\n \n return msg;\n\n}\n\nreturn null;","outputs":1,"noerr":0,"x":1274.5,"y":736,"wires":[["f2912f77.e1acc8"]]},{"id":"f2912f77.e1acc8","type":"homekit-service","z":"7c7cc80f.76d888","accessory":"d89c95f5.b65cf8","name":"ZONT","serviceName":"Thermostat","x":1346.5,"y":612,"wires":[["acfc65cd.2d7588"]]},{"id":"bffa9d3.e530be","type":"comment","z":"7c7cc80f.76d888","name":"TODO","info":"1. Fix device discovery\nNow only one ZONT H-1V is supported.\n\n2. Boiler Alert support\nNeed to support boiler error alerts\n","x":122.5,"y":66,"wires":[]},{"id":"d89c95f5.b65cf8","type":"homekit-accessory","z":"","accessoryName":"Zont","pinCode":"111-11-111","port":"","manufacturer":"[email protected]","model":"20161216","serialNo":"1"}]