From 2300f481098a5d326b4a035fe18611a31b872666 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 31 Oct 2013 09:40:25 +0100 Subject: [PATCH 01/16] Use TLS for SpaceAPI updates --- spaceapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spaceapi.py b/spaceapi.py index 89e3678..dd722b9 100644 --- a/spaceapi.py +++ b/spaceapi.py @@ -20,7 +20,7 @@ class SpaceApi: state_val = 1 if state else 0 try: logger.info("Setting SpaceAPI to %d" % state_val) - url = "http://spaceapi.hacksaar.de/status.php?action=update&key=%s&status=%d" % (spaceApiKey, state_val) + url = "https://spaceapi.hacksaar.de/status.php?action=update&key=%s&status=%d" % (spaceApiKey, state_val) response = urllib.request.urlopen(url, timeout=5.0) responseText = response.read().decode('utf-8').strip() if response.getcode() == 200 and responseText == "UpdateSuccessful": return True -- 2.30.2 From b8b6e14e6923809efc61c71dd94b1e66026aad9b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 1 Nov 2013 12:22:30 +0100 Subject: [PATCH 02/16] update the SpaceAPI at least once every 10 minutes (so the server could check for this heartbeat) --- spaceapi.py | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/spaceapi.py b/spaceapi.py index dd722b9..eea73f6 100644 --- a/spaceapi.py +++ b/spaceapi.py @@ -1,17 +1,20 @@ from threading import Lock from libtuer import ThreadFunction, logger -import urllib.request +import urllib.request, time from config import spaceApiKey +RETRY_TIME = 30 +HEARTBEAT_TIME = 10*60 + class SpaceApi: def __init__ (self, waker): - self._state_to_set = None - self._state_last_set = None + self._local_state = None + self._remote_state = None + self._last_set_at = 0 self._running = True self.set_state = ThreadFunction(self._set_state, "Space API") - self._set_state_lock = Lock() - waker.register(self.set_state, 10.0) # re-try setting the state every 10 seconds + waker.register(self.set_state, RETRY_TIME) def stop (self): self.set_state.stop() @@ -34,24 +37,14 @@ class SpaceApi: def _set_state (self, state = None): '''Sets the state, if None: leave state unchanged and re-try if previous attempts failed''' if state is not None: - self._state_to_set = state - else: - # always have a local variable because of parallelism - state = self._state_to_set - - if not self._set_state_lock.acquire(False): - # we don't want many threads to wait here - # the next status update will fix everything anyways - pass - else: - # got the lock - try: - # check if there's something we need to do - if self._state_last_set == state: return - # take action! - success = self._do_request(state) - # TODO error too often -> log critical to send mails - if success: - self._state_last_set = state - finally: - self._set_state_lock.release() + self._local_state = state + # check if there's something we need to do: There's a valid state, and either the state has changed or + # we need to refresh our heartbeta) + now = time.time() + if self._local_state is not None and (self._local_state != self._remote_state or now > self._last_set_at+HEARTBEAT_TIME): + # take action! + success = self._do_request(self._local_state) + # TODO error too often -> log critical to send mails + if success: + self._remote_state = self._local_state + self._last_set_at = now -- 2.30.2 From d084286e81b121106a0a165e3c151762635466e0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 9 Nov 2013 13:46:26 +0100 Subject: [PATCH 03/16] there can be more than one message on the socket; empty the queue when shutting down a callback-thread; increase the SpaceAPI-retry-time --- libtuer.py | 7 +++++++ spaceapi.py | 2 +- statemachine.py | 19 ++++++++++--------- tryshell | 6 ++++-- tuerd | 3 ++- tyshell | 6 ++++-- tysock.py | 5 +++-- 7 files changed, 31 insertions(+), 17 deletions(-) diff --git a/libtuer.py b/libtuer.py index 01c04ae..f79dcc6 100644 --- a/libtuer.py +++ b/libtuer.py @@ -111,6 +111,13 @@ class ThreadFunction(): self._q.put((ThreadFunction._CALL, arg)) def stop(self): + # empty the queue + try: + while True: + self._q.get_nowait() + except queue.Empty: + pass + # now wait till the job-in-progress is done self._q.put((ThreadFunction._TERM, None)) self._t.join() diff --git a/spaceapi.py b/spaceapi.py index eea73f6..8e36b0f 100644 --- a/spaceapi.py +++ b/spaceapi.py @@ -4,7 +4,7 @@ import urllib.request, time from config import spaceApiKey -RETRY_TIME = 30 +RETRY_TIME = 60 HEARTBEAT_TIME = 10*60 class SpaceApi: diff --git a/statemachine.py b/statemachine.py index a937f50..5ad83dd 100644 --- a/statemachine.py +++ b/statemachine.py @@ -205,19 +205,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) diff --git a/tryshell b/tryshell index 3d4e3ed..1880dd5 100755 --- a/tryshell +++ b/tryshell @@ -10,6 +10,8 @@ while True: s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(tuerSock) s.send(command.encode()) - data = s.recv(64) + while True: + data = s.recv(256) + if not len(data): break + print(data) s.close() - print(str(data)) diff --git a/tuerd b/tuerd index 220b2bc..c0b4413 100755 --- a/tuerd +++ b/tuerd @@ -41,9 +41,10 @@ try: the_socket.accept() except KeyboardInterrupt: # this is what we waited for! - logger.info("Got SIGINT, terminating...") pass +logger.info("Terminating...") + # bring 'em all down the_waker.stop() # this one first, it "randomly" calls other threads the_pins.stop() # as does this diff --git a/tyshell b/tyshell index 37873bc..2df0a22 100755 --- a/tyshell +++ b/tyshell @@ -43,9 +43,11 @@ def sendcmd(addr, cmd): s.connect(addr) s.settimeout(60.0) s.send(cmd.encode()) - data = s.recv(256) + while True: + data = s.recv(256) + if not len(data): break + print(data.decode('utf-8')) s.close() - print(data.decode('utf-8')) return run def exitcmd(c): diff --git a/tysock.py b/tysock.py index 0984b2a..11b5183 100644 --- a/tysock.py +++ b/tysock.py @@ -44,10 +44,11 @@ class TySocket(): self._sock.listen(1) def _answer(self, conn): - def answer(msg): + def answer(msg, lastMsg = True): # this is called in another thread, so it should be quick and not touch the TySocket waynesend(conn, msg) - conn.close() + if lastMsg: + conn.close() return answer def accept(self): -- 2.30.2 From 97036270b630059dc55e70725fb4b65554bc36ba Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 9 Nov 2013 13:46:37 +0100 Subject: [PATCH 04/16] add a script to add tuer users --- adduser | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 adduser diff --git a/adduser b/adduser new file mode 100755 index 0000000..19b10ce --- /dev/null +++ b/adduser @@ -0,0 +1,20 @@ +#!/bin/bash + +USER="$1" +if [[ -z "$USER" ]]; then + echo "Usage: $0 username" + exit 1 +fi +if [[ "$(whoami)" != "root" ]]; then + echo "Please get root first" + exit 1 +fi + +adduser "$USER" --disabled-password +sudo adduser "$USER" tuer +cd /home/"$USER" +mkdir .ssh +echo -n "I will now open nano, please paste the SSH key in there! " +read +nano .ssh/authorized_keys +chown "$USER": -R . -- 2.30.2 From dfd60ef69a6fa4b790250431f9b7fde6da084ec9 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 9 Nov 2013 13:49:44 +0100 Subject: [PATCH 05/16] send an email when updating the SpaceAI consistently fails --- spaceapi.py | 7 ++++++- tuerd | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spaceapi.py b/spaceapi.py index 8e36b0f..7f788ab 100644 --- a/spaceapi.py +++ b/spaceapi.py @@ -13,6 +13,7 @@ class SpaceApi: self._remote_state = None self._last_set_at = 0 self._running = True + self._fail_count = 0 # number of consecutive fails self.set_state = ThreadFunction(self._set_state, "Space API") waker.register(self.set_state, RETRY_TIME) @@ -44,7 +45,11 @@ class SpaceApi: if self._local_state is not None and (self._local_state != self._remote_state or now > self._last_set_at+HEARTBEAT_TIME): # take action! success = self._do_request(self._local_state) - # TODO error too often -> log critical to send mails if success: self._remote_state = self._local_state self._last_set_at = now + self._fail_count = 0 + else: + self._fail_count += 1 + if self._fail_count in (5, 50, 500): + logger.critical("Updating the SpaceAPI failed %d times in a row" % self._fail_count) diff --git a/tuerd b/tuerd index c0b4413..942c495 100755 --- a/tuerd +++ b/tuerd @@ -43,7 +43,7 @@ except KeyboardInterrupt: # this is what we waited for! pass -logger.info("Terminating...") +logger.info("Terminating...") # somehow this does not arrive in the syslog # bring 'em all down the_waker.stop() # this one first, it "randomly" calls other threads -- 2.30.2 From 5871e71831a0938f6bfb36f6a90e00fd7284e367 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 9 Nov 2013 13:51:12 +0100 Subject: [PATCH 06/16] rename adduser -> addtueruser --- adduser => addtueruser | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename adduser => addtueruser (100%) diff --git a/adduser b/addtueruser similarity index 100% rename from adduser rename to addtueruser -- 2.30.2 From 7a99a7eb3b2f2c539836875d2cc8ea15a42ea49f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 23 Nov 2013 13:23:56 +0100 Subject: [PATCH 07/16] ignore socket timeouts; refine tuer-warnstate-nervlist --- statemachine.py | 19 +++++++++++++++---- tysock.py | 5 ++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/statemachine.py b/statemachine.py index 5ad83dd..b647ac9 100644 --- a/statemachine.py +++ b/statemachine.py @@ -13,6 +13,15 @@ def play_sound (what): soundfile = SOUNDS_DIRECTORY + what + '/' + random.choice(soundfiles) fire_and_forget_cmd ([SOUNDS_PLAYER,soundfile], logger.error, "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)) + return nervlist_rel + # StateUnlocking constants OPEN_REPEAT_TIMEOUT = 7 @@ -26,10 +35,12 @@ 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"))] +ABOUTOPEN_NERVLIST = nervlist_abs2rel([(5, lambda : play_sound("flipswitch")), (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("flipswitch")), + (60, lambda:logger.critical("Space open but switch not flipped for 60 seconds")), (120, lambda:play_sound("flipswitch")), + (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 diff --git a/tysock.py b/tysock.py index 11b5183..676f573 100644 --- a/tysock.py +++ b/tysock.py @@ -71,8 +71,11 @@ class TySocket(): # _answer will be called, and it will close the connection else: waynesend(conn, 'Command not found') - conn.close() except KeyboardInterrupt: raise # forward Ctrl-C to the outside + except socket.timeout: + # it's okay + logger.info("TySocket: Connection timed out") except Exception as e: logger.critical("TySocket: Something went wrong: %s" % str(e)) + conn.close() -- 2.30.2 From b43971d227ed5ab3e86ae29f3496e84d07edd202 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 27 Nov 2013 20:20:47 +0100 Subject: [PATCH 08/16] Be less spammy when SpaceAPI update fails --- spaceapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spaceapi.py b/spaceapi.py index 7f788ab..181981a 100644 --- a/spaceapi.py +++ b/spaceapi.py @@ -51,5 +51,5 @@ class SpaceApi: self._fail_count = 0 else: self._fail_count += 1 - if self._fail_count in (5, 50, 500): + if self._fail_count in (5, 100): logger.critical("Updating the SpaceAPI failed %d times in a row" % self._fail_count) -- 2.30.2 From e56993183ffa8362215d92913445106aea245d97 Mon Sep 17 00:00:00 2001 From: Constantin Berhard Date: Wed, 4 Dec 2013 21:18:52 +0100 Subject: [PATCH 09/16] tidied up fire_and_forget_cmd for sound playing --- libtuer.py | 5 +++-- statemachine.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libtuer.py b/libtuer.py index f79dcc6..c9eceec 100644 --- a/libtuer.py +++ b/libtuer.py @@ -71,12 +71,13 @@ def fire_and_forget(f): # run a command asynchronously and log the return value if not 0 # prefix must be a string identifying the code position where the call came from -def fire_and_forget_cmd (cmd, log, prefix): +def fire_and_forget_cmd (cmd, log_prefix): + logger.debug("Firing and forgetting command from %s: %s" % (log_prefix,str(cmd))) def _fire_and_forget_cmd (): with open("/dev/null", "w") as fnull: retcode = subprocess.call(cmd, stdout=fnull, stderr=fnull) if retcode is not 0: - logger.error("%sReturn code %d at command: %s" % (prefix,retcode,str(cmd))) + logger.error("%sReturn code %d at command: %s" % (log_prefix,retcode,str(cmd))) fire_and_forget(_fire_and_forget_cmd) # Threaded callback class diff --git a/statemachine.py b/statemachine.py index b647ac9..3e22206 100644 --- a/statemachine.py +++ b/statemachine.py @@ -11,7 +11,7 @@ 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): -- 2.30.2 From 77625890f7be83890ca4a99fe9571a9a13772624 Mon Sep 17 00:00:00 2001 From: Constantin Berhard Date: Wed, 4 Dec 2013 23:28:36 +0100 Subject: [PATCH 10/16] abs2rel works and more sounds --- statemachine.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/statemachine.py b/statemachine.py index 3e22206..4fccee4 100644 --- a/statemachine.py +++ b/statemachine.py @@ -20,8 +20,11 @@ def nervlist_abs2rel(nervlist_abs): 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 @@ -35,10 +38,11 @@ CLOSE_REPEAT_NUMBER = 3 FALLBACK_LEAVE_DELAY_LOCK = 5 # seconds # StateAboutToOpen constants +SWITCH_PRAISE_PROBABILITY = 0.5 ABOUTOPEN_NERVLIST = nervlist_abs2rel([(5, lambda : play_sound("flipswitch")), (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("flipswitch")), - (60, lambda:logger.critical("Space open but switch not flipped for 60 seconds")), (120, lambda:play_sound("flipswitch")), + (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"))]) @@ -247,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() @@ -265,6 +271,8 @@ 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) -- 2.30.2 From 0644bed139c320b03b54f1e5a4fecadc96fa67c8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 7 Dec 2013 10:55:56 +0100 Subject: [PATCH 11/16] Wait a bit more before starting to complain --- statemachine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statemachine.py b/statemachine.py index 4fccee4..0fa485a 100644 --- a/statemachine.py +++ b/statemachine.py @@ -39,7 +39,7 @@ FALLBACK_LEAVE_DELAY_LOCK = 5 # seconds # StateAboutToOpen constants SWITCH_PRAISE_PROBABILITY = 0.5 -ABOUTOPEN_NERVLIST = nervlist_abs2rel([(5, lambda : play_sound("flipswitch")), (10, lambda:play_sound("flipswitch")),\ +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")), -- 2.30.2 From 5c494522e49b0d3f17c3bc8912cc9a6011df152b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 29 Jan 2014 20:35:10 +0100 Subject: [PATCH 12/16] Be quiet at night --- statemachine.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/statemachine.py b/statemachine.py index 0fa485a..62f523a 100644 --- a/statemachine.py +++ b/statemachine.py @@ -1,6 +1,6 @@ from libtuer import ThreadFunction, logger, fire_and_forget, fire_and_forget_cmd from actor import Actor -import os, random, time, threading +import os, random, time, threading, datetime # logger.{debug,info,warning,error,critical} @@ -11,7 +11,9 @@ 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], "StateMachine: ") + hour = datetime.datetime.time(datetime.datetime.now()).hour + volume = 60 if hour >= 22 or hour <= 6 else 95 + fire_and_forget_cmd ([SOUNDS_PLAYER, "-volume", str(volume), soundfile], "StateMachine: ") # convert an absolute nervlist to a relative one def nervlist_abs2rel(nervlist_abs): -- 2.30.2 From 98411bd460ad1474b8c3100bf8241c04519b7a2d Mon Sep 17 00:00:00 2001 From: Constantin Berhard Date: Wed, 19 Feb 2014 22:34:47 +0100 Subject: [PATCH 13/16] fallback mode usable from tyshell --- statemachine.py | 34 +++++++++++++++++++++++++++++----- tyshell | 14 ++++++++++++++ tysock.py | 3 +++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/statemachine.py b/statemachine.py index 62f523a..e6a2757 100644 --- a/statemachine.py +++ b/statemachine.py @@ -83,6 +83,9 @@ class StateMachine(): CMD_UNLOCK = 2 CMD_WAKEUP = 3 CMD_LAST = 4 + CMD_LOCK = 5 + CMD_FALLBACK_ON = 6 + CMD_FALLBACK_OFF = 7 class State(): def __init__(self, state_machine, nervlist = None): @@ -99,6 +102,14 @@ class StateMachine(): def handle_wakeup_event(self): if self._nerver is not None: return self._nerver.nerv() + def handle_cmd_lock_event(self,arg): + arg("412 Precondition Failed: If not in fallback mode, use the hardware switch to lock the space.") + def handle_cmd_fallback_on_event(self,arg): + arg("200 okay: Entering fallback mode and notifying admins.") + logger.critical("Entering fallback mode. Somebody thinks, the hardware sensors are broken.") + return StateMachine.StateFallback(self.state_machine) + def handle_cmd_fallback_off_event(self,arg): + arg("412 Precondition Failed: Not in fallback mode!") def on_leave(self): pass def pins(self): @@ -118,6 +129,12 @@ class StateMachine(): return self.handle_cmd_unlock_event(arg) elif ev == StateMachine.CMD_WAKEUP: return self.handle_wakeup_event() + elif ev == StateMachine.CMD_LOCK: + return self.handle_cmd_lock_event(arg) + elif ev == StateMachine.CMD_FALLBACK_ON: + return self.handle_cmd_fallback_on_event(arg) + elif ev == StateMachine.CMD_FALLBACK_OFF: + return self.handle_cmd_fallback_off_event(arg) else: raise Exception("Unknown command number: %d" % ev) @@ -178,10 +195,6 @@ class StateMachine(): self._red_state = False def handle_pins_event(self): pins = self.pins() - # buzz if open and bell rang - if pins.space_active and pins.bell_ringing and not self.old_pins().bell_ringing: - logger.info("StateFallback: Space switch on and door bell rung => buzzing") - self.actor().act(Actor.CMD_BUZZ) # set green LED according to space switch if pins.space_active: self.actor().act(Actor.CMD_GREEN_ON) @@ -204,8 +217,18 @@ class StateMachine(): self._red_state = True def handle_cmd_unlock_event(self,arg): if arg is not None: - arg("298 Fallback Okay: Trying to unlock the door. The System is in fallback mode, success information is not available.") + arg("200 okay: Trying to unlock the door. The System is in fallback mode, success information is not available.") self.actor().act(Actor.CMD_UNLOCK) + def handle_cmd_lock_event(self,arg): + if arg is not None: + arg("200 okay: Trying to lock the door. The System is in fallback mode, success information is not available.") + self.actor().act(Actor.CMD_LOCK) + def handle_cmd_fallback_on_event(self,arg): + arg("412 Precondition Failed: Fallback mode already active.") + def handle_cmd_fallback_off_event(self,arg): + arg("200 okay: Leaving fallback mode and notifying admins.") + logger.critical("Leaving fallback mode. Somebody thinks, the sensors are working again.") + return StateMachine.StateStart(self.state_machine) class StateZu(AbstractLockedState): def handle_cmd_unlock_event(self,callback): @@ -340,6 +363,7 @@ class StateMachine(): def stop (self): self.callback.stop() + # actually call this.callback (is set in the constructor to make this thread safe) def _callback(self, cmd, arg=None): # update pins if cmd == StateMachine.CMD_PINS: diff --git a/tyshell b/tyshell index 2df0a22..70ad6df 100755 --- a/tyshell +++ b/tyshell @@ -64,14 +64,28 @@ def alias (cmds, aliases): cmds[newname] = cmds[oldname] return cmds +def prompt_sure(f,msg): + def run(c): + try: + command = input("%s Are you sure? (yes/no) > " % msg) + except EOFError: + print() + return + if command[0] == 'y': + return f(c) + return run + CmdEntry = namedtuple('CmdEntry','function helpstring') commands = alias({ 'exit': CmdEntry(exitcmd, 'Quits this shell'), 'help': CmdEntry(helpcmd, 'Helps you getting to know the available commands'), 'unlock': CmdEntry(sendcmd(tuerSock, 'unlock'), 'Will try to unlock the apartment door'), + 'lock': CmdEntry(sendcmd(tuerSock, 'lock'), 'If in fallback mode, try to lock the apartment door. If not in fallback mode, you must use the switch near the door.'), 'buzz': CmdEntry(sendcmd(tuerSock, 'buzz'), 'Will buzz the buzzer for the street door'), 'who': CmdEntry(whocmd, 'Shows the list of people, who are allowed to control this system'), + 'fallback_mode_on': CmdEntry(prompt_sure(sendcmd(tuerSock, 'fallback_mode_on'),'WARNING: This action will be reported to the admins. Use this only in case of Sphinx hardware failure when you need to ignore erroneous sensor input!'), 'Sets the system in a state where it is less dependent on sensoric input. Use it only when sensors are broken.'), + 'fallback_mode_off': CmdEntry(prompt_sure(sendcmd(tuerSock, 'fallback_mode_off'),'WARNING: This action will be reported to the admins. Use this only if you have fixed the sensors of the Sphinx or activated fallback mode by accident!'), 'Resets the system to the default state. Use this when you have just repaired the sensors of the Sphinx.'), },{ # aliases 'open': 'unlock', diff --git a/tysock.py b/tysock.py index 676f573..b7cb1cc 100644 --- a/tysock.py +++ b/tysock.py @@ -27,6 +27,9 @@ class TySocket(): CMDs = { b'buzz': StateMachine.CMD_BUZZ, b'unlock': StateMachine.CMD_UNLOCK, + b'lock': StateMachine.CMD_LOCK, + b'fallback_mode_on': StateMachine.CMD_FALLBACK_ON, + b'fallback_mode_off': StateMachine.CMD_FALLBACK_OFF, } def __init__(self, sm): -- 2.30.2 From 986e711c18a3960baa5dda8b9919372e6a387909 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 19 Feb 2014 23:14:38 +0100 Subject: [PATCH 14/16] fix: don't close client sockets too early --- tysock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tysock.py b/tysock.py index b7cb1cc..2a5e193 100644 --- a/tysock.py +++ b/tysock.py @@ -72,6 +72,7 @@ class TySocket(): if data in self.CMDs: self._sm.callback(self.CMDs[data], self._answer(conn)) # _answer will be called, and it will close the connection + continue # make sure we break so we don't close it else: waynesend(conn, 'Command not found') except KeyboardInterrupt: -- 2.30.2 From 73cd08c855a77bc0df90d6e94f6d75d42d6f5e5e Mon Sep 17 00:00:00 2001 From: Constantin Berhard Date: Wed, 19 Feb 2014 23:41:34 +0100 Subject: [PATCH 15/16] status report command introduced checking pin states is yet to implement --- statemachine.py | 6 ++++++ tyshell | 1 + tysock.py | 1 + 3 files changed, 8 insertions(+) diff --git a/statemachine.py b/statemachine.py index e6a2757..dd9dc54 100644 --- a/statemachine.py +++ b/statemachine.py @@ -86,6 +86,7 @@ class StateMachine(): CMD_LOCK = 5 CMD_FALLBACK_ON = 6 CMD_FALLBACK_OFF = 7 + CMD_STATUS = 8 class State(): def __init__(self, state_machine, nervlist = None): @@ -110,6 +111,9 @@ class StateMachine(): return StateMachine.StateFallback(self.state_machine) def handle_cmd_fallback_off_event(self,arg): arg("412 Precondition Failed: Not in fallback mode!") + def handle_cmd_status_event(self,arg): + # TODO use a proper JSON lib + arg('200 okay: {state:\"%s\"}' % self.__class__.__name__) def on_leave(self): pass def pins(self): @@ -135,6 +139,8 @@ class StateMachine(): return self.handle_cmd_fallback_on_event(arg) elif ev == StateMachine.CMD_FALLBACK_OFF: return self.handle_cmd_fallback_off_event(arg) + elif ev == StateMachine.CMD_STATUS: + return self.handle_cmd_status_event(arg) else: raise Exception("Unknown command number: %d" % ev) diff --git a/tyshell b/tyshell index 70ad6df..b535f2b 100755 --- a/tyshell +++ b/tyshell @@ -86,6 +86,7 @@ commands = alias({ 'who': CmdEntry(whocmd, 'Shows the list of people, who are allowed to control this system'), 'fallback_mode_on': CmdEntry(prompt_sure(sendcmd(tuerSock, 'fallback_mode_on'),'WARNING: This action will be reported to the admins. Use this only in case of Sphinx hardware failure when you need to ignore erroneous sensor input!'), 'Sets the system in a state where it is less dependent on sensoric input. Use it only when sensors are broken.'), 'fallback_mode_off': CmdEntry(prompt_sure(sendcmd(tuerSock, 'fallback_mode_off'),'WARNING: This action will be reported to the admins. Use this only if you have fixed the sensors of the Sphinx or activated fallback mode by accident!'), 'Resets the system to the default state. Use this when you have just repaired the sensors of the Sphinx.'), + 'status': CmdEntry(sendcmd(tuerSock, 'status'), 'Shows internal state and sensor information.'), },{ # aliases 'open': 'unlock', diff --git a/tysock.py b/tysock.py index 2a5e193..b987567 100644 --- a/tysock.py +++ b/tysock.py @@ -30,6 +30,7 @@ class TySocket(): b'lock': StateMachine.CMD_LOCK, b'fallback_mode_on': StateMachine.CMD_FALLBACK_ON, b'fallback_mode_off': StateMachine.CMD_FALLBACK_OFF, + b'status': StateMachine.CMD_STATUS, } def __init__(self, sm): -- 2.30.2 From 82c945d5140bce7c1884c8bc734f3403626bb5f1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 1 Mar 2014 16:34:48 +0100 Subject: [PATCH 16/16] more information in the logfile --- actor.py | 16 ++++++++++------ libtuer.py | 11 ++++------- statemachine.py | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/actor.py b/actor.py index f2f1ad2..fb72df7 100644 --- a/actor.py +++ b/actor.py @@ -12,15 +12,19 @@ class Actor: CMD_RED_OFF = 6 class CMD(): - def __init__(self, name, pin, tid, todo): + def __init__(self, name, pin, tid, todo, verbose = True): self.name = name self.pin = pin self.tid = tid self.todo = todo + self.verbose = verbose # don't do the GPIO setup here, the main init did not yet run def execute(self): - logger.info("Actor: Running command %s" % self.name) + if self.verbose: + logger.info("Actor: Running command %s" % self.name) + else: + logger.debug("Actor: Running command %s" % self.name) for (value, delay) in self.todo: if value is not None: logger.debug("Actor: Setting pin %d to %d" % (self.pin, value)) @@ -32,10 +36,10 @@ class Actor: CMD_UNLOCK: CMD("unlock", pin=12, tid=0, todo=[(True, 0.3), (False, 0.1)]), CMD_LOCK: CMD("lock", pin=16, tid=0, todo=[(True, 0.3), (False, 0.1)]), CMD_BUZZ: CMD("buzz", pin=22, tid=1, todo=[(True, 2.5), (False, 0.1)]), - CMD_GREEN_ON: CMD("green on", pin=23, tid=2, todo=[(True, 0)]), - CMD_GREEN_OFF: CMD("green off", pin=23, tid=2, todo=[(False, 0)]), - CMD_RED_ON: CMD("red on", pin=26, tid=2, todo=[(True, 0)]), - CMD_RED_OFF: CMD("red off", pin=26, tid=2, todo=[(False, 0)]), + CMD_GREEN_ON: CMD("green on", pin=23, tid=2, verbose=False, todo=[(True, 0)]), + CMD_GREEN_OFF: CMD("green off", pin=23, tid=2, verbose=False, todo=[(False, 0)]), + CMD_RED_ON: CMD("red on", pin=26, tid=2, verbose=False, todo=[(True, 0)]), + CMD_RED_OFF: CMD("red off", pin=26, tid=2, verbose=False, todo=[(False, 0)]), } def __init__(self): diff --git a/libtuer.py b/libtuer.py index c9eceec..f1e8d54 100644 --- a/libtuer.py +++ b/libtuer.py @@ -63,9 +63,8 @@ def fire_and_forget(f): def _fire_and_forget(): try: f() - except Exception as e: - logger.critical("fire_and_forget: Got exception out of callback: %s" % str(e)) - logger.debug(traceback.format_exc()) + except Exception: + logger.critical("fire_and_forget: Got exception out of callback:\n%s" % traceback.format_exc()) t = threading.Thread(target=_fire_and_forget) t.start() @@ -100,8 +99,7 @@ class ThreadFunction(): try: self._f(*data) except Exception as e: - logger.critical("ThreadFunction: Got exception out of handler thread %s: %s" % (self.name, str(e))) - logger.debug(traceback.format_exc()) + logger.critical("ThreadFunction: Got exception out of handler thread %s:\n%s" % (self.name, traceback.format_exc())) elif cmd == ThreadFunction._TERM: assert data is None break @@ -139,8 +137,7 @@ class ThreadRepeater(): 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()) + logger.critical("ThreadRepeater: Got exception out of handler thread %s:\n%s" % (self.name, traceback.format_exc())) time.sleep(self._sleep_time) def stop(self): diff --git a/statemachine.py b/statemachine.py index dd9dc54..645737c 100644 --- a/statemachine.py +++ b/statemachine.py @@ -380,6 +380,6 @@ class StateMachine(): while newstate is not None: assert isinstance(newstate, StateMachine.State), "I should get a state" self.current_state.on_leave() - logger.debug("StateMachine: Doing state transition %s -> %s" % (self.current_state.__class__.__name__, newstate.__class__.__name__)) + logger.info("StateMachine: Doing state transition %s -> %s" % (self.current_state.__class__.__name__, newstate.__class__.__name__)) self.current_state = newstate newstate = self.current_state.handle_event(StateMachine.CMD_PINS, self.pins) -- 2.30.2