starting to work on state machine for backend

master
James Gao 2014-10-21 14:21:43 -07:00
rodzic 96767913d1
commit a1b47bd1b6
6 zmienionych plików z 190 dodań i 72 usunięć

Wyświetl plik

@ -1,70 +1,69 @@
import stepper
import time
import random
import thermo
import warnings
import threading
import traceback
class StateMachine(object):
class Manager(threading.Thread):
def __init__(self):
import stepper
import thermo
self.monitor = thermo.Monitor()
self.regulator = stepper.Regulator()
"""
Create a Manager instance that manages the electronics for the kiln.
Fundamentally, this just means that four components need to be connected:
class KilnController(object):
def __init__(self, schedule, monitor, interval=5, start_time=None, Kp=.025, Ki=.01, Kd=.001, simulate=True):
The thermocouple, the regulator stepper, and PID controller, and the webserver
"""
self._send = None
self.monitor = thermo.Monitor(self._send_state)
self.regulator = stepper.Regulator(self._regulator_error)
self.profile = None
def register(self, webapp):
self._send = webapp.send
def _send_state(self, time, temp):
profile = None
if self.profile is not None:
profile.get_state()
state = dict(
output=self.regulator.output
profile=profile,
time=time,
temp=temp,
)
if self._send is not None:
self._send(state)
def _regulator_error(self, msg):
if self._send is not None:
self._send(dict())
class Profile(object):
def __init__(self, schedule, monitor, regulator, interval=5, start_time=None, Kp=.025, Ki=.01, Kd=.005):
self.schedule = schedule
self.monitor = monitor
self.interval = interval
self.start_time = start_time
if start_time is None:
self.start_time = time.time()
self.regulator = stepper.Regulator(simulate=simulate)
self.regulator = regulator
self.pid = PID(Kp, Ki, Kd)
self.simulate = simulate
if simulate:
self.schedule.insert(0, [0, 15])
else:
self.schedule.insert(0, [0, 15])
self.running = True
@property
def elapsed(self):
''' Returns the elapsed time from start in seconds'''
return time.time() - self.start_time
def get_state(self):
state = dict(
start_time=self.start_time,
elapsed=self.elapsed,
)
return state
def run(self):
try:
self.regulator.ignite()
print self.elapsed, self.schedule[-1][0]
while self.elapsed < self.schedule[-1][0]:
now = time.time()
ts = self.elapsed
#find epoch
for i in range(len(self.schedule)-1):
if self.schedule[i][0] < ts < self.schedule[i+1][0]:
time0, temp0 = self.schedule[i]
time1, temp1 = self.schedule[i+1]
frac = (ts - time0) / (time1 - time0)
setpoint = frac * (temp1 - temp0) + temp0
self.pid.setPoint(setpoint)
print "In epoch %d, elapsed time %f, temperature %f"%(i, ts, setpoint)
if self.simulate:
temp = setpoint + random.gauss(-20, 15)
else:
temp = self.monitor.temperature
pid_out = self.pid.update(temp)
if pid_out < 0: pid_out = 0
if pid_out > 1: pid_out = 1
self.regulator.set(pid_out, block=True)
time.sleep(self.interval - (time.time()-now))
except:
import traceback
traceback.print_exc()
print "Started at %f"%self.start_time
self.regulator.off()
def stop(self):
self.running = False
class PID(object):
"""

Wyświetl plik

@ -22,9 +22,17 @@ class DataRequest(tornado.web.RequestHandler):
self.monitor = monitor
def get(self):
output = [dict(time=ts[0], temp=ts[1]) for ts in self.monitor.history]
data = self.monitor.history
output = [dict(time=ts[0], temp=ts[1]) for ts in ]
self.write(json.dumps(output))
class DoAction(tornado.web.RequestHandler):
def initialize(self, controller):
self.controller = controller
def get(self, action):
pass
class WebApp(object):
def __init__(self, monitor, port=8888):
self.handlers = [

95
kiln/states.py 100644
Wyświetl plik

@ -0,0 +1,95 @@
"""Based on the pattern provided here:
http://python-3-patterns-idioms-test.readthedocs.org/en/latest/StateMachine.html
"""
import traceback
class State(object):
def __init__(self, machine):
self.parent = machine
def run(self):
"""Action that must be continuously run while in this state"""
pass
class Idle(State):
def ignite(self):
_ignite(self.parent.regulator, self.parent.notify)
return Lit,
def start(self):
_ignite(self.parent.regulator, self.parent.notify)
return Running,
class Lit(State):
def set(self, value):
try:
self.parent.regulator.set(value)
self.parent.notify(dict(type="success"))
except:
self.parent.notify(dict(type="error", msg=traceback.format_exc()))
def start(self, schedule, interval=5, start_time=None):
return Running, dict(schedule=schedule, interval=interval, start_time=start_time)
def stop(self):
_shutoff(self.parent.regulator, self.parent.notify)
return Idle,
class Running(State):
@property
def elapsed(self):
''' Returns the elapsed time from start in seconds'''
return time.time() - self.start_time
def run(self):
now = time.time()
ts = self.elapsed
#find epoch
for i in range(len(self.schedule)-1):
if self.schedule[i][0] < ts < self.schedule[i+1][0]:
time0, temp0 = self.schedule[i]
time1, temp1 = self.schedule[i+1]
frac = (ts - time0) / (time1 - time0)
setpoint = frac * (temp1 - temp0) + temp0
self.pid.setPoint(setpoint)
temp = self.monitor.temperature
pid_out = self.pid.update(temp)
if pid_out < 0: pid_out = 0
if pid_out > 1: pid_out = 1
self.regulator.set(pid_out, block=True)
if ts > self.schedule[-1][0]:
_shutoff(self.parent.regulator, self.parent.notify)
return Idle,
time.sleep(self.interval - (time.time()-now))
def pause(self):
return Lit,
def stop(self):
_shutoff(self.parent.regulator, self.parent.notify)
return Idle,
def _ignite(regulator, notify):
try:
regulator.ignite()
msg = dict(type="success")
except ValueError:
msg = dict(type="error", msg="Cannot ignite: regulator not off")
except Exception as e:
msg = dict(type="error", msg=traceback.format_exc())
notify(msg)
def _shutoff(regulator, notify):
try:
regulator.off()
msg = dict(type="success")
except ValueError:
msg = dict(type="error", msg="Cannot shutoff: regulator not lit")
except Exception as e:
msg = dict(type="error", msg=traceback.format_exc())
notify(msg)

Wyświetl plik

@ -3,6 +3,9 @@ import atexit
import threading
import warnings
import Queue
import logging
logger = logging.get_logger("Stepper")
try:
from RPi import GPIO
@ -17,6 +20,9 @@ class Stepper(threading.Thread):
[1,0,0,1]]
def __init__(self, pin1=5, pin2=6, pin3=13, pin4=19, timeout=1):
super(Stepper, self).__init__()
self.daemon = True
self.queue = Queue.Queue()
self.finished = threading.Event()
@ -29,8 +35,7 @@ class Stepper(threading.Thread):
self.phase = 0
self.timeout = timeout
super(Stepper, self).__init__()
self.daemon = True
self.start()
def stop(self):
self.queue.put((None, None, None))
@ -135,12 +140,15 @@ class StepperSim(object):
def stop(self):
print "stopping"
class Regulator(object):
def __init__(self, maxsteps=4500, minsteps=2480, speed=150, ignite_pin=None, simulate=False):
class Regulator(threading.Thread):
def __init__(self, maxsteps=4500, minsteps=2480, speed=150, ignite_pin=None, flame_pin=None, simulate=False):
"""Set up a stepper-controlled regulator. Implement some safety measures
to make sure everything gets shut off at the end
TODO: integrate flame sensor by converting this into a thread, and checking
flame state regularly. If flame sensor off, immediately increase gas and attempt
reignition, or shut off after 5 seconds of failure.
Parameters
----------
maxsteps : int
@ -152,11 +160,13 @@ class Regulator(object):
ignite_pin : int or None
If not None, turn on this pin during the ignite sequence
"""
if simulate:
self.stepper = StepperSim()
else:
self.stepper = Stepper()
self.stepper.start()
self.current = 0
self.max = maxsteps
self.min = minsteps
@ -173,7 +183,10 @@ class Regulator(object):
atexit.register(exit)
def ignite(self, start=2800, delay=1):
print "Ignition..."
if self.current != 0:
raise ValueError("Must be off to ignite")
logger.log("Ignition start")
self.stepper.step(start, self.speed, block=True)
if self.ignite_pin is not None:
GPIO.output(self.ignite_pin, True)
@ -182,13 +195,12 @@ class Regulator(object):
GPIO.output(self.ignite_pin, False)
self.stepper.step(self.min - start, self.speed, block=True)
self.current = self.min
print "Done!"
logger.log("Ignition complete")
def off(self, block=True):
print "Turning off..."
logger.log("Shutting off gas")
self.stepper.step(-self.current, self.speed, block=block)
self.current = 0
print "Done!"
def set(self, value, block=False):
if self.current == 0:
@ -200,3 +212,7 @@ class Regulator(object):
print "Currently at %d, target %d, stepping %d"%(self.current, target, nsteps)
self.current = target
self.stepper.step(nsteps, self.speed, block=block)
@property
def output(self):
return (self.current - self.min) / (self.max - self.min)

Wyświetl plik

@ -59,8 +59,7 @@ var tempgraph = (function(module) {
module.Monitor.prototype.last = function() {
return this.temperature[this.temperature.length-1];
}
module.Monitor.prototype.setScale = function(scale) {
$("a#temp_scale_C").parent().removeClass("active");
$("a#temp_scale_F").parent().removeClass("active");
@ -121,7 +120,7 @@ var tempgraph = (function(module) {
return module;
}(tempgraph || {}));
d3.json("data.json", function(error, data) {
d3.json("data2.json", function(error, data) {
monitor = new tempgraph.Monitor(data);
});

Wyświetl plik

@ -3,7 +3,7 @@ import time
import datetime
import logging
import threading
from collections import deque
from collections import deque, namedtuple
logger = logging.getLogger("thermo")
@ -18,8 +18,13 @@ def temp_to_cone(temp):
return names[i]+'.%d'%int(frac*10)
return "13+"
tempsample = namedtuple("tempsample", ['time', 'temp'])
class Monitor(threading.Thread):
def __init__(self, name="3b-000000182b57", callback=None):
super(Monitor, self).__init__()
self.daemon = True
self.device = "/sys/bus/w1/devices/%s/w1_slave"%name
self.history = deque(maxlen=1048576)
self.callback = callback
@ -33,8 +38,8 @@ class Monitor(threading.Thread):
logger.info("Could not start AlphaScroller")
self.display = None
super(Monitor, self).__init__()
self.running = True
self.start()
def _read_temp(self):
with open(self.device, 'r') as f:
@ -59,16 +64,12 @@ class Monitor(threading.Thread):
return self.history[-1][1]
def run(self):
# with open("/home/pi/data.txt", "w") as f:
# f.write("time\ttemp\n")
while self.running:
temp = self._read_temp()
now = time.time()
self.history.append((now, temp))
self.history.append(tempsample(now, temp))
if self.callback is not None:
self.callback(now, temp)
with open("/home/pi/data.txt", "a") as f:
f.write("%f\t%f\n"%(now, temp))
if self.display is not None:
if temp > 50:
@ -84,4 +85,4 @@ class Monitor(threading.Thread):
if __name__ == "__main__":
mon = Monitor()
mon.start()
mon.join()