Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
.DStore/
thumbs.db
#storage/profiles
#config.py
config.py
.idea/*
state.json
venv/*
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Download [Raspberry PI OS](https://www.raspberrypi.org/software/). Use Rasberry

$ sudo apt-get update
$ sudo apt-get dist-upgrade
$ sudo apt-get install python3-dev
$ git clone https://github.com/jbruce12000/kiln-controller
$ cd kiln-controller
$ python3 -m venv venv
Expand All @@ -86,6 +87,10 @@ If you're done playing around with simulations and want to deploy the code on a

## Configuration

Before you begin, make sure to copy the default settings:

$cp config-default.py config.py

All parameters are defined in config.py. You need to read through config.py carefully to understand each setting. Here are some of the most important settings:

| Variable | Default | Description |
Expand Down
40 changes: 25 additions & 15 deletions config.py → config-default.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
# This is used to calculate a cost estimate before a run. It's also used
# to produce the actual cost during a run. My kiln has three
# elements that when my switches are set to high, consume 9460 watts.
kwh_rate = 0.1319 # cost per kilowatt hour per currency_type to calculate cost to run job
kw_elements = 9.460 # if the kiln elements are on, the wattage in kilowatts
kwh_rate = 1 # cost per kilowatt hour per currency_type to calculate cost to run job
kw_elements = 6.9 # if the kiln elements are on, the wattage in kilowatts
currency_type = "$" # Currency Symbol to show when calculating cost to run job

########################################################################
Expand Down Expand Up @@ -84,11 +84,11 @@

try:
import board
spi_sclk = board.D17 #spi clock
spi_miso = board.D27 #spi Microcomputer In Serial Out
spi_cs = board.D22 #spi Chip Select
spi_sclk = board.D11 #spi clock
spi_miso = board.D9 #spi Microcomputer In Serial Out
spi_cs = board.D8 #spi Chip Select
spi_mosi = board.D10 #spi Microcomputer Out Serial In (not connected)
gpio_heat = board.D23 #output that controls relay
gpio_heat = board.D25 #output that controls relay
gpio_heat_invert = False #invert the output state
except (NotImplementedError,AttributeError):
print("not running on blinka recognized board, probably a simulation")
Expand All @@ -102,8 +102,8 @@
max31855 = 1
max31856 = 0
# uncomment these two lines if using MAX-31856
import adafruit_max31856
thermocouple_type = adafruit_max31856.ThermocoupleType.K
# import adafruit_max31856
# thermocouple_type = adafruit_max31856.ThermocoupleType.S

# here are the possible max-31856 thermocouple types
# ThermocoupleType.B
Expand Down Expand Up @@ -140,9 +140,9 @@
# well with the simulated oven. You must tune them to work well with
# your specific kiln. Note that the integral pid_ki is
# inverted so that a smaller number means more integral action.
pid_kp = 10 # Proportional 25,200,200
pid_ki = 80 # Integral
pid_kd = 220.83497910261562 # Derivative
pid_kp = 25 # Proportional 25,200,200
pid_ki = 200 # Integral
pid_kd = 200 # Derivative

########################################################################
#
Expand Down Expand Up @@ -176,7 +176,7 @@
#
# If you change the temp_scale, all settings in this file are assumed to
# be in that scale.
temp_scale = "f" # c = Celsius | f = Fahrenheit - Unit to display
temp_scale = "c" # c = Celsius | f = Fahrenheit - Unit to display
time_scale_slope = "h" # s = Seconds | m = Minutes | h = Hours - Slope displayed in temp_scale per time_scale_slope
time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile

Expand Down Expand Up @@ -214,7 +214,7 @@
temperature_average_samples = 10

# Thermocouple AC frequency filtering - set to True if in a 50Hz locale, else leave at False for 60Hz locale
ac_freq_50hz = False
ac_freq_50hz = True

########################################################################
# Emergencies - or maybe not
Expand All @@ -238,12 +238,12 @@
ignore_tc_temp_high = False
ignore_tc_temp_low = False
ignore_tc_voltage_error = False
ignore_tc_short_errors = False
ignore_tc_short_errors = True
ignore_tc_unknown_error = False

# This overrides all possible thermocouple errors and prevents the
# process from exiting.
ignore_tc_too_many_errors = False
ignore_tc_too_many_errors = True

########################################################################
# automatic restarts - if you have a power brown-out and the raspberry pi
Expand Down Expand Up @@ -277,3 +277,13 @@
# To prevent throttling, set throttle_percent to 100.
throttle_below_temp = 300
throttle_percent = 20


#mqtt config
mqtt_enable = True
mqtt_host = 'example.com'
mqtt_port = 1883
mqtt_user = 'my_user'
mqtt_pass = 'my_passwd'
mqtt_topic = 'kiln/sensor'
mqtt_kiln_name = 'My Kiln'
67 changes: 59 additions & 8 deletions lib/ovenWatcher.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
import threading,logging,json,time,datetime
import config
import paho.mqtt.client as mqtt
import json

from oven import Oven
log = logging.getLogger(__name__)

# Callback when the client successfully connects to the broker
def on_connect(client, userdata, flags, rc):
if rc == 0:
log.info("Connected to MQTT broker")
else:
log.warning(f"MQTT connection failed, return code: {rc}")

# Callback when the client disconnects from the broker
def on_disconnect(client, userdata, rc):
log.warning(f"Disconnected from MQTT broker (code: {rc}), retrying in 5s...")
time.sleep(5)
try:
client.reconnect()
except Exception as e:
log.error(f"Reconnection attempt failed: {e}")


class OvenWatcher(threading.Thread):
def __init__(self,oven):
self.last_profile = None
Expand All @@ -12,6 +33,8 @@ def __init__(self,oven):
threading.Thread.__init__(self)
self.daemon = True
self.oven = oven
if config.mqtt_enabled:
self._setup_mqtt()
self.start()

# FIXME - need to save runs of schedules in near-real-time
Expand All @@ -22,18 +45,46 @@ def __init__(self,oven):
# out more than N minutes, don't restart
# FIXME - this should not be done in the Watcher, but in the Oven class


def _setup_mqtt(self):
self.client = mqtt.Client()
self.client.username_pw_set(config.mqtt_user, config.mqtt_pass)
#self.client.on_connect = on_connect
#self.client.on_disconnect = on_disconnect
self.client.connect(config.mqtt_host, config.mqtt_port)
self.client.loop_start()

def run(self):
# Main loop: each iteration handles exceptions internally to stay alive
while True:
oven_state = self.oven.get_state()

# record state for any new clients that join
if oven_state.get("state") == "RUNNING":
self.last_log.append(oven_state)
else:
self.recording = False
self.notify_all(oven_state)
try:
oven_state = self.oven.get_state()

# Save state for new observers if the oven is running
if oven_state.get("state") == "RUNNING":
self.last_log.append(oven_state)
else:
self.recording = False

# Notify all connected observers via WebSocket
self.notify_all(oven_state)

# Publish temperature to MQTT topic
if config.mqtt_enabled:
oven_state["name"] = config.mqtt_kiln_name
payload = json.dumps(oven_state)
result = self.client.publish(config.mqtt_topic, payload)
if result.rc != mqtt.MQTT_ERR_SUCCESS:
log.error(f"Publish failed, code: {result.rc}")

except Exception as exc:
log.exception(f"Exception in OvenWatcher iteration: {exc}")

# Sleep for configured time_step, even after exception
time.sleep(self.oven.time_step)



def lastlog_subset(self,maxpts=50):
'''send about maxpts from lastlog by skipping unwanted data'''
totalpts = len(self.last_log)
Expand Down
35 changes: 10 additions & 25 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
setuptools
greenlet
bottle
gevent
gevent-websocket
websocket-client
requests

# for folks running raspberry pis
# we have no proof of anyone using another board yet, but when that
# happens, you might want to comment this out.
RPi.GPIO

# List of all supported adafruit modules for thermocouples
adafruit-circuitpython-max31855
adafruit-circuitpython-max31856

# for folks using sw spi (bit banging)
adafruit-circuitpython-bitbangio

# untested - for PT100 platinum thermocouples
#adafruit-circuitpython-max31865

# untested - for mcp9600 and mcp9601
#adafruit-circuitpython-mcp9600
adafruit_blinka==8.58.1
adafruit_circuitpython_bitbangio==1.3.18
adafruit_circuitpython_max31855==3.2.24
adafruit_circuitpython_max31856==0.12.3
bottle==0.13.3
gevent==25.5.1
gevent_websocket==0.10.1
paho_mqtt==2.1.0
Requests==2.32.4
websocket_client==1.8.0
4 changes: 3 additions & 1 deletion start-on-boot
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/bin/bash
sudo cp /home/pi/kiln-controller/lib/init/kiln-controller.service /etc/systemd/system/
path=$PWD
sed -i "/ExecStart=/c\ExecStart\=$path/venv/bin/python $path/kiln-controller.py" lib/init/kiln-controller.service
sudo cp $path/lib/init/kiln-controller.service /etc/systemd/system/
sudo systemctl enable kiln-controller