diff --git a/oven.py b/oven.py index 5a883c6..80f8710 100644 --- a/oven.py +++ b/oven.py @@ -1,4 +1,10 @@ -import threading,time,random,datetime,logging,json +import threading +import time +import random +import datetime +import logging +import json + import config log = logging.getLogger(__name__) @@ -21,25 +27,29 @@ try: gpio_available = True except ImportError: - log.warning("Could not initialize GPIOs, oven operation will only be simulated!") + msg = "Could not initialize GPIOs, oven operation will only be simulated!" + log.warning(msg) gpio_available = False -class Oven (threading.Thread): - STATE_IDLE = "IDLE" - STATE_RUNNING = "RUNNING" - def __init__(self,simulate = False,time_step=0.5): +class Oven (threading.Thread): + STATE_IDLE = "IDLE" + STATE_RUNNING = "RUNNING" + + def __init__(self, simulate=False, time_step=0.5): threading.Thread.__init__(self) self.daemon = True self.simulate = simulate self.time_step = time_step self.reset() if simulate: - self.temp_sensor = TempSensorSimulate(self,0.5,self.time_step) + self.temp_sensor = TempSensorSimulate(self, 0.5, self.time_step) if sensor_available: self.temp_sensor = TempSensorReal(self.time_step) else: - self.temp_sensor = TempSensorSimulate(self,self.time_step,self.time_step) + self.temp_sensor = TempSensorSimulate(self, + self.time_step, + self.time_step) self.temp_sensor.start() self.start() @@ -54,10 +64,10 @@ class Oven (threading.Thread): self.set_heat(False) self.set_cool(False) self.set_air(False) - 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): - log.info("Running profile %s"%profile.name) + log.info("Running profile %s" % profile.name) self.profile = profile self.totaltime = profile.get_duration() self.state = Oven.STATE_RUNNING @@ -75,12 +85,13 @@ class Oven (threading.Thread): if self.simulate: self.runtime += 0.5 else: - self.runtime = (datetime.datetime.now() - self.start_time).total_seconds() - log.info("running at %.1f deg C (Target: %.1f) , heat %.2f, cool %.2f, air %.2f, door %s (%.1fs/%.0f)"%(self.temp_sensor.temperature,self.target,self.heat,self.cool,self.air,self.door,self.runtime,self.totaltime)) + runtime_delta = datetime.datetime.now() - self.start_time + self.runtime = runtime_delta.total_seconds() + log.info("running at %.1f deg C (Target: %.1f) , heat %.2f, cool %.2f, air %.2f, door %s (%.1fs/%.0f)" % (self.temp_sensor.temperature, self.target, self.heat, self.cool, self.air, self.door, self.runtime, self.totaltime)) self.target = self.profile.get_target_temperature(self.runtime) pid = self.pid.compute(self.target, self.temp_sensor.temperature) - log.info("pid: %.3f"%pid) + log.info("pid: %.3f" % pid) self.set_cool(pid <= -1) self.set_heat(pid > 0) @@ -92,18 +103,17 @@ class Oven (threading.Thread): # self.set_heat(False) # self.set_cool(self.temp_sensor.temperature > self.target) - if self.temp_sensor.temperature>200: + if self.temp_sensor.temperature > 200: self.set_air(False) - elif self.temp_sensor.temperature<180: + elif self.temp_sensor.temperature < 180: self.set_air(True) if self.runtime >= self.totaltime: self.reset() time.sleep(self.time_step) - - def set_heat(self,value): + def set_heat(self, value): if value: self.heat = 1.0 if gpio_available: @@ -113,7 +123,7 @@ class Oven (threading.Thread): if gpio_available: GPIO.output(config.gpio_heat, GPIO.HIGH) - def set_cool(self,value): + def set_cool(self, value): if value: self.cool = 1.0 if gpio_available: @@ -123,7 +133,7 @@ class Oven (threading.Thread): if gpio_available: GPIO.output(config.gpio_cool, GPIO.HIGH) - def set_air(self,value): + def set_air(self, value): if value: self.air = 1.0 if gpio_available: @@ -141,7 +151,7 @@ class Oven (threading.Thread): 'state': self.state, 'heat': self.heat, 'cool': self.cool, - 'air' : self.air, + 'air': self.air, 'totaltime': self.totaltime, 'door': self.door } @@ -155,29 +165,30 @@ class Oven (threading.Thread): class TempSensor(threading.Thread): - def __init__(self,time_step): + def __init__(self, time_step): threading.Thread.__init__(self) self.daemon = True self.temperature = 0 self.time_step = time_step - + + class TempSensorReal(TempSensor): - def __init__(self,time_step): - TempSensor.__init__(self,time_step) - self.thermocouple = MAX31855(config.gpio_sensor_cs, - config.gpio_sensor_clock, - config.gpio_sensor_data, - "c" - ) + def __init__(self, time_step): + TempSensor.__init__(self, time_step) + self.thermocouple = MAX31855(config.gpio_sensor_cs, + config.gpio_sensor_clock, + config.gpio_sensor_data, + "c") def run(self): while True: self.temperature = self.thermocouple.get() time.sleep(self.time_step) + class TempSensorSimulate(TempSensor): - def __init__(self,oven,time_step,sleep_time): - TempSensor.__init__(self,time_step) + def __init__(self, oven, time_step, sleep_time): + TempSensor.__init__(self, time_step) self.oven = oven self.sleep_time = sleep_time @@ -189,10 +200,10 @@ class TempSensorSimulate(TempSensor): R_o_nocool = config.sim_R_o_nocool R_o_cool = config.sim_R_o_cool R_ho_noair = config.sim_R_ho_noair - R_ho_air = config.sim_R_ho_air + R_ho_air = config.sim_R_ho_air - t = t_env #deg C temp in oven - t_h = t #deg C temp of heat element + t = t_env # deg C temp in oven + t_h = t # deg C temp of heat element while True: #heating energy Q_h = p_heat * self.time_step * self.oven.heat @@ -209,9 +220,8 @@ class TempSensorSimulate(TempSensor): p_ho = (t_h - t) / R_ho #temperature change of oven and heat el - t += p_ho *self.time_step / c_oven - t_h -= p_ho *self.time_step / c_heat - + t += p_ho * self.time_step / c_oven + t_h -= p_ho * self.time_step / c_heat #energy flux oven -> env if self.oven.cool: @@ -220,24 +230,25 @@ class TempSensorSimulate(TempSensor): p_env = (t - t_env) / R_o_nocool #temperature change of oven by cooling to env - t -= p_env *self.time_step / c_oven - log.debug("energy sim: -> %dW heater: %.0f -> %dW oven: %.0f -> %dW env"%(int(p_heat * self.oven.heat),t_h,int(p_ho),t,int(p_env))) + t -= p_env * self.time_step / c_oven + log.debug("energy sim: -> %dW heater: %.0f -> %dW oven: %.0f -> %dW env" % (int(p_heat * self.oven.heat), t_h, int(p_ho), t, int(p_env))) self.temperature = t time.sleep(self.sleep_time) + class Profile(): - def __init__(self,json_data): + def __init__(self, json_data): obj = json.loads(json_data) self.name = obj["name"] self.data = sorted(obj["data"]) def get_duration(self): - return max([t for (t,x) in self.data]) + return max([t for (t, x) in self.data]) - def get_surrounding_points(self,time): + def get_surrounding_points(self, time): if time > self.get_duration(): - return (None,None) + return (None, None) prev_point = None next_point = None @@ -248,27 +259,28 @@ class Profile(): next_point = self.data[i] break - return (prev_point,next_point) + return (prev_point, next_point) - def is_rising(self,time): - (prev_point,next_point) = self.get_surrounding_points(time) + def is_rising(self, time): + (prev_point, next_point) = self.get_surrounding_points(time) if prev_point and next_point: return prev_point[1] < next_point[1] else: return False - def get_target_temperature(self,time): + def get_target_temperature(self, time): if time > self.get_duration(): return 0 - (prev_point,next_point) = self.get_surrounding_points(time) + (prev_point, next_point) = self.get_surrounding_points(time) incl = float(next_point[1] - prev_point[1]) / float(next_point[0] - prev_point[0]) temp = prev_point[1] + (time - prev_point[0]) * incl return temp + class PID(): - def __init__(self,ki=1,kp=1,kd=1): + def __init__(self, ki=1, kp=1, kd=1): self.ki = ki self.kp = kp self.kd = kd @@ -276,17 +288,17 @@ class PID(): self.iterm = 0 self.lastErr = 0 - def compute(self,setpoint,ispoint): + def compute(self, setpoint, ispoint): now = datetime.datetime.now() timeDelta = (now - self.lastNow).total_seconds() error = float(setpoint - ispoint) self.iterm += (error * timeDelta * self.ki) - self.iterm=sorted([-1,self.iterm,1])[1] + self.iterm = sorted([-1, self.iterm, 1])[1] dErr = (error - self.lastErr) / timeDelta output = self.kp * error + self.iterm + self.kd * dErr - output = sorted([-1,output,1])[1] + output = sorted([-1, output, 1])[1] self.lastErr = error self.lastNow = now diff --git a/picoreflowd.py b/picoreflowd.py index 1292552..ffe2318 100644 --- a/picoreflowd.py +++ b/picoreflowd.py @@ -1,4 +1,6 @@ -import os,logging,json +import os +import logging +import json import bottle from gevent.pywsgi import WSGIServer @@ -11,7 +13,7 @@ except: print "Copy config.py.EXAMPLE to config.py and adapt it for your setup." exit(1) -logging.basicConfig(level = config.log_level, format = config.log_format) +logging.basicConfig(level=config.log_level, format=config.log_format) log = logging.getLogger("picoreflowd") log.info("Starting picoreflowd") @@ -22,22 +24,29 @@ app = bottle.Bottle() oven = Oven() ovenWatcher = OvenWatcher(oven) +script_dir = os.path.dirname(os.path.realpath(__file__)) +profile_path = os.path.join(script_dir, "storage", "profiles") + + @app.route('/') def index(): return bottle.redirect('/picoreflow/index.html') + @app.route('/picoreflow/:filename#.*#') def send_static(filename): - log.debug("serving %s"%filename) + log.debug("serving %s" % filename) return bottle.static_file(filename, root='./public/') + def get_websocket_from_request(): - env = bottle.request.environ; + env = bottle.request.environ wsock = env.get('wsgi.websocket') if not wsock: abort(400, 'Expected WebSocket request.') return wsock + @app.route('/control') def handle_control(): wsock = get_websocket_from_request() @@ -45,7 +54,7 @@ def handle_control(): while True: try: message = wsock.receive() - log.info("Received (control): %s"% message) + log.info("Received (control): %s" % message) msgdict = json.loads(message) if msgdict.get("cmd") == "RUN": log.info("RUN command received") @@ -61,7 +70,7 @@ def handle_control(): if profile_obj: profile_json = json.dumps(profile_obj) profile = Profile(profile_json) - simulated_oven = Oven(simulate=True,time_step=0.05) + simulated_oven = Oven(simulate=True, time_step=0.05) simulation_watcher = OvenWatcher(simulated_oven) simulation_watcher.add_observer(wsock) #simulated_oven.run_profile(profile) @@ -73,6 +82,7 @@ def handle_control(): break log.info("websocket (control) closed") + @app.route('/storage') def handle_storage(): wsock = get_websocket_from_request() @@ -82,7 +92,7 @@ def handle_storage(): message = wsock.receive() if not message: break - log.debug("websocket (storage) received: %s"%message) + log.debug("websocket (storage) received: %s" % message) try: msgdict = json.loads(message) @@ -95,14 +105,14 @@ def handle_storage(): elif msgdict.get("cmd") == "PUT": log.info("PUT command received") profile_obj = msgdict.get('profile') - force = msgdict.get('force',False) + force = msgdict.get('force', False) if profile_obj: #del msgdict["cmd"] - if save_profile(profile_obj,force): - msgdict["resp"]="OK" + if save_profile(profile_obj, force): + msgdict["resp"] = "OK" else: - msgdict["resp"]="FAIL" - log.debug("websocket (storage) sent: %s"%message) + msgdict["resp"] = "FAIL" + log.debug("websocket (storage) sent: %s" % message) wsock.send(json.dumps(msgdict)) wsock.send(get_profiles()) @@ -110,6 +120,7 @@ def handle_storage(): break log.info("websocket (storage) closed") + @app.route('/status') def handle_status(): wsock = get_websocket_from_request() @@ -123,41 +134,42 @@ def handle_status(): break log.info("websocket (status) closed") -script_dir = os.path.dirname(os.path.realpath(__file__)) -profile_path = os.path.join(script_dir,"storage","profiles") def get_profiles(): - try : + try: profile_files = os.listdir(profile_path) - except : + except: profile_files = [] profiles = [] for filename in profile_files: - with open(os.path.join(profile_path,filename), 'r') as f: + with open(os.path.join(profile_path, filename), 'r') as f: profiles.append(json.load(f)) return json.dumps(profiles) + def save_profile(profile, force=False): profile_json = json.dumps(profile) filename = profile['name']+".json" - filepath = os.path.join(profile_path,filename) + filepath = os.path.join(profile_path, filename) if not force and os.path.exists(filepath): - log.error("Could not write, %s already exists"%filepath) + log.error("Could not write, %s already exists" % filepath) return False with open(filepath, 'w+') as f: f.write(profile_json) f.close() - log.info("Wrote %s"%filepath) + log.info("Wrote %s" % filepath) return True + def main(): ip = config.listening_ip port = config.listening_port - log.info("listening on %s:%d"%(ip,port)) + log.info("listening on %s:%d" % (ip, port)) - server = WSGIServer((ip,port), app, - handler_class=WebSocketHandler) + server = WSGIServer((ip, port), app, + handler_class=WebSocketHandler) server.serve_forever() + if __name__ == "__main__": main()