2014-10-15 11:37:45 +00:00
|
|
|
import time
|
|
|
|
import atexit
|
|
|
|
import threading
|
2014-10-18 18:41:26 +00:00
|
|
|
import warnings
|
2014-10-15 11:37:45 +00:00
|
|
|
import Queue
|
2014-10-21 21:21:43 +00:00
|
|
|
import logging
|
|
|
|
|
2014-10-22 01:08:10 +00:00
|
|
|
logger = logging.getLogger("kiln.Stepper")
|
2014-10-15 11:37:45 +00:00
|
|
|
|
2014-10-18 19:41:05 +00:00
|
|
|
try:
|
|
|
|
from RPi import GPIO
|
|
|
|
except ImportError:
|
|
|
|
pass
|
2014-10-15 11:37:45 +00:00
|
|
|
|
|
|
|
class Stepper(threading.Thread):
|
|
|
|
pattern = [
|
2014-10-15 18:02:56 +00:00
|
|
|
[1,1,0,0],
|
2014-10-15 11:37:45 +00:00
|
|
|
[0,1,1,0],
|
2014-10-15 18:02:56 +00:00
|
|
|
[0,0,1,1],
|
2014-10-15 11:37:45 +00:00
|
|
|
[1,0,0,1]]
|
|
|
|
|
2014-10-26 02:54:40 +00:00
|
|
|
def __init__(self, pin1=5, pin2=6, pin3=13, pin4=19, timeout=1, home_pin=None):
|
2014-10-21 21:21:43 +00:00
|
|
|
super(Stepper, self).__init__()
|
|
|
|
self.daemon = True
|
|
|
|
|
2014-10-15 11:37:45 +00:00
|
|
|
self.queue = Queue.Queue()
|
|
|
|
self.finished = threading.Event()
|
|
|
|
|
|
|
|
self.pins = [pin1, pin2, pin3, pin4]
|
2014-10-15 18:02:56 +00:00
|
|
|
GPIO.setmode(GPIO.BCM)
|
2014-10-15 11:37:45 +00:00
|
|
|
GPIO.setup(pin1, GPIO.OUT)
|
|
|
|
GPIO.setup(pin2, GPIO.OUT)
|
|
|
|
GPIO.setup(pin3, GPIO.OUT)
|
|
|
|
GPIO.setup(pin4, GPIO.OUT)
|
2014-10-26 02:54:40 +00:00
|
|
|
self.home_pin = home_pin
|
|
|
|
GPIO.setup(home_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
2014-10-15 11:37:45 +00:00
|
|
|
|
|
|
|
self.timeout = timeout
|
2014-10-26 02:54:40 +00:00
|
|
|
self.home()
|
2014-10-21 21:21:43 +00:00
|
|
|
self.start()
|
2014-10-15 11:37:45 +00:00
|
|
|
|
|
|
|
def stop(self):
|
2014-10-17 18:17:42 +00:00
|
|
|
self.queue.put((None, None, None))
|
2014-10-15 11:37:45 +00:00
|
|
|
|
|
|
|
def step(self, num, speed=10, block=False):
|
|
|
|
"""Step the stepper motor
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
num : int
|
|
|
|
Number of steps
|
|
|
|
speed : int
|
|
|
|
Number of steps per second
|
|
|
|
block : bool
|
|
|
|
Block while stepping?
|
|
|
|
"""
|
|
|
|
self.finished.clear()
|
2014-10-17 18:17:42 +00:00
|
|
|
self.queue.put((num, speed, block))
|
|
|
|
self.finished.wait()
|
2014-10-15 11:37:45 +00:00
|
|
|
|
2014-10-26 02:54:40 +00:00
|
|
|
def home(self):
|
|
|
|
if self.home_pin is None:
|
|
|
|
raise ValueError("No homing switch defined")
|
|
|
|
|
|
|
|
while GPIO.input(self.home_pin):
|
|
|
|
for i in range(len(self.pattern)):
|
|
|
|
for pin, out in zip(self.pins, self.pattern[i]):
|
|
|
|
GPIO.output(pin, out)
|
|
|
|
time.sleep(1. / 150.)
|
|
|
|
|
|
|
|
self.phase = 0
|
|
|
|
|
2014-10-15 11:37:45 +00:00
|
|
|
def run(self):
|
2014-10-18 19:41:05 +00:00
|
|
|
try:
|
|
|
|
step, speed, block = self.queue.get()
|
|
|
|
while step is not None:
|
|
|
|
for pin, out in zip(self.pins, self.pattern[self.phase%len(self.pattern)]):
|
|
|
|
GPIO.output(pin, out)
|
2014-10-15 11:37:45 +00:00
|
|
|
|
2014-10-18 19:41:05 +00:00
|
|
|
if block:
|
|
|
|
self._step(step, speed)
|
|
|
|
self.finished.set()
|
|
|
|
else:
|
|
|
|
self.finished.set()
|
|
|
|
self._step_noblock(step, speed)
|
|
|
|
|
|
|
|
try:
|
|
|
|
step, speed, block = self.queue.get(True, self.timeout)
|
|
|
|
except Queue.Empty:
|
|
|
|
#handle the timeout, turn off all pins
|
|
|
|
for pin in self.pins:
|
|
|
|
GPIO.output(pin, False)
|
|
|
|
step, speed, block = self.queue.get()
|
|
|
|
except:
|
|
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
2014-10-15 11:37:45 +00:00
|
|
|
|
|
|
|
for pin in self.pins:
|
|
|
|
GPIO.output(pin, False)
|
2014-10-15 18:02:56 +00:00
|
|
|
GPIO.cleanup()
|
2014-10-15 11:37:45 +00:00
|
|
|
|
2014-10-17 18:17:42 +00:00
|
|
|
def _step_noblock(self, step, speed):
|
|
|
|
ispeed = 1. / (2.*speed)
|
|
|
|
target = self.phase + step
|
|
|
|
while self.phase != target:
|
|
|
|
now = time.time()
|
2014-10-18 18:41:26 +00:00
|
|
|
self.phase += 1 if target > self.phase else -1
|
2014-10-17 18:17:42 +00:00
|
|
|
output = self.pattern[self.phase%len(self.pattern)]
|
|
|
|
for pin, out in zip(self.pins, output):
|
|
|
|
GPIO.output(pin, out)
|
|
|
|
|
|
|
|
if not self.queue.empty():
|
|
|
|
step, speed, block = self.queue.get()
|
|
|
|
ispeed = 1. / (2.*speed)
|
|
|
|
target += step
|
|
|
|
if block:
|
|
|
|
self._step(target - self.phase, speed)
|
2014-10-18 18:41:26 +00:00
|
|
|
self.finished.set()
|
2014-10-17 18:17:42 +00:00
|
|
|
|
|
|
|
diff = ispeed - (time.time() - now)
|
|
|
|
if (diff) > 0:
|
|
|
|
time.sleep(diff)
|
|
|
|
else:
|
|
|
|
warnings.warn("Step rate too high, stepping as fast as possible")
|
|
|
|
|
2014-10-15 11:37:45 +00:00
|
|
|
def _step(self, step, speed):
|
2014-10-15 18:02:56 +00:00
|
|
|
print "Stepping %d steps at %d steps / second"%(step, speed)
|
2014-10-15 11:37:45 +00:00
|
|
|
if step < 0:
|
|
|
|
steps = range(step, 0)[::-1]
|
2014-10-15 18:02:56 +00:00
|
|
|
else:
|
|
|
|
steps = range(step)
|
2014-10-15 11:37:45 +00:00
|
|
|
|
|
|
|
for i in steps:
|
|
|
|
now = time.time()
|
|
|
|
output = self.pattern[(self.phase+i)%len(self.pattern)]
|
|
|
|
for pin, out in zip(self.pins, output):
|
|
|
|
GPIO.output(pin, out)
|
|
|
|
|
|
|
|
diff = 1. / (2*speed) - (time.time() - now)
|
|
|
|
if (diff) > 0:
|
|
|
|
time.sleep(diff)
|
|
|
|
|
|
|
|
self.phase += step
|
|
|
|
|
2014-10-18 19:41:05 +00:00
|
|
|
class StepperSim(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.phase = 0
|
|
|
|
|
|
|
|
def step(self, num, speed=10, block=False):
|
|
|
|
print "Simulated stepping %d steps at %d steps / second"%(num, speed)
|
|
|
|
if block:
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
def stop(self):
|
2014-11-30 04:27:48 +00:00
|
|
|
print "stopping simulated regulator"
|
2014-10-18 19:41:05 +00:00
|
|
|
|
2014-10-21 21:21:43 +00:00
|
|
|
class Regulator(threading.Thread):
|
|
|
|
def __init__(self, maxsteps=4500, minsteps=2480, speed=150, ignite_pin=None, flame_pin=None, simulate=False):
|
2014-10-15 11:37:45 +00:00
|
|
|
"""Set up a stepper-controlled regulator. Implement some safety measures
|
|
|
|
to make sure everything gets shut off at the end
|
|
|
|
|
2014-10-21 21:21:43 +00:00
|
|
|
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.
|
|
|
|
|
2014-10-15 11:37:45 +00:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
maxsteps : int
|
|
|
|
The max value for the regulator, in steps
|
|
|
|
minsteps : int
|
|
|
|
The minimum position to avoid extinguishing the flame
|
|
|
|
speed : int
|
|
|
|
Speed to turn the stepper, in steps per second
|
|
|
|
ignite_pin : int or None
|
|
|
|
If not None, turn on this pin during the ignite sequence
|
|
|
|
"""
|
2014-10-21 21:21:43 +00:00
|
|
|
|
2014-10-18 19:41:05 +00:00
|
|
|
if simulate:
|
|
|
|
self.stepper = StepperSim()
|
|
|
|
else:
|
|
|
|
self.stepper = Stepper()
|
|
|
|
self.stepper.start()
|
2014-10-21 21:21:43 +00:00
|
|
|
|
2014-10-15 11:37:45 +00:00
|
|
|
self.current = 0
|
|
|
|
self.max = maxsteps
|
|
|
|
self.min = minsteps
|
|
|
|
self.speed = speed
|
|
|
|
|
|
|
|
self.ignite_pin = ignite_pin
|
|
|
|
if ignite_pin is not None:
|
|
|
|
GPIO.setup(ignite_pin, OUT)
|
2014-10-22 01:08:10 +00:00
|
|
|
self.flame_pin = flame_pin
|
|
|
|
if flame_pin is not None:
|
|
|
|
GPIO.setup(flame_pin, IN)
|
2014-10-15 18:02:56 +00:00
|
|
|
|
|
|
|
def exit():
|
2014-10-18 19:41:05 +00:00
|
|
|
if self.current != 0:
|
|
|
|
self.off()
|
2014-10-15 18:02:56 +00:00
|
|
|
self.stepper.stop()
|
|
|
|
atexit.register(exit)
|
2014-10-15 11:37:45 +00:00
|
|
|
|
2014-10-18 19:41:05 +00:00
|
|
|
def ignite(self, start=2800, delay=1):
|
2014-10-21 21:21:43 +00:00
|
|
|
if self.current != 0:
|
|
|
|
raise ValueError("Must be off to ignite")
|
|
|
|
|
2014-10-22 19:11:46 +00:00
|
|
|
logger.info("Ignition start")
|
2014-10-15 11:37:45 +00:00
|
|
|
self.stepper.step(start, self.speed, block=True)
|
|
|
|
if self.ignite_pin is not None:
|
|
|
|
GPIO.output(self.ignite_pin, True)
|
|
|
|
time.sleep(delay)
|
|
|
|
if self.ignite_pin is not None:
|
|
|
|
GPIO.output(self.ignite_pin, False)
|
2014-10-18 20:38:43 +00:00
|
|
|
self.stepper.step(self.min - start, self.speed, block=True)
|
2014-10-15 18:02:56 +00:00
|
|
|
self.current = self.min
|
2014-10-22 19:11:46 +00:00
|
|
|
logger.info("Ignition complete")
|
2014-10-15 11:37:45 +00:00
|
|
|
|
|
|
|
def off(self, block=True):
|
2014-10-22 19:11:46 +00:00
|
|
|
logger.info("Shutting off gas")
|
2014-11-30 04:27:48 +00:00
|
|
|
self.stepper.step(-self.current, self.speed, block=block)
|
|
|
|
#self.stepper.home()
|
2014-10-15 11:37:45 +00:00
|
|
|
self.current = 0
|
|
|
|
|
2014-10-17 18:17:42 +00:00
|
|
|
def set(self, value, block=False):
|
2014-10-18 19:41:05 +00:00
|
|
|
if self.current == 0:
|
|
|
|
raise ValueError("System must be ignited to set value")
|
2014-10-18 18:41:26 +00:00
|
|
|
if not 0 <= value <= 1:
|
2014-10-15 11:37:45 +00:00
|
|
|
raise ValueError("Must give fraction between 0 and 1")
|
2014-10-15 18:02:56 +00:00
|
|
|
target = int(value * (self.max - self.min) + self.min)
|
2014-10-15 11:37:45 +00:00
|
|
|
nsteps = target - self.current
|
2014-10-15 18:02:56 +00:00
|
|
|
print "Currently at %d, target %d, stepping %d"%(self.current, target, nsteps)
|
2014-10-15 11:37:45 +00:00
|
|
|
self.current = target
|
|
|
|
self.stepper.step(nsteps, self.speed, block=block)
|
2014-10-21 21:21:43 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def output(self):
|
2014-10-22 19:11:46 +00:00
|
|
|
out = (self.current - self.min) / float(self.max - self.min)
|
|
|
|
if out < 0:
|
|
|
|
return -1
|
|
|
|
return out
|
2014-10-22 01:08:10 +00:00
|
|
|
|
|
|
|
def run(self):
|
|
|
|
"""Check the status of the flame sensor"""
|
|
|
|
#since the flame sensor does not yet exist, we'll save this for later
|
2014-11-29 17:04:33 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
class Breakout(object):
|
2015-02-22 21:55:20 +00:00
|
|
|
def __init__(self, addr, maxsteps=6500, minsteps=((2600, 0), (2300, 15)) ):
|
2014-11-29 17:04:33 +00:00
|
|
|
import breakout
|
|
|
|
self.device = breakout.Breakout(addr)
|
2015-02-22 07:39:51 +00:00
|
|
|
self.min_interp = minsteps
|
2014-11-29 17:04:33 +00:00
|
|
|
self.max = maxsteps
|
|
|
|
|
|
|
|
def exit():
|
2014-11-30 04:27:48 +00:00
|
|
|
if self.device.motor != 0:
|
2014-11-29 17:04:33 +00:00
|
|
|
self.off()
|
|
|
|
atexit.register(exit)
|
|
|
|
|
2015-02-22 07:39:51 +00:00
|
|
|
@property
|
|
|
|
def min(self):
|
|
|
|
temp = self.device.status.aux_temp0
|
|
|
|
if temp > self.min_interp[1][1]:
|
|
|
|
return self.min_interp[1][0]
|
|
|
|
elif temp <= self.min_interp[0][1]:
|
|
|
|
return self.min_interp[0][0]
|
|
|
|
else:
|
|
|
|
mrange = self.min_interp[0][0] - self.min_interp[1][0]
|
|
|
|
trange = self.min_interp[1][1] - self.min_interp[0][1]
|
|
|
|
mix = (temp - self.min_interp[0][1]) / float(trange)
|
|
|
|
return mrange * mix + self.min_interp[1][0]
|
|
|
|
|
2015-02-22 21:55:20 +00:00
|
|
|
def ignite(self, start=2400):
|
2014-11-30 04:27:48 +00:00
|
|
|
logger.info("Igniting system")
|
2014-11-29 17:04:33 +00:00
|
|
|
self.device.motor = start
|
2014-11-30 06:54:09 +00:00
|
|
|
while self.device.motor != start:
|
|
|
|
time.sleep(.1)
|
2014-11-29 17:04:33 +00:00
|
|
|
self.device.motor = self.min
|
|
|
|
|
|
|
|
@property
|
|
|
|
def output(self):
|
2015-02-22 07:39:51 +00:00
|
|
|
m = self.min
|
|
|
|
out = (self.device.motor - m) / float(self.max - m)
|
2014-11-29 17:04:33 +00:00
|
|
|
if out < 0:
|
|
|
|
return -1
|
|
|
|
return out
|
|
|
|
|
|
|
|
def set(self, value):
|
2015-02-22 07:39:51 +00:00
|
|
|
m = self.min
|
2014-11-29 17:04:33 +00:00
|
|
|
if self.device.motor == 0:
|
|
|
|
raise ValueError('Must ignite first')
|
|
|
|
if not 0 <= value <= 1:
|
|
|
|
raise ValueError('Must give value between 0 and 1')
|
2015-02-22 07:39:51 +00:00
|
|
|
self.device.motor = int((self.max - m)*value + m)
|
2014-11-29 17:04:33 +00:00
|
|
|
|
|
|
|
def off(self):
|
|
|
|
self.device.motor = 0
|
2014-11-30 06:24:59 +00:00
|
|
|
self.device.ignite = 0
|
2014-11-30 06:54:09 +00:00
|
|
|
logger.info("Shutting off regulator")
|