pyledriver/stateMachine.py

216 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 datetime import datetime
from threading import Lock
from functools import partial
from collections import namedtuple
from auxilary import CountdownTimer, ConfigFile
from sensors import setupDoorSensor, setupMotionSensor
from notifier import intruderAlert
from listeners import KeypadListener, PipeListener
from blinkenLights import Blinkenlights
from soundLib import SoundLib
from webInterface import initWebInterface
2016-12-30 02:51:56 -05:00
logger = logging.getLogger(__name__)
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):
logger.debug('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):
logger.debug('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
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)
def actionVideo(pin):
2017-05-25 02:41:58 -04:00
if self.currentState in (self.states.armed, self.states.armedCountdown, self.states.triggered):
2016-12-30 02:51:56 -05:00
self.selectState(SIGNALS.TRIGGER)
while GPIO.input(pin):
2017-05-25 02:41:58 -04:00
# TODO: check that this path exists
2016-12-30 02:51:56 -05:00
path = '/mnt/glusterfs/pyledriver/images/%s.jpg'
#~ with open(path % datetime.now(), 'wb') as f:
#~ f.write(camera.getFrame())
2016-12-30 02:51:56 -05:00
time.sleep(0.2)
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:
logger.error('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'):
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__()
if hasattr(self, 'pipeListener'):
2016-12-30 02:51:56 -05:00
self.pipeListener.__del__()
if hasattr(self, 'keypadListener'):
2016-12-30 02:51:56 -05:00
self.keypadListener.__del__()