From 05d80f06d0d28433e7b730e6290b79c7d0e32c6b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 15 Oct 2013 10:36:18 +0200 Subject: [PATCH] introduce abstract states for each side of the Rubikon --- actor.py | 6 ++-- libtuer.py | 2 +- statemachine.py | 95 +++++++++++++++++++++++++------------------------ tysock.py | 2 +- 4 files changed, 53 insertions(+), 52 deletions(-) diff --git a/actor.py b/actor.py index 8dd7323..0a53263 100644 --- a/actor.py +++ b/actor.py @@ -8,9 +8,9 @@ class Actor: CMD_LOCK = 2 CMDs = { - 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)]), + CMD_UNLOCK: ("unlock", 12, [(True, 0.3), (False, 0.1)]), + CMD_LOCK: ("lock", 16, [(True, 0.3), (False, 0.1)]), + CMD_BUZZ: ("buzz", 22, [(True, 2.0), (False, 0.1)]), } def __init__(self): diff --git a/libtuer.py b/libtuer.py index afef36e..05607f6 100644 --- a/libtuer.py +++ b/libtuer.py @@ -62,7 +62,7 @@ def fire_and_forget (cmd, log, prefix): with open("/dev/null", "w") as fnull: retcode = subprocess.call(cmd, stdout=fnull, stderr=fnull) if retcode is not 0: - log("%sReturn code %d at command: %s" % (prefix,retcode,str(cmd))) + logger.error("%sReturn code %d at command: %s" % (prefix,retcode,str(cmd))) t = threading.Thread(target=_fire_and_forget) t.start() diff --git a/statemachine.py b/statemachine.py index 9e50327..f2fb817 100644 --- a/statemachine.py +++ b/statemachine.py @@ -25,7 +25,8 @@ 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"))] + (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")), + (59*60, lambda:logger.critical("Space open but switch not flipped for one hour"))] # 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 @@ -74,7 +75,7 @@ class StateMachine(): arg("200 okay: buzz executed") def handle_cmd_unlock_event(self,arg): if arg is not None: - arg("412 Precondition Failed: The current state (%s) cannot handle the OPEN event" % self.__class__.__name__) + arg("412 Precondition Failed: The current state (%s) cannot handle the UNLOCK command. Try again later." % self.__class__.__name__) def handle_wakeup_event(self): if self._nerver is not None: return self._nerver.nerv() @@ -96,49 +97,59 @@ class StateMachine(): else: raise Exception("Unknown command number: %d" % ev) - class StateStart(State): + class AbstractLockedState(State): + '''A state with invariant "The space is locked", switching to StateAboutToOpen when the space becomes unlocked''' def handle_pins_event(self): - super().handle_pins_event() - pins = self.pins() - if pins.door_locked is None or pins.door_closed is None or pins.space_active 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 StateMachine.StateAuf(self.state_machine) + if not self.pins().door_locked: + return StateAboutToOpen(self.state_machine) + return super().handle_pins_event - class StateZu(State): + class AbstractUnlockedState(State): + '''A state with invariant "The space is unlocked", switching to StateZu when the space becomes locked''' + def handle_pins_event(self): + if self.pins().door_locked: + if self.pins().space_active: + # FIXME the same state can be reached by first locking the door, and then activating the space. What to do then? + logger.info("StateMachine: door manually locked, but space switch is still on - going to StateZu") + play_sound("manual_lock") + return StateZu(self.state_machine) + return super().handle_pins_event + + class StateStart(State): def handle_pins_event(self): - super().handle_pins_event() pins = self.pins() - if not pins.door_locked: - return StateMachine.StateAboutToOpen(self.state_machine) + if not (pins.door_locked is None or pins.door_closed is None or pins.space_active is None or pins.bell_ringing is None): + # All sensors got a value, switch to a proper state + if pins.door_locked: + return StateMachine.StateZu(self.state_machine) + else: + return StateMachine.StateAboutToOpen(self.state_machine) + return super().handle_pins_event() + + class StateZu(AbstractLockedState): def handle_cmd_unlock_event(self,callback): - # intentionally not calling super() implementation return StateMachine.StateUnlocking(self.state_machine, callback) - class StateUnlocking(State): + class StateUnlocking(AbstractLockedState): def __init__(self,sm,callback=None): # construct a nervlist 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] - # TODO: can we send "202 processing: Trying to open the door" here? Are the callbacks multi-use? + # TODO: can we send "202 processing: Trying to unlock the door" here? Are the callbacks multi-use? self.actor().act(Actor.CMD_UNLOCK) def notify(self, did_it_work): - s = "200 okay: door open" if did_it_work else ("500 internal server error: Couldn't open door with %d tries à %f seconds" % (OPEN_REPEAT_NUMBER,OPEN_REPEAT_TIMEOUT)) + s = "200 okay: door unlocked" if did_it_work else ("500 internal server error: Couldn't unlock door with %d tries à %f seconds" % (OPEN_REPEAT_NUMBER,OPEN_REPEAT_TIMEOUT)) for cb in self.callbacks: if cb is not None: cb(s) def handle_pins_event(self): - super().handle_pins_event() - pins = self.pins() - if not pins.door_locked: + # overriding superclass as we need to do notification (TODO can this be done better? on_leave?) + if not self.pins().door_locked: self.notify(True) 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 self.callbacks.append(callback) def could_not_open(self): @@ -146,45 +157,37 @@ class StateMachine(): self.notify(False) return StateMachine.StateZu(self.state_machine) - class AbstractStateWhereOpeningIsRedundant(State): + class AbstractStateWhereOpeningIsRedundant(AbstractUnlockedState): def handle_cmd_unlock_event(self, callback): - # intentionally not calling super() implementation callback("299 redundant: Space seems to be already open. Still processing your request tough.") - logger.info("StateMachine: Received UNLOCK command in StateAboutToOpen. This should not be necessary.") + logger.info("StateMachine: Received UNLOCK command in %s. This should not be necessary." % self.__class__.__name__) self.actor().act(Actor.CMD_UNLOCK) class StateAboutToOpen(AbstractStateWhereOpeningIsRedundant): def __init__(self, sm): super().__init__(sm, ABOUTOPEN_NERVLIST) def handle_pins_event(self): - super().handle_pins_event() pins = self.pins() - if pins.door_locked: - return StateMachine.StateZu(self.state_machine) - elif pins.space_active: + if pins.space_active: return StateMachine.StateAuf(self.state_machine) + return super().handle_pins_event() class StateAuf(AbstractStateWhereOpeningIsRedundant): def __init__(self,sm): super().__init__(sm) self.last_buzzed = None def handle_pins_event(self): - super().handle_pins_event() pins = self.pins() if pins.bell_ringing and not self.old_pins().bell_ringing: # someone just pressed the bell - logger.info("StateMachine: buzzing because of bell ringing in state auf") + logger.info("StateMachine: buzzing because of bell ringing in StateAuf") self.actor().act(Actor.CMD_BUZZ) if not pins.space_active: - logger.info("StateMachine: space switch off - starting leaving procedure") + logger.info("StateMachine: space switch turned off - starting leaving procedure") return StateMachine.StateAboutToLeave(self.state_machine) - if pins.door_locked: - logger.info("StateMachine: door manually locked, but space switch on - going to StateZu") - play_sound("manual_lock") - return StateMachine.StateZu(self.state_machine) + return super().handle_pins_event() - class StateLocking(State): - # TODO: share code with StateUnlocking + class StateLocking(AbstractUnlockedState): def __init__(self,sm): # construct a nervlist nervlist = [(CLOSE_REPEAT_TIMEOUT, lambda: self.actor().act(Actor.CMD_LOCK)) for t in range(CLOSE_REPEAT_NUMBER)] @@ -198,37 +201,35 @@ class StateMachine(): # TODO play a sound? This shouldn't happen, door was opened while we are locking logger.warning("StateMachine: door manually opened during locking") return StateMachine.StateAboutToOpen(self.state_machine) - if pins.door_locked: - return StateMachine.StateZu(self.state_machine) + # TODO do anything here if the switch is activated now? + return super().handle_pins_event() def handle_cmd_unlock_event(self,callback): callback("409 conflict: The sphinx is currently trying to lock the door. Try again later.") def could_not_close(self): logger.critical("StateMachine: Couldn't close door after %d tries. Going back to StateAboutToOpen." % CLOSE_REPEAT_NUMBER) return StateMachine.StateAboutToOpen(self.state_machine) - class StateAboutToLeave(State): + class StateAboutToLeave(AbstractUnlockedState): def __init__(self, sm): 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 StateMachine.StateLeaving(self.state_machine) - if self.pins().door_locked: - return StateMachine.StateZu(self.state_machine) if self.pins().space_active: return StateMachine.StateAuf(self.state_machine) + return super().handle_pins_event() - class StateLeaving(State): + class StateLeaving(AbstractUnlockedState): def __init__(self, sm): nervlist = [(LEAVE_TIMEOUT, lambda: StateMachine.StateAboutToOpen(self.state_machine))] super().__init__(sm, nervlist) def handle_pins_event(self): if self.pins().door_closed: return StateMachine.StateLocking(self.state_machine) - if self.pins().door_locked: - return StateMachine.StateZu(self.state_machine) if self.pins().space_active: return StateMachine.StateAuf(self.state_machine) + return super().handle_pins_event() def __init__(self, actor): self.actor = actor diff --git a/tysock.py b/tysock.py index cd11214..82d26a8 100644 --- a/tysock.py +++ b/tysock.py @@ -74,4 +74,4 @@ class TySocket(): except KeyboardInterrupt: raise # forward Ctrl-C to the outside except Exception as e: - logger.error("TySocket: Something went wrong: %s" % str(e)) + logger.critical("TySocket: Something went wrong: %s" % str(e)) -- 2.30.2