pyledriver/stateMachine.py

221 lines
6.0 KiB
Python
Raw Normal View History

2016-12-30 02:51:56 -05:00
import RPi.GPIO as GPIO
import time, logging
from threading import Lock
from functools import partial
from collections import namedtuple
from auxilary import CountdownTimer, ConfigFile, resetUSBDevice
2016-12-30 02:51:56 -05:00
from sensors import setupDoorSensor, setupMotionSensor
2017-05-31 00:41:42 -04:00
from gmail import intruderAlert
2016-12-30 02:51:56 -05:00
from listeners import KeypadListener, PipeListener
from blinkenLights import Blinkenlights
from soundLib import SoundLib
from webInterface import initWebInterface
2017-06-02 00:15:05 -04:00
from stream import initCamera, FileDump
2016-12-30 02:51:56 -05:00
logger = logging.getLogger(__name__)
resetUSBDevice('1-1')
2016-12-30 02:51:56 -05:00
class SIGNALS:
ARM = 1
INSTANT_ARM = 2
DISARM = 3
TIMOUT = 4
TRIGGER = 5
class State:
def __init__(self, stateMachine, name, entryCallbacks=[], exitCallbacks=[], blinkLED=True, sound=None):
self.stateMachine = stateMachine
self.name = name
self.entryCallbacks = entryCallbacks
self.exitCallbacks = exitCallbacks
self.blinkLED = blinkLED
if not sound and name in stateMachine.soundLib.soundEffects:
self.sound = stateMachine.soundLib.soundEffects[name]
else:
self.sound = sound
def entry(self):
2017-05-30 02:11:15 -04:00
logger.info('entering ' + self.name)
if self.sound:
self.sound.play()
self.stateMachine.LED.blink = self.blinkLED
self.stateMachine.keypadListener.resetBuffer()
2016-12-30 02:51:56 -05:00
for c in self.entryCallbacks:
c()
def exit(self):
2017-05-30 02:11:15 -04:00
logger.info('exiting ' + self.name)
if self.sound:
self.sound.stop()
2016-12-30 02:51:56 -05:00
for c in self.exitCallbacks:
c()
def next(self, signal):
t = (self, signal)
return self if t not in self.stateMachine.transitionTable else self.stateMachine.transitionTable[t]
def __str__(self):
return self.name
def __eq__(self, other):
return self.name == other
def __hash__(self):
return hash(self.name)
class StateMachine:
def __init__(self):
self.soundLib = SoundLib()
2016-12-30 02:51:56 -05:00
self._cfg = ConfigFile('config.yaml')
def startTimer(t, sound):
self._timer = CountdownTimer(t, partial(self.selectState, SIGNALS.TIMOUT), sound)
def stopTimer():
if self._timer.is_alive():
self._timer.stop()
self._timer = None
States = namedtuple('States', ['disarmed', 'disarmedCountdown', 'armed', 'armedCountdown', 'triggered'])
self.states = States(
State(
self,
name = 'disarmed',
blinkLED = False
),
State(
self,
name='disarmedCountdown',
entryCallbacks = [partial(startTimer, 30, self.soundLib.soundEffects['disarmedCountdown'])],
exitCallbacks = [stopTimer]
),
State(
self,
name = 'armed'
),
State(
self,
name = 'armedCountdown',
entryCallbacks = [partial(startTimer, 30, self.soundLib.soundEffects['armedCountdown'])],
exitCallbacks = [stopTimer]
),
State(
self,
name = 'triggered',
entryCallbacks = [intruderAlert]
)
)
self.currentState = getattr(self.states, self._cfg['state'])
self.transitionTable = {
(self.states.disarmed, SIGNALS.ARM): self.states.disarmedCountdown,
(self.states.disarmed, SIGNALS.INSTANT_ARM): self.states.armed,
(self.states.disarmedCountdown, SIGNALS.DISARM): self.states.disarmed,
(self.states.disarmedCountdown, SIGNALS.TIMOUT): self.states.armed,
(self.states.disarmedCountdown, SIGNALS.INSTANT_ARM): self.states.armed,
(self.states.armed, SIGNALS.DISARM): self.states.disarmed,
(self.states.armed, SIGNALS.TRIGGER): self.states.armedCountdown,
(self.states.armedCountdown, SIGNALS.DISARM): self.states.disarmed,
(self.states.armedCountdown, SIGNALS.ARM): self.states.armed,
(self.states.armedCountdown, SIGNALS.TIMOUT): self.states.triggered,
(self.states.triggered, SIGNALS.DISARM): self.states.disarmed,
(self.states.triggered, SIGNALS.ARM): self.states.armed,
}
self._lock = Lock()
self.LED = Blinkenlights(17)
2016-12-30 02:51:56 -05:00
2017-05-30 01:43:09 -04:00
initCamera()
2016-12-30 02:51:56 -05:00
def action():
2017-05-25 02:41:58 -04:00
if self.currentState == self.states.armed:
2016-12-30 02:51:56 -05:00
self.selectState(SIGNALS.TRIGGER)
2017-06-02 00:15:05 -04:00
fileDump = FileDump()
sensitiveStates = (self.states.armed, self.states.armedCountdown, self.states.triggered)
2016-12-30 02:51:56 -05:00
def actionVideo(pin):
2017-06-02 00:15:05 -04:00
if self.currentState in sensitiveStates:
2016-12-30 02:51:56 -05:00
self.selectState(SIGNALS.TRIGGER)
2017-06-02 00:15:05 -04:00
fileDump.addInitiator(pin)
while GPIO.input(pin) and self.currentState in sensitiveStates:
time.sleep(0.1)
fileDump.removeInitiator(pin)
2016-12-30 02:51:56 -05:00
setupMotionSensor(5, 'Nate\'s room', action)
setupMotionSensor(19, 'front door', action)
setupMotionSensor(26, 'Laura\'s room', action)
setupMotionSensor(6, 'deck window', partial(actionVideo, 6))
setupMotionSensor(13, 'kitchen bar', partial(actionVideo, 13))
2016-12-30 02:51:56 -05:00
setupDoorSensor(22, action, self.soundLib.soundEffects['door'])
2016-12-30 02:51:56 -05:00
secretTable = {
"dynamoHum": partial(self.selectState, SIGNALS.DISARM),
"zombyWoof": partial(self.selectState, SIGNALS.ARM),
"imTheSlime": partial(self.selectState, SIGNALS.INSTANT_ARM)
}
def secretCallback(secret, logger):
if secret in secretTable:
secretTable[secret]()
logger.debug('Secret pipe listener received: \"%s\"', secret)
elif logger:
2017-05-30 02:11:15 -04:00
logger.debug('Secret pipe listener received invalid secret')
self.secretListener = PipeListener(
callback = secretCallback,
path = '/tmp/secret'
)
self.keypadListener = KeypadListener(
stateMachine = self,
callbackDisarm = partial(self.selectState, 'disarm'),
callbackArm = partial(self.selectState, 'arm'),
soundLib = self.soundLib,
passwd = '5918462'
)
2017-05-30 01:03:07 -04:00
initWebInterface(self)
2016-12-30 02:51:56 -05:00
self.currentState.entry()
def selectState(self, signal):
self._lock.acquire() # make state transitions threadsafe
try:
nextState = self.currentState.next(signal)
if nextState != self.currentState:
self.currentState.exit()
self.currentState = nextState
self.currentState.entry()
finally:
self._lock.release()
self._cfg['state'] = self.currentState.name
self._cfg.sync()
logger.info('state changed to %s', self.currentState)
def __del__(self):
if hasattr(self, 'LED'):
2017-05-30 02:11:15 -04:00
self.LED.__del__()
2016-12-30 02:51:56 -05:00
if hasattr(self, 'soundLib'):
2016-12-30 02:51:56 -05:00
self.soundLib.__del__()
2017-05-30 02:11:15 -04:00
if hasattr(self, 'secretListener'):
self.secretListener.__del__()
2016-12-30 02:51:56 -05:00
if hasattr(self, 'keypadListener'):
2016-12-30 02:51:56 -05:00
self.keypadListener.__del__()