import RPi.GPIO as GPIO import time, logging, enum, weakref from threading import Lock from functools import partial from collections import namedtuple from auxilary import CountdownTimer, resetUSBDevice from config import stateFile from sensors import startDoorSensor, startMotionSensor from gmail import intruderAlert from listeners import KeypadListener, PipeListener from blinkenLights import Blinkenlights from soundLib import SoundLib from webInterface import startWebInterface from stream import Camera, FileDump logger = logging.getLogger(__name__) class SIGNALS(enum.Enum): ARM = enum.auto() INSTANT_ARM = enum.auto() DISARM = enum.auto() TIMOUT = enum.auto() TRIGGER = enum.auto() class State: def __init__(self, name, entryCallbacks=[], exitCallbacks=[], sound=None): self.name = name self.entryCallbacks = entryCallbacks self.exitCallbacks = exitCallbacks self._transTbl = {} self._sound = sound def entry(self): logger.info('entering ' + self.name) if self._sound: self._sound.play() for c in self.entryCallbacks: c() def exit(self): logger.info('exiting ' + self.name) if self._sound: self._sound.stop() for c in self.exitCallbacks: c() def next(self, signal): if signal in SIGNALS: return self if signal not in self._transTbl else self._transTbl[signal] else: raise Exception('Illegal signal') def addTransition(self, signal, state): self._transTbl[signal] = weakref.ref(state) 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._lock = Lock() self.soundLib = SoundLib() self.LED = Blinkenlights(17) self.camera = Camera() self.fileDump = FileDump() # add signals to self to avoid calling partial every time for sig in SIGNALS: setattr(self, sig.name, partial(self.selectState, sig)) secretTable = { 'dynamoHum': self.DISARM, 'zombyWoof': self.ARM, 'imTheSlime': self.INSTANT_ARM } def secretCallback(secret, logger): if secret in secretTable: secretTable[secret]() logger.debug('Secret pipe listener received: \"%s\"', secret) elif logger: logger.debug('Secret pipe listener received invalid secret') self.secretListener = PipeListener(callback=secretCallback, name= 'secret') self.keypadListener = KeypadListener( stateMachine = self, callbackDisarm = self.DISARM, callbackArm = self.ARM, soundLib = self.soundLib, passwd = '5918462' ) def startTimer(t, sound): self._timer = CountdownTimer(t, self.TIMOUT, sound) def stopTimer(): if self._timer.is_alive(): self._timer.stop() self._timer = None blinkingLED = partial(self.LED.setBlink, True) sfx = self.soundLib.soundEffects stateObjs = [ State( name = 'disarmed', entryCallbacks = [partial(self.LED.setBlink, False)], sound = sfx['disarmed'] ), State( name = 'disarmedCountdown', entryCallbacks = [blinkingLED, partial(startTimer, 30, sfx['disarmedCountdown'])], exitCallbacks = [stopTimer], sound = sfx['disarmedCountdown'] ), State( name = 'armed', entryCallbacks = [blinkingLED], sound = sfx['armed'] ), State( name = 'armedCountdown', entryCallbacks = [blinkingLED, partial(startTimer, 30, sfx['armedCountdown'])], exitCallbacks = [stopTimer], sound = sfx['armedCountdown'] ), State( name = 'triggered', entryCallbacks = [blinkingLED, intruderAlert], sound = sfx['triggered'] ) ] for obj in stateObjs: obj.entryCallbacks.append(self.keypadListener.resetBuffer) self.states = st = namedtuple('States', [obj.name for obj in stateObjs])(*stateObjs) st.disarmed.addTransition( SIGNALS.ARM, st.disarmedCountdown) st.disarmed.addTransition( SIGNALS.INSTANT_ARM, st.armed) st.disarmedCountdown.addTransition( SIGNALS.DISARM, st.disarmed) st.disarmedCountdown.addTransition( SIGNALS.TIMOUT, st.armed) st.disarmedCountdown.addTransition( SIGNALS.INSTANT_ARM, st.armed) st.armed.addTransition( SIGNALS.DISARM, st.disarmed) st.armed.addTransition( SIGNALS.TRIGGER, st.armedCountdown) st.armedCountdown.addTransition( SIGNALS.DISARM, st.disarmed) st.armedCountdown.addTransition( SIGNALS.TIMOUT, st.triggered) st.armedCountdown.addTransition( SIGNALS.ARM, st.armed) st.armedCountdown.addTransition( SIGNALS.INSTANT_ARM, st.armed) st.triggered.addTransition( SIGNALS.DISARM, st.disarmed) st.triggered.addTransition( SIGNALS.ARM, st.armed) st.triggered.addTransition( SIGNALS.INSTANT_ARM, st.armed) self.currentState = getattr(self.states, stateFile['state']) def __enter__(self): resetUSBDevice('1-1', logger) self.soundLib.start() self.LED.start() self.keypadListener.start() self.secretListener.start() self.camera.start() self.fileDump.start() def action(): if self.currentState == self.states.armed: self.selectState(SIGNALS.TRIGGER) sensitiveStates = (self.states.armed, self.states.armedCountdown, self.states.triggered) def actionVideo(pin): if self.currentState in sensitiveStates: self.selectState(SIGNALS.TRIGGER) self.fileDump.addInitiator(pin) while GPIO.input(pin) and self.currentState in sensitiveStates: time.sleep(0.1) self.fileDump.removeInitiator(pin) startMotionSensor(5, 'Nate\'s room', action) startMotionSensor(19, 'front door', action) startMotionSensor(26, 'Laura\'s room', action) startMotionSensor(6, 'deck window', partial(actionVideo, 6)) startMotionSensor(13, 'kitchen bar', partial(actionVideo, 13)) startDoorSensor(22, action, self.soundLib.soundEffects['door']) startWebInterface(self) self.currentState.entry() def __exit__(self, exception_type, exception_value, traceback): for i in ['LED', 'camera', 'fileDump', 'soundLib', 'secretListener', 'keypadListener']: try: getattr(self, i).stop() except AttributeError: pass def selectState(self, signal): with self._lock: nextState = self.currentState.next(signal) if nextState != self.currentState: self.currentState.exit() self.currentState = nextState self.currentState.entry() stateFile['state'] = self.currentState.name