Commit 58cb71a3 authored by Ralf's avatar Ralf
Browse files

lots of changes to make things actually work

parent d70ca0e9
from libtuer import ThreadFunction, logger
import RPi.GPIO as GPIO
import time
class Actor:
CMD_BUZZ = 0
......@@ -7,13 +8,13 @@ class Actor:
CMD_LOCK = 2
CMDs = {
CMD_BUZZ: ("buzz", 12, [(True, 0.3), (False, 2.0)]),
CMD_UNLOCK: ("unlock", 16, [(None, 0.2), (True, 0.3), (False, 1.0)]),
CMD_LOCK: ("lock", 22, [(None, 0.2), (True, 0.3), (False, 1.0)]),
CMD_UNLOCK: ("unlock", 12, [(None, 0.2), (True, 0.3), (False, 0.5)]),
CMD_LOCK: ("lock", 16, [(None, 0.2), (True, 0.3), (False, 0.5)]),
CMD_BUZZ: ("buzz", 22, [(None, 0.2), (True, 2.0), (False, 0.5)]),
}
def __init__(self):
self.act = ThreadFunction(self._act)
self.act = ThreadFunction(self._act, name="Actor")
for (name, pin, todo) in self.CMDs.values():
GPIO.setup(pin, GPIO.OUT)
......@@ -23,10 +24,11 @@ class Actor:
logger.info("Actor: Running command %s" % name)
for (value, delay) in todo:
if value is not None:
logger.debug("Setting pin %d to %d" % (pin, value))
GPIO.output(pin, value)
time.sleep(delay)
else:
logger.error("Actor: Gut unknown command %d" % cmd)
def stop(self):
pass
self.act.stop()
import logging, logging.handlers, os, time, queue, threading, subprocess
import traceback, smtplib
from email.mime.text import MIMEText
# Logging configuration
syslogLevel = logging.INFO
mailLevel = logging.CRITICAL # must be "larger" than syslog level!
mailAddress = 'post+tuer'+'@'+'ralfj.de'
# Mail logging handler
def sendeMail(subject, text, receivers, sender='sphinx@hacksaar.de', replyTo=None):
if not isinstance(type(receivers), list): receivers = [receivers]
# construct content
msg = MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8')
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = ', '.join(receivers)
if replyTo is not None:
msg['Reply-To'] = replyTo
# put into envelope and send
s = smtplib.SMTP('ralfj.de')
s.sendmail(sender, receivers, msg.as_string())
s.quit()
# logging function
class Logger:
def __init__ (self):
self.logger = logging.getLogger("tuerd")
self.logger.setLevel(logging.INFO)
self.handler = logging.handlers.SysLogHandler(address = '/dev/log', facility = logging.handlers.SysLogHandler.LOG_LOCAL0)
self.logger.addHandler(self.handler)
self.syslog = logging.getLogger("tuerd")
self.syslog.setLevel(syslogLevel)
self.syslog.addHandler(logging.handlers.SysLogHandler(address = '/dev/log',
facility = logging.handlers.SysLogHandler.LOG_LOCAL0))
def log (self, lvl, what):
def _log (self, lvl, what):
thestr = "%s[%d]: %s" % ("osspd", os.getpid(), what)
print (thestr)
self.logger.log(lvl, thestr)
# console log
print(thestr)
# syslog
self.syslog.log(lvl, thestr)
# mail log
if lvl >= mailLevel:
sendeMail('Kritischer Türfehler', what, mailAddress)
def debug(self, what):
self.log(logging.DEBUG, what)
self._log(logging.DEBUG, what)
def info(self, what):
self.log(logging.INFO, what)
self._log(logging.INFO, what)
def warning(self, what):
self.log(logging.WARNING, what)
self._log(logging.WARNING, what)
def error(self, what):
self.log(logging.ERROR, what)
self._log(logging.ERROR, what)
def critical(self, what):
self.log(logging.CRITICAL, what)
self._log(logging.CRITICAL, what)
logger = Logger()
......@@ -42,7 +69,8 @@ class ThreadFunction():
_CALL = 0
_TERM = 1
def __init__(self, f):
def __init__(self, f, name):
self.name = name
self._f = f
self._q = queue.Queue()
self._t = threading.Thread(target=self._thread_func)
......@@ -52,39 +80,45 @@ class ThreadFunction():
while True:
(cmd, data) = self._q.get()
# run command
if cmd == _CALL:
if cmd == ThreadFunction._CALL:
try:
self._f(*data)
except Exception:
logger.error("ThreadFunction: Got exception out of handler thread: %s" % str(e))
elif cmd == _TERM:
except Exception as e:
logger.critical("ThreadFunction: Got exception out of handler thread %s: %s" % (self.name, str(e)))
logger.debug(traceback.format_exc())
elif cmd == ThreadFunction._TERM:
assert data is None
break
else:
logger.error("ThreadFunction: Command %d does not exist" % cmd)
def __call__(self, *arg):
self._q.put((self._CALL, arg))
self._q.put((ThreadFunction._CALL, arg))
def stop(self):
self._q.put((_TERM, None))
self._q.put((ThreadFunction._TERM, None))
self._t.join()
# Thread timer-repeater class: Call a function every <sleep_time> seconds
class ThreadRepeater():
def __init__(self, f, sleep_time):
def __init__(self, f, sleep_time, name):
self.name = name
self._f = f
self._stop = False
self._sleep_time = sleep_time
self._t = threading.Thread(target=self._thread_func)
self._t.start()
def _thread_func():
def _thread_func(self):
while True:
if self._stop:
break
self._f()
time.sleep(sleep_time)
try:
self._f()
except Exception as e:
logger.critical("ThreadRepeater: Got exception out of handler thread %s: %s" % (self.name, str(e)))
logger.debug(traceback.format_exc())
time.sleep(self._sleep_time)
def stop(self):
self._stop = True
......
......@@ -20,7 +20,7 @@ class PinWatcher():
def read(self):
curstate = GPIO.input(self.pin)
assert curstate in (0, 1)
if curstate != self._state:
if curstate != self.state:
# the state is about to change
if curstate == self._newstate:
# we already saw this new state
......@@ -41,31 +41,32 @@ class PinWatcher():
class PinsWatcher():
def __init__(self, state_machine):
self.pins = {
self._pins = {
'bell_ringing': PinWatcher(18, 2),
'door_closed': PinWatcher(8, 5),
'door_locked': PinWatcher(10, 5),
'space_active': PinWatcher(24, 5),
'door_closed': PinWatcher(8, 4),
'door_locked': PinWatcher(10, 4),
'space_active': PinWatcher(24, 4),
}
self._sm = state_machine
# start a thread doing the work
self._t = ThreadRepeater(self._read, 0.02)
def _read():
self._t = ThreadRepeater(self._read, 0.02, name="PinsWatcher")
def _read(self):
saw_change = False
for name in self.pins.keys():
pin = pins[name]
for name in self._pins.keys():
pin = self._pins[name]
if pin.read():
saw_change = True
logger.debug("Pin %s changed to %d" % (name, pin.state)
if not saw_change: return
logger.debug("Pin %s changed to %d" % (name, pin.state))
if not saw_change:
return None
# create return object
pinsState = PinsState()
for name in self.pins.keys():
setattr(pinsState, name, self.pins[name].state)
for name in self._pins.keys():
setattr(pinsState, name, self._pins[name].state)
# send it to state machine
self._sm.callback(StateMachine.CMD_PINS, pinsState)
def stop():
def stop(self):
self._t.stop()
8from libtuer import ThreadFunction, logger, fire_and_forget
from libtuer import ThreadFunction, logger, fire_and_forget
from actor import Actor
import os, random
import os, random, time
# logger.{debug,info,warning,error,critical}
......@@ -15,17 +15,17 @@ def play_sound (what):
# StateUnlocking constants
OPEN_REPEAT_TIMEOUT = 8
OPEN_REPEAT_TIMEOUT = 7
OPEN_REPEAT_NUMBER = 3
# StateLocking constants
CLOSE_REPEAT_TIMEOUT = 8
CLOSE_REPEAT_TIMEOUT = 7
CLOSE_REPEAT_NUMBER = 3
# StateAboutToOpen constants
ABOUTOPEN_NERVLIST = [(5, lambda : play_sound("flipswitch")), (5, lambda:play_sound("flipswitch")), (0, lambda:Logger.warning("Space open but switch not flipped for 10 seconds")),\
(10, lambda:play_sound("flipswitch")), (10, lambda:play_sound("flipswitch")), (0, lambda:Logger.error("Space open but switch not flipped for 30 seconds")),\
(10, lambda:play_sound("flipswitch")), (10, lambda:play_sound("flipswitch")), (6, lambda:play_sound("flipswitch")), (4, lambda:Logger.critical("Space open but switch not flipped for 60 seconds"))]
ABOUTOPEN_NERVLIST = [(5, lambda : play_sound("flipswitch")), (5, lambda:play_sound("flipswitch")), (0, lambda:logger.warning("Space open but switch not flipped for 10 seconds")),\
(10, lambda:play_sound("flipswitch")), (10, lambda:play_sound("flipswitch")), (0, lambda:logger.error("Space open but switch not flipped for 30 seconds")),\
(10, lambda:play_sound("flipswitch")), (10, lambda:play_sound("flipswitch")), (6, lambda:play_sound("flipswitch")), (4, lambda:logger.critical("Space open but switch not flipped for 60 seconds"))]
# StateAuf constants
# time that must pass between two bell_ringing events to buzz the door again (seconds)
......@@ -33,7 +33,7 @@ AUF_BELLBUZZ_REPEAT_TIME = 2
# Timeout we wait after the switch was switched to "Closed", until we assume nobody will open the door and we just lock it
# ALso the time we wait after the door was opend, till we assume something went wrong and start nerving
LEAVE_TIMEOUT = 30
LEAVE_TIMEOUT = 20
# play_sound constants
SOUNDS_DIRECTORY = "/opt/tuer/sounds/"
......@@ -49,11 +49,11 @@ class Nerver():
def nerv(self):
if len(self.nervlist):
(time, f) = self.nervlist[0]
(wait_time, f) = self.nervlist[0]
now = time.time()
time_gone = now-self.last_event_time
# check if the first element is to be triggered
if time_gone >= time:
if time_gone >= wait_time:
self.nervlist = self.nervlist[1:] # "pop" the first element, but do not modify original list
self.last_event_time = now
return f()
......@@ -74,7 +74,7 @@ class StateMachine():
def handle_pins_event(self):
pass # one needn't implement this
def handle_buzz_event(self,arg): # this shouldn't be overwritten
self.actor.act(Actor.CMD_BUZZ)
self.actor().act(Actor.CMD_BUZZ)
arg("200 okay: buzz executed")
def handle_cmd_unlock_event(self,arg):
if arg is not None:
......@@ -87,43 +87,42 @@ class StateMachine():
def actor(self):
return self.state_machine.actor
def handle_event(self,ev,arg): # don't override
if arg is CMD_PINS:
self.handle_pins_event()
elif arg is CMD_BUZZ:
self.handle_buzz_event(arg)
elif arg is CMD_UNLOCK:
self.handle_cmd_unlock_event(arg)
elif arg is CMD_WAKEUP:
self.handle_wakeup_event()
if ev == StateMachine.CMD_PINS:
return self.handle_pins_event()
elif ev == StateMachine.CMD_BUZZ:
return self.handle_buzz_event(arg)
elif ev == StateMachine.CMD_UNLOCK:
return self.handle_cmd_unlock_event(arg)
elif ev == StateMachine.CMD_WAKEUP:
return self.handle_wakeup_event()
else:
raise Exception("Unknown command number: %d" % ev)
class StateStart(State):
def handle_pins_event(self):
super().handle_pins_event()
thepins = self.pins()
for pin in thepins:
if pin is None:
return None
if thepins.door_locked:
return StateZu(self.state_machine)
pins = self.pins()
if pins.door_locked is None or pins.door_closed is None or pins.space_dactive is None or pins.bell_ringing is None:
return None # wait till we have all sensors non-None
if pins.door_locked:
return StateMachine.StateZu(self.state_machine)
else:
return StateAuf(self.state_machine)
return StateMachine.StateAuf(self.state_machine)
class StateZu(State):
def handle_pins_event(self):
super().handle_pins_event()
pins = self.pins()
if not pins.door_locked:
return StateAboutToOpen(self.state_machine)
return StateMachine.StateAboutToOpen(self.state_machine)
def handle_cmd_unlock_event(self,callback):
# intentionally not calling super() implementation
return StateUnlocking(self.state_machine, callback)
return StateMachine.StateUnlocking(self.state_machine, callback)
class StateUnlocking(State):
def __init__(self,sm,callback=None):
# construct a nervlist
nervlist = [(OPEN_REPEAT_TIMEOUT, lambda: self.actor().act(Actor.CMD_UNLOCK)) for t in xrange(OPEN_REPEAT_NUMBER)]
nervlist = [(OPEN_REPEAT_TIMEOUT, lambda: self.actor().act(Actor.CMD_UNLOCK)) for t in range(OPEN_REPEAT_NUMBER)]
nervlist += [(OPEN_REPEAT_TIMEOUT, self.could_not_open)]
super().__init__(sm,nervlist)
self.callbacks=[callback]
......@@ -139,7 +138,7 @@ class StateMachine():
pins = self.pins()
if not pins.door_locked:
self.notify(True)
return StateAboutToOpen(self.state_machine)
return StateMachine.StateAboutToOpen(self.state_machine)
def handle_cmd_unlock_event(self,callback):
# intentionally not calling super() implementation
# TODO: 202 notification also here if possible
......@@ -147,7 +146,7 @@ class StateMachine():
def could_not_open(self):
logger.critical("Couldn't open door after %d tries. Going back to StateZu." % OPEN_REPEAT_NUMBER)
self.notify(False)
return StateZu(self.state_machine)
return StateMachine.StateZu(self.state_machine)
class AbstractStateWhereOpeningIsRedundant(State):
def handle_cmd_unlock_event(self, callback):
......@@ -163,9 +162,9 @@ class StateMachine():
super().handle_pins_event()
pins = self.pins()
if pins.door_locked:
return StateZu(self.state_machine)
return StateMachine.StateZu(self.state_machine)
elif pins.space_active:
return StateAuf(self.state_machine)
return StateMachine.StateAuf(self.state_machine)
class StateAuf(AbstractStateWhereOpeningIsRedundant):
def __init__(self,sm):
......@@ -183,11 +182,11 @@ class StateMachine():
self.last_buzzed = now
if not pins.space_active:
logger.info("space switch off - starting leaving procedure")
return StateAboutToLeave(self.state_machine)
return StateMachine.StateAboutToLeave(self.state_machine)
if pins.door_locked:
logger.info("door manually locked, but space switch on - going to StateZu")
play_sound("manual_lock")
return StateZu(self.state_machine)
return StateMachine.StateZu(self.state_machine)
class StateLocking(State):
# TODO: share code with StateUnlocking
......@@ -196,46 +195,50 @@ class StateMachine():
nervlist = [(CLOSE_REPEAT_TIMEOUT, lambda: self.actor().act(Actor.CMD_LOCK)) for t in range(CLOSE_REPEAT_NUMBER)]
nervlist += [(CLOSE_REPEAT_TIMEOUT, self.could_not_close)]
super().__init__(sm, nervlist)
if not pins.door_closed: # this should always be true, but just to be sure...
if self.pins().door_closed: # this should always be true, but just to be sure...
self.actor().act(Actor.CMD_LOCK)
def handle_pins_event(self):
pins = self.pins()
if not pins.door_closed:
# TODO play a sound? This shouldn't happen, door was opened while we are locking
logger.warning("door manually opened during locking")
return StateAboutToOpen(self.state_machine)
return StateMachine.StateAboutToOpen(self.state_machine)
if pins.door_locked:
return SpaceZu(self.state_machine)
return StateMachine.StateZu(self.state_machine)
def handle_cmd_unlock_event(self,callback):
callback("409 conflict: The server is currently trying to lock the door. Try again later.")
def could_not_close(self):
logger.critical("Couldn't close door after %d tries. Going back to StateAboutToOpen." % CLOSE_REPEAT_NUMBER)
return StateAboutToOpen(self.state_machine)
return StateMachine.StateAboutToOpen(self.state_machine)
class StateAboutToLeave(State):
def __init__(self, sm):
nervlist = [(LEAVE_TIMEOUT, lambda: StateLocking(self.state_machine))]
nervlist = [(LEAVE_TIMEOUT, lambda: StateMachine.StateLocking(self.state_machine))]
super().__init__(sm, nervlist)
def handle_pins_event(self):
if not self.pins().door_closed:
return StateLeaving(self.state_machine)
return StateMachine.StateLeaving(self.state_machine)
if self.pins().door_locked:
return StateZu(self.state_machine)
return StateMachine.StateZu(self.state_machine)
if self.pins().space_active:
return StateMachine.StateAuf(self.state_machine)
class StateLeaving(State):
def __init__(self, sm):
nervlist = [(LEAVE_TIMEOUT, lambda: StateAboutToOpen(self.state_machine))]
nervlist = [(LEAVE_TIMEOUT, lambda: StateMachine.StateAboutToOpen(self.state_machine))]
super().__init__(sm, nervlist)
def handle_pins_event(self):
if self.pins().door_closed:
return StateLocking(self.state_machine)
return StateMachine.StateLocking(self.state_machine)
if self.pins().door_locked:
return StateZu(self.state_machine)
return StateMachine.StateZu(self.state_machine)
if self.pins().space_active:
return StateMachine.StateAuf(self.state_machine)
def __init__(self, actor):
self.actor = actor
self.callback = ThreadFunction(self._callback)
self.current_state = StateStart(self)
self.callback = ThreadFunction(self._callback, name="StateMachine")
self.current_state = StateMachine.StateStart(self)
self.pins = None
self.old_pins = None
......@@ -250,6 +253,6 @@ class StateMachine():
newstate = self.current_state.handle_event(cmd,arg) # returns None or an instance of the new state
self.old_pins = self.pins
while newstate is not None:
logger.info("StateMachine: new state = %s" % newstate.__class__.__name__)
logger.debug("StateMachine: new state = %s" % newstate.__class__.__name__)
self.current_state = newstate
newstate = self.current_state.handle_event(StateMachine.CMD_PINS, self.pins)
#!/usr/bin/python3
import RPi.GPIO as GPIO
import statemachine, actor, pins, tysock, waker
from libtuer import logger
# initialize GPIO stuff
GPIO.setmode(GPIO.BOARD)
......@@ -17,12 +18,12 @@ try:
the_socket.accept()
except KeyboardInterrupt:
# this is what we waited for!
logger.info("Got SIGINT, terminating...")
pass
# bring 'em all down
the_waker.stop()
the_pins.stop()
the_socket.stop()
the_machine.stop()
the_actor.stop()
......
......@@ -33,9 +33,9 @@ def sendcmd(addr, cmd):
print("Running %s..." % (cmd))
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(addr)
s.settimeout(10.0)
s.settimeout(60.0)
s.send(cmd.encode())
data = s.recv(4)
data = s.recv(256)
s.close()
print(data.decode('utf-8'))
return run
......
import socket, os, stat
import socket, os, stat, struct, pwd
from statemachine import StateMachine
from libtuer import logger
SO_PEERCRED = 17 # DO - NOT - TOUCH
......@@ -75,6 +75,3 @@ class TySocket():
raise # forward Ctrl-C to the outside
except Exception as e:
logger.error("TySocket: Something went wrong: %s" % str(e))
def stop(self):
pass
......@@ -4,7 +4,7 @@ from statemachine import StateMachine
class Waker():
def __init__(self, sm):
self._sm = sm
self._t = ThreadRepeater(self._wake, 0.5)
self._t = ThreadRepeater(self._wake, 0.5, name="Waker")
def _wake(self):
self._sm.callback(StateMachine.CMD_WAKEUP)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment