Making Yoghurt with Home Assistant
, by 4eyes.I have recently heard about SDR (Software Defined Radio) programs. I obviously don't know how they are working but basically what I understand is, you can tune in to radio signals and listen 433 Mhz smart devices. Also apparently you can listen FM radio or TV with them but it's not the point.
Anyways, I was looking to buy one of those bluetooth BBQ thermometers and after reading about SDR, I thought I can get away with cheaper wireless grill thermometers works with 433 Mhz radio frequency. And surprisingly, they went well than I expected. So here is how I am monitoring home made yoghurt process with Home Assistant.
Ingredients
This is my grandmother's home made yoghurt reciper. Here are the ingredients:
Hardware
- RTL-SDR dongle. This one from amazon worked well for me but I think any device based on the chip RTL2832U would work. Also these devices comes with many names, you can read about them at rtl-sdr.com.
- RF BBQ Thermometer that works with 433Mhz radio. This one had good rating at Amazon, has 2 thermometers.
- 1 lt Milk. I prefer full fat but it's up to you...
- Yoghurt to start fermentation. You can use unswetened Turkish or Greek yoghurt you got from store to start. Depends on the country but I think they should have some live bacteria. And moving forward just save 1/3 cup of yoghurt you made for next batch.
- A pot big enough to boil the milk.
- Optional: Another non-metal cup for fermentation.
- Optional: A tablet running for wallpanel for notification but any type of notification platform will work.
Software
- Home Assistant
- Node Red
- rtl_433. There are also some hassio addons or docker containers but I am just running this programm in another PC I am using. This is the main program we need and needs to be installed on the same machine the dongle connected.
- mqtt. Any mqtt broker will do.
Instructions
Important: Please don't put milk on the stove until you finish the installation. Most probably you will forget about it, it will boil over and make a huge mess.
Installation
Step 1: Connect the RTL-SDR dongle to the PC you want to use. There are a few ways to manage this but it doesn't have to be same computer you are running home assistant. Because the communication between this and home-assitant (Node-Red really) will be via mqtt. So as long as you can install rtl_433 software and mqtt client, you can go with windows PC, raspberry pi or hassio box you have. As I said there are also add-ons but I didn't use them.
Step 2: Test if the rtl_433 and dongle is working. Just run rtl_433
command in the console. rtl_433 will connect the first device it finds and print the decoded message to the console. Plug the prongs to the transmitter piece of the BBQ thermometer and turn it on. Soon you should see the readings from your thermometer.
Step 3: 1. We need to run the program as a deamon and make it publish the readings as mqtt messages as explained here. The program is very flexible and have many options but my setup isn't that complicated.
Step 3.a: You can edit "default" configuration. rtl_433 looks for rtl_433.conf file in /etc/rtl_433/rtl_433.conf
file. Mine just looks like below. This will publish the reading as mqtt message and we will pick up the payload with node-red.
output mqtt://192.168.178.31:1883,retain=0,events=rtl_433
Step 3.b: We will create a systemd service so the program can run at the background and keep publishing messages whenever picks up a signal. If you monitor the mqtt topic for example you may see TPMS messages coming from nearby cars. I think these are tyre pressure sensors... or weather station signals if you or your neighbours have one. Create a this file /etc/systemd/system/rtl_433-mqtt.service
as below but be sure the ExecStart parameter points to correct binary. You can locate it with which rtl_433
command.
[Unit]
Description=rtl_433 to MQTT publisher
After=network.target
[Service]
ExecStart=/usr/bin/rtl_433
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Step 3.c: Enable and start the service with these commands:
systemctl enable rtl_433-mqtt.service
systemctl enable rtl_433-mqtt.start
Step 3.d: You can controll if everything is ok with systemctl status rtl_433-mqtt.service
Step 4: We are done with the RF part, we can move to home assistant. First node-red flow we need is for creating temperature sensor from the BBQ thermometer. The mqtt message from rtl_433 looks like this:
{"json":"json"}
So, the flow looks like this.
[{"id":"43e819b9.a18c38","type":"mqtt in","z":"f2e679e5.847728","name":"","topic":"rtl_433/#","qos":"2","datatype":"json","broker":"f23573b9.c5acf","x":180,"y":160,"wires":[["5c7f6f5a.415a","31cff454.23fa4c"]]},{"id":"3a6d5643.7ba39a","type":"ha-entity","z":"f2e679e5.847728","name":"rtl radio","server":"9999f305.d47be","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"RTL 433"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:radio-tower"},{"property":"unit_of_measurement","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":540,"y":120,"wires":[[]]},{"id":"5c7f6f5a.415a","type":"json","z":"f2e679e5.847728","name":"","property":"payload","action":"str","pretty":false,"x":370,"y":120,"wires":[["3a6d5643.7ba39a"]]},{"id":"3e4369cd.2d2406","type":"ha-entity","z":"f2e679e5.847728","name":"ThermoPro 1","server":"9999f305.d47be","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"ThermoPro 1"},{"property":"device_class","value":"temperature"},{"property":"icon","value":""},{"property":"unit_of_measurement","value":"°C"}],"state":"payload.temperature_1_C","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":550,"y":180,"wires":[[]]},{"id":"31cff454.23fa4c","type":"switch","z":"f2e679e5.847728","name":"","property":"payload.model","propertyType":"msg","rules":[{"t":"eq","v":"Thermopro-TP12","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":370,"y":180,"wires":[["3e4369cd.2d2406"]]},{"id":"f23573b9.c5acf","type":"mqtt-broker","name":"home_mqtt","broker":"192.168.178.31","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"9999f305.d47be","type":"server","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]
Step 5: The second flow is to run the fermentation process. Without getting in to too much details, basically it uses node-red switch entities and delay nodes for timing. mqtt publish node to send a notification to wall panel. Also an important component here is the a derivative sensor to track the trend of thermometer. To see if the milk is cooling. First the trend sensor:
binary_sensor:
- platform: trend
sensors:
yoghurt_cooling:
entity_id: sensor.thermopro_1
sample_duration: 300
max_samples: 40
min_gradient: -0.0016
device_class: cold
And the node-red flow. This flow creates 3 switches (Start, Move to Fermentation and Reset the process), 1 sensor entity to show which state we are and running the notifications.
[{"id":"323fc8e3.04fdf8","type":"ha-entity","z":"a0c9c58.806d338","name":"Start","server":"9999f305.d47be","version":1,"debugenabled":false,"outputs":2,"entityType":"switch","config":[{"property":"name","value":"Yoghurt Start"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:step-forward"},{"property":"unit_of_measurement","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":110,"y":100,"wires":[[],["c6b55bc1.4b5928"]]},{"id":"eac0ad44.7975","type":"ha-entity","z":"a0c9c58.806d338","name":"Ferment","server":"9999f305.d47be","version":1,"debugenabled":false,"outputs":2,"entityType":"switch","config":[{"property":"name","value":"Yoghurt Fermentation"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:step-forward-2"},{"property":"unit_of_measurement","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":120,"y":280,"wires":[[],["ddf712bf.ea26d"]]},{"id":"c200701c.efec7","type":"ha-entity","z":"a0c9c58.806d338","name":"Reset","server":"9999f305.d47be","version":1,"debugenabled":false,"outputs":2,"entityType":"switch","config":[{"property":"name","value":"Yoghurt Reset"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:restart-alert"},{"property":"unit_of_measurement","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":110,"y":340,"wires":[[],["644382be.61cfbc","ba23306e.d4f31"]]},{"id":"f314d5b0.a50718","type":"ha-entity","z":"a0c9c58.806d338","name":"yoghurt state","server":"9999f305.d47be","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"Yogurt State"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:pot-steam"},{"property":"unit_of_measurement","value":""}],"state":"payload","stateType":"msg","attributes":[{"property":"details","value":"detail","valueType":"msg"}],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":910,"y":220,"wires":[[]]},{"id":"644382be.61cfbc","type":"change","z":"a0c9c58.806d338","name":"state idle","rules":[{"t":"set","p":"payload","pt":"msg","to":"Idle","tot":"str"},{"t":"set","p":"detail","pt":"msg","to":"Waiting...","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":660,"y":340,"wires":[["f314d5b0.a50718"]]},{"id":"c6b55bc1.4b5928","type":"api-current-state","z":"a0c9c58.806d338","name":"is idle","server":"9999f305.d47be","version":1,"outputs":2,"halt_if":"Idle","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"sensor.yogurt_state","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","blockInputOverrides":false,"x":310,"y":100,"wires":[["60204e42.6492b"],[]]},{"id":"60204e42.6492b","type":"change","z":"a0c9c58.806d338","name":"state started","rules":[{"t":"set","p":"payload","pt":"msg","to":"Started","tot":"str"},{"t":"set","p":"detail","pt":"msg","to":"Process started.","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":650,"y":100,"wires":[["f314d5b0.a50718"]]},{"id":"3a334d42.aa1dc2","type":"trigger-state","z":"a0c9c58.806d338","name":"yoghurt cooling","server":"9999f305.d47be","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityid":"binary_sensor.yoghurt_cooling","entityidfiltertype":"exact","debugenabled":false,"constraints":[{"targetType":"this_entity","targetValue":"","propertyType":"current_state","comparatorType":"is","comparatorValueDatatype":"str","comparatorValue":"on","propertyValue":"new_state.state"}],"outputs":2,"customoutputs":[],"outputinitially":false,"state_type":"str","x":140,"y":160,"wires":[["33a486d3.17873a"],[]]},{"id":"33a486d3.17873a","type":"api-current-state","z":"a0c9c58.806d338","name":"is started","server":"9999f305.d47be","version":1,"outputs":2,"halt_if":"Started","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"sensor.yogurt_state","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","blockInputOverrides":false,"x":320,"y":160,"wires":[["e24cb622.a05ad8"],[]]},{"id":"e24cb622.a05ad8","type":"change","z":"a0c9c58.806d338","name":"state cooling","rules":[{"t":"set","p":"payload","pt":"msg","to":"Cooling","tot":"str"},{"t":"set","p":"detail","pt":"msg","to":"Wait for temperature to drop 47.5","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":650,"y":160,"wires":[["f314d5b0.a50718"]]},{"id":"bdbb4a65.68cdb8","type":"trigger-state","z":"a0c9c58.806d338","name":"Yeast Temp","server":"9999f305.d47be","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityid":"sensor.thermopro_1","entityidfiltertype":"exact","debugenabled":false,"constraints":[{"targetType":"this_entity","targetValue":"","propertyType":"current_state","comparatorType":"<","comparatorValueDatatype":"num","comparatorValue":"47.6","propertyValue":"new_state.state"}],"outputs":2,"customoutputs":[],"outputinitially":false,"state_type":"str","x":130,"y":220,"wires":[["bbcbbdbd.4801d"],[]]},{"id":"bbcbbdbd.4801d","type":"api-current-state","z":"a0c9c58.806d338","name":"is cooling","server":"9999f305.d47be","version":1,"outputs":2,"halt_if":"Cooling","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"sensor.yogurt_state","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","blockInputOverrides":false,"x":320,"y":220,"wires":[["d811d5f.6b10128"],[]]},{"id":"d811d5f.6b10128","type":"change","z":"a0c9c58.806d338","name":"state add yeast","rules":[{"t":"set","p":"payload","pt":"msg","to":"Add yeast","tot":"str"},{"t":"set","p":"detail","pt":"msg","to":"Click fermantation to go 2nd phase.","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":220,"wires":[["f314d5b0.a50718"]]},{"id":"ddf712bf.ea26d","type":"api-current-state","z":"a0c9c58.806d338","name":"Add yeast","server":"9999f305.d47be","version":1,"outputs":2,"halt_if":"Add yeast","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"sensor.yogurt_state","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","blockInputOverrides":false,"x":320,"y":280,"wires":[["cc99330.e8e3dd"],[]]},{"id":"cc99330.e8e3dd","type":"change","z":"a0c9c58.806d338","name":"state fermenting","rules":[{"t":"set","p":"payload","pt":"msg","to":"Fermenting","tot":"str"},{"t":"set","p":"detail","pt":"msg","to":"Open the yoghurt in 3.5 hours.","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":280,"wires":[["f314d5b0.a50718"]]},{"id":"9346c3af.a5031","type":"trigger-state","z":"a0c9c58.806d338","name":"Yeast time?","server":"9999f305.d47be","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityid":"sensor.yogurt_state","entityidfiltertype":"exact","debugenabled":false,"constraints":[{"targetType":"this_entity","targetValue":"","propertyType":"current_state","comparatorType":"is","comparatorValueDatatype":"str","comparatorValue":"Add yeast","propertyValue":"new_state.state"}],"outputs":2,"customoutputs":[],"outputinitially":false,"state_type":"str","x":130,"y":560,"wires":[["d012ef6b.dc296"],[]]},{"id":"36614dc8.76b3c2","type":"mqtt out","z":"a0c9c58.806d338","name":"","topic":"","qos":"","retain":"","broker":"f23573b9.c5acf","x":930,"y":480,"wires":[]},{"id":"d012ef6b.dc296","type":"change","z":"a0c9c58.806d338","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"wallpanel/entrance/command","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"{\"speak\":\"Milk is ready, add yeast!\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":560,"wires":[["6cea36df.f76e48"]]},{"id":"6cea36df.f76e48","type":"looptimer","z":"a0c9c58.806d338","duration":"30","units":"Second","maxloops":"100","maxtimeout":"1","maxtimeoutunits":"Hour","name":"","x":700,"y":520,"wires":[["36614dc8.76b3c2"],[]]},{"id":"a8b70899.c93438","type":"trigger-state","z":"a0c9c58.806d338","name":"Fermenting","server":"9999f305.d47be","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityid":"sensor.yogurt_state","entityidfiltertype":"exact","debugenabled":false,"constraints":[{"targetType":"this_entity","targetValue":"","propertyType":"current_state","comparatorType":"is","comparatorValueDatatype":"str","comparatorValue":"Fermenting","propertyValue":"new_state.state"},{"targetType":"this_entity","targetValue":"","propertyType":"previous_state","comparatorType":"is","comparatorValueDatatype":"str","comparatorValue":"Add yeast","propertyValue":"old_state.state"}],"outputs":2,"customoutputs":[],"outputinitially":false,"state_type":"str","x":130,"y":440,"wires":[["35f9e7f9.461f38","2f463cee.09be04"],[]]},{"id":"35f9e7f9.461f38","type":"change","z":"a0c9c58.806d338","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"STOP","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":500,"wires":[["6cea36df.f76e48"]]},{"id":"2f463cee.09be04","type":"delay","z":"a0c9c58.806d338","name":"","pauseType":"delay","timeout":"210","timeoutUnits":"minutes","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":490,"y":440,"wires":[["cbd93a8.7512fc8","644382be.61cfbc"]]},{"id":"cbd93a8.7512fc8","type":"change","z":"a0c9c58.806d338","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"wallpanel/entrance/command","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"{\"speak\":\"Yoghurt is ready. Open it\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":440,"wires":[["36614dc8.76b3c2"]]},{"id":"ba23306e.d4f31","type":"change","z":"a0c9c58.806d338","name":"","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":380,"wires":[["2f463cee.09be04"]]},{"id":"9999f305.d47be","type":"server","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true},{"id":"f23573b9.c5acf","type":"mqtt-broker","name":"home_mqtt","broker":"192.168.178.31","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]
Step 6: Finally the lovelace card. It's a very rudimentary card using the basic card types.
type: vertical-stack
title: Yoghurt
cards:
- type: horizontal-stack
cards:
- type: button
tap_action:
action: call-service
service: nodered.trigger
service_data:
entity_id: switch.yoghurt_start
entity: switch.yoghurt_start
name: Start
- type: button
tap_action:
action: call-service
service: nodered.trigger
service_data:
entity_id: switch.yoghurt_fermentation
entity: switch.yoghurt_fermentation
name: Fermentation
- type: button
tap_action:
action: call-service
service: nodered.trigger
service_data:
entity_id: switch.yoghurt_reset
entity: switch.yoghurt_reset
name: Reset
- type: entity
entity: sensor.yogurt_state
name: Status
- type: horizontal-stack
cards:
- type: sensor
entity: sensor.thermopro_1
graph: line
- type: markdown
content: '{{ state_attr(''sensor.yogurt_state'', ''details'') }}'
title: Details
Step 7: We are done, put the milk on the stove and continue with the next section.
Making the yoghurt
This section explains how evertyhing falls together. Basically, the bacteria eats the sugar in milk in the right conditions and give yoghurt the characteristic taste and texture.
- First step is bringing the milk to boil. But beware, it may boil over and make a big mess so watch it. In reality probably you don't need to boil the milk because most probably you don't have access to unpasteurized milk anyway. But this is how our mothers used to prepare yoghurt at home.
- Once the milk start boiling, pour it to another non-metal, non-plastic cup. Metal may interfere with the fermentation process and we get much smoother yoghurt when we use a heat resistance glass bowl with lid.
- Turn on the thermometer, dip the prong to milk and click start button on lovelace. The status will change from idle to started. After a while, the trend sensor state will change to cold and status will be cooling.
- Once the temperature drops the 47.5 C, add the 2 table spoon yoghurt and stir lightly. This will start the fermentation process. You can use store bought plain yoghurt. It would contain enough live bacteria and moving forward you can use your own yoghurt. The temperature is critical. The traditional way to test if milk is at the right temperature is by dipping your pinky to milk. If you can keep your finger for 6 seconds then it means ready. If it is too cold or too hot, you will not have good results.
- After adding the yoghurt, close the lid and cover the the bowl with a table cloth or small blanket to keep it warm during the fermentation process. Put somewhere in the kitchen or in the oven, out of traffic. Click the fermentation button, this will start the timer and it will notify when time is up. Here you have to find the sweet spot by trial and error. Depending on your setup (blanket, bowl or amount of the milk) the fermentation could take 3 to 4 hours. if you let it ferment too long, it will be sour. If you end up too early, it will not have the nice consistency.
- Once the fermentation is done, move the bowl to refrigerator.
I hope you know what to do with plain yoghurt...