Wait a bit more before starting to complain
[saartuer.git] / statemachine.py
index 12e8bd5b79280e9601e3050839f3ab7bfddaa436..0fa485abcd7d25a146d578357a3803aa8f1905b6 100644 (file)
@@ -1,4 +1,4 @@
-from libtuer import ThreadFunction, logger, fire_and_forget
+from libtuer import ThreadFunction, logger, fire_and_forget, fire_and_forget_cmd
 from actor import Actor
 import os, random, time, threading
 
@@ -11,8 +11,20 @@ def play_sound (what):
                logger.error("StateMachine: Unable to list sound files in %s" % (SOUNDS_DIRECTORY+what))
                return
        soundfile = SOUNDS_DIRECTORY + what + '/' + random.choice(soundfiles)
-       fire_and_forget_cmd ([SOUNDS_PLAYER,soundfile], logger.error, "StateMachine: ")
+       fire_and_forget_cmd ([SOUNDS_PLAYER,soundfile], "StateMachine: ")
 
+# convert an absolute nervlist to a relative one
+def nervlist_abs2rel(nervlist_abs):
+       nervlist_rel = []
+       last_t = 0
+       for (t, f) in nervlist_abs:
+               assert t >= last_t
+               nervlist_rel.append((t-last_t, f))
+               last_t = t
+       return nervlist_rel
+
+# StateAuf constants
+HELLO_PROBABILITY = 0.2
 
 # StateUnlocking constants
 OPEN_REPEAT_TIMEOUT = 7
@@ -26,10 +38,13 @@ CLOSE_REPEAT_NUMBER = 3
 FALLBACK_LEAVE_DELAY_LOCK = 5 # seconds
 
 # 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")),
-       (59*60, lambda:logger.critical("Space open but switch not flipped for one hour"))]
+SWITCH_PRAISE_PROBABILITY = 0.5
+ABOUTOPEN_NERVLIST = nervlist_abs2rel([(10, lambda:play_sound("flipswitch")),\
+       (20, lambda:play_sound("flipswitch")), (30, lambda:play_sound("flipswitch")), (30, lambda:logger.error("Space open but switch not flipped for 30 seconds")),\
+       (40, lambda:play_sound("flipswitch")), (50, lambda:play_sound("flipswitch")), (60, lambda:play_sound("mail_sent")),
+       (60, lambda:logger.critical("Space open but switch not flipped for 60 seconds")), (120, lambda:play_sound("mail_sent")),
+       (10*60, lambda:logger.critical("Space open but switch not flipped for 10 minutes")),
+       (60*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
@@ -90,6 +105,8 @@ class StateMachine():
                        return self.state_machine.old_pins
                def actor(self):
                        return self.state_machine.actor
+               def api(self):
+                       return self.state_machine.api
                def handle_event(self,ev,arg): # don't override
                        if ev == StateMachine.CMD_PINS:
                                return self.handle_pins_event()
@@ -203,19 +220,20 @@ class StateMachine():
                        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 unlock the door" here? Are the callbacks multi-use?
+                       self.callbacks = []
                        self.actor().act(Actor.CMD_UNLOCK)
-               def notify(self, did_it_work):
-                       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))
+                       # enqueue the callback
+                       self.handle_cmd_unlock_event(callback)
+               def notify(self, s, lastMsg):
                        for cb in self.callbacks:
-                               if cb is not None:
-                                       cb(s)
+                               cb(s, lastMsg)
                def on_leave(self):
-                       self.notify(not self.pins().door_locked)
+                       s = "200 okay: door unlocked" if not self.pins().door_locked else ("500 internal server error: Couldn't unlock door with %d tries à %f seconds" % (OPEN_REPEAT_NUMBER,OPEN_REPEAT_TIMEOUT))
+                       self.notify(s, lastMsg=True)
                def handle_cmd_unlock_event(self,callback):
-                       # TODO: 202 notification also here if possible
-                       self.callbacks.append(callback)
+                       if callback is not None:
+                               callback("202 processing: Trying to unlock the door", lastMsg=False)
+                               self.callbacks.append(callback)
                def could_not_open(self):
                        logger.critical("StateMachine: Couldn't open door after %d tries. Going back to StateZu." % OPEN_REPEAT_NUMBER)
                        return StateMachine.StateZu(self.state_machine)
@@ -233,6 +251,8 @@ class StateMachine():
                        pins = self.pins()
                        if pins.space_active:
                                logger.info("Space activated, opening procedure completed")
+                               if not self.old_pins().space_active and random.random() <= SWITCH_PRAISE_PROBABILITY:
+                                       play_sound("success")
                                return StateMachine.StateAuf(self.state_machine)
                        return super().handle_pins_event()
        
@@ -241,6 +261,7 @@ class StateMachine():
                        nervlist = [(24*60*60, lambda: logger.critical("Space is now open for 24h. Is everything all right?"))]
                        super().__init__(sm, nervlist)
                        self.last_buzzed = None
+                       self.api().set_state(True)
                def handle_pins_event(self):
                        pins = self.pins()
                        if pins.bell_ringing and not self.old_pins().bell_ringing: # first thing to check: edge detection
@@ -250,7 +271,11 @@ class StateMachine():
                        if not pins.space_active:
                                logger.info("StateMachine: space switch turned off - starting leaving procedure")
                                return StateMachine.StateAboutToLeave(self.state_machine)
+                       if not pins.door_closed and self.old_pins().door_closed and random.random() <= HELLO_PROBABILITY:
+                               play_sound("hello")
                        return super().handle_pins_event()
+               def on_leave(self):
+                       self.api().set_state(False)
        
        class StateLocking(AbstractUnlockedState):
                def __init__(self,sm):
@@ -299,13 +324,16 @@ class StateMachine():
                                return StateMachine.StateAuf(self.state_machine)
                        return super().handle_pins_event()
        
-       def __init__(self, actor, waker, fallback = False):
+       def __init__(self, actor, waker, api, fallback = False):
                self.actor = actor
+               self.api = api
                self.callback = ThreadFunction(self._callback, name="StateMachine")
                self.current_state = StateMachine.StateStart(self, fallback=fallback)
                self.pins = None
                self.old_pins = None
                waker.register(lambda: self.callback(StateMachine.CMD_WAKEUP), 1.0) # wake up every second
+               # initially, the space is closed
+               api.set_state(False)
        
        def stop (self):
                self.callback.stop()