consolidate pertinent functions in statemachine module

This commit is contained in:
petrucci4prez 2017-06-10 02:48:50 -04:00
parent 532b0d8ecd
commit 083d3a8295
2 changed files with 68 additions and 71 deletions

View File

@ -3,36 +3,6 @@ Various helper functions and classes
''' '''
import time, os import time, os
from subprocess import check_output, DEVNULL, CalledProcessError
from threading import Event
from exceptionThreading import ExceptionThread
class CountdownTimer(ExceptionThread):
'''
Launches thread which self terminates after some time (given in seconds).
Termination triggers some action (a function). Optionally, a sound can be
assigned to each 'tick'
'''
def __init__(self, countdownSeconds, action, sound=None):
self._stopper = Event()
def countdown():
for i in range(countdownSeconds, 0, -1):
if self._stopper.isSet():
return None
if sound and i < countdownSeconds:
sound.play()
time.sleep(1)
action()
super().__init__(target=countdown, daemon=True)
self.start()
def stop(self):
self._stopper.set()
def __del__(self):
self.stop()
def mkdirSafe(path, logger): def mkdirSafe(path, logger):
''' '''
@ -57,15 +27,3 @@ def waitForPath(path, logger, timeout=30):
time.sleep(1) time.sleep(1)
logger.error('Could not find %s after %s seconds', path, timeout) logger.error('Could not find %s after %s seconds', path, timeout)
raise SystemExit raise SystemExit
def resetUSBDevice(device, logger):
'''
Resets a USB device using the de/reauthorization method. This is really
crude but works beautifully
'''
devpath = os.path.join('/sys/bus/usb/devices/' + device + '/authorized')
with open(devpath, 'w') as f:
f.write('0')
with open(devpath, 'w') as f:
f.write('1')
logger.debug('Reset USB device: %s', devpath)

View File

@ -1,10 +1,10 @@
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import time, logging, enum import time, logging, enum, os
from threading import Lock from threading import Lock
from functools import partial from functools import partial
from collections import namedtuple from collections import namedtuple
from auxilary import CountdownTimer, resetUSBDevice from exceptionThreading import ExceptionThread
from config import stateFile from config import stateFile
from sensors import startDoorSensor, startMotionSensor from sensors import startDoorSensor, startMotionSensor
from gmail import intruderAlert from gmail import intruderAlert
@ -16,14 +16,53 @@ from stream import Camera, FileDump
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SIGNALS(enum.Enum): class _SIGNALS(enum.Enum):
ARM = enum.auto() ARM = enum.auto()
INSTANT_ARM = enum.auto() INSTANT_ARM = enum.auto()
DISARM = enum.auto() DISARM = enum.auto()
TIMOUT = enum.auto() TIMOUT = enum.auto()
TRIGGER = enum.auto() TRIGGER = enum.auto()
class _CountdownTimer(ExceptionThread):
'''
Launches thread which self terminates after some time (given in seconds).
Termination triggers some action (a function). Optionally, a sound can be
assigned to each 'tick'
'''
def __init__(self, countdownSeconds, action, sound=None):
self._stopper = Event()
class State: def countdown():
for i in range(countdownSeconds, 0, -1):
if self._stopper.isSet():
return None
if sound and i < countdownSeconds:
sound.play()
time.sleep(1)
action()
super().__init__(target=countdown, daemon=True)
self.start()
def stop(self):
self._stopper.set()
def __del__(self):
self.stop()
def _resetUSBDevice(device):
'''
Resets a USB device using the de/reauthorization method. This is really
crude but works beautifully
'''
devpath = os.path.join('/sys/bus/usb/devices/' + device + '/authorized')
with open(devpath, 'w') as f:
f.write('0')
with open(devpath, 'w') as f:
f.write('1')
logger.debug('Reset USB device: %s', devpath)
class _State:
def __init__(self, name, entryCallbacks=[], exitCallbacks=[], sound=None): def __init__(self, name, entryCallbacks=[], exitCallbacks=[], sound=None):
self.name = name self.name = name
self.entryCallbacks = entryCallbacks self.entryCallbacks = entryCallbacks
@ -47,7 +86,7 @@ class State:
c() c()
def next(self, signal): def next(self, signal):
if signal in SIGNALS: if signal in _SIGNALS:
return self if signal not in self._transTbl else self._transTbl[signal] return self if signal not in self._transTbl else self._transTbl[signal]
else: else:
raise Exception('Illegal signal') raise Exception('Illegal signal')
@ -74,7 +113,7 @@ class StateMachine:
self.fileDump = FileDump() self.fileDump = FileDump()
# add signals to self to avoid calling partial every time # add signals to self to avoid calling partial every time
for sig in SIGNALS: for sig in _SIGNALS:
setattr(self, sig.name, partial(self.selectState, sig)) setattr(self, sig.name, partial(self.selectState, sig))
secretTable = { secretTable = {
@ -101,7 +140,7 @@ class StateMachine:
) )
def startTimer(t, sound): def startTimer(t, sound):
self._timer = CountdownTimer(t, self.TIMOUT, sound) self._timer = _CountdownTimer(t, self.TIMOUT, sound)
def stopTimer(): def stopTimer():
if self._timer.is_alive(): if self._timer.is_alive():
@ -112,29 +151,29 @@ class StateMachine:
sfx = self.soundLib.soundEffects sfx = self.soundLib.soundEffects
stateObjs = [ stateObjs = [
State( _State(
name = 'disarmed', name = 'disarmed',
entryCallbacks = [partial(self.LED.setBlink, False)], entryCallbacks = [partial(self.LED.setBlink, False)],
sound = sfx['disarmed'] sound = sfx['disarmed']
), ),
State( _State(
name = 'disarmedCountdown', name = 'disarmedCountdown',
entryCallbacks = [blinkingLED, partial(startTimer, 30, sfx['disarmedCountdown'])], entryCallbacks = [blinkingLED, partial(startTimer, 30, sfx['disarmedCountdown'])],
exitCallbacks = [stopTimer], exitCallbacks = [stopTimer],
sound = sfx['disarmedCountdown'] sound = sfx['disarmedCountdown']
), ),
State( _State(
name = 'armed', name = 'armed',
entryCallbacks = [blinkingLED], entryCallbacks = [blinkingLED],
sound = sfx['armed'] sound = sfx['armed']
), ),
State( _State(
name = 'armedCountdown', name = 'armedCountdown',
entryCallbacks = [blinkingLED, partial(startTimer, 30, sfx['armedCountdown'])], entryCallbacks = [blinkingLED, partial(startTimer, 30, sfx['armedCountdown'])],
exitCallbacks = [stopTimer], exitCallbacks = [stopTimer],
sound = sfx['armedCountdown'] sound = sfx['armedCountdown']
), ),
State( _State(
name = 'triggered', name = 'triggered',
entryCallbacks = [blinkingLED, intruderAlert], entryCallbacks = [blinkingLED, intruderAlert],
sound = sfx['triggered'] sound = sfx['triggered']
@ -146,29 +185,29 @@ class StateMachine:
self.states = st = namedtuple('States', [obj.name for obj in stateObjs])(*stateObjs) self.states = st = namedtuple('States', [obj.name for obj in stateObjs])(*stateObjs)
st.disarmed.addTransition( SIGNALS.ARM, st.disarmedCountdown) st.disarmed.addTransition( _SIGNALS.ARM, st.disarmedCountdown)
st.disarmed.addTransition( SIGNALS.INSTANT_ARM, st.armed) st.disarmed.addTransition( _SIGNALS.INSTANT_ARM, st.armed)
st.disarmedCountdown.addTransition( SIGNALS.DISARM, st.disarmed) st.disarmedCountdown.addTransition( _SIGNALS.DISARM, st.disarmed)
st.disarmedCountdown.addTransition( SIGNALS.TIMOUT, st.armed) st.disarmedCountdown.addTransition( _SIGNALS.TIMOUT, st.armed)
st.disarmedCountdown.addTransition( SIGNALS.INSTANT_ARM, st.armed) st.disarmedCountdown.addTransition( _SIGNALS.INSTANT_ARM, st.armed)
st.armed.addTransition( SIGNALS.DISARM, st.disarmed) st.armed.addTransition( _SIGNALS.DISARM, st.disarmed)
st.armed.addTransition( SIGNALS.TRIGGER, st.armedCountdown) st.armed.addTransition( _SIGNALS.TRIGGER, st.armedCountdown)
st.armedCountdown.addTransition( SIGNALS.DISARM, st.disarmed) st.armedCountdown.addTransition( _SIGNALS.DISARM, st.disarmed)
st.armedCountdown.addTransition( SIGNALS.TIMOUT, st.triggered) st.armedCountdown.addTransition( _SIGNALS.TIMOUT, st.triggered)
st.armedCountdown.addTransition( SIGNALS.ARM, st.armed) st.armedCountdown.addTransition( _SIGNALS.ARM, st.armed)
st.armedCountdown.addTransition( SIGNALS.INSTANT_ARM, st.armed) st.armedCountdown.addTransition( _SIGNALS.INSTANT_ARM, st.armed)
st.triggered.addTransition( SIGNALS.DISARM, st.disarmed) st.triggered.addTransition( _SIGNALS.DISARM, st.disarmed)
st.triggered.addTransition( SIGNALS.ARM, st.armed) st.triggered.addTransition( _SIGNALS.ARM, st.armed)
st.triggered.addTransition( SIGNALS.INSTANT_ARM, st.armed) st.triggered.addTransition( _SIGNALS.INSTANT_ARM, st.armed)
self.currentState = getattr(self.states, stateFile['state']) self.currentState = getattr(self.states, stateFile['state'])
def __enter__(self): def __enter__(self):
resetUSBDevice('1-1', logger) _resetUSBDevice('1-1')
self.soundLib.start() self.soundLib.start()
self.LED.start() self.LED.start()
@ -179,13 +218,13 @@ class StateMachine:
def action(): def action():
if self.currentState == self.states.armed: if self.currentState == self.states.armed:
self.selectState(SIGNALS.TRIGGER) self.selectState(_SIGNALS.TRIGGER)
sensitiveStates = (self.states.armed, self.states.armedCountdown, self.states.triggered) sensitiveStates = (self.states.armed, self.states.armedCountdown, self.states.triggered)
def actionVideo(pin): def actionVideo(pin):
if self.currentState in sensitiveStates: if self.currentState in sensitiveStates:
self.selectState(SIGNALS.TRIGGER) self.selectState(_SIGNALS.TRIGGER)
self.fileDump.addInitiator(pin) self.fileDump.addInitiator(pin)
while GPIO.input(pin) and self.currentState in sensitiveStates: while GPIO.input(pin) and self.currentState in sensitiveStates:
time.sleep(0.1) time.sleep(0.1)