start implementing the new all-great tuerd
authorRalf Jung <post@ralfj.de>
Thu, 3 Oct 2013 15:58:48 +0000 (17:58 +0200)
committerRalf Jung <post@ralfj.de>
Thu, 3 Oct 2013 15:58:48 +0000 (17:58 +0200)
Makefile
actor.py [new file with mode: 0644]
libtuer.py
pins.py [new file with mode: 0644]
ringd [deleted file]
statemachine.py [new file with mode: 0644]
tuerd
tyshell
tysock.py [new file with mode: 0644]
waker.py [new file with mode: 0644]

index 25f344b75357f9c50a9853e82591e0212cf19171..1bdb0744ec9613511f1967c5959c3600d501a509 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-files = libtuer.py tuerd ringd tyshell
+files = *.py tuerd tryshell tyshell
 target = /opt/tuer/
 
 all: install
diff --git a/actor.py b/actor.py
new file mode 100644 (file)
index 0000000..3abd232
--- /dev/null
+++ b/actor.py
@@ -0,0 +1,32 @@
+from libtuer import ThreadFunction, logger
+import RPi.GPIO as GPIO
+       
+class Actor:
+       CMD_BUZZ = 0
+       CMD_OPEN = 1
+       CMD_CLOSE = 2
+       
+       CMDs = {
+               CMD_BUZZ:  ("buzz", 12, [(True, 0.3), (False, 2.0)]),
+               CMD_OPEN:  ("open", 16, [(None, 0.2), (True, 0.3), (False, 1.0)]),
+               CMD_CLOSE: ("close", 22, [(None, 0.2), (True, 0.3), (False, 1.0)]),
+       }
+       
+       def __init__(self):
+               self.act = ThreadFunction(self._act)
+               for (name, pin, todo) in self.CMDs.values():
+                       GPIO.setup(pin, GPIO.OUT)
+       
+       def _act(self, cmd):
+               if cmd in self.CMDs:
+                       (name, pin, todo) = self.CMDs[cmd]
+                       logger.info("Actor: Running command %s" % name)
+                       for (value, delay) in todo:
+                               if value is not None:
+                                       GPIO.output(pin, value)
+                               time.sleep(delay)
+               else:
+                       logger.error("Actor: Gut unknown command %d" % cmd)
+       
+       def stop(self):
+               pass
index 0df4051203cf8cad29da53d439dd263e39449922..60f491be2d8226035b382e78ff97c2784ca3066c 100644 (file)
@@ -1,4 +1,4 @@
-import logging, logging.handlers, syslog, os
+import logging, logging.handlers, os, time, queue, threading
 
 # logging function
 class Logger:
@@ -9,17 +9,25 @@ class Logger:
                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):
+       
+       def log (self, lvl, what):
                thestr = "%s[%d]: %s" % (self.name,os.getpid(),what)
                print (thestr)
-               self.logger.info(thestr)
+               self.logger.log(lvl, thestr)
+       
+       def debug(self, what):
+               self.log(logging.DEBUG, what)
+       def info(self, what):
+               self.log(logging.INFO, what)
+       def warning(self, what):
+               self.log(logging.WARNING, what)
+       def error(self, what):
+               self.log(logging.ERROR, what)
+       def critical(self, what):
+               self.log(logging.CRITICAL, what)
 
 logger = Logger()
 
-def log (what):
-       logger.log(what)
-
 # Threaded callback class
 class ThreadFunction():
        _CALL = 0
@@ -36,12 +44,15 @@ class ThreadFunction():
                        (cmd, data) = self._q.get()
                        # run command
                        if cmd == _CALL:
-                               self._f(*data)
+                               try:
+                                       self._f(*data)
+                               except Exception:
+                                       logger.error("ThreadFunction: Got exception out of handler thread: %s" % str(e))
                        elif cmd == _TERM:
                                assert data is None
                                break
                        else:
-                               raise NotImplementedError("Command %d does not exist" % cmd)
+                               logger.error("ThreadFunction: Command %d does not exist" % cmd)
        
        def __call__(self, *arg):
                self._q.put((self._CALL, arg))
@@ -49,3 +60,23 @@ class ThreadFunction():
        def stop(self):
                self._q.put((_TERM, None))
                self._t.join()
+
+# Thread timer-repeater class: Call a function every <sleep_time> seconds
+class ThreadRepeater():
+       def __init__(self, f, sleep_time):
+               self._f = f
+               self._stop = False
+               self._sleep_time = sleep_time
+               self._t = threading.Thread(target=self._thread_func)
+               self._t.start()
+       
+       def _thread_func():
+               while True:
+                       if self._stop:
+                               break
+                       self._f()
+                       time.sleep(sleep_time)
+       
+       def stop(self):
+               self._stop = True
+               self._t.join()
diff --git a/pins.py b/pins.py
new file mode 100644 (file)
index 0000000..8b699be
--- /dev/null
+++ b/pins.py
@@ -0,0 +1,71 @@
+import RPi.GPIO as GPIO
+from collections import namedtuple
+from libtuer import ThreadRepeater, logger
+from statemachine import StateMachine
+
+class PinsState():
+       pass
+
+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
+       
+       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:
+                                       # we saw it often enough to declare it the new state
+                                       self.state = curstate
+                                       self._newstate = None
+                                       return True
+                       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
+               return False
+
+class PinsWatcher():
+       def __init__(self, state_machine):
+               self.pins = {
+                       'bell_ringing': PinWatcher(18, 2),
+                       'door_closed': PinWatcher(8, 5),
+                       'door_locked': PinWatcher(9, 5),
+                       'space_active': PinWatcher(10, 5),
+               }
+               self._sm = state_machine
+               
+               # start a thread doing the work
+               self._t = ThreadRepeater(self._read, 0.02)
+               
+       def _read():
+               saw_change = False
+               for name in self.pins.keys():
+                       pin = pins[name]
+                       if pin.read():
+                               saw_change = True
+                               logger.debug("Pin %s changed to %d" % (name, pin.state)
+               if not saw_change: return
+               # create return object
+               pinsState = PinsState()
+               for name in self.pins.keys():
+                       setattr(pinsState, name, self.pins[name].state)
+               # send it to state machine
+               self._sm.callback(StateMachine.CMD_PINS, pinsState)
+       
+       def stop():
+               self._t.stop()
diff --git a/ringd b/ringd
deleted file mode 100755 (executable)
index b6a27f9..0000000
--- a/ringd
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/python3
-import time, socket, atexit
-import queue, threading, select
-from libtuer import log, ThreadFunction
-import RPi.GPIO as GPIO
-GPIO.setmode(GPIO.BOARD)
-atexit.register(GPIO.cleanup)
-
-tuerSock = "/run/tuer.sock"
-ringPin = 18
-
-
-# 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._callback = ThreadFunction(self.callback)
-               self.stop = self._callback.stop
-       
-       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._callback(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
-
-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')
-               s.close()
-
-# 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.stop()
diff --git a/statemachine.py b/statemachine.py
new file mode 100644 (file)
index 0000000..b3d1e2a
--- /dev/null
@@ -0,0 +1,158 @@
+from libtuer import ThreadFunction, logger
+from actor import Actor
+
+# logger.{debug,info,warning,error,critical}
+
+# StateOpening constants
+OPEN_REPEAT_TIMEOUT = 8
+OPEN_REPEAT_NUMBER = 3
+
+def play_sound (what):
+       print ("I would now play the sound %s... IF I HAD SOUNDS!" % what)
+
+# StateAboutToOpen constants
+ABOUTOPEN_NERVLIST = [(5, lambda : play_sound("heydrückdenknopf.mp3")), (10, lambda:play_sound("alterichmeinsernst.mp3"))]
+# TODO: erzeuge mehr nerv
+
+class StateMachine():
+       # commands you can send
+       CMD_PINS = 0
+       CMD_BUZZ = 1
+       CMD_OPEN = 2
+       CMD_WAKEUP = 3
+       CMD_LAST = 4
+       
+       class State():
+               def __init__(self, state_machine):
+                       self.state_machine = state_machine
+                       self.time_entered = time.time()
+                       self.theDict = None
+               def handle_pins_event(self):
+                       pass # one needn't implement this
+               def handle_buzz_event(self,arg): # this shouldn't be overwritten
+                       self.actor.act(Actor.CMD_BUZZ)
+                       arg("200 okay: buzz executed")
+               def handle_open_event(self,arg):
+                       if arg is not None:
+                               arg("412 Precondition Failed: The current state (%s) cannot handle the OPEN event" % self.__class__.__name__)
+               def handle_wakeup_event(self):
+                       pass # one needn't implement this
+               def pins(self):
+                       return self.state_machine.pins
+               def actor(self):
+                       return self.state_machine.actor
+               def handle_event(self,ev,arg):
+                       if arg is CMD_PINS:
+                               self.handle_pins_event()
+                       elif arg is CMD_BUZZ:
+                               self.handle_buzz_event(arg)
+                       elif arg is CMD_OPEN:
+                               self.handle_open_event(arg)
+                       elif arg is CMD_WAKEUP:
+                               self.handle_wakeup_event()
+                       else:
+                               raise Exception("Unknown command number: %d" % ev)
+       
+       class StateStart(State):
+               def __init__(self, sm):
+                       State.__init__(self,sm)
+               def handle_pins_event(self):
+                       thepins = self.pins()
+                       for pin in thepins:
+                               if pin is None:
+                                       return None
+                       if thepins.door_locked:
+                               return StateZu
+                       else:
+                               return StateAuf
+
+       class StateZu(State):
+               def __init__(self,sm):
+                       State.__init__(self,sm)
+               def handle_pins_event(self):
+                       pins = self.pins()
+                       if not pins.door_locked:
+                               return StateAboutToOpen(self.state_machine)
+               def handle_open_event(self,callback):
+                       return StateOpening(callback,self.state_machine)
+       
+       class StateOpening(State):
+               def __init__(self,callback,sm):
+                       State.__init__(self,sm)
+                       self.callbacks=[callback]
+                       self.tries = 0
+                       self.actor().act(Actor.CMD_OPEN)
+               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))
+                       for cb in self.callbacks:
+                               if cb is not None:
+                                       cb(s)
+               def handle_pins_event(self):
+                       pins = self.pins()
+                       if not pins.door_locked:
+                               self.notify(True)
+                               return StateAboutToOpen(self.state_machine)
+               def handle_open_event(self,callback):
+                       self.callbacks.append(callback)
+               def handle_wakeup_event(self):
+                       over = time.time() - self.time_entered
+                       nexttry = (self.tries+1) * OPEN_REPEAT_TIMEOUT
+                       if over > nexttry:
+                               if self.tries < OPEN_REPEAT_NUMBER:
+                                       self.actor().act(Actor.CMD_OPEN)
+                                       self.tries += 1
+                               else:
+                                       #TODO: LOG ERROR und EMAIL an Admins
+                                       self.notify(False)
+                                       return StateZu(self.state_machine)
+       
+       class StateAboutToOpen(State):
+               def __init__(self, sm):
+                       State.__init__(sm)
+               def handle_pins_event(self):
+                       pins = self.pins()
+                       if pins.door_locked:
+                               return StateZu(self.state_machine)
+                       elif pins.space_active:
+                               return StateAuf(self.state_machine)
+                       else:
+                               over = time.time() - self.time_entered
+                               # TODO: Nerv
+                               logger.debug("AboutToOpen since %f seconds. TODO: nerv the user" % over)
+               # TODO
+       
+       class StateAuf(State):
+               #TODO
+               pass
+       
+       class StateClosing(State):
+               #TODO
+               pass
+       
+       class StateAboutToLeave(State):
+               #TODO
+               pass
+       
+       class StateLeaving(State):
+               #TODO
+               pass
+       
+       def __init__(self, actor):
+               self.actor = actor
+               self.callback = ThreadFunction(self._callback)
+               self.current_state = None
+               self.pins = None
+       
+       def stop (self):
+               self.callback.stop()
+       
+       def _callback(self, cmd, arg=None):
+               # update pins
+               if cmd == StateMachine.CMD_PINS:
+                       self.pins = arg
+               # handle stuff
+               newstate = self.current_state.handle_event(cmd,arg) # returns None or an instance of the new state
+               while newstate is not None:
+                       logger.info("StateMachine: new state = %s" % newstate.__class__.__name__)
+                       self.current_state = newstate
+                       newstate = self.current_state.handle_event(StateMachine.CMD_PINS, self.pins)
diff --git a/tuerd b/tuerd
index f03d5dbc69d17d382a1757b8b2fc44851fe668f1..c3a5a43763b89b2a7bb5005169d116d6333864fb 100755 (executable)
--- a/tuerd
+++ b/tuerd
 #!/usr/bin/python3
-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)
-
-#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
+import statemachine, actor, pins, tysock, waker
 
-# ******** definitions *********
-# send to client for information but don't care if it arrives
-def waynesend (conn, what):
-       try:
-               conn.send(what)
-       except:
-               log("Couldn't send %s" % str(what))
+# initialize GPIO stuff
+GPIO.setmode(GPIO.BOARD)
 
-# for command not found: do nothing with the pins and send a "0" to the client
-def doNothing (conn):
-       log ("doing nothing")
-       waynesend(conn,b"0")
+# bring 'em all up
+the_actor = actor.Actor()
+the_machine = statemachine.StateMachine(the_actor)
+the_socket = tysock.TySocket(the_machine)
+the_pins = pins.PinsWatcher(the_machine)
+the_waker = waker.Waker(the_machine)
+
+# we do the socket accept thing in the main thread
+try:
+       the_socket.accept()
+except KeyboardInterrupt:
+       # this is what we waited for!
        pass
 
-# delete a file, don't care if it did not exist in the first place
-def forcerm(name):
-       try:
-               os.unlink (name)
-       except OSError as e:
-               # only ignore error if it was "file didn't exist"
-               if e.errno != errno.ENOENT:
-                       raise
-
-# commands: on a pin do a series of timed on/off switches
-class Pinoutput:
-       # name is for logging and also used for mapping command names to instances of this class
-       # actionsanddelays is a list of pairs: (bool to set on pin, delay in seconds to wait afterwards)
-       def __init__ (self, name, pinnumber, actionsanddelays):
-               self.name = name
-               self.pin = pinnumber
-               self.todo = actionsanddelays
-               GPIO.setup(pinnumber, GPIO.OUT)
-               log ("Pin %d set to be an output pin for %s." % (pinnumber,name))
-       # actually send the signal to the pins
-       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)))
-                       time.sleep(delay)
-               # notify success
-               log 
-               waynesend(conn,b"1")
-
-# ******** configuration *********
-
-tuergroupid = 1005
-socketname = "/run/tuer.sock"
-pinlist = [Pinoutput("open", 12, [(True, 0.3), (False, 5.0)]),
-       Pinoutput("close", 16, [(True, 0.3), (False, 5.0)]),
-       Pinoutput("buzz", 22, [(True, 2.0), (False, 0.1)])]
-
-
-# ******** main *********
-# convert list of pin objects to dictionary for command lookup
-pindict = {}
-for pin in pinlist:
-       pindict[pin.name.encode()] = pin
-
-# create socket
-sock = socket.socket (socket.AF_UNIX, socket.SOCK_STREAM)
-# delete old socket file and don't bitch around if it's not there
-forcerm(socketname)
-# bind socket to file name
-sock.bind (socketname)
-# 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()
-       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))
+# bring 'em all down
+the_waker.stop()
+the_pins.stop()
+the_socket.stop()
+the_machine.stop()
+the_actor.stop()
 
+# shutdown GPIO stuff
+GPIO.cleanup()
diff --git a/tyshell b/tyshell
index 4429c354a2102fbef3d0a75d937e3668921b3bc4..22809262fd4598b150428e7d8a4ea0fe1aef54e2 100755 (executable)
--- a/tyshell
+++ b/tyshell
@@ -16,7 +16,6 @@ except IOError:
     pass
 import atexit
 atexit.register(readline.write_history_file, histfile)
-atexit.register(print, "Bye")
 
 # available commands
 def helpcmd(c):
@@ -34,16 +33,16 @@ def sendcmd(addr, cmd):
                print("Running %s..." % (cmd))
                s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                s.connect(addr)
+               s.settimeout(10.0)
                s.send(cmd.encode())
                data = s.recv(4)
                s.close()
-               print("...done")
-               if data != b'1':
-                       print("Received unexpected answer %s" % str(data))
+               print(data.decode('utf-8'))
        return run
 
 def exitcmd(c):
-       sys.exit(0)
+       print("Bye")
+       return True
 
 commands = {
        'exit': exitcmd,
@@ -68,11 +67,13 @@ while True:
                cmdoptions = [command[0]]
        else:
                cmdoptions = list(filter(lambda x: command[0].startswith(x), commands.keys()))
+       # check how many we found
        if len(cmdoptions) == 0: # no commands fit prefix
                print("Command %s not found. Use help." % command[0])
        elif len(cmdoptions) == 1: # exactly one command fits (prefix)
                try:
-                       commands[cmdoptions[0]](command)
+                       res = commands[cmdoptions[0]](command)
+                       if res: break
                except Exception as e:
                        print("Error while executing %s: %s" % (command[0], str(e)))
        else: # multiple commands fit the prefix
diff --git a/tysock.py b/tysock.py
new file mode 100644 (file)
index 0000000..3951d1f
--- /dev/null
+++ b/tysock.py
@@ -0,0 +1,80 @@
+import socket, os, stat
+from statemachine import StateMachine
+from libtuer import logger
+SO_PEERCRED = 17 # DO - NOT - TOUCH
+
+tuergroupid = 1005
+socketname = "/run/tuer.sock"
+
+# send to client for information but don't care if it arrives
+def waynesend (conn, what):
+       try:
+               conn.send(what.encode())
+       except:
+               pass # we do not care
+
+# delete a file, don't care if it did not exist in the first place
+def forcerm(name):
+       try:
+               os.unlink (name)
+       except OSError as e:
+               # only ignore error if it was "file didn't exist"
+               if e.errno != errno.ENOENT:
+                       raise
+
+# the class doing the actual work
+class TySocket():
+       CMDs = {
+               b'buzz': StateMachine.CMD_BUZZ,
+               b'open': StateMachine.CMD_OPEN,
+       }
+       
+       def __init__(self, sm):
+               self._sm = sm
+               # create socket
+               self._sock = socket.socket (socket.AF_UNIX, socket.SOCK_STREAM)
+               # delete old socket file and don't bitch around if it's not there
+               forcerm(socketname)
+               # bind socket to file name
+               self._sock.bind (socketname)
+               # 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
+               self._sock.listen(1)
+       
+       def _answer(self, conn):
+               def answer(msg):
+                       # this is called in another thread, so it should be quick and not touch the TySocket
+                       waynesend(conn, msg)
+                       conn.close()
+               return answer
+       
+       def accept(self):
+               '''Handles incoming connections and keyboard events'''
+               self._sock.settimeout(None)
+               while True:
+                       # accept connections
+                       conn, addr = self._sock.accept()
+                       conn.settimeout(0.1)
+                       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
+                               logger.info("TySocket: Received command from %s (uid %d): %s" % (pwd.getpwuid(uid).pw_name, uid, str(data)))
+                               # lookup the command, send it to state machine
+                               if data in self.CMDs:
+                                       self._sm.callback(self.CMDs[data], self._answer(conn))
+                                       # _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 Exception as e:
+                               logger.error("TySocket: Something went wrong: %s" % str(e))
+       
+       def stop(self):
+               pass
diff --git a/waker.py b/waker.py
new file mode 100644 (file)
index 0000000..b1d475b
--- /dev/null
+++ b/waker.py
@@ -0,0 +1,13 @@
+from libtuer import ThreadRepeater
+from statemachine import StateMachine
+
+class Waker():
+       def __init__(self, sm):
+               self._sm = sm
+               self._t = ThreadRepeater(self._wake, 0.5)
+       
+       def _wake(self):
+               self._sm.callback(StateMachine.CMD_WAKEUP)
+       
+       def stop(self):
+               self._t.stop()