consolidate pertinent functions in statemachine module
This commit is contained in:
parent
532b0d8ecd
commit
083d3a8295
42
auxilary.py
42
auxilary.py
|
@ -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)
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue