Example - Venstar Wifi enabled Thermostat (Color touch) #530
glasscake
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
These are probably the best local wifi control thermostats I've found. a well-documented API that requires no internet access for any functionality. The Color Touch model can be found on eBay for around $100, cheaper than most smart thermostats. The Explorer model range is even cheaper. I only have experience with the color touch as of now.
This is an example of my flow for Venstar Local API Wifi enabled thermostats. There is blocking functionality to stop input loops. It also allows outside inputs and enables feedback to homekit when changes to the thermostat itself are made. There is a child fanv2 which allows you to set the fan from auto to on by pressing the smaller blue button on homekit. Note the larger toggle flip switch in the homekit app is just an indicator and does not change the fan state.
Local API functionality needs to be enabled on the thermostat and if a pin is used a pin needs to be provided as well.
https://venstar.com/thermostats/colortouch/
https://developer.venstar.com/documentation/
There are additional features not implemented in my flow like remote sensors, air quality sensors for the explorer IAQ, ext.
Example flow:
[ { "id": "a182e8c5380dd68d", "type": "http request", "z": "c16b8cbf103f2790", "name": "info", "method": "GET", "ret": "obj", "paytoqs": "ignore", "url": "http://xxx.xxx.xxx.xxx/query/info", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "credentials": {}, "x": 150, "y": 2180, "wires": [ [ "a8a22ccaa54c3b93" ] ] }, { "id": "306f49d46d5ff675", "type": "homekit-service", "z": "c16b8cbf103f2790", "isParent": true, "hostType": "0", "bridge": "", "accessoryId": "", "parentService": "", "name": "Centeral HVAC", "serviceName": "Thermostat", "topic": "", "filter": false, "manufacturer": "NRCHKB", "model": "1.5.0", "serialNo": "Default Serial Number", "firmwareRev": "1.5.0", "hardwareRev": "1.5.0", "softwareRev": "1.5.0", "cameraConfigVideoProcessor": "ffmpeg", "cameraConfigSource": "", "cameraConfigStillImageSource": "", "cameraConfigMaxStreams": 2, "cameraConfigMaxWidth": 1280, "cameraConfigMaxHeight": 720, "cameraConfigMaxFPS": 10, "cameraConfigMaxBitrate": 300, "cameraConfigVideoCodec": "libx264", "cameraConfigAudioCodec": "libfdk_aac", "cameraConfigAudio": false, "cameraConfigPacketSize": 1316, "cameraConfigVerticalFlip": false, "cameraConfigHorizontalFlip": false, "cameraConfigMapVideo": "0:0", "cameraConfigMapAudio": "0:1", "cameraConfigVideoFilter": "scale=1280:720", "cameraConfigAdditionalCommandLine": "-tune zerolatency", "cameraConfigDebug": false, "cameraConfigSnapshotOutput": "disabled", "cameraConfigInterfaceName": "", "characteristicProperties": "{\"TemperatureDisplayUnits\":1,\"CurrentRelativeHumidity\":0,\"CoolingThresholdTemperature\":85,\"HeatingThresholdTemperature\":70}", "waitForSetupMsg": false, "outputs": 2, "x": 740, "y": 2180, "wires": [ [ "6397fa647c8295e9" ], [] ] }, { "id": "3f82ddd925956d7e", "type": "function", "z": "c16b8cbf103f2790", "name": "Venstar to Homekit", "func": "\n//get correct target temp depending on hvac mode\n//save it to context so when switching form off to on it remembers\nif (msg.payload.mode == 1)\n{\n context.set('targttemp', msg.payload.heattemp)\n}\nelse if (msg.payload.mode == 2)\n{\n context.set('targttemp', msg.payload.cooltemp)\n}\nvar targtemp = context.get('targttemp')||75\n\n//do farh to cel conversion\nvar cooltresh = ((msg.payload.cooltemp - 32) * 5 / 9)\nvar heatthresh = ((msg.payload.heattemp - 32) * 5 / 9)\nvar curtemp = ((msg.payload.spacetemp - 32) * 5 / 9)\ntargtemp = ((targtemp - 32) * 5 / 9)\n\n//build the HVAC payload\nvar msghvac = {}\nmsghvac.payload = {\n \"CurrentRelativeHumidity\": msg.payload.hum,\n \"CoolingThresholdTemperature\": cooltresh,\n \"HeatingThresholdTemperature\": heatthresh,\n \"CurrentHeatingCoolingState\": msg.payload.state,\n \"TargetHeatingCoolingState\": msg.payload.mode,\n \"CurrentTemperature\": curtemp,\n \"TargetTemperature\": targtemp\n}\n\n//build the fan payload\nvar msgfan = {}\nmsgfan.payload = {\n \"Active\": msg.payload.fanstate,\n \"TargetFanState\": msg.payload.fan\n}\n\nreturn [msghvac,msgfan];", "outputs": 2, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 530, "y": 2180, "wires": [ [ "306f49d46d5ff675" ], [ "c43d4ee68c67c355" ] ] }, { "id": "fa728eb5e36a3654", "type": "homekit-status", "z": "c16b8cbf103f2790", "serviceNodeId": "c968cad40bdb3904", "name": "HVAC Status", "outputs": 1, "x": 1170, "y": 2180, "wires": [ [ "1f58139789715846" ] ] }, { "id": "17978a93f461afd0", "type": "homekit-status", "z": "c16b8cbf103f2790", "serviceNodeId": "28c4858590dde16b", "name": "Auto Fan Status", "outputs": 1, "x": 1160, "y": 2220, "wires": [ [ "1cf524ee424104e7" ] ] }, { "id": "1cf524ee424104e7", "type": "function", "z": "c16b8cbf103f2790", "name": "filter", "func": "\n//venstar 1 = manual\n//homekit 1 = \"auto\" (blue dot is lit.) using \"manual\" (blue dot not lit) as auto mode\nvar msg1 = {}\nmsg1.payload = {\n \"msgtype\": 1,\n \"fan\": msg.payload.characteristics[2].value\n }\nreturn msg1;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1310, "y": 2220, "wires": [ [ "2a03ba88bf57a644", "251ff87ba0d4f7eb" ] ] }, { "id": "1f58139789715846", "type": "function", "z": "c16b8cbf103f2790", "name": "filter", "func": "\n\nvar msg1 = {}\nmsg1.payload = {\n //hvac message\n \"msgtype\": 0,\n //Target Heating Cooling State\n \"mode\": msg.payload.characteristics[2].value,\n //Current Heating Cooling State\n \"state\": msg.payload.characteristics[1].value,\n //Target Temperature\n \"targettemp\": (msg.payload.characteristics[4].value * 1.8 + 32).toFixed(2),\n //Cooling Threshold Temperature\n \"cooltemp\": (msg.payload.characteristics[7].value * 1.8 + 32).toFixed(2),\n //Heating Threshold Temperature\n \"heattemp\": (msg.payload.characteristics[8].value * 1.8 + 32).toFixed(2)\n }\nreturn msg1;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1310, "y": 2180, "wires": [ [ "2a03ba88bf57a644", "251ff87ba0d4f7eb" ] ] }, { "id": "c43d4ee68c67c355", "type": "homekit-service", "z": "c16b8cbf103f2790", "isParent": false, "hostType": "0", "bridge": "", "accessoryId": "", "parentService": "c968cad40bdb3904", "name": "Centeral Fan", "serviceName": "Fanv2", "topic": "", "filter": false, "manufacturer": "NRCHKB", "model": "1.5.0", "serialNo": "Default Serial Number", "firmwareRev": "1.5.0", "hardwareRev": "1.5.0", "softwareRev": "1.5.0", "cameraConfigVideoProcessor": "ffmpeg", "cameraConfigSource": "", "cameraConfigStillImageSource": "", "cameraConfigMaxStreams": 2, "cameraConfigMaxWidth": 1280, "cameraConfigMaxHeight": 720, "cameraConfigMaxFPS": 10, "cameraConfigMaxBitrate": 300, "cameraConfigVideoCodec": "libx264", "cameraConfigAudioCodec": "libfdk_aac", "cameraConfigAudio": false, "cameraConfigPacketSize": 1316, "cameraConfigVerticalFlip": false, "cameraConfigHorizontalFlip": false, "cameraConfigMapVideo": "0:0", "cameraConfigMapAudio": "0:1", "cameraConfigVideoFilter": "scale=1280:720", "cameraConfigAdditionalCommandLine": "-tune zerolatency", "cameraConfigDebug": false, "cameraConfigSnapshotOutput": "disabled", "cameraConfigInterfaceName": "", "characteristicProperties": "{\"TargetFanState\":1}", "waitForSetupMsg": false, "outputs": 2, "x": 730, "y": 2240, "wires": [ [ "6397fa647c8295e9" ], [] ] }, { "id": "b0bf975f369e3b28", "type": "inject", "z": "c16b8cbf103f2790", "name": "", "props": [ { "p": "payload" } ], "repeat": "1", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 170, "y": 2020, "wires": [ [ "c3c94fed433f8c99" ] ] }, { "id": "2a03ba88bf57a644", "type": "function", "z": "c16b8cbf103f2790", "name": "Save State Locally", "func": "\n\nif (msg.payload.msgtype == 0)\n{\n //HVAC message\n context.set('mode', msg.payload.mode)\n context.set('state', msg.payload.state)\n context.set('targettemp', msg.payload.targettemp)\n context.set('cooltemp', msg.payload.cooltemp)\n context.set('heattemp', msg.payload.heattemp)\n return null\n}\n\nif (msg.payload.msgtype == 1)\n{\n //fan status message\n context.set('fan', msg.payload.fan)\n return null\n}\n\nmsg.payload = {\n 'mode':context.get('mode'),\n 'state': context.get('state'),\n 'targettemp': context.get('targettemp'),\n 'cooltemp': context.get('cooltemp'),\n 'heattemp' : context.get('heattemp'),\n 'fan': context.get('fan')\n}\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1650, "y": 2200, "wires": [ [ "17d1ce7fddc63b56" ] ] }, { "id": "17d1ce7fddc63b56", "type": "function", "z": "c16b8cbf103f2790", "name": "What setpoint?", "func": "\nif (msg.payload.mode == 1)\n{\n //heating mode\n msg.payload.heattemp = msg.payload.targettemp\n}\nelse if (msg.payload.mode == 2)\n{\n //cooling mode\n msg.payload.cooltemp = msg.payload.targettemp\n}\n\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1840, "y": 2200, "wires": [ [ "d71789287572e7c3", "848b42034aa075c7" ] ] }, { "id": "848b42034aa075c7", "type": "function", "z": "c16b8cbf103f2790", "name": "create url query", "func": "\n//create our long string\nvar builder\nbuilder = \"http://xxx.xxx.xxx.xxx/control?\"\nbuilder = builder + \"mode=\" + String(msg.payload.mode)\nbuilder = builder + \"&state=\" + String(msg.payload.state)\nbuilder = builder + \"&cooltemp=\" + String(msg.payload.cooltemp)\nbuilder = builder + \"&heattemp=\" + String(msg.payload.heattemp)\nbuilder = builder + \"&fan=\" + String(msg.payload.fan)\nmsg.url = builder\n\n\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 660, "y": 2320, "wires": [ [ "c1a7c1ab0430cdb7", "48ff2d52f54aa227" ] ] }, { "id": "c1a7c1ab0430cdb7", "type": "http request", "z": "c16b8cbf103f2790", "name": "", "method": "POST", "ret": "obj", "paytoqs": "ignore", "url": "", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "credentials": {}, "x": 890, "y": 2340, "wires": [ [ "391a52c96d73ee96", "b71d7cfc398e5e2c" ] ] }, { "id": "391a52c96d73ee96", "type": "debug", "z": "c16b8cbf103f2790", "name": "Response", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1120, "y": 2320, "wires": [] }, { "id": "48ff2d52f54aa227", "type": "debug", "z": "c16b8cbf103f2790", "name": "URL", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "url", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 890, "y": 2300, "wires": [] }, { "id": "d71789287572e7c3", "type": "debug", "z": "c16b8cbf103f2790", "name": "Json Settings", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1860, "y": 2140, "wires": [] }, { "id": "cbe8289b2fdcee15", "type": "link in", "z": "c16b8cbf103f2790", "name": "link in 20", "links": [ "c3c94fed433f8c99" ], "x": 55, "y": 2180, "wires": [ [ "a182e8c5380dd68d" ] ] }, { "id": "c3c94fed433f8c99", "type": "link out", "z": "c16b8cbf103f2790", "name": "link out 6", "mode": "link", "links": [ "cbe8289b2fdcee15" ], "x": 295, "y": 2020, "wires": [] }, { "id": "251ff87ba0d4f7eb", "type": "trigger", "z": "c16b8cbf103f2790", "name": "", "op1": "", "op2": "", "op1type": "nul", "op2type": "date", "duration": "20", "extend": true, "overrideDelay": false, "units": "ms", "reset": "", "bytopic": "all", "topic": "topic", "outputs": 1, "x": 1470, "y": 2120, "wires": [ [ "2a03ba88bf57a644" ] ] }, { "id": "49e248aefe68e77d", "type": "inject", "z": "c16b8cbf103f2790", "name": "", "props": [ { "p": "payload" } ], "repeat": "5", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 810, "y": 2040, "wires": [ [ "fa728eb5e36a3654", "17978a93f461afd0" ] ] }, { "id": "fcbe87067b365e25", "type": "status", "z": "c16b8cbf103f2790", "name": "", "scope": [ "fa728eb5e36a3654", "17978a93f461afd0" ], "x": 340, "y": 2120, "wires": [ [ "a8a22ccaa54c3b93" ] ] }, { "id": "a8a22ccaa54c3b93", "type": "function", "z": "c16b8cbf103f2790", "name": "Block", "func": "//block all messages until the device has updated itself\n//this also stops loops and other odd issues\n\n\n//is this a status message?\ntry{\n if (msg.status.text == \"Done\")\n {\n //that means the homekit service is sending data to the thermostat\n //block all messages untill the thermostat responds\n context.set('blocking', true)\n node.status({ fill: \"red\", shape: \"dot\", text: \"blocking\" });\n return null\n }\n if(msg.status != null)\n {\n return null\n }\n}\ncatch{}\n\ntry{\n if (msg.payload.success != null)\n {\n //the respponse from the thermostat unblock the message.\n context.set('blocking', false)\n node.status({ fill: \"green\", shape: \"dot\", text: \"passing\" });\n //throw an error if the thermostat failed for some reason\n if (msg.payload.success != true)\n {\n node.error(\"thermostat could not update settings\", msg);\n node.status({ fill: \"red\", shape: \"dot\", text: \"Failed to Update\" });\n }\n return null\n }\n}\ncatch {}\n\n//get the current blocking state\nvar blocking = context.get('blocking')\n\nif (blocking == true)\n{\n return null\n}\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 350, "y": 2180, "wires": [ [ "3f82ddd925956d7e" ] ] }, { "id": "b71d7cfc398e5e2c", "type": "delay", "z": "c16b8cbf103f2790", "name": "", "pauseType": "delay", "timeout": "1", "timeoutUnits": "seconds", "rate": "1", "nbRateUnits": "1", "rateUnits": "second", "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", "drop": false, "allowrate": false, "outputs": 1, "x": 340, "y": 2340, "wires": [ [ "a8a22ccaa54c3b93" ] ] }, { "id": "6397fa647c8295e9", "type": "change", "z": "c16b8cbf103f2790", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "", "tot": "date" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 940, "y": 2220, "wires": [ [ "fa728eb5e36a3654", "17978a93f461afd0" ] ] } ]
Beta Was this translation helpful? Give feedback.
All reactions