FireSmartHeating – intelligent control board for electric floor heating

Goal

My goal was to create a device that could control my house’s electric floor heating system. I do some research, but I can’t find any suitable solution. So I decided to come up with my own device.

  • it must fit in a standard electric box (every room has its own, circa 73,5 mm)
  • it must have the possibility to connect temp probe (NTC 10k at 25°C, beta 3963,5)
  • it must have wifi connection with MQTT
  • it must be easy for maintenance
  • it must be easy to install without any special tools
  • it must be easy co add “blocks conditions” – opened window, high temperature etc.

Electronic and PCB

I decided to design my own PCB because as I write on top I can’t find a suitable device. There is no problem to find the device that will control the heating (it is nothing more than a smart relay) but I also need the input for the NTC temp sensor. Because the device needs to sense the temperature of the floor to protect it from overheating).

I also decided to use the through hole components, because I do not have enough experience with SMD soldering and I have many THS part at home.

PCB was designed in EasyEda software because:

  • easy export right to JLCPCB
  • easy to add new parts because the user’s designs database
  • nice and easy user interface

Schematic

PCB

Parts

  • MCU – Wemos D1 mini – I use this all-in-one solution, I do not want to design all stuff around the ESP MCU and I also want to be able quick swap MCU for example if I need to reflash it with new firmware.
  • U1 – PC817 – optocoupler for relay
  • SW1 – tactile switch – was not used in the final firmware setting but it could be handy in future
  • R1 – pot trim 20k – to calibrate temp sensor readings
  • Q1 – MOSFET 2N7000 – to control the relay coil
  • Q2-Q4 – transistor BC639 – to control the RGB led
  • U3 – RGB LED 5mm
  • LED1 – redLED
  • D1 – flyback diode 1N4007
  • U$1 – AC to DC converter – HLK-PM01
  • K1 – Relay

Firmware

This part was tricky, I started by writing my custom firmware in Visual Studio. The used MCU is ESP so it is not a big deal to write firmware but why reinvent the wheel? So I decide to play it safe and use the Tasmota firmware. I need just to compile my own version with rules enabled and expressions enabled. It also offers an easy way to connect it to a Home assistant.

Configuration and rules

In Tasmota we just need to prepare a basic config and assign the pins to the required modules and add custom rules. The whole system use the “block conditions”. The block conditions are opened window, high outsite temperature etc.

RULE 1 - publish data to home assistant and set color to status LED
ON System#Boot DO Var2 %Mem4% ENDON ON Var2#State DO publish %topic%/data {\"state\": \"%var1%\",\"temp\": \"%var2%\",\"requiredrelay\": \"%mem1%\",\"mft\": \"%mem4%\",\"block\": \"%mem2%\"} ENDON ON Var1#State==1 DO Color2 1 BREAK ON Var1#State==2 DO Color2 4 BREAK ON Var1#State==3 DO Color2 3 BREAK ON Var1#State==4 DO Color2 11 BREAK ON Var1#State==5 DO Color2 12 BREAK ON Var1#State==6 DO Backlog Color2 9; Power2 3 BREAK ON Var1#State==7 DO Color2 2 BREAK

RULE2 - main logic of heating and block also failsafe
ON Mem2#State==1 DO Backlog Var1 3; Power1 0 BREAK ON Mem2#State==2 DO Backlog Var1 4; Power1 0 BREAK ON Mem2#State==3 DO Backlog Var1 5; Power1 0 BREAK ON Var2#State<5 DO Backlog Powe1 0; Var1 6 BREAK ON Var2#State>30 DO Backlog Powe1 0; Var1 6 BREAK ON Var2#State DO IF ((Mem1==0) AND (Mem2==0)) Backlog Power1 0; Var1 7 ENDIF ENDON ON Var2#State DO IF ((Mem1==1) AND (Var2<=Mem4) AND (Mem2==0)) Backlog Power1 1; Var1 1 ELSEIF ((Var1==1) AND (Var2<=Mem4+0.5)) ELSEIF ((Mem1==1) AND (Var2>=Mem4)) Backlog Power1 0; Var1 2 ENDIF ENDON

RULE3 - calculate floor temperature
ON Tele-Analog#a0 DO var2=%value%*0.0916-21.500 ENDON

Tasmota variables

mem1 - required relay
mem2 - block
mem3 - required room temp
mem4 - max floor temp
var1 - status
var2 - floor temp

Different heating statuses

1) mem1 = 1; floor act < floor max; block 0;  -- color 1 (red)  heating
2) mem1 = 1; floor act >= floor max; block 0; -- color 4 (orange) heated
3) block 1;- - color 3 (blue) - turned off
4) block 2;- - color 11 (pink) - turned by window
5) block 3;- -- color 12 (green)  - turned by outside temp
6) floor act < 8°C or floor act > 32 °C-  color2 9; - error
7) temp ok (mem1 =0) --color 2 -thermostat temp OK

Home Assistant integration

Config in HA

In HA is required setup some helpers variables. So you need to go to settings – devices – helpers and create a new helper.

  • input_boolean.heating_bedroom_override – type toggle

Other settings in config files

climate:
# BEDROOM THERMOSTAT
- platform: simple_thermostat
  name: heating bedroom
  heater: switch.heating_bedroom_required_relay
  target_sensor: sensor.bedroom_temp_temperature
  min_temp: 16
  max_temp: 26
  precision: 0.5
  min_cycle_duration: "00:01:00"
  keep_alive: "00:02:00"
number:
# heating bedroom
- platform: mqtt
  name: "Bedroom heating block"
  command_topic: "cmnd/fsfh_bedroom/mem2"
  state_topic: "fsfh_bedroom/data"
  availability_topic: "tele/fsfh_bedroom/LWT"
  value_template: "{{ value_json['block'] }}"
  payload_available: "Online"
  payload_not_available: "Offline"
  min: 0
  max: 3
- platform: mqtt
  name: "Bedroom heating max floor temp"
  command_topic: "cmnd/fsfh_bedroom/mem4"
  state_topic: "fsfh_bedroom/data"
  availability_topic: "tele/fsfh_bedroom/LWT"
  value_template: "{{ value_json['mft'] }}"
  payload_available: "Online"
  payload_not_available: "Offline"
  min: 0
  max: 35
sensor:
# bedroom
- platform: mqtt
  name: Heating bedroom state
  state_topic: "fsfh_bedroom/data"
  value_template: *heatingstatus_new
  availability_topic: "tele/fsfh_bedroom/LWT"
  payload_available: "Online"
  payload_not_available: "Offline"
  json_attributes_topic: "tele/fsfh_bedroom/STATE"
  json_attributes_template: "{{ value_json.Wifi | tojson }}"
  icon: mdi:information
- platform: mqtt
  device_class: temperature
  expire_after: 120
  name: Heating bedroom floor temp
  state_topic: "fsfh_bedroom/data"
  value_template: '{{ value_json["temp"]|round(1,"half") }}'
  availability_topic: "tele/fsfh_bedroom/LWT"
  payload_available: "Online"
  payload_not_available: "Offline"
  json_attributes_topic: "tele/fsfh_bedroom/STATE"
  json_attributes_template: "{{ value_json.Wifi | tojson }}"
  unit_of_measurement: °C   
switch:
# bedroom
- platform: mqtt
  name: "Heating bedroom required relay"
  state_topic: "fsfh_bedroom/data"
  value_template: "{{ value_json['requiredrelay'] }}"
  command_topic: "cmnd/fsfh_bedroom/Mem1"
  payload_on: "1"
  payload_off: "0"
  availability_topic: "tele/fsfh_bedroom/LWT"
  json_attributes_topic: "tele/fsfh_bedroom/STATE"
  json_attributes_template: "{{ value_json.Wifi | tojson }}"
  payload_available: "Online"
  payload_not_available: "Offline"
  qos: 1
  retain: false

Node-RED

I use Node-red to control the relay behavior, also it checks for other events like window open etc., and edits behavior to reflect this event. It checks also the outside temperature.

[{"id":"607270a5e71b987e","type":"group","z":"c2e27764dd36158e","name":"Heating","style":{"label":true},"nodes":["68f85bf92b6bd8d0","f7e7513c75cf25fb","9f11c0084f3595e4","55711029ba40d454","ee7d20548ea2b054","0d6390260e6dcba4","e43d8343b2f6780f","373d24013d7db546","fe9be152697e7174","65de69ce7ce14ca1","dd9a8c65c02b58cb","170a58063bfa5420"],"x":34,"y":19,"w":1002,"h":302},{"id":"68f85bf92b6bd8d0","type":"poll-state","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Bedroom-Mode","server":"847ef13.1f4d81","version":2,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"updateinterval":"30","updateIntervalType":"num","updateIntervalUnits":"seconds","outputinitially":false,"outputonchanged":false,"entity_id":"climate.heating_bedroom","state_type":"str","halt_if":"off","halt_if_type":"str","halt_if_compare":"is","outputs":2,"x":140,"y":100,"wires":[["65de69ce7ce14ca1"],["ee7d20548ea2b054","0d6390260e6dcba4"]]},{"id":"f7e7513c75cf25fb","type":"api-current-state","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Window ?","server":"847ef13.1f4d81","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"binary_sensor.bedroom_window_contact","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"2","valueType":"num"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":320,"y":220,"wires":[["55711029ba40d454"],["9f11c0084f3595e4"]]},{"id":"9f11c0084f3595e4","type":"api-current-state","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Outside temp?","server":"847ef13.1f4d81","version":3,"outputs":2,"halt_if":"18","halt_if_type":"num","halt_if_compare":"gte","entity_id":"sensor.openweathermap_temperature","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"3","valueType":"num"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":340,"y":280,"wires":[["55711029ba40d454"],["fe9be152697e7174"]]},{"id":"55711029ba40d454","type":"api-call-service","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Set block","server":"847ef13.1f4d81","version":5,"debugenabled":false,"domain":"number","service":"set_value","areaId":[],"deviceId":[],"entityId":["number.bedroom_heating_block"],"data":"{\"value\": msg.payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":700,"y":180,"wires":[["dd9a8c65c02b58cb"]]},{"id":"ee7d20548ea2b054","type":"api-current-state","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Override enabled?","server":"847ef13.1f4d81","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.heating_bedroom_override","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"0","valueType":"num"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":370,"y":160,"wires":[["55711029ba40d454"],["f7e7513c75cf25fb"]]},{"id":"0d6390260e6dcba4","type":"api-current-state","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Get thermostat temperature","server":"847ef13.1f4d81","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"climate.heating_bedroom","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entity"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":400,"y":100,"wires":[["e43d8343b2f6780f"]]},{"id":"e43d8343b2f6780f","type":"change","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$number(msg.payload.attributes.temperature)+3","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":60,"wires":[["373d24013d7db546"]]},{"id":"373d24013d7db546","type":"api-call-service","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Set max floor temp","server":"847ef13.1f4d81","version":5,"debugenabled":false,"domain":"number","service":"set_value","areaId":[],"deviceId":[],"entityId":["number.bedroom_heating_max_floor_temp"],"data":"{\"value\":msg.payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":730,"y":120,"wires":[[]]},{"id":"fe9be152697e7174","type":"change","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"No block","rules":[{"t":"set","p":"payload","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":280,"wires":[["55711029ba40d454"]]},{"id":"65de69ce7ce14ca1","type":"change","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Turned off block","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":60,"wires":[["55711029ba40d454"]]},{"id":"dd9a8c65c02b58cb","type":"debug","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":920,"y":180,"wires":[]},{"id":"170a58063bfa5420","type":"comment","z":"c2e27764dd36158e","g":"607270a5e71b987e","name":"Bedroom","info":"bedroom","x":120,"y":60,"wires":[]},{"id":"847ef13.1f4d81","type":"server","name":"Home Assistant","version":2,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":false,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30}]

Summary

What is good?

  • works nice
  • easy to integrate into Home Assistant
  • easy to install

What is bad?

  • fits is really tight so next time I will use SMD parts
  • I forgot to add support parts for the AC/DC converter (it should not be big deal it could only be more susceptible  to overvoltage on the AC side)

Open source

If you are interested in some development or help. Feel free to contact me via email. I will provide you with source codes etc.

GITHUB REPOSITORY

WARNING!

I do not have any responsibility if you build it or use it and it causes damage to health or property. All on this blog is for educational purposes and for inspiration.