Explain the prefix stuff a bit more
[saartuer.git] / tuerd
1 #!/usr/bin/python3
2 import time, socket, os, stat, atexit, errno, struct, pwd
3 from libtuer import log
4 from datetime import datetime
5 import RPi.GPIO as GPIO
6 SO_PEERCRED = 17 # DO - NOT - TOUCH
7 GPIO.setmode(GPIO.BOARD)
8 atexit.register(GPIO.cleanup)
9
10 #tmp
11 def recv_timeout(conn, size, time):
12         (r, w, x) = select.select([conn], [], [], time)
13         if len(r):
14                 assert r[0] == conn
15                 return conn.recv(size)
16         return None
17
18 # ******** definitions *********
19 # send to client for information but don't care if it arrives
20 def waynesend (conn, what):
21         try:
22                 conn.send(what)
23         except:
24                 log("Couldn't send %s" % str(what))
25
26 # for command not found: do nothing with the pins and send a "0" to the client
27 def doNothing (conn):
28         log ("doing nothing")
29         waynesend(conn,b"0")
30         pass
31
32 # delete a file, don't care if it did not exist in the first place
33 def forcerm(name):
34         try:
35                 os.unlink (name)
36         except OSError as e:
37                 # only ignore error if it was "file didn't exist"
38                 if e.errno != errno.ENOENT:
39                         raise
40
41 # commands: on a pin do a series of timed on/off switches
42 class Pinoutput:
43         # name is for logging and also used for mapping command names to instances of this class
44         # actionsanddelays is a list of pairs: (bool to set on pin, delay in seconds to wait afterwards)
45         def __init__ (self, name, pinnumber, actionsanddelays):
46                 self.name = name
47                 self.pin = pinnumber
48                 self.todo = actionsanddelays
49                 GPIO.setup(pinnumber, GPIO.OUT)
50                 log ("Pin %d set to be an output pin for %s." % (pinnumber,name))
51         # actually send the signal to the pins
52         def __call__ (self, conn):
53                 for (value,delay) in self.todo:
54                         GPIO.output(self.pin, value)
55                         # log ("%s: Pin %d set to %s." % (self.name,self.pin,str(value)))
56                         time.sleep(delay)
57                 # notify success
58                 log 
59                 waynesend(conn,b"1")
60
61 # ******** configuration *********
62
63 tuergroupid = 1005
64 socketname = "/run/tuer.sock"
65 pinlist = [Pinoutput("open", 12, [(True, 0.3), (False, 5.0)]),
66         Pinoutput("close", 16, [(True, 0.3), (False, 5.0)]),
67         Pinoutput("buzz", 22, [(True, 2.0), (False, 0.1)])]
68
69
70 # ******** main *********
71 # convert list of pin objects to dictionary for command lookup
72 pindict = {}
73 for pin in pinlist:
74         pindict[pin.name.encode()] = pin
75
76 # create socket
77 sock = socket.socket (socket.AF_UNIX, socket.SOCK_STREAM)
78 # delete old socket file and don't bitch around if it's not there
79 forcerm(socketname)
80 # bind socket to file name
81 sock.bind (socketname)
82 # allow only users in the tuergroup to write to the socket
83 os.chown (socketname, 0, tuergroupid)
84 os.chmod (socketname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP)
85 # listen to the people, but not too many at once
86 sock.listen(1)
87
88 # shutdown handling
89 def shutdown():
90         log("Shutting down")
91         sock.close()
92         forcerm(socketname)
93 atexit.register(shutdown)
94
95 # main loop
96 # FIXME: DoS by opening socket but not sending data, because this loop is single threaded; maybe settimeout helps a bit.
97 while True:
98         # accept connections
99         conn, addr = sock.accept()
100         try:
101                 # get peer information
102                 (pid, uid, gid) = (struct.unpack('3i', conn.getsockopt(socket.SOL_SOCKET, SO_PEERCRED, struct.calcsize('3i'))))
103                 # get some data from the client (enough to hold any valid command)
104                 data = conn.recv (32)
105                 # log the command
106                 log("received command from %s (uid %d): %s" % (pwd.getpwuid(uid).pw_name,uid, str(data)))
107                 # lookup the command, if it's not in the dict, use the doNothing function instead
108                 # and execute the looked up command or doNothing with the connection, so it can respond to the client
109                 pindict.get(data,doNothing)(conn)
110                 log("done")
111                 # close connection cleanly
112                 conn.close()
113         except Exception as e:
114                 log("Something went wrong: %s\n...continuing." % str(e))
115