Node Red: trigger gas boiler from several sensors and thermostats

With help from @Mikefila I was able to automate the radiator thermostats in individual rooms to adapt to presence, time and so on. (Node-Red: Help with radiator automation - #9 by Mikefila)

Now, I’d like to also trigger the relay of the gas boiler if one room is below it’s set temperature.
At the moment, this only works with the generic thermostat which triggers based on the living rooms temperature. Therefore, at the moment, it might happen that other rooms are cold and wont be heated since the gas boiler is not running.

Did anyone implement something like this yet?
For now, I only set up something which calculates the difference between set and current temperature and then provides an “on” or “off” which should then be used to trigger the relay of the gas boiler:

[{"id":"f3a2cc92cc861295","type":"tab","label":"Thermostat","disabled":false,"info":""},{"id":"7e2862f898a49014","type":"poll-state","z":"f3a2cc92cc861295","name":"Get Current Temperature Living Room","server":"aadbde09.d5ab3","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"10","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"sensor.ble_temperature_a4c13843b90c","state_type":"num","halt_if":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"x":1280,"y":1080,"wires":[["b3bea274edc91fe0"]]},{"id":"b2aa81bdefdd0cf7","type":"poll-state","z":"f3a2cc92cc861295","name":"","server":"aadbde09.d5ab3","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"10","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"climate.temperatur_wohnzimmer","state_type":"num","halt_if":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"x":1280,"y":1180,"wires":[["072d1257d551c1ac"]]},{"id":"072d1257d551c1ac","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"move","p":"data.attributes.temperature","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1660,"y":1180,"wires":[["b3bea274edc91fe0"]]},{"id":"b3bea274edc91fe0","type":"join","z":"f3a2cc92cc861295","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1630,"y":1040,"wires":[["b18eb933cd32c155"]]},{"id":"b18eb933cd32c155","type":"function","z":"f3a2cc92cc861295","name":"Diff Living Room","func":"msg.topic = \"diff\"\nmsg.payload = msg.payload[\"sensor.ble_temperature_a4c13843b90c\"] - msg.payload[\"climate.temperatur_wohnzimmer\"]\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1920,"y":1080,"wires":[["02c5d80834f5aab8"]]},{"id":"f73863987e9cf712","type":"poll-state","z":"f3a2cc92cc861295","name":"Get Current Temperature Sleeping Room","server":"aadbde09.d5ab3","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"10","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"sensor.schlafzimmer_temperatur","state_type":"num","halt_if":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"x":1280,"y":1420,"wires":[["372d79823d278971"]]},{"id":"a68f977224249ff0","type":"poll-state","z":"f3a2cc92cc861295","name":"","server":"aadbde09.d5ab3","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"10","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"climate.heizkorper_schlafzimmer_heating_1","state_type":"num","halt_if":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"x":1310,"y":1520,"wires":[["09f0fa1ef910e33b"]]},{"id":"09f0fa1ef910e33b","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"move","p":"data.attributes.temperature","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1720,"y":1520,"wires":[["372d79823d278971"]]},{"id":"372d79823d278971","type":"join","z":"f3a2cc92cc861295","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1620,"y":1380,"wires":[["542142ec549ad37c"]]},{"id":"542142ec549ad37c","type":"function","z":"f3a2cc92cc861295","name":"Diff Sleeping Room","func":"msg.topic = \"diff\"\nmsg.payload = msg.payload[\"sensor.schlafzimmer_temperatur\"] - msg.payload[\"climate.heizkorper_schlafzimmer_heating_1\"]\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1870,"y":1420,"wires":[["02c5d80834f5aab8"]]},{"id":"2b68eda9462e7eeb","type":"rbe","z":"f3a2cc92cc861295","name":"","func":"rbe","gap":"","start":"","inout":"out","septopics":true,"property":"payload","topi":"topic","x":2390,"y":1280,"wires":[[]]},{"id":"5299401afd040e9f","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2190,"y":1260,"wires":[["2b68eda9462e7eeb","a1cffe3b70db6594"]]},{"id":"02c5d80834f5aab8","type":"switch","z":"f3a2cc92cc861295","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"lte","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":2000,"y":1280,"wires":[["5299401afd040e9f"],["931e6c4a7ad9d2eb"]]},{"id":"931e6c4a7ad9d2eb","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2190,"y":1300,"wires":[["2b68eda9462e7eeb","223dcbda80e4bb92"]]},{"id":"a1cffe3b70db6594","type":"debug","z":"f3a2cc92cc861295","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2440,"y":1200,"wires":[]},{"id":"223dcbda80e4bb92","type":"debug","z":"f3a2cc92cc861295","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2460,"y":1380,"wires":[]},{"id":"aadbde09.d5ab3","type":"server","name":"Home Assistant","version":1,"legacy":false,"addon":false,"rejectUnauthorizedCerts":false,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

Would you either
., override the generic thermostats target sensor and the target temperature to keep all other settings like min runtime and so on or
., just trigger the relay from node-red including some additional safety settings?

The problem I face at the moment is on how to have the relay turned on / the boiler running as long as the individual rooms send an “on” command and especially what happens if two or more separate rooms send different target temps / how to prioritize them.

Thanks

1 Like

Unless the boiler is modulated, it doesn’t matter about the target temps being different. Just set up generic thermostats for each room and use the relay you have connected to the thermostat line as the switch.

Closing the thermostat line doesn’t override the safety system of the boiler. Worst case it will continue to produce heat, but not just run nonstop until it bursts into flames.

Thanks for you reply and sry for mine being late.

If one of the thermostats switches to off while others are running: would this lead to short “offs” or would the switch keep being set to on?

Since there are min_cycle_duration and such things set: Wouldn’t this create some interference/overwrites?

As far as the boiler is concerned it has no idea one of the thermostats was turned off, so it would continue to run.

You’ll need individual relays each thermostat, don’t use one and have all the thermostats use same relay entity. Then you will have problems. Each relay should be connected in parallel.

Finally use one flow for each thermostat rather than have them joined.

Well multiple relais would be a space problem.

I can either trigger the same relay with all generic thermostats or just use one generic thermostat, changed by node-red in terms of target temperature and so on.

Or I only trigger the relais with node-red without using generic thermostat at all …

I’m thinking along the lines of a simple 4ch relay mounted by the boiler using esphome.

Otherwise it’s going to get overly complicated. You would have to check the other 2 thermostats before letting the relay shut off. This is just the start you’d have to account for anything else like say something being unavailable.

[{"id":"8428b9379b1b581f","type":"server-state-changed","z":"ec78a76b.a0a4d","name":"","server":"","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"climate.t1_thermostat","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":410,"y":2000,"wires":[["107d962a1a81937c"]]},{"id":"35f20397ac4e6d42","type":"switch","z":"ec78a76b.a0a4d","name":"heating(on)/idle(off)","property":"data.new_state.attributes.hvac_action","propertyType":"msg","rules":[{"t":"eq","v":"heating","vt":"str"},{"t":"eq","v":"idle","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":770,"y":2000,"wires":[[],["c142f580f2ccc48d"]]},{"id":"107d962a1a81937c","type":"switch","z":"ec78a76b.a0a4d","name":"","property":"data.new_state.attributes.hvac_action","propertyType":"msg","rules":[{"t":"neq","v":"data.old_state.attributes.hvac_action","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":615,"y":2000,"wires":[["35f20397ac4e6d42"]],"l":false},{"id":"c142f580f2ccc48d","type":"api-render-template","z":"ec78a76b.a0a4d","name":"t2 & t3 state","server":"","version":0,"template":"{% if is_state(\"climate.t2.attibutes.hvac_action\", \"idle\") and\nis_state(\"climate.t3.attibutes.hvac_action\", \"idle\") %}off\n{%-else %}on{% endif %}\n","resultsLocation":"payload","resultsLocationType":"msg","templateLocation":"","templateLocationType":"none","x":1030,"y":2020,"wires":[["ccad8a49ea356533"]]},{"id":"ccad8a49ea356533","type":"switch","z":"ec78a76b.a0a4d","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"off","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":1210,"y":2020,"wires":[["d5c624aaa7737e44"]]},{"id":"d5c624aaa7737e44","type":"api-call-service","z":"ec78a76b.a0a4d","name":"relay off","server":"","version":3,"debugenabled":false,"service_domain":"","service":"","entityId":"","data":"","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1440,"y":2020,"wires":[[]]}]

So I actually have an 8 channel (one wire) relay controlling the boiler.
I have 2 generic Climate Thermostats. One for upstairs and one for downstairs.

My relay has all channels OFF when the value is 255. Then each relay is 1, 2, 4, 8, 16, 32, 64, 128. So it’s using a logic gate.

So in NodeRed all I do is intercept the MQTT message and override it if either of the upstairs or downstairs thermostats are calling for heat so the relay always gets the right value.

[{"id":"1f2b6a5c.234546","type":"mqtt in","z":"7733b31c.a177dc","name":"","topic":"building/dhjm/boiler/control/+","qos":"2","datatype":"utf8","broker":"d1fa4ea6.88271","x":160,"y":40,"wires":[["a659fcb8.24c1b"]]},{"id":"96653ee9.2ee8f","type":"function","z":"7733b31c.a177dc","name":"","func":"var raw = msg.topic.split(\"/\");\nvar channels = [];\nchannels['house'] = 1;\nchannels['greenroom'] = 2;\nchannels['theatre'] = 4;\nchannels['boiler_n_open_relay'] = 8;\nchannels['bedrooms'] = 16;\nvar defstate = {};\ndefstate.house = 0;\ndefstate.greenroom = 0;\ndefstate.theatre = 0;\ndefstate.theatre_relay = 0;\ndefstate.bedrooms = 0;\nvar oldstate = flow.get(\"heating\") || defstate;\nvar curr = raw.splice(-1)[0];\nvar newstate = msg.payload;\nvar binval = (msg.payload == \"on\" ? 1 : 0);\noldstate[curr] = binval;\n\nvar relay = 255;\nif (oldstate.house || oldstate.bedrooms) { relay = relay - channels['house']; }\nif (oldstate.bedrooms) { relay = relay - channels['bedrooms']; }\nif (oldstate.greenroom) { relay = relay - channels['greenroom']; }\nif (oldstate.theatre) { relay = relay - channels['theatre']; }\nif (relay == 255) {\n    relay = relay - channels['boiler_n_open_relay'];\n}\n\nvar status = curr + \" :: \" + newstate + \"(Relay: \" + relay + \")\";\nmsg.payload = status;\nflow.set(\"heating\",oldstate);\nvar result = {};\nresult.payload = relay;\nresult.topic = \"heating_control\";\nreturn [msg,result];","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":100,"wires":[["70f3ce58.a82f"],["6325da2f.d87354"]]},{"id":"a659fcb8.24c1b","type":"delay","z":"7733b31c.a177dc","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"3","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":160,"y":100,"wires":[["96653ee9.2ee8f"]]},{"id":"70f3ce58.a82f","type":"debug","z":"7733b31c.a177dc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":560,"y":100,"wires":[]},{"id":"6325da2f.d87354","type":"delay","z":"7733b31c.a177dc","name":"","pauseType":"queue","timeout":"15","timeoutUnits":"seconds","rate":"1","nbRateUnits":"30","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":540,"y":180,"wires":[["288bd6fd.1fc93a"]]},{"id":"288bd6fd.1fc93a","type":"function","z":"7733b31c.a177dc","name":"","func":"var relay = msg.payload;\nmsg.payload = \"Final Result sent: \" + relay;\nmsg.url = \"http://192.168.2.10/devices.htm?rom=BC000000082EA729&variable=PIOOutputLatchState&value=\" + relay;\nmsg.method = \"GET\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":260,"wires":[["c517dd33.3de16","70f3ce58.a82f"]]},{"id":"c517dd33.3de16","type":"http request","z":"7733b31c.a177dc","name":"","method":"use","ret":"txt","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":660,"y":280,"wires":[[]]},{"id":"d1fa4ea6.88271","type":"mqtt-broker","name":"Henry","broker":"192.168.2.9","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

As you can see I store the current value of whether each channel is on or off in the flow context, and wait a period of time for all the messages to come in rather than updating the relay immediately.

Now in my setup Relay 4 actually turns the boiler on or off (it’s set to Normally Closed, so that if the relay board dies - the relay being open will allow the boiler to still work)

Relays 1 - 3 open motorised valves to allow the water to flow. So in my setup if any of relay 1 - 3 is on, then relay 4 must be turned off to make the boiler run.

I then have 2 generic thermostats in Home Assistant:

sensor:
# snipped irrelevant stuff
  - platform: min_max
    entity_ids:
      - sensor.living_room_temperature
      - sensor.hall_temperature
      - sensor.livingroom_temperature_temperature
      - sensor.zigbee_hall_temperature_temperature
      - sensor.livingroom_east_temperature
    type: mean
    name: "average_house_temperature"
    round_digits: 2
  - platform: min_max
    entity_ids:
      - sensor.andy_bedroom_temperature
      - sensor.zigbee_bedroom_temperature_temperature
      - sensor.bedroom_east_temperature
    type: mean
    name: "average_upper_bedrooms"
    round_digits: 2

switch:
  - platform: mqtt
    name: "House Boiler"
    state_topic: "building/dhjm/boiler/states/house"
    command_topic: "building/dhjm/boiler/control/house"
    payload_on: "on"
    payload_off: "off"
    state_on: "on"
    state_off: "off"
  - platform: mqtt
    name: "Upper House Boiler"
    state_topic: "building/dhjm/boiler/states/bedrooms"
    command_topic: "building/dhjm/boiler/control/bedrooms"
    payload_on: "on"
    payload_off: "off"
    state_on: "on"
    state_off: "off"

climate:
  - platform: generic_thermostat
    name: Lower House
    heater: switch.house_boiler
    target_sensor: sensor.average_house_temperature
    min_temp: 14
    max_temp: 25
    min_cycle_duration:
      minutes: 15
    initial_hvac_mode: "heat"
    away_temp: 16.5
    cold_tolerance: 0.3
    hot_tolerance: 0.3
    keep_alive:
      minutes: 5
    precision: 0.1
  - platform: generic_thermostat
    name: Upper House
    heater: switch.upper_house_boiler
    target_sensor: sensor.average_upper_bedrooms
    min_temp: 14
    max_temp: 25
    min_cycle_duration:
      minutes: 15
    initial_hvac_mode: "heat"
    away_temp: 16.5
    cold_tolerance: 0.3
    hot_tolerance: 0.3
    keep_alive:
      minutes: 5
    precision: 0.1

I then handle the times, temperature settings and some logic based on presence and external conditions with Schedy running in AppDaemon - which is amazing.

schedy_heating:  # This is our app instance name.
  module: hass_apps_loader
  class: SchedyApp

  actor_type: thermostat

  schedule_snippets:
    weather:
      - x: "Add(+0.125) if is_on('binary_sensor.spring_season') else Next()"
      - x: "Add(-0.25) if is_on('binary_sensor.summer_season') else Next()"
      - x: "Add(+0.125) if is_on('binary_sensor.autumn_season') else Next()"
      - x: "Add(+0.250) if is_on('binary_sensor.winter_season') else Next()"
      - x: "Add(-0.1) if is_on('input_boolean.heating_eco_mode') else Next()"
      - x: "Add(-0.1) if is_off('input_boolean.home_state_home') else Next()"
      - x: "Add(+0.1) if float(state('sensor.cc_30_min_gust')) > 15 and (state('sensor.wind_dir_cardinal')[:1] == 'N' or state('sensor.wind_dir_cardinal')[:1] == 'E') else Next()"
#      - x: "Add(+0.025) if (float(state('sensor.cc_outside_temperature')) < 15.1 and float(state('sensor.cc_rain_rate')) > 0) else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < -9.9 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < -4.9 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 0.1 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 5.1 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 10.1 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 14.1 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 30.1 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 24.9 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 22.9 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 21.9 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 20.9 else Next()"
      - x: "Add(-0.1) if float(state('sensor.cc_outside_temperature')) > 16.8 else Next()"
      - x: "Add(+0.025) if (float(state('sensor.cc_outside_temperature')) < 15.1 and float(state('sensor.openweathermap_forecast_precipitation_probability')) > 75) else Next()"
      - x: "Postprocess(lambda result: round(result, 1))"

  rooms:

    house:
      actors:
        climate.lower_house:
      rescheduling_delay: 120
      watched_entities:
        - "input_boolean.heating_night_mode"
        - "input_boolean.home_state_home"
        - "sensor.cc_outside_temperature"
        - "sensor.cc_30_min_gust"
        - "sensor.cc_rain_rate"
        - "sensor.wind_dir_cardinal"
        - "input_boolean.heating_eco_mode"
        - "sensor.livingroom_temperature_temperature"
        - "binary_sensor.livingroom_presence"
        - "binary_sensor.spring_season"
        - "binary_sensor.summer_season"
        - "binary_sensor.autumn_season"
        - "binary_sensor.winter_season"
        - "sensor.openweathermap_forecast_precipitation_probability"
      friendly_name: House
      schedule:
        - v: 18.5
          rules:
            - x: "IncludeSchedule(schedule_snippets['weather'])"
            - x: "Add(-0.2) if is_on('input_boolean.heating_night_mode') else Next()"
            - x: "Add(+0.1) if is_on('binary_sensor.livingroom_presence') else Next()"
            - x: "Add(+0.1) if (float(state('sensor.livingroom_temperature_temperature')) < 17.8 and is_on('binary_sensor.livingroom_presence')) else Next()"
            - { v: 16.5, start: "23:00", end: "07:00" }
            - { v: 17.8, start: "07:00", end: "09:00" }
            - { v: 17.9, start: "09:00", end: "10:00" }
            - { v: 18.0, start: "10:00", end: "10:30" }
            - { v: 18.1, start: "10:30", end: "11:00" }
            - { v: 18.2, start: "11:30", end: "12:00" }
            - { v: 18.3, start: "12:00", end: "12:30" }
            - { v: 18.4, start: "12:30", end: "13:00" }
            - { v: 18.5, start: "13:00", end: "14:00" }
            - { v: 18.6, start: "14:00", end: "15:00" }
            - { v: 18.7, start: "15:00", end: "16:00" }
            - { v: 18.8, start: "16:00", end: "18:00" }
            - { v: 18.9, start: "18:00", end: "20:00" }
            - { v: 18.8, start: "20:00", end: "21:00" }
            - { v: 18.7, start: "21:00", end: "22:00" }
            - { v: 18.6, start: "22:00", end: "23:00" }

    upper_house:
      actors:
        climate.upper_house:
      rescheduling_delay: 45
      watched_entities:
        - "input_boolean.home_state_home"
        - "sensor.cc_outside_temperature"
        - "sensor.cc_30_min_gust"
        - "sensor.cc_rain_rate"
        - "sensor.wind_dir_cardinal"
        - "input_boolean.heating_eco_mode"
        - "sensor.average_upper_bedrooms"
        - "binary_sensor.bedroom_presence"
        - "binary_sensor.spring_season"
        - "binary_sensor.summer_season"
        - "binary_sensor.autumn_season"
        - "binary_sensor.winter_season"
        - "sensor.openweathermap_forecast_precipitation_probability"
      friendly_name: Upper House
      schedule:
        - v: 18
          rules:
            - x: "IncludeSchedule(schedule_snippets['weather'])"
            - x: "Add(+0.1) if is_on('binary_sensor.bedroom_presence') else Next()"
            - { v: "OFF", start: "08:00", end: "21:00" }
            - { v: 18.0, start: "21:00", end: "22:00" }
            - { v: 17.9, start: "22:00", end: "23:00" }
            - { v: 17.8, start: "23:00", end: "00:00" }
            - { v: 17.7, start: "00:00", end: "01:00" }
            - { v: 17.6, start: "01:00", end: "02:00" }
            - { v: 17.5, start: "02:00", end: "03:00" }
            - { v: 17.4, start: "03:00", end: "04:00" }
            - { v: 17.4, start: "04:00", end: "06:00" }
            - { v: 17.4, start: "06:00", end: "08:00" }

This works really well for me, and I hope gives you some guidance as to have achieve your goal.

Thanks for all your feedback.

I’m currently trying to avoid any appdeamon things or so and am working on a flow which should only turn on or off the relay:

[{"id":"f3a2cc92cc861295","type":"tab","label":"Thermostat","disabled":false,"info":""},{"id":"7e2862f898a49014","type":"poll-state","z":"f3a2cc92cc861295","name":"Get Current Temperature Living Room","server":"aadbde09.d5ab3","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"10","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"sensor.ble_temperature_a4c13843b90c","state_type":"num","halt_if":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"x":1280,"y":1080,"wires":[["b3bea274edc91fe0"]]},{"id":"b2aa81bdefdd0cf7","type":"poll-state","z":"f3a2cc92cc861295","name":"","server":"aadbde09.d5ab3","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"10","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"climate.temperatur_wohnzimmer","state_type":"num","halt_if":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"x":1280,"y":1180,"wires":[["072d1257d551c1ac"]]},{"id":"072d1257d551c1ac","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"move","p":"data.attributes.temperature","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1660,"y":1180,"wires":[["b3bea274edc91fe0"]]},{"id":"b3bea274edc91fe0","type":"join","z":"f3a2cc92cc861295","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1630,"y":1040,"wires":[["b18eb933cd32c155"]]},{"id":"b18eb933cd32c155","type":"function","z":"f3a2cc92cc861295","name":"Diff Living Room","func":"msg.topic = \"diff_livingroom\"\nmsg.payload = msg.payload[\"sensor.ble_temperature_a4c13843b90c\"] - msg.payload[\"climate.temperatur_wohnzimmer\"]\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1840,"y":1040,"wires":[["02c5d80834f5aab8"]]},{"id":"f73863987e9cf712","type":"poll-state","z":"f3a2cc92cc861295","name":"Get Current Temperature Sleeping Room","server":"aadbde09.d5ab3","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"10","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"sensor.schlafzimmer_temperatur","state_type":"num","halt_if":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"x":1280,"y":1420,"wires":[["372d79823d278971"]]},{"id":"a68f977224249ff0","type":"poll-state","z":"f3a2cc92cc861295","name":"","server":"aadbde09.d5ab3","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"10","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"climate.heizkorper_schlafzimmer_heating_1","state_type":"num","halt_if":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"x":1310,"y":1520,"wires":[["09f0fa1ef910e33b"]]},{"id":"09f0fa1ef910e33b","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"move","p":"data.attributes.temperature","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1720,"y":1520,"wires":[["372d79823d278971"]]},{"id":"372d79823d278971","type":"join","z":"f3a2cc92cc861295","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1620,"y":1380,"wires":[["542142ec549ad37c"]]},{"id":"542142ec549ad37c","type":"function","z":"f3a2cc92cc861295","name":"Diff Sleeping Room","func":"msg.topic = \"diff_sleepingroom\"\nmsg.payload = msg.payload[\"sensor.schlafzimmer_temperatur\"] - msg.payload[\"climate.heizkorper_schlafzimmer_heating_1\"]\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1870,"y":1420,"wires":[["2047ac9bb075cee5"]]},{"id":"2b68eda9462e7eeb","type":"rbe","z":"f3a2cc92cc861295","name":"","func":"rbe","gap":"","start":"","inout":"out","septopics":false,"property":"payload","topi":"topic","x":2430,"y":1040,"wires":[["6371901e917a52f4"]]},{"id":"5299401afd040e9f","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2230,"y":1020,"wires":[["2b68eda9462e7eeb"]]},{"id":"02c5d80834f5aab8","type":"switch","z":"f3a2cc92cc861295","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"lte","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":2040,"y":1040,"wires":[["5299401afd040e9f"],["931e6c4a7ad9d2eb"]]},{"id":"931e6c4a7ad9d2eb","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2230,"y":1060,"wires":[["2b68eda9462e7eeb"]]},{"id":"ed24454474e19b43","type":"rbe","z":"f3a2cc92cc861295","name":"","func":"rbe","gap":"","start":"","inout":"out","septopics":false,"property":"payload","topi":"topic","x":2430,"y":1420,"wires":[["6371901e917a52f4"]]},{"id":"e656cf2ed2e5112a","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2250,"y":1400,"wires":[["ed24454474e19b43"]]},{"id":"2047ac9bb075cee5","type":"switch","z":"f3a2cc92cc861295","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"lte","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":2060,"y":1420,"wires":[["e656cf2ed2e5112a"],["1c6d42ae5632c0f9"]]},{"id":"1c6d42ae5632c0f9","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2250,"y":1440,"wires":[["ed24454474e19b43"]]},{"id":"5f56622c0ed23230","type":"debug","z":"f3a2cc92cc861295","name":"on","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":3330,"y":1120,"wires":[]},{"id":"6371901e917a52f4","type":"join","z":"f3a2cc92cc861295","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":2590,"y":1200,"wires":[["bd30b7076918239c"]]},{"id":"081e1d9e0efe5aff","type":"switch","z":"f3a2cc92cc861295","name":"","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"on","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":2950,"y":1200,"wires":[["8fded71cd0f09d6f"],["67a763ed56821bed"]]},{"id":"1f50cfaf987fa5a1","type":"debug","z":"f3a2cc92cc861295","name":"off","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":3330,"y":1220,"wires":[]},{"id":"8fded71cd0f09d6f","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":3120,"y":1120,"wires":[["5f56622c0ed23230"]]},{"id":"67a763ed56821bed","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":3120,"y":1220,"wires":[["1f50cfaf987fa5a1"]]},{"id":"55cccbe797e5119b","type":"debug","z":"f3a2cc92cc861295","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2890,"y":1020,"wires":[]},{"id":"bd30b7076918239c","type":"change","z":"f3a2cc92cc861295","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"on_off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2750,"y":1200,"wires":[["081e1d9e0efe5aff","55cccbe797e5119b"]]},{"id":"aadbde09.d5ab3","type":"server","name":"Home Assistant","version":1,"legacy":false,"addon":false,"rejectUnauthorizedCerts":false,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

At the moment, I’m having troubles getting the switch node to search for “on” in the received message.

Any idea why? Also tried it with regex …

If setting it like so doesn’t work, then the message must be malformed. Set the last debug node to complete message object and post the output.

I guess the proposed config won’t work as there are multiple object values in each payload. And the flow should react on them while some may be “on” and others “off”.

If at least one object value is “on” it should go to output 1, else to output 2.

That’s why I tried contains and regex but both didn’t work. (and “otherwise” for output 2)

Try, the output from join as an array or string.

Well from debug in front of the last switch, I get the following:

{"diff_livingroom":"off","diff_sleepingroom":"on"}

with “on_off : msg.payload : Object”

What would you set the switch outputs to if I want to achieve what I wrote before? (output 1 if at least one of the values is “on”, else output 2)

Thanks

Hi @Mikefila, any idea?

Thanks

This should work

[{"id":"b5271d4488ed3a2c","type":"switch","z":"f3a2cc92cc861295","name":"","property":"payload","propertyType":"msg","rules":[{"t":"regex","v":"on","vt":"str","case":true},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":1530,"y":1160,"wires":[["ad85f5a4d66ea198"],["546bd03d82a4fba5"]]},{"id":"17ef5e26a6472a84","type":"inject","z":"f3a2cc92cc861295","name":"1 on","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"diff_livingroom\":\"on\",\"diff_sleepingroom\":\"off\"}","payloadType":"str","x":1270,"y":1160,"wires":[["b5271d4488ed3a2c"]]},{"id":"ad85f5a4d66ea198","type":"debug","z":"f3a2cc92cc861295","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1710,"y":1080,"wires":[]},{"id":"546bd03d82a4fba5","type":"debug","z":"f3a2cc92cc861295","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1710,"y":1180,"wires":[]},{"id":"0795793add8cc4ed","type":"inject","z":"f3a2cc92cc861295","name":"both off","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"diff_livingroom\":\"off\",\"diff_sleepingroom\":\"off\"}","payloadType":"str","x":1270,"y":1240,"wires":[["b5271d4488ed3a2c"]]}]

Unfortunately this is also putting out “off” for me all the time …

Your flow or my snippet? If I set the snippet on a repeating loop, it at least appears to work as expected. Set the debug node to complete message object and post the message that slips the switch.

After a lot more trial and error I found a solution. Might not be the most beautiful but it works:

[{"id":"d03c3631da50d4b1","type":"join","z":"6c5752a65dc0e4cd","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"4","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":2690,"y":1600,"wires":[["ab6a54e6218727de"]]},{"id":"337a4cdf162ebb5c","type":"change","z":"6c5752a65dc0e4cd","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":3580,"y":1520,"wires":[["aef92195115f6e7d","e996e68ae2048426"]]},{"id":"8786a834adccd789","type":"change","z":"6c5752a65dc0e4cd","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":3580,"y":1620,"wires":[["83144326af7aced8","c51e46e91ae0a197"]]},{"id":"87c180b41e023468","type":"change","z":"6c5752a65dc0e4cd","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"on_off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":3230,"y":1600,"wires":[["88333c10dcee93a7"]]},{"id":"88333c10dcee93a7","type":"switch","z":"6c5752a65dc0e4cd","name":"","property":"payload","propertyType":"msg","rules":[{"t":"regex","v":"on","vt":"str","case":false},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":3370,"y":1600,"wires":[["337a4cdf162ebb5c"],["8786a834adccd789"]]},{"id":"ab6a54e6218727de","type":"function","z":"6c5752a65dc0e4cd","name":"Combine Results","func":"msg.topic = \"result\"\nmsg.payload = msg.payload.diff_livingroom + \" \" + msg.payload.diff_sleepingroom + \" \" + msg.payload.diff_lenasroom + \" \" + msg.payload.diff_hannahsroom;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2850,"y":1600,"wires":[["4b3fd82e1680cdfb","aff9b6dc5a12b01e"]]},{"id":"4b3fd82e1680cdfb","type":"debug","z":"6c5752a65dc0e4cd","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2980,"y":1740,"wires":[]},{"id":"aef92195115f6e7d","type":"debug","z":"6c5752a65dc0e4cd","name":"on","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":3760,"y":1520,"wires":[]},{"id":"83144326af7aced8","type":"debug","z":"6c5752a65dc0e4cd","name":"off","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":3770,"y":1620,"wires":[]},{"id":"aff9b6dc5a12b01e","type":"rbe","z":"6c5752a65dc0e4cd","name":"","func":"rbe","gap":"","start":"","inout":"out","septopics":true,"property":"payload","topi":"topic","x":3050,"y":1600,"wires":[["87c180b41e023468"]]},{"id":"e996e68ae2048426","type":"api-call-service","z":"6c5752a65dc0e4cd","name":"","server":"aadbde09.d5ab3","version":3,"debugenabled":false,"service_domain":"switch","service":"turn_on","entityId":"switch.main_heater_relay","data":"","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":3820,"y":1560,"wires":[[]]},{"id":"c51e46e91ae0a197","type":"api-call-service","z":"6c5752a65dc0e4cd","name":"","server":"aadbde09.d5ab3","version":3,"debugenabled":false,"service_domain":"switch","service":"turn_off","entityId":"switch.main_heater_relay","data":"","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":3830,"y":1660,"wires":[[]]},{"id":"aadbde09.d5ab3","type":"server","name":"Home Assistant","version":2,"addon":false,"rejectUnauthorizedCerts":false,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30}]

The function takes the values of each payload and puts them out combined as one string. The switch with “contains” now works.

Now I only have to think about on how to implement a minimum “on-time” of the switch and some other details like Hysteresis and so on.