#!/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

# ******** 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))

# 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")
	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))