eQ-3 MAX! integration

honestly i have a lot of doubts you can use windows contact sensor with this integration. Quite sure the official integration behaves the same (since i did not touch the update part).
As i wrote above (eQ-3 MAX! integration - #23 by MassiPi) to have a different refresh time you shoul write a proper coordinator. And it would be in any case a poll refresh, not a push from the sensors. My workaround for climate entities is to call a refresh after a command is sent, but i have no windows sensor so that’s not an issue for me (i have aqara windows sensors)

Hi,
thanks for the quick reply. I will once more read through your postings. My difficulty is to understand what you actually mean with the coordinator. Is this a term used for a specific kind of implementation of a integration?
Same for the “refresh after a command is sent”. Does this mean, that in your integration of the climate you have called a refresh command or function, after the integration has sent a command TO the cube?
Because in the case of a window contact/sensor, at least to my understanding this is not an option, because the communication is always the other way around…
Nevertheless, I will try to dig a bit deeper and maybe you will provide a response to help me to get on the right track. :smiley:
BR

I think I have found what you have referred to as a refresh…
line 381 in maxcube/cube.py

#trigger an update
self.update() code here

is this correct?

The question is where is the main update function called, by whom on which cycle?
Because this seems to be the reason for not having faster update rates… in my humble opinion.

@MassiPi after reading a bit through the documentation of integrations, I believe I at least have a basic understanding, of what you are telling.
Maybe within the next days or weeks I will give it a try and put in a basic coordinator and test it.
My main misunderstanding was, that scan_interval is NOT the update_interval…

you totally got it.
The only part that is not correct of what you wrote is this:

Wrong. it SHOULD be (push and not poll). But with MAX system you can’t, because the information is sent from the sensor to the cube and the cube has no method to push the info somewhere. You can get the info later (when you refresh the cube), not just when it happens. So if you plan to use it for heating (what it is intended to be: turn down thermostats when windows are open) the cube is managing it and you can afford a “lagged” info of the sensor, but if you plan to use it for other purposes time-sensitive (alarm?) i assume you should change the system :slight_smile:

yes, coordinator is something connected with HA integrations. The MAX integration is as old as hell and does not have a proper coordinator, for my use case it is working ok how it is today (with the custom refresh as soon as i send a cube command), but if you want to have some fun coding i’m with you lol

probably the integration would need a full rewrite (async based, for example), a lot of work and probably there are at maximun 10 guys out there still using MAX :slight_smile:

Hi.
Thanks for the reply. The system is to my taste still good enough. I don’t want wifi or bluetooth devices to take over the job. That is totally oversized for my small setup.
My use-case is something maybe closer to the alarm. I want to count how often (and maybe long) the fridge is opened during a day. I want to correlate the data with the energy consumption of the fridge. It is more a fun “side-project” than a real life serious use-case… I admit.
Coming back to the technical aspect of our thread:
For sure the CUBE must be polled, but to my knowledge, as well as for the button, the communication is initiated by the device (and not the CUBE). That is different to sending a new temperature set value to a valve. Or am I wrong?
Anyhow, I guess I will start modifying the existing integration a bit, and maybe I am lucky. A complete rewrite is not what I am starting. :smiley:
PS: HA counts 200 active installations of the original integration… there is still some hope.

i don’t trust that number: the original integration is totally bugged, noone can be using it…
for the push/poll, you are right that it is the window sensor to initiate the connection to the cube to send data, your problem is that you in any case need to poll the cube, so the fact that the sensor is pushing data to the cube for you is totally transparent. You’ll read window open only if it lasts less than your reading period.
however, if you can manage to write a coordinator, since the integration has static connection with the cube you can poll data every second or so, probably good enough for your use case

1 Like

Hi, trying to install your eq-3 integration. Everything looks fine except that when I try to change a thermostat value in HA, nothing happens and after a few seconds it switches back to its original value.

Here is what I can see in the logs :

2024-09-15 11:49:50.677 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.cube] Setting temperature 18.0 and mode 1 on device 1A7EA8! Room 0C - starting device mode 1 (command: 0004400000001A7EA80C64)
2024-09-15 11:49:50.679 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.connection] readline timed out
2024-09-15 11:49:50.679 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.connection] sent: s:AARAAAAAGn6oDGQ=
2024-09-15 11:49:50.835 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.connection] received: S:00,0,31
2024-09-15 11:49:50.835 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.commander] Radio message s:AARAAAAAGn6oDGQ= was sent [DutyCycle:00, StatusCode:0, FreeSlots:31]
2024-09-15 11:49:50.837 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.connection] readline timed out
2024-09-15 11:49:50.838 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.connection] sent: l:
2024-09-15 11:49:50.936 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.connection] received: L:Cxp+QvEaGQ4oANcACxp+/0AaGVAuAOQACxp+pAMaGQ4qAOYACxqBMPEaGQAqAO4ACxp+qAMaCQAjAAAACxp+nUAaGRgqAOMACxp+pwMaGR4qAOIACxqCGwMaGQAjAAAACxp+jQMaGQAjAAAACxp/CAMaGQAmAAAACxp/A0AaGQ0oANYACxp+PwMaGRgqAOcACxp+PEAaGUUuAOoACxqCyAMaGWQuAAAACxp/GAMaGRgqAOIA
2024-09-15 11:49:50.936 DEBUG (SyncWorker_28) [custom_components.maxcube.maxcube.cube] Parsing l_message: Cxp+QvEaGQ4oANcACxp+/0AaGVAuAOQACxp+pAMaGQ4qAOYACxqBMPEaGQAqAO4ACxp+qAMaCQAjAAAACxp+nUAaGRgqAOMACxp+pwMaGR4qAOIACxqCGwMaGQAjAAAACxp+jQMaGQAjAAAACxp/CAMaGQAmAAAACxp/A0AaGQ0oANYACxp+PwMaGRgqAOcACxp+PEAaGUUuAOoACxqCyAMaGWQuAAAACxp/GAMaGRgqAOIA

How can I correct this ? Thanks

it’s strangely timeoutting on receive (i assume from error message), even then the cube is returning che correct feedback (che S: message is saying the cube received and worked the message), that’s very strange
Would you try editing in connection.py file, line 34, from this

self.__socket.settimeout(deadline.remaining(lower_bound=0.001))

to something like this (just a try)

self.__socket.settimeout(deadline.remaining(lower_bound=0.1))

or

self.__socket.settimeout(deadline.remaining(lower_bound=0.5))

0.1 or 0.5 both give the same result as 0.001

Hi, any hints on how to go more in-depth on this bug?

As i had lots of issue with official MAX! integration, i’ve made this rednode flow that works since 2 years i think.

[{"id":"fb5296f3c4de2a23","type":"subflow","name":"Create HA Helpers","info":"","category":"HA Actions","in":[{"x":84,"y":96,"wires":[{"id":"fc18bdbb57e2fd63"}]}],"out":[],"env":[{"name":"serverName","type":"str","value":"Home Assistant","ui":{"label":{"en-US":"HA Server Name"},"type":"input","opts":{"types":["str"]}}},{"name":"helpers","type":"json","value":"[]","ui":{"label":{"en-US":"Helpers"},"type":"input","opts":{"types":["json"]}}}],"meta":{},"color":"#46B1EF","status":{"x":246,"y":48,"wires":[{"id":"ea6dc8ad6d9ece17","port":0}]}},{"id":"fc18bdbb57e2fd63","type":"function","z":"fb5296f3c4de2a23","name":"process helpers","func":"const serverName = toCamelCase(env.get('serverName'));\nconst haServer = global.get(\"homeassistant\")[serverName];\nif(!haServer) {\n    node.error(\"Invalid HA server name\");\n    return;\n}\nconst states = haServer.states;\nconst helpers = env.get(\"helpers\");\n\nhelpers.forEach(h => {\n    const entityId = `${h.type}.${h.id}`;\n    if(!states[entityId]) {\n        const {id, type, ...data} = h;\n        const apiData = {\n            entity: h,\n            payload: { \n                data \n            }\n        };\n        apiData.payload.data.type = `${type}/create`;\n    \n        node.send(apiData);\n        node.status({text: `Creating ${entityId}`});\n    }\n});\n\nnode.done();\n\nfunction toCamelCase(str) {\n    return str.replace(/(?:^\\w|[A-Z]|\\b\\w|\\s+)/g, (match, index) => {\n        if (+match === 0) return '';\n        return index === 0 ? match.toLowerCase() : match.toUpperCase();\n    });\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":224,"y":96,"wires":[["2b8e82c6d93d38e5"]]},{"id":"2b8e82c6d93d38e5","type":"ha-api","z":"fb5296f3c4de2a23","name":"create helper","server":"e89e2dad.1bf6","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"","dataType":"json","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":390,"y":96,"wires":[["767624d22ee0c819"]]},{"id":"372232586f5e138f","type":"ha-api","z":"fb5296f3c4de2a23","name":"rename helper entity id","server":"e89e2dad.1bf6","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\t   \"type\":\"config/entity_registry/update\",\t   \"entity_id\": entity.type & \".\" & payload.id,\t   \"new_entity_id\": entity.type & \".\" & entity.id\t}","dataType":"jsonata","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":788,"y":96,"wires":[[]]},{"id":"767624d22ee0c819","type":"switch","z":"fb5296f3c4de2a23","name":"need to rename?","property":"payload.id","propertyType":"msg","rules":[{"t":"neq","v":"entity.id","vt":"msg"}],"checkall":"true","repair":false,"outputs":1,"x":570,"y":96,"wires":[["372232586f5e138f","815fe31e9d239e53"]]},{"id":"ea6dc8ad6d9ece17","type":"status","z":"fb5296f3c4de2a23","name":"","scope":["fc18bdbb57e2fd63","815fe31e9d239e53"],"x":124,"y":48,"wires":[[]]},{"id":"815fe31e9d239e53","type":"function","z":"fb5296f3c4de2a23","name":"rename status","func":"const oldEntityId = `${msg.entity.type}.${msg.payload.id}`;\nconst newEntityId = `${msg.entity.type}.${msg.entity.id}`;\n\nnode.status({text: `Renaming ${oldEntityId} to ${newEntityId}`});","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":768,"y":144,"wires":[[]]},{"id":"bae136fa5a77790e","type":"subflow","name":"On Restart","info":"","category":"","in":[],"out":[{"x":320,"y":320,"wires":[{"id":"c67631f89e6451f4","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"4a91394f75614da1","type":"server-events","z":"bae136fa5a77790e","name":"","server":"e89e2dad.1bf6","version":3,"exposeAsEntityConfig":"","eventType":"home_assistant_client","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"}],"x":140,"y":220,"wires":[["95e9d47ed2be5007"]]},{"id":"95e9d47ed2be5007","type":"switch","z":"bae136fa5a77790e","name":"running?","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"running","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":352,"y":220,"wires":[["4b7a32290de02cf7"]]},{"id":"648dd9ac108befdc","type":"api-call-service","z":"bae136fa5a77790e","name":"","server":"e89e2dad.1bf6","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.home_assistant_restarted"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":758,"y":220,"wires":[["3754cbf709193071"]]},{"id":"4b7a32290de02cf7","type":"api-current-state","z":"bae136fa5a77790e","name":"has restarted?","server":"e89e2dad.1bf6","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.home_assistant_restarted","state_type":"str","blockInputOverrides":false,"outputProperties":[],"for":0,"forType":"num","forUnits":"minutes","x":516,"y":220,"wires":[["648dd9ac108befdc"],[]]},{"id":"f89ae68a99bb4e65","type":"server-state-changed","z":"bae136fa5a77790e","name":"restarted?","server":"e89e2dad.1bf6","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"input_boolean.home_assistant_restarted","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"on","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":false,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[],"x":528,"y":268,"wires":[["648dd9ac108befdc"],[]]},{"id":"b5993f580d9eb5b8","type":"comment","z":"bae136fa5a77790e","name":"Home Assistant Restarted","info":"","x":130,"y":124,"wires":[]},{"id":"3754cbf709193071","type":"link out","z":"bae136fa5a77790e","name":"Home Assistant Restarted","links":["c67631f89e6451f4"],"x":1010,"y":220,"wires":[],"l":true},{"id":"c67631f89e6451f4","type":"link in","z":"bae136fa5a77790e","name":"Home Assistant Restarted","links":["3754cbf709193071"],"x":130,"y":316,"wires":[[]],"l":true},{"id":"bcfea616e335904d","type":"inject","z":"bae136fa5a77790e","name":"Click to create","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":106,"y":172,"wires":[["447721875af4431a","5a92fd8241edd308"]]},{"id":"447721875af4431a","type":"ha-api","z":"bae136fa5a77790e","name":"HA automation","server":"e89e2dad.1bf6","version":1,"debugenabled":false,"protocol":"http","method":"post","path":"/api/config/automation/config/nrrestartnotification","data":"{\t   \"alias\": \"Home Assistant Restart\",\t   \"description\": \"\",\t   \"trigger\": [\t       {\t           \"platform\": \"homeassistant\",\t           \"event\": \"start\"       \t       }    \t   ],\t   \"condition\": [],\t   \"action\": [\t       {\t           \"service\": \"input_boolean.turn_on\",\t           \"data\": {\t               \"entity_id\": \"input_boolean.home_assistant_restarted\"       \t            }\t       }    \t   ],\t   \"mode\": \"single\" \t}","dataType":"jsonata","responseType":"json","outputProperties":[],"x":372,"y":124,"wires":[[]]},{"id":"5a92fd8241edd308","type":"subflow:fb5296f3c4de2a23","z":"bae136fa5a77790e","name":"","env":[{"name":"serverName","value":"Home Assistant Working","type":"str"},{"name":"helpers","value":"[{\"id\":\"home_assistant_restarted\",\"type\":\"input_boolean\",\"name\":\"Home Assistant Restarted\",\"icon\":\"mdi:restart-alert\"}]","type":"json"}],"x":382,"y":172,"wires":[]},{"id":"e89e2dad.1bf6","type":"server","name":"Home Assistant Working","addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","entitySelector":"id","statusSeparator":"","enableGlobalContextStore":false},{"id":"103b1fd4c70bd5ad","type":"tab","label":"Max! 2 MQTT","disabled":false,"info":"","env":[]},{"id":"fd3ce805ed926c80","type":"inject","z":"103b1fd4c70bd5ad","name":"timer","props":[{"p":"reason","v":"repeat","vt":"str"}],"repeat":"30","crontab":"","once":true,"onceDelay":"1","topic":"","x":90,"y":540,"wires":[["248a31587c7960fe"]]},{"id":"7b8330524ea2f4c4","type":"mqtt out","z":"103b1fd4c70bd5ad","name":"mqtt boiler","topic":"tasmota/chaudiere/cmnd/POWER","qos":"0","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"d7efe4ef4af81eb9","x":770,"y":360,"wires":[]},{"id":"9fabcb15eb75a432","type":"function","z":"103b1fd4c70bd5ad","name":"compute action","func":"var res = {};\nvar miss = 0;\nfor (const [key, value] of Object.entries(msg.payload)) {\n    if (value.device_type == 3) {\n        name = value.room_name.replace(\" \", \"_\");// + \"_\" + value.device_name.replace(\" \", \"_\");\n        if (value.temp < value.setpoint) {\n            if (value.room_name != 'WC')\n                miss += value.setpoint - value.temp;\n        }\n        res[name] = { \"status\": \"off\" };\n    }\n}\n\nvar action = null;\nif (miss <= 0.5) {\n    action = 'OFF';\n}\nif (miss >= 1.2) {\n    action = 'ON';\n    for (const [key, value] of Object.entries(msg.payload)) {\n        if (value.device_type == 3) {\n            name = value.room_name.replace(\" \", \"_\");// + \"_\" + value.device_name.replace(\" \", \"_\")\n            if (value.temp < value.setpoint && value.room_name != 'WC') {\n                status = 'heat';\n            } else {\n                status = 'off';\n            }\n            res[name] = { \"status\": status };\n        }\n    }\n}\nreturn [ { 'payload': action, 'heat': miss }, { \"payload\": res } ];","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":460,"wires":[["7b8330524ea2f4c4","87831a7d8384ba61","eaa0ef6ec5828c4b"],["f1d30ee021d42cf6"]]},{"id":"3355f9d8134bb8d5","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":580,"wires":[["d09de1b4a1e90b50"]]},{"id":"2ebfae9701dd6b68","type":"function","z":"103b1fd4c70bd5ad","name":"decode","func":"var res = {};\nfor (const [key, value] of Object.entries(msg.payload)) {\n    if (value.device_type) {\n        name = value.room_name.replace(\" \", \"_\") + \"_\" + value.device_name.replace(\" \", \"_\");\n        if (!(value.device_type in res))\n            res[value.device_type] = {};\n        res[value.device_type][name] = value;\n    }\n}\nreturn [ { \"payload\": res[1] }, { \"payload\": res[3] }, { \"payload\": res[4] }, res ];","outputs":4,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":600,"wires":[["f3a618cb9b217476"],["3355f9d8134bb8d5"],["da37c30786c72470"],[]]},{"id":"f1d30ee021d42cf6","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":480,"wires":[["835b0a70c0092e13"]]},{"id":"835b0a70c0092e13","type":"function","z":"103b1fd4c70bd5ad","name":"thermostat state","func":"msg.topic = 'maxcube2mqtt/thermostat/' + msg.parts.key;\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":480,"wires":[["f855b4166b9fe922"]]},{"id":"f3a618cb9b217476","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":540,"wires":[["221d232e309d8974"]]},{"id":"da37c30786c72470","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":620,"wires":[["b64957542172ae46"]]},{"id":"87831a7d8384ba61","type":"function","z":"103b1fd4c70bd5ad","name":"boiler total","func":"var msgo = {};\nmsgo.payload = msg;\nmsgo.payload.heat = msgo.payload.heat.toFixed(1)\nmsgo.topic = 'homeassistant/sensor/maxcube_heat';\nmsgo.payload.config = {}\nmsgo.payload.config[\"~\"] = msgo.topic;\nmsgo.payload.config[\"uniq_id\"] = \"maxcube_heat\";\nmsgo.payload.config[\"name\"] = \"Chaudière Total\";\nmsgo.payload.config[\"state_topic\"] = \"~/heat\";\nmsgo.payload.config[\"unit_of_measurement\"] = \"°\";\nreturn msgo;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":440,"wires":[["f855b4166b9fe922"]]},{"id":"9288a4f985382537","type":"mqtt out","z":"103b1fd4c70bd5ad","name":"mqtt","topic":"","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"d7efe4ef4af81eb9","x":1190,"y":440,"wires":[]},{"id":"f855b4166b9fe922","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"key","x":950,"y":440,"wires":[["9f45cc50a1240208"]]},{"id":"9f45cc50a1240208","type":"function","z":"103b1fd4c70bd5ad","name":"suffix","func":"msg.topic = msg.topic + '/' + msg.key;\ndelete msg.key;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1070,"y":440,"wires":[["9288a4f985382537"]]},{"id":"29949c9d2834f922","type":"comment","z":"103b1fd4c70bd5ad","name":"boiler","info":"boiler management.\nCompute total of missing degrees in each room and turn on the boiler if needed.","x":410,"y":420,"wires":[]},{"id":"af681e641dcfc3b7","type":"comment","z":"103b1fd4c70bd5ad","name":"mqtt receive","info":"send/receive to mqtt","x":430,"y":540,"wires":[]},{"id":"248a31587c7960fe","type":"maxcube out","z":"103b1fd4c70bd5ad","server":"9a07e5adcaeb56f7","singleMessage":true,"x":220,"y":540,"wires":[["9fabcb15eb75a432","2ebfae9701dd6b68"]]},{"id":"eaa0ef6ec5828c4b","type":"function","z":"103b1fd4c70bd5ad","name":"boiler state","func":"msg.payload = 'OFF';\nif (msg.payload == null)\n  return null;\nvar msgo = {};\nmsgo.payload = {};\nmsgo.topic = 'homeassistant/binary_sensor/maxcube_state';\nmsgo.payload.state = msg.payload;\nmsgo.payload.config = {}\nmsgo.payload.config[\"~\"] = msgo.topic;\nmsgo.payload.config[\"uniq_id\"] = \"maxcube_state\";\nmsgo.payload.config[\"name\"] = \"Chaudière État\";\nmsgo.payload.config[\"state_topic\"] = \"~/state\";\nreturn msgo;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":400,"wires":[["f855b4166b9fe922"]]},{"id":"d09de1b4a1e90b50","type":"function","z":"103b1fd4c70bd5ad","name":"thermostat","func":"msg.topic = 'maxcube2mqtt/thermostat/' + msg.payload.room_name.replace(\" \", \"_\");\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":580,"wires":[["5ba40e51ea31021b"]]},{"id":"221d232e309d8974","type":"function","z":"103b1fd4c70bd5ad","name":"valve","func":"msg.topic = 'maxcube2mqtt/valve/' + msg.payload.room_name.replace(\" \", \"_\") + \"_\" + msg.payload.device_name.replace(\" \", \"_\");\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":540,"wires":[["5ba40e51ea31021b"]]},{"id":"5ba40e51ea31021b","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"key","x":910,"y":580,"wires":[["f834c4de1ba53a86"]]},{"id":"f834c4de1ba53a86","type":"function","z":"103b1fd4c70bd5ad","name":"suffix","func":"msg.topic = msg.topic + '/' + msg.key;\ndelete msg.key;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1030,"y":580,"wires":[["efcb1347e477233d"]]},{"id":"b64957542172ae46","type":"function","z":"103b1fd4c70bd5ad","name":"window","func":"msg.topic =  'maxcube2mqtt/window/' + msg.payload.room_name.replace(\" \", \"_\") + \" / \" + msg.payload.device_name.replace(\" \", \"_\");\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":620,"wires":[["5ba40e51ea31021b"]]},{"id":"3d2761b7773ae8d5","type":"inject","z":"103b1fd4c70bd5ad","name":"on start","props":[{"p":"reason","v":"start","vt":"str"}],"repeat":"3600","crontab":"","once":true,"onceDelay":"1","topic":"","x":160,"y":60,"wires":[["554700ca0a7b0288"]]},{"id":"554700ca0a7b0288","type":"maxcube out","z":"103b1fd4c70bd5ad","server":"9a07e5adcaeb56f7","singleMessage":true,"x":320,"y":60,"wires":[["262018a32e7e029b"]]},{"id":"262018a32e7e029b","type":"function","z":"103b1fd4c70bd5ad","name":"decode","func":"var res = {};\nfor (const [key, value] of Object.entries(msg.payload)) {\n    if (value.device_type) {\n        name = value.room_name.replace(\" \", \"_\") + \"_\" + value.device_name.replace(\" \", \"_\");\n        if (!(value.device_type in res))\n            res[value.device_type] = {};\n        res[value.device_type][name] = value;\n    }\n}\nreturn [ { \"payload\": res[1] }, { \"payload\": res[3] }, { \"payload\": res[4] }, res ];","outputs":4,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":60,"wires":[["45a48dfb50ec27a3"],["cf5b45a47dc5be43"],["1e0f3bbea406afcd"],[]]},{"id":"45a48dfb50ec27a3","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":650,"y":60,"wires":[["c4bef775bb3de4a4","3ec8ad146c23b99e"]]},{"id":"c4bef775bb3de4a4","type":"function","z":"103b1fd4c70bd5ad","name":"valve","func":"var res = {}\nres.topic = 'homeassistant/climate/' + msg.payload.room_name.replace(\" \", \"_\") + \"-\" + msg.payload.device_name.replace(\" \", \"_\") + '/config';\nres.payload = {}\nres.payload[\"~\"] = 'maxcube2mqtt/valve/' + msg.payload.room_name.replace(\" \", \"_\") + \"_\" + msg.payload.device_name.replace(\" \", \"_\");\nres.payload[\"unique_id\"] = \"maxcube_valve_\" + msg.parts.key.replace(\" \", \"_\");\nres.payload[\"name\"] = \"Valve \" + msg.payload.room_name + \" / \" + msg.payload.device_name;\nres.payload[\"temp_cmd_t\"] = \"~/setpoint/set\";\nres.payload[\"temp_stat_t\"] = \"~/setpoint\";\nres.payload[\"curr_temp_t\"] = \"~/temp\";\nres.payload[\"min_temp\"] = \"12\";\nres.payload[\"max_temp\"] = \"25\";\nres.payload[\"temp_step\"] = \"0.5\";\nres.payload[\"device\"] = {};\nres.payload.device[\"manufacturer\"] = \"ELV\";\nres.payload.device[\"model\"] = \"valve\";\nres.payload.device[\"name\"] = res.payload[\"name\"];\nres.payload.device[\"identifiers\"] = [ \"maxcube_\" + msg.payload.rf_address];\n// res.payload.device[\"connections\"] = [ [ \"mac\", msg.payload.rf_address ] ];\n// res.payload[\"origin\"] = {};\n// res.payload.origin[\"name\"] = \"maxcube\";\nreturn res;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":60,"wires":[["3c3e46d6fee1be7b"]]},{"id":"3c3e46d6fee1be7b","type":"mqtt out","z":"103b1fd4c70bd5ad","name":"mqtt","topic":"","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"d7efe4ef4af81eb9","x":1010,"y":60,"wires":[]},{"id":"3ec8ad146c23b99e","type":"function","z":"103b1fd4c70bd5ad","name":"valve bat","func":"var res = {}\nres.topic = 'homeassistant/sensor/' + msg.payload.room_name.replace(\" \", \"_\") + \"-\" + msg.payload.device_name.replace(\" \", \"_\") + \"_battery/config\";\nres.payload = {}\nres.payload[\"~\"] = 'maxcube2mqtt/valve/' + msg.payload.room_name.replace(\" \", \"_\") + \"_\" + msg.payload.device_name.replace(\" \", \"_\");\nres.payload[\"uniq_id\"] = \"maxcube_\" + msg.parts.key.replace(\" \", \"_\") + \"_battery\";\nres.payload[\"name\"] = \"Valve \" + msg.payload.room_name + \" / \" + msg.payload.device_name + \" Battery\";\nres.payload[\"device_class\"] = \"battery\";\nres.payload[\"entity_category\"] = \"diagnostic\";\nres.payload[\"state_class\"] = \"measurement\";\nres.payload[\"state_topic\"] = \"~/battery_low\";\nres.payload[\"unit_of_measurement\"] = \"%\";\nres.payload[\"value_template\"] = '{{ 0 if value == \"true\" else 100 }}';\nres.payload[\"device\"] = {};\nres.payload.device[\"manufacturer\"] = \"ELV\";\nres.payload.device[\"model\"] = \"valve\";\nres.payload.device[\"name\"] = res.payload[\"name\"];\nres.payload.device[\"identifiers\"] = [ \"maxcube_\" + msg.payload.rf_address];\nreturn res;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":100,"wires":[["3c3e46d6fee1be7b"]]},{"id":"6ad662a6b39283fa","type":"function","z":"103b1fd4c70bd5ad","name":"thermostat ","func":"var res = {}\nres.topic = 'homeassistant/climate/' + msg.payload.room_name.replace(\" \", \"_\") + \"/config\";\nres.payload = {}\nres.payload[\"~\"] = 'maxcube2mqtt/thermostat/' + msg.payload.room_name.replace(\" \", \"_\");\nres.payload[\"uniq_id\"] = \"maxcube_\" + msg.payload.room_name.replace(\" \", \"_\");\nres.payload[\"name\"] = \"Thermostat \" + msg.payload.room_name;\nres.payload[\"modes\"] = [ \"heat\", \"off\" ]\nres.payload[\"mode_state_topic\"] = \"~/status\";\nres.payload[\"temp_cmd_t\"] = \"~/setpoint/set\";\nres.payload[\"temp_stat_t\"] = \"~/setpoint\";\nres.payload[\"curr_temp_t\"] = \"~/temp\";\nres.payload[\"min_temp\"] = \"12\";\nres.payload[\"max_temp\"] = \"25\";\nres.payload[\"temp_step\"] = \"0.5\";\nres.payload[\"preset_modes\"] = [ \"AUTO\", \"MANUAL\", \"BOOST\" ];\nres.payload[\"preset_mode_command_topic\"] = \"~/mode/set\";\nres.payload[\"preset_mode_state_topic\"] = \"~/mode\";\nres.payload[\"device\"] = {};\nres.payload.device[\"manufacturer\"] = \"ELV\";\nres.payload.device[\"model\"] = \"thermostat\";\nres.payload.device[\"name\"] = res.payload[\"name\"];\nres.payload.device[\"identifiers\"] = [ \"maxcube_\" + msg.payload.rf_address];\nreturn res;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":140,"wires":[["3c3e46d6fee1be7b"]]},{"id":"794a0609769ac85c","type":"function","z":"103b1fd4c70bd5ad","name":"thermostat bat ","func":"var res = {}\nres.topic = 'homeassistant/sensor/' + msg.parts.key.replace(\" \", \"_\") + \"_battery/config\";\nres.payload = {}\nres.payload = {}\nres.payload[\"~\"] = 'maxcube2mqtt/thermostat/' + msg.payload.room_name.replace(\" \", \"_\");\nres.payload[\"uniq_id\"] = \"maxcube_\" + msg.parts.key.replace(\" \", \"_\") + \"_battery\";\nres.payload[\"name\"] = \"Thermostat \" + msg.payload.room_name + \" / \" + msg.payload.device_name + \" Battery\";\nres.payload[\"device_class\"] = \"battery\";\nres.payload[\"entity_category\"] = \"diagnostic\";\nres.payload[\"state_class\"] = \"measurement\";\nres.payload[\"state_topic\"] = \"~/battery_low\";\nres.payload[\"unit_of_measurement\"] = \"%\";\nres.payload[\"value_template\"] = '{{ 0 if value == \"true\" else 100 }}';\nres.payload[\"device\"] = {};\nres.payload.device[\"manufacturer\"] = \"ELV\";\nres.payload.device[\"model\"] = \"thermostat\";\nres.payload.device[\"name\"] = res.payload[\"name\"];\nres.payload.device[\"identifiers\"] = [ \"maxcube_\" + msg.payload.rf_address];\nreturn res;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":800,"y":180,"wires":[["3c3e46d6fee1be7b"]]},{"id":"cf5b45a47dc5be43","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":650,"y":140,"wires":[["6ad662a6b39283fa","794a0609769ac85c"]]},{"id":"1e3df1f548625467","type":"comment","z":"103b1fd4c70bd5ad","name":"HA discovery","info":"boiler management.\nCompute total of missing degrees in each room and turn on the boiler if needed.","x":330,"y":20,"wires":[]},{"id":"b215271d477e76a1","type":"function","z":"103b1fd4c70bd5ad","name":"window","func":"var res = {};\nres.topic = 'homeassistant/binary_sensor/' + msg.parts.key.replace(\" \", \"_\") + '/config';\nres.payload = {}\nres.payload[\"~\"] = 'maxcube2mqtt/window/' + msg.payload.room_name.replace(\" \", \"_\");\nres.payload[\"uniq_id\"] = \"maxcube_\" + msg.parts.key.replace(\" \", \"_\");\nres.payload[\"name\"] = \"Contact \" + msg.payload.room_name + \" / \" + msg.payload.device_name;\nres.payload[\"state_topic\"] = \"~/open\";\nres.payload[\"device_class\"] = \"window\";\nres.payload[\"payload_off\"] = \"false\";\nres.payload[\"payload_on\"] = \"true\";\nreturn res;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":220,"wires":[["3c3e46d6fee1be7b"]]},{"id":"b022f80ec1f025d0","type":"function","z":"103b1fd4c70bd5ad","name":"window bat","func":"var res = {}\nres.topic = 'homeassistant/sensor/' + msg.parts.key.replace(\" \", \"_\") + \"_battery/config\";\nres.payload = {}\nres.payload[\"~\"] = 'maxcube2mqtt/window/' + msg.payload.room_name.replace(\" \", \"_\");\nres.payload[\"uniq_id\"] = \"maxcube_\" + msg.parts.key.replace(\" \", \"_\") + \"_battery\";\nres.payload[\"name\"] = \"Contact \" + msg.payload.room_name + \" / \" + msg.payload.device_name + \" Battery\";\nres.payload[\"device_class\"] = \"battery\";\nres.payload[\"entity_category\"] = \"diagnostic\";\nres.payload[\"state_class\"] = \"measurement\";\nres.payload[\"state_topic\"] = \"~/battery_low\";\nres.payload[\"unit_of_measurement\"] = \"%\";\nres.payload[\"value_template\"] = '{{ 0 if value == \"true\" else 100 }}';\nreturn res;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":260,"wires":[["3c3e46d6fee1be7b"]]},{"id":"1e0f3bbea406afcd","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":650,"y":220,"wires":[["b215271d477e76a1","b022f80ec1f025d0"]]},{"id":"efcb1347e477233d","type":"mqtt out","z":"103b1fd4c70bd5ad","name":"mqtt","topic":"","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"d7efe4ef4af81eb9","x":1150,"y":580,"wires":[]},{"id":"3d3e1fea5d7df796","type":"function","z":"103b1fd4c70bd5ad","name":"decode","func":"var res = {};\nfor (const [key, value] of Object.entries(msg.payload)) {\n    if (value.device_type) {\n        name = value.room_name.replace(\" \", \"_\") + \"_\" + value.device_name.replace(\" \", \"_\");\n        if (!(value.device_type in res))\n            res[value.device_type] = {};\n        res[value.device_type][name] = value;\n    }\n}\nreturn [ { \"payload\": res[1] }, { \"payload\": res[3] }, { \"payload\": res[4] }, res ];","outputs":4,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":780,"wires":[[],["f9b774ccf31a5c1f"],[],[]]},{"id":"dca227a30d266fdf","type":"comment","z":"103b1fd4c70bd5ad","name":"mqtt send","info":"send/receive to mqtt","x":420,"y":720,"wires":[]},{"id":"f9b774ccf31a5c1f","type":"split","z":"103b1fd4c70bd5ad","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":590,"y":780,"wires":[["8d6bf2e8cf66f9fb"]]},{"id":"4d772a73d166d0d8","type":"mqtt in","z":"103b1fd4c70bd5ad","name":"","topic":"","qos":"2","datatype":"auto","broker":"d7efe4ef4af81eb9","nl":false,"rap":true,"rh":0,"inputs":1,"x":850,"y":780,"wires":[["b06952c575c8a5a6"]]},{"id":"8d6bf2e8cf66f9fb","type":"function","z":"103b1fd4c70bd5ad","name":"subscribe","func":"var msgo = {}\nmsgo.action = 'subscribe'\nmsgo.topic = 'maxcube2mqtt/thermostat/' + msg.payload.room_name.replace(\" \", \"_\") + \"/setpoint/set\";\nvar persist = flow.get('persist', 'file');\nif (typeof persist == 'undefined') {\n    persist = {};\n}\npersist[msgo.topic] = msg.payload.rf_address;\nflow.set('persist', persist, 'file');\nreturn msgo;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":780,"wires":[["4d772a73d166d0d8"]]},{"id":"b06952c575c8a5a6","type":"function","z":"103b1fd4c70bd5ad","name":"get_rfaddress","func":"var persist = flow.get('persist', 'file');\nreturn { \"payload\": { \"rf_address\": persist[msg.topic], \"degrees\": parseFloat(msg.payload), \"mode\": \"MANUAL\" } };","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1000,"y":780,"wires":[["1b8a19e60d1941d5"]]},{"id":"b6d09b6a82b25db2","type":"delay","z":"103b1fd4c70bd5ad","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1300,"y":780,"wires":[["248a31587c7960fe"]]},{"id":"1b8a19e60d1941d5","type":"maxcube in","z":"103b1fd4c70bd5ad","server":"9a07e5adcaeb56f7","x":1160,"y":780,"wires":[["b6d09b6a82b25db2"]]},{"id":"7897f3ffd2481dcc","type":"inject","z":"103b1fd4c70bd5ad","name":"on start","props":[],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","x":120,"y":780,"wires":[["d0d37f63eb3c771c"]]},{"id":"d0d37f63eb3c771c","type":"maxcube out","z":"103b1fd4c70bd5ad","server":"9a07e5adcaeb56f7","singleMessage":true,"x":260,"y":780,"wires":[["3d3e1fea5d7df796"]]},{"id":"1201580fe85fe424","type":"subflow:bae136fa5a77790e","z":"103b1fd4c70bd5ad","name":"","x":160,"y":100,"wires":[["554700ca0a7b0288"]]},{"id":"d7efe4ef4af81eb9","type":"mqtt-broker","name":"MQTT","broker":"10.68.69.5","port":"1883","clientid":"nodered","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"nodered/status","birthQos":"0","birthRetain":"true","birthPayload":"online","birthMsg":{},"closeTopic":"nodered/status","closeQos":"0","closeRetain":"true","closePayload":"offline","closeMsg":{},"willTopic":"nodered/status","willQos":"0","willRetain":"true","willPayload":"timeout","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"9a07e5adcaeb56f7","type":"maxcube-server","host":"10.68.69.120","port":"62910","disabled":false}]