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.
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.