From 83d646a505bed8c75eef15855da7046e5854106d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 18 Sep 2013 22:45:18 +0200 Subject: [PATCH 1/1] lots of updates, amongst others: * logging to syslog * multithreaded input-PIN handling --- libtuer.py | 21 +++++++++++ ringd | 106 ++++++++++++++++++++++++++++++++++++++++++----------- tuerd | 59 +++++++++++++++++------------ tyshell | 7 ++-- 4 files changed, 143 insertions(+), 50 deletions(-) create mode 100644 libtuer.py diff --git a/libtuer.py b/libtuer.py new file mode 100644 index 0000000..a655fcd --- /dev/null +++ b/libtuer.py @@ -0,0 +1,21 @@ +import logging, logging.handlers, syslog, os + +# logging function +class Logger: + def __init__ (self): + import __main__ as main + self.name = os.path.basename(main.__file__) + self.logger = logging.getLogger(self.name) + self.logger.setLevel(logging.INFO) + self.handler = logging.handlers.SysLogHandler(address = '/dev/log', facility = logging.handlers.SysLogHandler.LOG_LOCAL0) + self.logger.addHandler(self.handler) + def log (self, what): + thestr = "%s[%d]: %s" % (self.name,os.getpid(),what) + print (thestr) + self.logger.info(thestr) + +logger = Logger() + +def log (what): + logger.log(what) + diff --git a/ringd b/ringd index 3835d9c..201322f 100755 --- a/ringd +++ b/ringd @@ -1,37 +1,99 @@ #!/usr/bin/python3 import time, socket, atexit +import queue, threading, select +from libtuer import log import RPi.GPIO as GPIO +GPIO.setmode(GPIO.BOARD) +atexit.register(GPIO.cleanup) tuerSock = "/run/tuer.sock" - ringPin = 18 -GPIO.setmode(GPIO.BOARD) -GPIO.setup(ringPin, GPIO.IN) -atexit.register(GPIO.cleanup); - -lastEvent = 0 - +# Main classes +class PinWatcher(): + def __init__(self, pin, histlen): + GPIO.setup(pin, GPIO.IN) + assert histlen > 1 # otherwise our logic goes nuts... + self._pin = pin + self._histlen = histlen + # state change detection + self._state = None + self._newstate = None # != None iff we are currently seeing a state change + self._newstatelen = 0 # only valid if newstate != None + # start state change handler thread + self._q = queue.Queue() + self._t = threading.Thread(target=self.queue_consumer) + self._t.start() + + def queue_consumer(self): + while True: + el = self._q.get() + if el is None: return # we are supposed to terminate + # handle the state change + (oldstate, newstate) = el + self.callback(oldstate, newstate) + + def read(self): + curstate = GPIO.input(self._pin) + assert curstate in (0, 1) + if curstate != self._state: + # the state is about to change + if curstate == self._newstate: + # we already saw this new state + self._newstatelen += 1 + if self._newstatelen >= self._histlen: + self._q.put((self._state, curstate)) # send stuff to the other thread + self._state = curstate + self._newstate = None + else: + # now check for how long we see this new state + self._newstate = curstate + self._newstatelen = 1 + else: + # old state is preserved + self._newstate = None + + def quit(self): + self._q.put(None) + self._t.join() -while True: - GPIO.wait_for_edge(ringPin, GPIO.BOTH) - # measure time since event - now = time.time() - timePassed = now-lastEvent - print("Time between events %f" % timePassed) - # remember, remember - lastEvent = now - # action to be taken? - if timePassed >= 1.5 and timePassed <= 3: - print("Opening door") +class RingWatcher(PinWatcher): + def __init__(self): + super().__init__(ringPin, 2) + self.last1Event = None + + def callback(self, oldstate, newstate): + if oldstate is None: + return # ignore the very first state change + # now (oldstate, newstate) is either (0, 1) or (1, 0) + if newstate: + self.last1Event = time.time() + elif self.last1Event is not None: + # how long was this pressed? + timePressed = time.time() - self.last1Event + log("Ring button pressed for",timePressed) + if timePressed >= 1.5 and timePressed <= 3: + self.buzz() + + def buzz(self): + log("Opening door") # talk with tuerd s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(tuerSock) s.send(b'buzz') - data = s.recv(4) s.close() - print("...done") - if data != b'1': - print("Received unexpected answer %s" % str(data)) +# MAIN PROGRAM +pins = [ + RingWatcher(), +] +try: + log("entering loop") + while True: + for pin in pins: + pin.read() + time.sleep(0.02) +except KeyboardInterrupt: + for pin in pins: + pin.quit() diff --git a/tuerd b/tuerd index f679fa8..f03d5db 100755 --- a/tuerd +++ b/tuerd @@ -1,16 +1,21 @@ #!/usr/bin/python3 -import time, socket, os, stat, atexit, errno, struct +import time, socket, os, stat, atexit, errno, struct, pwd +from libtuer import log from datetime import datetime import RPi.GPIO as GPIO SO_PEERCRED = 17 # DO - NOT - TOUCH GPIO.setmode(GPIO.BOARD) +atexit.register(GPIO.cleanup) -# ******** definitions ********* -# logging function -# TODO: loglevel, log like a real daemon -def log (what): - print (datetime.now(),what) +#tmp +def recv_timeout(conn, size, time): + (r, w, x) = select.select([conn], [], [], time) + if len(r): + assert r[0] == conn + return conn.recv(size) + return None +# ******** definitions ********* # send to client for information but don't care if it arrives def waynesend (conn, what): try: @@ -47,9 +52,10 @@ class Pinoutput: def __call__ (self, conn): for (value,delay) in self.todo: GPIO.output(self.pin, value) - log ("%s: Pin %d set to %s." % (self.name,self.pin,str(value))) + # log ("%s: Pin %d set to %s." % (self.name,self.pin,str(value))) time.sleep(delay) # notify success + log waynesend(conn,b"1") # ******** configuration ********* @@ -62,9 +68,6 @@ pinlist = [Pinoutput("open", 12, [(True, 0.3), (False, 5.0)]), # ******** main ********* -# at the end do a cleanup -atexit.register(GPIO.cleanup); - # convert list of pin objects to dictionary for command lookup pindict = {} for pin in pinlist: @@ -76,29 +79,37 @@ sock = socket.socket (socket.AF_UNIX, socket.SOCK_STREAM) forcerm(socketname) # bind socket to file name sock.bind (socketname) -# ensure we close and delete the socket when we quit (atexit.register is LIFO!) -atexit.register(forcerm, socketname) -atexit.register(sock.close) # allow only users in the tuergroup to write to the socket os.chown (socketname, 0, tuergroupid) os.chmod (socketname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP) # listen to the people, but not too many at once sock.listen(1) +# shutdown handling +def shutdown(): + log("Shutting down") + sock.close() + forcerm(socketname) +atexit.register(shutdown) + # main loop # FIXME: DoS by opening socket but not sending data, because this loop is single threaded; maybe settimeout helps a bit. while True: # accept connections conn, addr = sock.accept() - # get peer information (TODO use it for logging) - (pid, uid, gid) = (struct.unpack('3i', conn.getsockopt(socket.SOL_SOCKET, SO_PEERCRED, struct.calcsize('3i')))) - # get some data from the client (enough to hold any valid command) - data = conn.recv (32) - # log the command - log("received command: %s" % str(data)) - # lookup the command, if it's not in the dict, use the doNothing function instead - # and execute the looked up command or doNothing with the connection, so it can respond to the client - pindict.get(data,doNothing)(conn) - # close connection cleanly - conn.close() + try: + # get peer information + (pid, uid, gid) = (struct.unpack('3i', conn.getsockopt(socket.SOL_SOCKET, SO_PEERCRED, struct.calcsize('3i')))) + # get some data from the client (enough to hold any valid command) + data = conn.recv (32) + # log the command + log("received command from %s (uid %d): %s" % (pwd.getpwuid(uid).pw_name,uid, str(data))) + # lookup the command, if it's not in the dict, use the doNothing function instead + # and execute the looked up command or doNothing with the connection, so it can respond to the client + pindict.get(data,doNothing)(conn) + log("done") + # close connection cleanly + conn.close() + except Exception as e: + log("Something went wrong: %s\n...continuing." % str(e)) diff --git a/tyshell b/tyshell index 090403e..9350e12 100755 --- a/tyshell +++ b/tyshell @@ -9,8 +9,7 @@ import socket tuerSock = "/run/tuer.sock" # use a histfile -# FIXME: Why not ".tyshellhist"? -histfile = os.path.join(os.path.expanduser("~"), ".pyshellhist") +histfile = os.path.join(os.path.expanduser("~"), ".tyshellhist") try: readline.read_history_file(histfile) except IOError: @@ -20,7 +19,7 @@ atexit.register(readline.write_history_file, histfile) atexit.register(print, "Bye") # available commands -def help(c): +def helpcmd(c): print("Available commands: %s" % ", ".join(sorted(commands.keys()))) def extcmd(cmd): @@ -48,7 +47,7 @@ def exitcmd(c): commands = { 'exit': exitcmd, - 'help': help, + 'help': helpcmd, 'open': sendcmd(tuerSock, 'open'), 'close': sendcmd(tuerSock, 'close'), 'buzz': sendcmd(tuerSock, 'buzz'), -- 2.30.2