Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Phil Speed
Published © GPL3+

Glass Fusing Kiln Controller with Network Monitor

My manually controlled kiln now has a programmable rate, target, hold and it graphs live results on my home network devices.

IntermediateShowcase (no instructions)4 hours1,518
Glass Fusing Kiln Controller with Network Monitor

Things used in this project

Hardware components

Adafruit Raspberry Pi Zero W with Header
×1
Adafruit max31855
×1

Software apps and online services

Raspbian
Raspberry Pi Raspbian

Story

Read more

Schematics

Kiln Controller Schematic

Code

Kiln Controller

Python
To control the temperature of a kiln during glass fusing
#Kiln Controller - Phil Speed 05/12/2020
from __future__ import division                    #so that you can work with float point numbers
import time
import sys
import os
import Adafruit_MAX31855 as MAX31855
import Adafruit_DHT
import paho.mqtt.client as mqtt


print ('Python version is :'),sys.version
print
#-------------------- set the global parameters

global kiln_temperature
kiln_temperature = 60
roomTemp = 60
global simulationMode
global soak_temperature
global high_temperature_warning
high_temperature_warning = 1700
speedFall = 1 # 1 sec delay
speedRise = 1
degFall = 10
degRise =10
out_of_sync_tolerance = 50             # the temperature the down ramp can be out of sync by
out_of_sync_cycles_allowed = 3      # the number cycles the ramp can be out of sync before alerting
out_of_sync_cyles = 0                     # the number of cycles currently out of sync
max_kiln_temperature = 1550  
soaked = False 
oldTemp = 0
current_segment = 0
verify = ""
 #-------------------- allocate storage for the segment parameters
#numOfcolumns = 3
#numOfrows= raw_input('Enter the number of segments :')
#progData = [[0 for x in range(numOfcolumns)] for y in range(numOfrows)]

#================ DEFINE ALL FUNCTIONS ====================

#-------------------- get the date and time
def get_date_time():
	global date_time
	now = time.localtime(time.time())
	date_time = (time.strftime("%a %b %d at %I:%M %p", now))
	print date_time
	return

#-------------------- get the kiln temperature
def readSensors():
	global kiln_temperature
	global simulationMode
	if simulationMode == True:
		client.publish("temp_kiln",kiln_temperature,2)
		time.sleep(1)
		return
	kiln_temperature = sensorMAX.readTempC() * 9.0 / 5.0 + 32.0 #converted to Fahrenheit
	client.publish("temp_kiln",kiln_temperature,2)
	return
#-------------------- turn the kiln on
def kilnON():
	global kiln_temperature
	if simulationMode == True:
		if kiln_temperature >= high_temperature_warning:
			return
		kiln_temperature = kiln_temperature + degRise
		print'kiln temperature :', kiln_temperature
		time.sleep(speedRise)
		client.publish("kiln_power","** ON **",2)
		time.sleep(1)	
		return	
	GPIO.output(relayPin, GPIO.HIGH)
	client.publish("kiln_power","** ON **",2)
	time.sleep(1)
	return
	
#-------------------- turn the kiln off		
def kilnOFF():
	global kiln_temperature
	if simulationMode == True:
		if kiln_temperature <= roomTemp:
			return
		kiln_temperature = kiln_temperature - degFall
		time.sleep(speedFall)
		client.publish("kiln_power","** OFF **",2)
		time.sleep(1)	
		return	
	GPIO.output(relayPin, GPIO.LOW)
	client.publish("kiln_power","** OFF **",2)
	time.sleep(1)
	return
		
#-------------------- search for the current time in the template and get ideal temperature

def getIdealTemp():                                                                                  
	global timeFound
	global idealTemp
	global lastMinute
	global thisMinute
	time_now = time.localtime(time.time())                                                   #Gets the current local computer time here
	day_hr_min = (time.strftime("%d %H %M", time_now))
	for d in range (0, ramp_len + 1):                                                           #Now find the local time in the data ramp array
		if day_hr_min in time_template[d]:                                               #When the time is found - get the temperature that 
			timeFound = False                                                                         #Make sure it starts out False
			current_ramp_temperature = temp_template[d]                       #the kiln should be at this temperature
			current_ramp_time = time_template[d]
			idealTemp = temp_template[d]			
			timeFound = True
			lastMinute = day_hr_min                                                              # Use this to tell when the computer clock changes
			thisMinute = day_hr_min			
			return
		else:                                                         
			timeFound = False
			
#-------------------- soak at current temperature

def goSoak(segment_temperature, segment_soakTime):
	#print'segment temp and seg soak time :', segment_temperature, segment_soakTime
	global soaked
	soaked = True # this must be set False elsewhere. It stays True to inform that this cycle was executed
	readSensors()
	soak_temperature = segment_temperature
	print"segment_soakTime =:", segment_soakTime          #Set the temperature to maintain
	soak_length = segment_soakTime * 60
	begining_soak_time = time.localtime(time.time())           #get the current time
	future = time.localtime(time.time()+(soak_length))         #add the soak time to the current time
	ending_soak_time = time.time() + soak_length              #end the soak cycle at this time
	currentTime = time.time()
	end_soak = (time.strftime("%a %b %d at %I:%M %p", future))
	client.publish("prog_status","Soaking until " + str(end_soak),2)
	time.sleep(1)
	print 'Soak cycle will end  ', (time.strftime("%a %b %d at %I:%M %p", future))
	while (currentTime < ending_soak_time):                   #maintain the soak temperature
		currentTime = time.time()                                       
		readSensors()
		if kiln_temperature > soak_temperature:
			kilnOFF()
		if kiln_temperature < soak_temperature:
			kilnON()
	return

server = "localhost"
client=mqtt.Client()
#client.on_connect = on_connect
#client.on_message = on_message
client.connect(server,1883,60)
client.loop_start()

#Kiln Controller by Phil Speed 05/12/2020	
#-------------------- get the speed, temperature goals, and soaking time for each segment

while True:
#-------------------- ask if this is a simulated run
	simulationMode = False
	simMode = raw_input('Will this be a simulated run? Type "yes" or "no"')
	if simMode != "no":
		simulationMode = True
	print'Simulation is ', simulationMode	
#-------------------- allocate storage for the segment parameters	
	numOfcolumns = 3
	numOfrows= input('Enter the number of segments :')
	progData = [[0 for x in range(numOfcolumns)] for y in range(numOfrows)]
#-------------------- input segment information	
	while verify != "y":
		for i in range(0, len(progData)):
			segNum = i + 1
			print
			print"Segment No. ",segNum
			for x in range(0,3):
				if x == 0:
					progData[i][x] = raw_input('Degree of rise per hour (Rate) :')
				if x == 1:
					progData[i][x] = raw_input('Enter the temperature goal (Temp) :')
				if x == 2:
					progData[i][x] = raw_input('Enter the time to hold this temperature (Hold):')
#-------------------- ask for verification of the data					
		for i in range(0, len(progData)):
				print
				print"Segment No. ",i + 1
				for x in range(0,3):
					if x == 0:
						print"Rate =: ",
					if x == 1:
						print"Temp =: ",
					if x == 2:
						print"Hold =: ",
					print(progData[i][x])
		print 'Please verify the data before continuing'
		verify = raw_input('If the data is okay type y :')
#-------------------- initialize node red dashboard		
	client.publish("alert","-No Alerts-",2)
	time.sleep(1)
	client.publish("error","-No Errors-",2)
	time.sleep(1)	
	get_date_time()
	client.publish("start_time",date_time,2)
	time.sleep(1)
	client.publish("stop_time"," -- ",2)
	time.sleep(1)
#==================== start the process of building the control template
					
	for segment in range (0, numOfrows):  
		time_template = []                        
		temp_template = []                      
		print ('-----Now running segment number -----> '),segment + 1
		current_segment = segment + 1
		client.publish("seg_num", current_segment, 2)
		time.sleep(1)	
#-------------------- get the necessay parameters for building the template
		
		segment_soakTime = int(progData[segment][2]) 
		soak_time = int(segment_soakTime)
		segment_rate = int(progData[segment][0])                                                   
		segment_temperature = int(progData[segment][1])                                        
		rate_per_hour = int(segment_rate)
		goal_temp = int(segment_temperature)
		rate_per_minute = rate_per_hour/60
		soak_time_seconds = soak_time * 60
		ramp_time_hours = goal_temp/rate_per_hour
		temperature_goal = segment_temperature
			                              
#-------------------- determine the direction to make the ramp
		                                           
		if segment_temperature > oldTemp:
			rampDirection = "up"
		if segment_temperature < oldTemp:
			rampDirection = "down"
		oldTemp = segment_temperature
		
#-------------------- if the rate is FAST asPOSSIBLE do this and don't build ramp

		if segment_rate == 9999:                                                       #Go to temperature as fast as possible and then go soak
			kilnOFF()
			temperature_goal = segment_temperature
			client.publish("prog_status","Fast as possible to  " + str(temperature_goal) + " degrees",2)
			time.sleep(1)
			if rampDirection == "up":
				print 'Rate is 9999. Going up as fast as possible to : ', segment_temperature
				readSensors()
				while kiln_temperature <= temperature_goal:
					readSensors()
					kilnON()
					time.sleep(1)
			if rampDirection == "down":
				print 'Rate is 9999. Going down as fast as possible to : ', segment_temperature
				readSensors()
				while kiln_temperature >= temperature_goal:
					readSensors()
					kilnOFF()
					time.sleep(1)
			goSoak(segment_temperature, segment_soakTime)
			soaked = False  #reset this for next segment
			continue		
#-------------------- build the ramp template

		print 'Calculating the ramp data'
		
		readSensors()                                                                                    #Get the current kiln temperature
		if rampDirection == "up":
			ramp_time_minutes = (goal_temp - kiln_temperature) / rate_per_minute      #Calculate the ramp time in minutes
			if kiln_temperature >= goal_temp:
				print'Kiln has been turned off. The kiln temperature already exceeds the segment goal'
				kilnOFF()
				client.publish("prog_status",'Kiln has been turned off. The kiln temperature already exceeds the segment goal',2)
				time.sleep(1)
				sys.exit()
		if rampDirection == "down":
			ramp_time_minutes = (kiln_temperature - goal_temp) / rate_per_minute      #Calculate the ramp time in minutes
			if kiln_temperature <= goal_temp:
				print'Kiln has been turned off. The kiln temperature is already below the segment goal'
				kilnOFF()
				client.publish("prog_status",'Kiln has been turned off. The kiln temperature already exceeds the segment goal',2)
				time.sleep(1)
				sys.exit()
		
		degrees_per_minute = rate_per_hour/60                                         #Calculate the degrees of change per minutes
		ramp_length = ramp_time_minutes                                                     # Ramp length 
		ramp_in_seconds = ramp_length * 60
		temp_incr = degrees_per_minute
		time_now = time.localtime(time.time())                                                #get the current local computer time
		formatted_time = (time.strftime("%d %H %M", time_now))
		time_bump=0                                                                                     
		ramp_temp = kiln_temperature
		ramp_len = int(ramp_length)
		for r in range (0, ramp_len + 1):                                                       #For each minute in the ramp length
			now = time.localtime(time.time()+(time_bump))                              #This routine will get the current time
			time_bump = time_bump + 60                                                       # Will add 60 secs each time through loop 
			future_time = (time.strftime("%d %H %M", now))                         #Will keep adding 60 seconds to the last time
			ramp_temperature = int(ramp_temp)
			time_template.append(future_time)                                           #Will store the future time and temperatures
			temp_template.append(ramp_temperature)
			if rampDirection == 'up':                                                            #===========for Ramp UP==================
				ramp_temp = ramp_temp + temp_incr                                      #Will keep adding the temperature increments
				if ramp_temp >= max_kiln_temperature:
					print 'Error in calculating ramp. Ramp data went above maximum allowed temperature'
					kilnOFF()
					client.publish("error",'Kiln OFF. Error in calculating ramp. Ramp data went above maximum',2)
					time.sleep(1)
					sys.exit()
			if rampDirection == 'down':                                                          #=================for Ramp DOWN=============
				ramp_temp = ramp_temp - temp_incr                                         #Will keep subtracting the temperature increments
				if ramp_temp <= 0:
					print 'Error in calculating ramp. Ramp data went below zero'
					kilnOFF()
					client.publish("error",'Kiln OFF. Error in calculating ramp. Ramp data went below zero',2)
					time.sleep(1)
					sys.exit()	
			
		future_time = ramp_in_seconds + soak_time_seconds
		now = time.localtime(time.time()+(future_time))                                 # Calculate the real time kiln will reach temperature goal
		segment_end_time = (time.strftime("%a %b %d at %I:%M %p", now))
		print "Segment completion will be at"
		print segment_end_time
		#client.publish("seg_end_time",segment_end_time,2)
		time.sleep(1)
		print '\n'
		print "The current kiln temperature is :",kiln_temperature                          #Temperature will first be room temperature
		print 'Ramp calculation complete'
		
	#==================== START THE FIRING PROCESS ====================
	
	#-------------------- search the template for the current time and ideal temperature
		status = 'Ramping ' + str(rampDirection) + ' to '+ str(segment_temperature) + ' degrees at a rate of ' + str(segment_rate) + ' degrees per hour'
		print status
		client.publish("prog_status",status,2)
		time.sleep(1)
		timeOffset = 0
		for step in range (0, ramp_len + 1):
			readSensors()
			getIdealTemp()
			if (rampDirection == "up" and kiln_temperature >= temperature_goal) or (rampDirection == "down" and kiln_temperature <= temperature_goal):                                                      #It is time to soak
				print 'Reached Segment Temperature of ',temperature_goal
				if segment_soakTime == 0:
					print'There is no soak time'
					break	                                                                                       # Get out of this loop - do not soak 
				goSoak(segment_temperature, segment_soakTime)
				soaked = False
				break
	#-------------------- the current time was found so step the temperature up to the next level											
			if timeFound == True:
				while thisMinute == lastMinute:
					readSensors()
					if kiln_temperature < idealTemp:	
						kilnON()
					if kiln_temperature > idealTemp:
						kilnOFF()
	#-------------------- get the real local computer time						
					time_now = time.localtime(time.time())                                            
					thisMinute = (time.strftime("%d %H %M", time_now))
					
	#-------------------- alert the user to in or out of sync condition
					if (rampDirection == "down" and (kiln_temperature + out_of_sync_tolerance)) > idealTemp:
						print 'Kiln is cooling slower than segment rate'
						client.publish("alert","Kiln is cooling slower than segment rate",2)
						time.sleep(1)
						client.publish("ramp_status","Out of sync",2)
						time.sleep(1)						
					else:
						client.publish("ramp_status","In sync",2)
						time.sleep(1)
		if (soaked == False and rampDirection == "down"):                                     # out of sync condition - the temperature goal
			                                                                                                              # was not reached before the ramp time completed
			client.publish("prog_status","Waiting for temperature to come down to the segment temperature goal.",2)
			time.sleep(1)
			kilnOFF()
			print"Waiting for temperature to come down",
			while kiln_temperature > temperature_goal:                                            #wait here until the kiln temperature comes down to the segment temperature goal
				readSensors()
				time.sleep(3)
			goSoak(segment_temperature, segment_soakTime)
			soaked == False                                                                                       # the soak function set this True so need to reset it
		if (soaked == False and rampDirection == "up"):                                           # out of sync condition - then the temperature goal was not reached before the ramp time completed - should be okay
			goSoak(segment_temperature, segment_soakTime)
			soaked == False                                                                                       # the soak function set this True so need to reset it
	run = False
#-------------------- program complete - make sure kiln is off			                                                                                      
	kilnOFF()
	time.sleep(5)                                                                                                    # wait 5 seconds and send kiln off again
	kilnOFF()
	time.sleep(1)					
	print 'End of firing schedule. Kiln powered off.'
	client.publish("prog_status","Project Completed. Kiln should be OFF",2)
	time.sleep(1)
	get_date_time()
	client.publish("stop_time",date_time,2)
	time.sleep(1)
	print"Program finished processing - starting new program"
	#-------------------- reset things to start over
	soaked = False 
	oldTemp = 0
	current_segment = 0
	verify = ""
	client.publish("ramp_status","-- Inactive --",2)
	time.sleep(1)
client.loop_forever()
	
	

Node-red Flow

Plain text
Import into node-red by pasting it into a tab
[{"id":"d411d61e.b5f","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"c4bf353b.7efd","type":"mqtt in","z":"d411d61e.b5f","name":"Temperature","topic":"temp_kiln","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":170,"y":100,"wires":[["98189808.e92308","12b82528.c4a6eb"]]},{"id":"98189808.e92308","type":"ui_gauge","z":"d411d61e.b5f","name":"Gauge","group":"4f986a86.2dd39c","order":2,"width":0,"height":0,"gtype":"gage","title":"Temperature","label":"°F","format":"{{value}}","min":0,"max":"2000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"1050","seg2":"1465","x":350,"y":100,"wires":[]},{"id":"a3436c70.0d047","type":"ui_text","z":"d411d61e.b5f","group":"4f986a86.2dd39c","order":1,"width":0,"height":0,"name":"","label":"Power","format":"{{msg.payload}}","layout":"row-spread","x":350,"y":220,"wires":[]},{"id":"6da35645.61cad","type":"ui_text","z":"d411d61e.b5f","group":"338b310b.77ee1e","order":5,"width":0,"height":0,"name":"","label":"Alerts","format":"{{msg.payload}}","layout":"row-spread","x":350,"y":280,"wires":[]},{"id":"c0fe9610.9a0d9","type":"ui_text","z":"d411d61e.b5f","group":"338b310b.77ee1e","order":7,"width":0,"height":0,"name":"","label":"Status","format":"{{msg.payload}}","layout":"row-spread","x":350,"y":340,"wires":[]},{"id":"adc449a0.98ce9","type":"mqtt in","z":"d411d61e.b5f","name":"Power","topic":"kiln_power","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":170,"y":220,"wires":[["a3436c70.0d047"]]},{"id":"dd491b0d.479908","type":"mqtt in","z":"d411d61e.b5f","name":"Alerts","topic":"alert","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":170,"y":280,"wires":[["6da35645.61cad"]]},{"id":"8cb3b271.36623","type":"mqtt in","z":"d411d61e.b5f","name":"Status","topic":"prog_status","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":170,"y":340,"wires":[["c0fe9610.9a0d9"]]},{"id":"9c260424.44e3e","type":"mqtt in","z":"d411d61e.b5f","name":"Error","topic":"error","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":170,"y":400,"wires":[["73e2b473.a64e44"]]},{"id":"73e2b473.a64e44","type":"ui_text","z":"d411d61e.b5f","group":"338b310b.77ee1e","order":6,"width":0,"height":0,"name":"","label":"Errors","format":"{{msg.payload}}","layout":"row-spread","x":350,"y":400,"wires":[]},{"id":"12b82528.c4a6eb","type":"ui_chart","z":"d411d61e.b5f","name":"Chart","group":"f706cd78.fcc7d8","order":1,"width":0,"height":0,"label":"Temperature","chartType":"line","legend":"false","xformat":"HH:mm","interpolate":"linear","nodata":"","dot":false,"ymin":"0","ymax":"","removeOlder":"24","removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":350,"y":140,"wires":[[]]},{"id":"7f762fc9.4373d8","type":"ui_button","z":"d411d61e.b5f","name":"","group":"f706cd78.fcc7d8","order":2,"width":0,"height":0,"passthru":false,"label":"Clear Chart","tooltip":"","color":"","bgcolor":"","icon":"","payload":"[]","payloadType":"json","topic":"","x":170,"y":160,"wires":[["12b82528.c4a6eb"]]},{"id":"44c63e64.2081f","type":"ui_text","z":"d411d61e.b5f","group":"338b310b.77ee1e","order":4,"width":0,"height":0,"name":"","label":"Ramp Status","format":"{{msg.payload}}","layout":"row-spread","x":370,"y":460,"wires":[]},{"id":"fc3a0c7b.fc041","type":"mqtt in","z":"d411d61e.b5f","name":"In or Out of Sync","topic":"ramp_status","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":140,"y":460,"wires":[["44c63e64.2081f"]]},{"id":"ac75d4f3.471a8","type":"mqtt in","z":"d411d61e.b5f","name":"Current Segment","topic":"seg_num","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":140,"y":580,"wires":[["7264cf50.9ad1c"]]},{"id":"288a786f.87d9e","type":"mqtt in","z":"d411d61e.b5f","name":"Start Time","topic":"start_time","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":160,"y":640,"wires":[["9ab6728f.7871e"]]},{"id":"7264cf50.9ad1c","type":"ui_text","z":"d411d61e.b5f","group":"338b310b.77ee1e","order":3,"width":0,"height":0,"name":"","label":"Current Segment","format":"{{msg.payload}}","layout":"row-spread","x":390,"y":580,"wires":[]},{"id":"9ab6728f.7871e","type":"ui_text","z":"d411d61e.b5f","group":"338b310b.77ee1e","order":1,"width":0,"height":0,"name":"","label":"Start Time","format":"{{msg.payload}}","layout":"row-spread","x":370,"y":640,"wires":[]},{"id":"d71d0fae.2086a","type":"ui_text","z":"d411d61e.b5f","group":"338b310b.77ee1e","order":2,"width":0,"height":0,"name":"","label":"Stop Time","format":"{{msg.payload}}","layout":"row-spread","x":360,"y":700,"wires":[]},{"id":"27683b83.81634c","type":"mqtt in","z":"d411d61e.b5f","name":"Stop Time","topic":"stop_time","qos":"2","datatype":"auto","broker":"e50aece8.dbc66","x":160,"y":700,"wires":[["d71d0fae.2086a"]]},{"id":"e50aece8.dbc66","type":"mqtt-broker","z":"","name":"RPIzw2","broker":"192.168.1.91","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"4f986a86.2dd39c","type":"ui_group","z":"","name":"Gauge","tab":"1f7e4b5f.38d2a5","order":1,"disp":true,"width":"6","collapse":false},{"id":"338b310b.77ee1e","type":"ui_group","z":"","name":"Status","tab":"1f7e4b5f.38d2a5","order":4,"disp":true,"width":"6","collapse":false},{"id":"f706cd78.fcc7d8","type":"ui_group","z":"","name":"History","tab":"1f7e4b5f.38d2a5","order":6,"disp":true,"width":"6","collapse":false},{"id":"1f7e4b5f.38d2a5","type":"ui_tab","z":"","name":"Kiln Status","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Credits

Phil Speed
4 projects • 1 follower
Repaired anything in a computer room back in the late 60s and 70s. Also, did some assembly and machine language programming.
Contact

Comments

Please log in or sign up to comment.