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.

Features
  • Stove body and flue gas temperature measurements
  • Flue gas over temperature warning buzzer
  • Air quality monitor for 0.3, 0.5 and 1.0µm - μg/m³
  • Ambient air and humidity sensor
  • Data logging for all measurements, both locally and pushed to Home Assistant database
  • Web server to display the graphs
  • Runs off Power over Ethernet

smoky Screenshot_20260206_141700

Code

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