commit
418dfd900e
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@ thumbs.db
|
|||||||
#storage/profiles
|
#storage/profiles
|
||||||
#config.py
|
#config.py
|
||||||
.idea/*
|
.idea/*
|
||||||
|
state.json
|
||||||
|
|||||||
1
Test/test-cases.json
Normal file
1
Test/test-cases.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"data": [[0, 200], [3600, 200], [4200, 500], [10800, 500], [14400, 2250], [16400, 2000], [19400, 2250]], "type": "profile", "name": "test-fast"}
|
||||||
1
Test/test-fast.json
Normal file
1
Test/test-fast.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"data": [[0, 200], [3600, 200], [10800, 2000], [14400, 2250], [16400, 2250], [19400, 700]], "type": "profile", "name": "test-fast"}
|
||||||
80
Test/test_Profile.py
Normal file
80
Test/test_Profile.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
from lib.oven import Profile
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
def get_profile(file = "test-fast.json"):
|
||||||
|
profile_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'Test', file))
|
||||||
|
print(profile_path)
|
||||||
|
with open(profile_path) as infile:
|
||||||
|
profile_json = json.dumps(json.load(infile))
|
||||||
|
profile = Profile(profile_json)
|
||||||
|
|
||||||
|
return profile
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_target_temperature():
|
||||||
|
profile = get_profile()
|
||||||
|
|
||||||
|
temperature = profile.get_target_temperature(3000)
|
||||||
|
assert int(temperature) == 200
|
||||||
|
|
||||||
|
temperature = profile.get_target_temperature(6004)
|
||||||
|
assert temperature == 801.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_time_from_temperature():
|
||||||
|
profile = get_profile()
|
||||||
|
|
||||||
|
time = profile.find_next_time_from_temperature(500)
|
||||||
|
assert time == 4800
|
||||||
|
|
||||||
|
time = profile.find_next_time_from_temperature(2004)
|
||||||
|
assert time == 10857.6
|
||||||
|
|
||||||
|
time = profile.find_next_time_from_temperature(1900)
|
||||||
|
assert time == 10400.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_time_odd_profile():
|
||||||
|
profile = get_profile("test-cases.json")
|
||||||
|
|
||||||
|
time = profile.find_next_time_from_temperature(500)
|
||||||
|
assert time == 4200
|
||||||
|
|
||||||
|
time = profile.find_next_time_from_temperature(2023)
|
||||||
|
assert time == 16676.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_x_given_y_on_line_from_two_points():
|
||||||
|
profile = get_profile()
|
||||||
|
|
||||||
|
y = 500
|
||||||
|
p1 = [3600, 200]
|
||||||
|
p2 = [10800, 2000]
|
||||||
|
time = profile.find_x_given_y_on_line_from_two_points(y, p1, p2)
|
||||||
|
|
||||||
|
assert time == 4800
|
||||||
|
|
||||||
|
y = 500
|
||||||
|
p1 = [3600, 200]
|
||||||
|
p2 = [10800, 200]
|
||||||
|
time = profile.find_x_given_y_on_line_from_two_points(y, p1, p2)
|
||||||
|
|
||||||
|
assert time == 0
|
||||||
|
|
||||||
|
y = 500
|
||||||
|
p1 = [3600, 600]
|
||||||
|
p2 = [10800, 600]
|
||||||
|
time = profile.find_x_given_y_on_line_from_two_points(y, p1, p2)
|
||||||
|
|
||||||
|
assert time == 0
|
||||||
|
|
||||||
|
y = 500
|
||||||
|
p1 = [3600, 500]
|
||||||
|
p2 = [10800, 500]
|
||||||
|
time = profile.find_x_given_y_on_line_from_two_points(y, p1, p2)
|
||||||
|
|
||||||
|
assert time == 0
|
||||||
|
|
||||||
|
|
||||||
11
config.py
11
config.py
@ -8,7 +8,7 @@ import busio
|
|||||||
# General options
|
# General options
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
log_level = logging.INFO
|
log_level = logging.DEBUG
|
||||||
log_format = '%(asctime)s %(levelname)s %(name)s: %(message)s'
|
log_format = '%(asctime)s %(levelname)s %(name)s: %(message)s'
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
@ -75,6 +75,13 @@ max31856 = 0
|
|||||||
# ThermocoupleType.S
|
# ThermocoupleType.S
|
||||||
# ThermocoupleType.T
|
# ThermocoupleType.T
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# If your kiln is above the scheduled starting temperature when you click the Start button this
|
||||||
|
# feature will start the schedule at that temperature.
|
||||||
|
#
|
||||||
|
seek_start = True
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
#
|
#
|
||||||
# duty cycle of the entire system in seconds
|
# duty cycle of the entire system in seconds
|
||||||
@ -109,7 +116,7 @@ stop_integral_windup = True
|
|||||||
#
|
#
|
||||||
# Simulation parameters
|
# Simulation parameters
|
||||||
simulate = True
|
simulate = True
|
||||||
sim_t_env = 60.0 # deg C
|
sim_t_env = 255 # deg C
|
||||||
sim_c_heat = 500.0 # J/K heat capacity of heat element
|
sim_c_heat = 500.0 # J/K heat capacity of heat element
|
||||||
sim_c_oven = 5000.0 # J/K heat capacity of oven
|
sim_c_oven = 5000.0 # J/K heat capacity of oven
|
||||||
sim_p_heat = 5450.0 # W heating power of oven
|
sim_p_heat = 5450.0 # W heating power of oven
|
||||||
|
|||||||
@ -73,6 +73,11 @@ def handle_api():
|
|||||||
if 'startat' in bottle.request.json:
|
if 'startat' in bottle.request.json:
|
||||||
startat = bottle.request.json['startat']
|
startat = bottle.request.json['startat']
|
||||||
|
|
||||||
|
#Shut off seek if start time has been set
|
||||||
|
allow_seek = True
|
||||||
|
if startat > 0:
|
||||||
|
allow_seek = False
|
||||||
|
|
||||||
# get the wanted profile/kiln schedule
|
# get the wanted profile/kiln schedule
|
||||||
profile = find_profile(wanted)
|
profile = find_profile(wanted)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
@ -81,7 +86,7 @@ def handle_api():
|
|||||||
# FIXME juggling of json should happen in the Profile class
|
# FIXME juggling of json should happen in the Profile class
|
||||||
profile_json = json.dumps(profile)
|
profile_json = json.dumps(profile)
|
||||||
profile = Profile(profile_json)
|
profile = Profile(profile_json)
|
||||||
oven.run_profile(profile,startat=startat)
|
oven.run_profile(profile, startat=startat, allow_seek=allow_seek)
|
||||||
ovenWatcher.record(profile)
|
ovenWatcher.record(profile)
|
||||||
|
|
||||||
if bottle.request.json['cmd'] == 'stop':
|
if bottle.request.json['cmd'] == 'stop':
|
||||||
|
|||||||
51
lib/oven.py
51
lib/oven.py
@ -103,7 +103,7 @@ class TempSensorSimulated(TempSensor):
|
|||||||
'''Simulates a temperature sensor '''
|
'''Simulates a temperature sensor '''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
TempSensor.__init__(self)
|
TempSensor.__init__(self)
|
||||||
self.simulated_temperature = 0
|
self.simulated_temperature = config.sim_t_env
|
||||||
def temperature(self):
|
def temperature(self):
|
||||||
return self.simulated_temperature
|
return self.simulated_temperature
|
||||||
|
|
||||||
@ -329,10 +329,28 @@ class Oven(threading.Thread):
|
|||||||
self.heat = 0
|
self.heat = 0
|
||||||
self.pid = PID(ki=config.pid_ki, kd=config.pid_kd, kp=config.pid_kp)
|
self.pid = PID(ki=config.pid_ki, kd=config.pid_kd, kp=config.pid_kp)
|
||||||
|
|
||||||
def run_profile(self, profile, startat=0):
|
@staticmethod
|
||||||
|
def get_start_from_temperature(profile, temp):
|
||||||
|
target_temp = profile.get_target_temperature(0)
|
||||||
|
if temp > target_temp + 5:
|
||||||
|
startat = profile.find_next_time_from_temperature(temp)
|
||||||
|
log.info("seek_start is in effect, starting at: {} s, {} deg".format(round(startat), round(temp)))
|
||||||
|
else:
|
||||||
|
startat = 0
|
||||||
|
return startat
|
||||||
|
|
||||||
|
def run_profile(self, profile, startat=0, allow_seek=True):
|
||||||
|
log.debug('run_profile run on thread' + threading.current_thread().name)
|
||||||
|
runtime = startat * 60
|
||||||
|
if allow_seek:
|
||||||
|
if self.state == 'IDLE':
|
||||||
|
if config.seek_start:
|
||||||
|
temp = self.board.temp_sensor.temperature() # Defined in a subclass
|
||||||
|
runtime += self.get_start_from_temperature(profile, temp)
|
||||||
|
|
||||||
self.reset()
|
self.reset()
|
||||||
self.startat = startat * 60
|
self.startat = startat * 60
|
||||||
self.runtime = self.startat
|
self.runtime = runtime
|
||||||
self.start_time = datetime.datetime.now() - datetime.timedelta(seconds=self.startat)
|
self.start_time = datetime.datetime.now() - datetime.timedelta(seconds=self.startat)
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.totaltime = profile.get_duration()
|
self.totaltime = profile.get_duration()
|
||||||
@ -468,7 +486,7 @@ class Oven(threading.Thread):
|
|||||||
with open(profile_path) as infile:
|
with open(profile_path) as infile:
|
||||||
profile_json = json.dumps(json.load(infile))
|
profile_json = json.dumps(json.load(infile))
|
||||||
profile = Profile(profile_json)
|
profile = Profile(profile_json)
|
||||||
self.run_profile(profile,startat=startat)
|
self.run_profile(profile, startat=startat, allow_seek=False) # We don't want a seek on an auto restart.
|
||||||
self.cost = d["cost"]
|
self.cost = d["cost"]
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.ovenwatcher.record(profile)
|
self.ovenwatcher.record(profile)
|
||||||
@ -479,6 +497,7 @@ class Oven(threading.Thread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while True:
|
||||||
|
log.debug('Oven running on ' + threading.current_thread().name)
|
||||||
if self.state == "IDLE":
|
if self.state == "IDLE":
|
||||||
if self.should_i_automatic_restart() == True:
|
if self.should_i_automatic_restart() == True:
|
||||||
self.automatic_restart()
|
self.automatic_restart()
|
||||||
@ -507,7 +526,7 @@ class SimulatedOven(Oven):
|
|||||||
self.R_ho = self.R_ho_noair
|
self.R_ho = self.R_ho_noair
|
||||||
|
|
||||||
# set temps to the temp of the surrounding environment
|
# set temps to the temp of the surrounding environment
|
||||||
self.t = self.t_env # deg C temp of oven
|
self.t = config.sim_t_env # deg C or F temp of oven
|
||||||
self.t_h = self.t_env #deg C temp of heating element
|
self.t_h = self.t_env #deg C temp of heating element
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -643,6 +662,28 @@ class Profile():
|
|||||||
def get_duration(self):
|
def get_duration(self):
|
||||||
return max([t for (t, x) in self.data])
|
return max([t for (t, x) in self.data])
|
||||||
|
|
||||||
|
# x = (y-y1)(x2-x1)/(y2-y1) + x1
|
||||||
|
@staticmethod
|
||||||
|
def find_x_given_y_on_line_from_two_points(y, point1, point2):
|
||||||
|
if point1[0] > point2[0]: return 0 # time2 before time1 makes no sense in kiln segment
|
||||||
|
if point1[1] >= point2[1]: return 0 # Zero will crach. Negative temeporature slope, we don't want to seek a time.
|
||||||
|
x = (y - point1[1]) * (point2[0] -point1[0] ) / (point2[1] - point1[1]) + point1[0]
|
||||||
|
return x
|
||||||
|
|
||||||
|
def find_next_time_from_temperature(self, temperature):
|
||||||
|
time = 0 # The seek function will not do anything if this returns zero, no useful intersection was found
|
||||||
|
for index, point2 in enumerate(self.data):
|
||||||
|
if point2[1] >= temperature:
|
||||||
|
if index > 0: # Zero here would be before the first segment
|
||||||
|
if self.data[index - 1][1] <= temperature: # We have an intersection
|
||||||
|
time = self.find_x_given_y_on_line_from_two_points(temperature, self.data[index - 1], point2)
|
||||||
|
if time == 0:
|
||||||
|
if self.data[index - 1][1] == point2[1]: # It's a flat segment that matches the temperature
|
||||||
|
time = self.data[index - 1][0]
|
||||||
|
break
|
||||||
|
|
||||||
|
return time
|
||||||
|
|
||||||
def get_surrounding_points(self, time):
|
def get_surrounding_points(self, time):
|
||||||
if time > self.get_duration():
|
if time > self.get_duration():
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|||||||
@ -79,6 +79,7 @@ class OvenWatcher(threading.Thread):
|
|||||||
def notify_all(self,message):
|
def notify_all(self,message):
|
||||||
message_json = json.dumps(message)
|
message_json = json.dumps(message)
|
||||||
log.debug("sending to %d clients: %s"%(len(self.observers),message_json))
|
log.debug("sending to %d clients: %s"%(len(self.observers),message_json))
|
||||||
|
|
||||||
for wsock in self.observers:
|
for wsock in self.observers:
|
||||||
if wsock:
|
if wsock:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user