We work from home most days and use wood as our primary source of heat. To help watch the stove while we're home and gain more insight on how the stove is operating I installed a Pi based temperature monitor. I had an old Pi1 laying around and some K type thermocouples so I turned it all into a monitoring server/DAQ. The combination of flue gas temperature and stove top temperature gives a great indication as to when someone needs to go downstairs and toss some more wood on to maintain the heat/efficiency. Its very convenient knowing how the stove did the night before or how its currently burning.

import io
import time
import datetime
from decimal import Decimal
import subprocess
import board
import busio
import digitalio
import adafruit_mcp9600 #for new thermocouple read
import microcontroller #for cpu die temp sensor
import adafruit_sht31d #for temp and humidity sensor
from multiprocessing import Process
#for indoor polution sensor
from digitalio import DigitalInOut, Direction, Pull
from adafruit_pm25.i2c import PM25_I2C
#for MQTT
import paho.mqtt.client as mqtt
reset_pin = None #config for polution sensor
# If you have a GPIO, its not a bad idea to connect it to the RESET pin
#Variables
TOPIC_CPU="smoky/cpu_temp"
TOPIC_TOP="smoky/stovetop_temp"
TOPIC_FLUE="smoky/flue_temp"
TOPIC_AMBIENT="smoky/ambient_temp"
TOPIC_HUMIDITY="smoky/humidity"
TOPIC_AIR_3="smoky/airquality_0.3um_temp"
TOPIC_AIR_5="smoky/airquality_0.5um_temp"
TOPIC_AIR_10="smoky/airquality_1.0um_temp"
# Define the MQTT broker details
ADDRESS = "192.168.200.5"
PORT = 1883
USERNAME = 'smoky'
PASSWORD = 'PASSWORD'
######Setup the I2C bus for the below devices
# Create library object, use 'slow' 100KHz frequency for air quality sensor
i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
#####Setup the new I2C thermocouple devices
thermo_flue = adafruit_mcp9600.MCP9600(i2c,address=0x65)
thermo_top = adafruit_mcp9600.MCP9600(i2c,address=0x66)
thermo_furnace = adafruit_mcp9600.MCP9600(i2c,address=0x67,tctype="J") #set the furance thermocouple as J type. K is the default.
######Setup the I2C temp and humidity sensor
sht31 = adafruit_sht31d.SHT31D(i2c)
######Setup the I2C air quality sensor
pm25 = PM25_I2C(i2c, reset_pin)
######Setup the buzzer outpit
relay1 = digitalio.DigitalInOut(board.D18)
relay1.direction = digitalio.Direction.OUTPUT
#Setup the buzzer sub-process. When the flue temp is too high sound the buzzer in a loop until temp is corrected.
def buzz_buzz():
#three short chirps
relay1.value = True
time.sleep(0.1)
relay1.value = False
time.sleep(0.1)
relay1.value = True
time.sleep(0.1)
relay1.value = False
time.sleep(0.1)
relay1.value = True
time.sleep(0.1)
relay1.value = False
#this will run every X seconds, whatever the main loops polling rate is.
#beep to announce the start of the script.
relay1.value = True
time.sleep(0.1)
relay1.value = False
time.sleep(0.5)
while True:
try:
print('Collecting thermocouple data')
#collect thermocouple data
flue_temp = thermo_flue.temperature
time.sleep(0.100)
top_temp = thermo_top.temperature
time.sleep(0.100)
furnace_temp = thermo_furnace.temperature
#collect CPU die temp
cpu_temp = open("/sys/class/thermal/thermal_zone0/temp", "r")
cpu_temp = cpu_temp.readline ()
cpu_temp = cpu_temp[:-4]
print('Collecting air quality data')
#collect air quality data
aqdata = pm25.read()
#Examples on how to use the data
#print("Particles > 0.3um / 0.1L air:", aqdata["particles 03um"])
#print("Particles > 0.5um / 0.1L air:", aqdata["particles 05um"])
#print("Particles > 1.0um / 0.1L air:", aqdata["particles 10um"])
print('Pushing all the data over MQTT')
#push all the data over MQTT to HASS
# Create a client instance
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
# Set the credentials
client.username_pw_set(USERNAME, PASSWORD)
# Connect to the broker
client.connect(ADDRESS, PORT)
# Publish CPU temperature
cpu_temp = Decimal(cpu_temp) # Convert the string to a Decimal object
cpu_temp = round(cpu_temp,2) # Round to two digits
print('CPU Temperature: {:.2f}C'.format(cpu_temp))
payload = '{"value":' + str(cpu_temp) + "}"
client.publish(TOPIC_CPU, payload) # Publish a message to a topic
# Publish stove top temperature
top_temp = Decimal(top_temp) # Convert the string to a Decimal object
top_temp = round(top_temp,2) # Round to two digits
print('Stove Top Temperature: {:.2f}C'.format(top_temp))
payload = '{"value":' + str(top_temp) + "}"
client.publish(TOPIC_TOP, payload) # Publish a message to a topic
# Publish stove flue temperature
flue_temp = Decimal(flue_temp) # Convert the string to a Decimal object
flue_temp = round(flue_temp,2) # Round to two digits
print('Flue Temperature: {:.2f}C'.format(flue_temp))
payload = '{"value":' + str(flue_temp) + "}"
client.publish(TOPIC_FLUE, payload) # Publish a message to a topic
# Publish furnace exhaust temperature
furnace_temp = Decimal(furnace_temp) # Convert the string to a Decimal object
furnace_temp = round(furnace_temp,2) # Round to two digits
print('Furnace Exhaust Temperature: {:.2f}C'.format(flue_temp))
# Publish ambient temperature
payload = '{"value":' + str(round(sht31.temperature,2)) + "}"
client.publish(TOPIC_AMBIENT, payload) # Publish a message to a topic
# Publish humidity
payload = '{"value":' + str(round(sht31.relative_humidity,2)) + "}"
client.publish(TOPIC_HUMIDITY, payload) # Publish a message to a topic
# Publish air quality
payload = '{"value":' + str(aqdata["particles 03um"]) + "}"
client.publish(TOPIC_AIR_3, payload) # Publish a message to a topic
payload = '{"value":' + str(aqdata["particles 05um"]) + "}"
client.publish(TOPIC_AIR_5, payload) # Publish a message to a topic
payload = '{"value":' + str(aqdata["particles 10um"]) + "}"
client.publish(TOPIC_AIR_10, payload) # Publish a message to a topic
# Disconnect from the broker
client.disconnect()
# **** BUZZ BUZZ LOOP ****
#if the flue temp is GREATER than 350C, buzz buzz. The 'Process' call runs the buzz loop in parallel so it doesn't mess with the main loop
if (flue_temp > 450):
#start the buzz routine
Process(target=buzz_buzz).start()
print("Too hot! Buzzer ahoy! Flue is",flue_temp)
print('Sleeping for 10 seconds and waiting for the next loop')
#sleep before going to the next loop
time.sleep(10)
except OSError:
pass
except RuntimeError:
pass