c93a1a367d44a79104c801dcfdea6ac9722dc8b3
[saartuer.git] / statemachine.py
1 from libtuer import ThreadFunction, logger, fire_and_forget
2 from actor import Actor
3 import os, random
4
5 # logger.{debug,info,warning,error,critical}
6
7 def play_sound (what):
8         try:
9                 soundfiles = os.listdir(SOUNDS_DIRECTORY+what)
10         except FileNotFoundError:
11                 logger.error("StateMachine: Unable to list sound files in %s" % (SOUNDS_DIRECTORY+what))
12                 return
13         soundfile = SOUNDS_DIRECTORY + what + '/' + random.choice(soundfiles)
14         fire_and_forget ([SOUNDS_PLAYER,soundfile], logger.error, "StateMachine: ")
15
16
17 # StateOpening constants
18 OPEN_REPEAT_TIMEOUT = 8
19 OPEN_REPEAT_NUMBER = 3
20
21 # StateAboutToOpen constants
22 ABOUTOPEN_NERVLIST = [(5, lambda : play_sound("flipswitch")), (10, lambda:play_sound("flipswitch")), (10, lambda:Logger.warning("Space open but switch not flipped for 10 seconds")),\
23         (20, lambda:play_sound("flipswitch")), (30, lambda:play_sound("flipswitch")), (30, lambda:Logger.error("Space open but switch not flipped for 30 seconds")),\
24         (40, lambda:play_sound("flipswitch")), (50, lambda:play_sound("flipswitch")), (56, lambda:play_sound("flipswitch")), (60, lambda:Logger.critical("Space open but switch not flipped for 60 seconds"))]
25
26 # play_sound constants
27 SOUNDS_DIRECTORY = "/opt/tuer/sounds/"
28 SOUNDS_PLAYER = "/usr/bin/mplayer"
29
30
31 class StateMachine():
32         # commands you can send
33         CMD_PINS = 0
34         CMD_BUZZ = 1
35         CMD_OPEN = 2
36         CMD_WAKEUP = 3
37         CMD_LAST = 4
38         
39         class State():
40                 def __init__(self, state_machine):
41                         self.state_machine = state_machine
42                         self.time_entered = time.time()
43                         self.theDict = None
44                 def handle_pins_event(self):
45                         pass # one needn't implement this
46                 def handle_buzz_event(self,arg): # this shouldn't be overwritten
47                         self.actor.act(Actor.CMD_BUZZ)
48                         arg("200 okay: buzz executed")
49                 def handle_open_event(self,arg):
50                         if arg is not None:
51                                 arg("412 Precondition Failed: The current state (%s) cannot handle the OPEN event" % self.__class__.__name__)
52                 def handle_wakeup_event(self):
53                         pass # one needn't implement this
54                 def pins(self):
55                         return self.state_machine.pins
56                 def actor(self):
57                         return self.state_machine.actor
58                 def handle_event(self,ev,arg):
59                         if arg is CMD_PINS:
60                                 self.handle_pins_event()
61                         elif arg is CMD_BUZZ:
62                                 self.handle_buzz_event(arg)
63                         elif arg is CMD_OPEN:
64                                 self.handle_open_event(arg)
65                         elif arg is CMD_WAKEUP:
66                                 self.handle_wakeup_event()
67                         else:
68                                 raise Exception("Unknown command number: %d" % ev)
69         
70         class StateStart(State):
71                 def __init__(self, sm):
72                         State.__init__(self,sm)
73                 def handle_pins_event(self):
74                         thepins = self.pins()
75                         for pin in thepins:
76                                 if pin is None:
77                                         return None
78                         if thepins.door_locked:
79                                 return StateZu
80                         else:
81                                 return StateAuf
82
83         class StateZu(State):
84                 def __init__(self,sm):
85                         State.__init__(self,sm)
86                 def handle_pins_event(self):
87                         pins = self.pins()
88                         if not pins.door_locked:
89                                 return StateAboutToOpen(self.state_machine)
90                 def handle_open_event(self,callback):
91                         return StateOpening(callback,self.state_machine)
92         
93         class StateOpening(State):
94                 def __init__(self,callback,sm):
95                         State.__init__(self,sm)
96                         self.callbacks=[callback]
97                         self.tries = 0
98                         self.actor().act(Actor.CMD_OPEN)
99                 def notify(self, did_it_work):
100                         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))
101                         for cb in self.callbacks:
102                                 if cb is not None:
103                                         cb(s)
104                 def handle_pins_event(self):
105                         pins = self.pins()
106                         if not pins.door_locked:
107                                 self.notify(True)
108                                 return StateAboutToOpen(self.state_machine)
109                 def handle_open_event(self,callback):
110                         self.callbacks.append(callback)
111                 def handle_wakeup_event(self):
112                         over = time.time() - self.time_entered
113                         nexttry = (self.tries+1) * OPEN_REPEAT_TIMEOUT
114                         if over > nexttry:
115                                 if self.tries < OPEN_REPEAT_NUMBER:
116                                         self.actor().act(Actor.CMD_OPEN)
117                                         self.tries += 1
118                                 else:
119                                         #TODO: LOG ERROR und EMAIL an Admins
120                                         self.notify(False)
121                                         return StateZu(self.state_machine)
122         
123         class StateAboutToOpen(State):
124                 def __init__(self, sm):
125                         State.__init__(sm)
126                 def handle_pins_event(self):
127                         pins = self.pins()
128                         if pins.door_locked:
129                                 return StateZu(self.state_machine)
130                         elif pins.space_active:
131                                 return StateAuf(self.state_machine)
132                         else:
133                                 over = time.time() - self.time_entered
134                                 # TODO: Nerv
135                                 logger.debug("AboutToOpen since %f seconds. TODO: nerv the user" % over)
136                 # TODO
137         
138         class StateAuf(State):
139                 #TODO
140                 pass
141         
142         class StateClosing(State):
143                 #TODO
144                 pass
145         
146         class StateAboutToLeave(State):
147                 #TODO
148                 pass
149         
150         class StateLeaving(State):
151                 #TODO
152                 pass
153         
154         def __init__(self, actor):
155                 self.actor = actor
156                 self.callback = ThreadFunction(self._callback)
157                 self.current_state = None
158                 self.pins = None
159         
160         def stop (self):
161                 self.callback.stop()
162         
163         def _callback(self, cmd, arg=None):
164                 # update pins
165                 if cmd == StateMachine.CMD_PINS:
166                         self.pins = arg
167                 # handle stuff
168                 newstate = self.current_state.handle_event(cmd,arg) # returns None or an instance of the new state
169                 while newstate is not None:
170                         logger.info("StateMachine: new state = %s" % newstate.__class__.__name__)
171                         self.current_state = newstate
172                         newstate = self.current_state.handle_event(StateMachine.CMD_PINS, self.pins)