diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef331f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.conf +*.pyc diff --git a/amivid.py b/amivid.py new file mode 100644 index 0000000..7a81375 --- /dev/null +++ b/amivid.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +""" +Connects to the AMIV REST Server +""" + +import ConfigParser +import random +import hashlib, hmac +import datetime +import urllib +import simplejson +from operator import itemgetter +import sys + +class AmivID: + + def __init__(self,apikey,secretkey,baseurl): + """Prepares a connection to AmivID + + :param apikey: Shared Secret string + :param baseurl: Optional, the URL of the REST service + """ + self.baseurl = baseurl + self.apikey = apikey + self.secretkey = secretkey + + def __sign(self,item,request): + """Computes the correct signature, sorting the request-array + + :param item: string which is searched for + :param request: list of tuples (param, value) + :returns: string which can be put in the GET-request + """ + sortedRequest = sorted(request, key=itemgetter(0)) + encodeRequest = '%s?%s'%(item,urllib.urlencode(request)) + signature = hmac.new(self.secretkey,encodeRequest,hashlib.sha256).hexdigest() + request.append(('signature', signature)) + finalRequest = '%s?%s'%(item,urllib.urlencode(request)) + return finalRequest + + def getUser(self,rfid): + """Gets a User-Dict based on a rfid-code + + :param rfid: 6-Digit RFID number from Legi + :returns: dict with user-infos + """ + #Create request + request = [('apikey', self.apikey), + ('token', int(datetime.datetime.now().strftime("%s"))), + ('type', 'rfid')] + + #Add query & signature + finalRequest = self.__sign("%06d"%(rfid),request) + + try: + userDict = simplejson.load(urllib.urlopen(self.baseurl+finalRequest)) + except ValueError as e: + print "Error in amivID.getUser(), %s"%(e) + return userDict + + def getBeer(self,username): + """Gets the Infos if a user may get a beer (and how many) + + :param username: n.ethz or amiv.ethz.ch username + :returns: True if he gets a beer, False otherwise + """ + request = [('apikey', self.apikey), + ('token', int(datetime.datetime.now().strftime("%s")))] + + #Add query & signature + finalRequest = self.__sign("%s/apps"%(username),request) + + try: + beerDict = simplejson.load(urllib.urlopen(self.baseurl+finalRequest)) + except ValueError as e: + print "Error in amivID.getBeer(), %s"%(e) + return beerDict diff --git a/core.conf b/core.conf new file mode 100644 index 0000000..95ac609 --- /dev/null +++ b/core.conf @@ -0,0 +1,13 @@ +#Config File for the Bierautomat + +[mysql] +user=root +pass= +db=bierlog +table=bierlog +host=localhost + +[amivid] +apikey= +secretkey= +baseurl= diff --git a/core.py b/core.py new file mode 100755 index 0000000..8f1eaf1 --- /dev/null +++ b/core.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python +""" +Heart of the Bier-Automat. Will connect to the LegiLeser and decide if you get a beer. +is also responsible for starting all the (future) fancy stuff +""" + +import MySQLdb as mdb +import ConfigParser +import datetime as dt +import legireader +import amivid +import visid +import os.path +import urllib + +debug = True + +def amivAuth(rfid): + """Checks if the user may get a beer sponsored by AMIV + :param rfid: Legi-Code of user + :returns: tuple (user,bool) where the first value says returns the user-login (or None) and the second if he may get a beer + """ + #Connect to amivid and get user object + aid = amivid.AmivID(apikey=config.get("amivid", "apikey"),secretkey=config.get("amivid", "secretkey"),baseurl=config.get("amivid", "baseurl")) + user = aid.getUser(rfid) + if not user: + return(None,False) + login = user['nethz'] + + #Use login-name to check if he may get a beer + #Old: API-Query beer = aid.getBeer(login) + #New: Take it from the user object + beer = user['apps'] + + #return result, first bool says if user was found, second if he may get a beer) + if (debug): print "Debug: Beer-Count %s"%(beer['beer']) + return (login,int(beer['beer']) > 0) + +def visAuth(rfid): + """Checks if the user may get a beer sponsored by AMIV + :param rfid: Legi-Code of user + :returns: tuple (user,bool) where the first value says returns the user-login (or None) and the second if he may get a beer + """ + #Get secret key + #config = ConfigParser.RawConfigParser() + #config.readfp(open('core.conf')) + + #Connect to visid and get user object + vid = visid.VisID(baseurl=config.get("visid", "baseurl")) + user = vid.getUser(rfid) + if not user: + return(None,False) + login = user['nethz'] + + #Use login-name to check if he may get a beer + beer = vid.getBeer(login) + + #return result, first bool says if user was found, second if he may get a beer) + return (login,int(beer['beer']) > 0) + + +def __getAuthorization(rfid): + """Here later the real auth must be done, returning None, "AMIV", "VIS" or "MONEY" (or whatever) + + :param rfid: Integer holding the Legi-Card-Number + :returns: Tuple of Username and auth. organization (can be 'noBeer') or (None,'notRegistered') if not registered + """ + returnString = 'notRegistered' + returnUser = None + #Ask AMIV for auth + aA = amivAuth(rfid) + if aA[0]: + if aA[1]: + return (aA[0],'amiv') + else: + returnUser = aA[0] + returnString = 'noBeer' + #Ask VIS if not true from AMIV + vA = visAuth(rfid) + if vA[0]: + if vA[1]: + return (vA[0],'vis') + else: + returnUser = vA[0] + returnString = 'noBeer' + + return (returnUser,returnString) + + +def sendToScreen(var, value): + """Sends a value using POST to the local (flask) server, used to communicate with the GUI""" + serverUrl = "http://localhost:5000/_checkLegi" + request = {var: value} + try: + reply = urllib.urlopen(serverUrl+"?"+urllib.urlencode(request)).read() + except IOError: + print "Could not connect to local webserver" + return + if (debug): print "sendToScreen with %s: %s, reply was %s"%(var,value,reply) + + +def showCoreMessage(page, code=None, sponsor=None, user=None): + """Displays a HTML-page on the Display in the core-part, showing the basic info if a legi was accepted""" + if page == 'authorized': + #Template for sending the info to the display + #sendToScreen('beerState','authorized') + print "%s authorized by %s, press the button!"%(user,sponsor) + + if page == 'notAuthorized': + print "Not authorized, user was: %s"%(user) + + if page == 'notRegistered': + print "Not registered, legi was: %s"%(code) + + if page == 'freeBeer': + print "%s got a free beer, sponsored by %s"%(user,sponsor) + +def startApp(appname): + """Starts a custom app""" + pass + + +if __name__ == "__main__": + debug = True + + #Load Config Parameters + config = ConfigParser.RawConfigParser() + config.readfp(open(os.path.dirname(__file__) + '/core.conf')) #changed this as well because not right directory otherwise (Fadri) + + dbuser = config.get("mysql", "user") + dbpass = config.get("mysql", "pass") + dbdatabase = config.get("mysql", "db") + dbtable = config.get("mysql", "table") + dbhost = config.get("mysql", "host") + + print "ConfigFile core.conf read" + + #Connect to Log-DB + conn = mdb.connect(dbhost,dbuser,dbpass,dbdatabase) + cursor = conn.cursor() + + #Initialize connection to LegiLeser + lr = legireader.LegiReader() #Also accepts device, baud and waittime parameters + + print "Connection to LegiReader established" + + authorize = None + lastUser = 0 + lastUserTime = dt.datetime.min + + #Start endless-loop + try: + while 1: + #Wait for new Legi or Button, read it + #print "Waiting for User Input (reading a Legi)" + (legi,button) = lr.getLegiOrButton() + + #print "Detected User Input - Legi=%s, Button=%s"%(legi,button) + + #check if legi may get a beer + if legi: + #For debug: Time the auth-query + #starttime = dt.datetime.now() + authorize = __getAuthorization(legi) + #print "Auth-Time: %s"%(dt.datetime.now()-starttime) + + if authorize[1] == 'amiv' or authorize[1] == 'vis': + showCoreMessage('authorized',sponsor=authorize[1],user=authorize[0]) + lastUserTime = dt.datetime.now() + lastUser = authorize[0] + #Activate free beer + lr.setFreeBeer() + elif authorize[1] == 'noBeer': + showCoreMessage('notAuthorized',user=authorize[0]) + elif authorize[1] == 'notRegistered': + showCoreMessage('notRegistered',code=legi) + else: + print >>sys.stderr, "Authorization failed" + + elif button and authorize: + #A Button was pressed, check if a legi was read in the last two seconds + if (dt.datetime.now()-lastUserTime < dt.timedelta(seconds=5)): + #Show that a free beer was server + showCoreMessage('freeBeer',sponsor=authorize[1],user=authorize[0]) + #Log it + queryString = "INSERT INTO %s(username, org, time, slot) VALUES (%%s,%%s,NOW(),%%s)"%(dbtable) + cursor.execute(queryString, + (lastUser,authorize[1], button)) + #Un-Authorize user + authorize = None + + else: + #Something else happened, send it to the Debug-Output and continue + if (debug): print "Did not detect anything useful while reading" + continue + finally: + #Process crashed or it was terminated, close open connections + if (debug): print "Program stopped, cleaning up..." + cursor.close() + conn.close() diff --git a/hello.html b/hello.html new file mode 100644 index 0000000..b8bec01 --- /dev/null +++ b/hello.html @@ -0,0 +1,7 @@ + +Are you getting a beer? + + diff --git a/legireader.py b/legireader.py new file mode 100644 index 0000000..9d085e6 --- /dev/null +++ b/legireader.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +"""Class which can connect to the Legi Reader +- Read Legi +- Read pressed Button +- Enables Free Beer +""" +import serial +import time + +class LegiReader: + """Constructor, establishes a connection with the given parameters + + :param device: Port where the Legi Reader is connected to + :param baud: Baudrate, default: 9600 + :param waittime: Timeout for Connection (float, in seconds). Default: 0.1 + """ + def __init__(self,device='/dev/ttyS0',baud=9600,waittime=0.1): + self.ser = serial.Serial(device, baud, timeout=waittime) + + """Close the serial connection when the object is terminated""" + def __del__(self): + self.ser.close() + + """Returns a tuple holding a legi-id (or False if Button pressed) and the Button which was pressed (or False if Legi read) + + :returns: (legi,button) + (c) by Stephan Mueller""" + def getLegiOrButton(self): + try: + while 1: + b = '' + # wait for start byte + while b == '' or b != '@': + b = self.ser.read(1) + + #First Byte says if it is a legi (l) or a button (c) which was pressed + b = self.ser.read(1) + + if b == 'l': + b = self.ser.read(6) + return (int(b),False) + elif b == 'c': + b = self.ser.read(1) + return (False,int(b)) + + except ValueError as e: + print "Not a Legi or Button which was pressed. This was read: %s"%(b) + raise e + + + legiNow = True + """Demo-Funktion, will simulate a 'random' legi every 5 seconds, and a button 1 second later + Returns a tuple holding a legi-id (or False if Button pressed) and the Button which was pressed (or False if Legi read) + + :returns: (legi,button)""" + def getLegiOrButtonFake(self): + if (self.legiNow): + print "Fake LegiReader: waiting 5 seconds until Legi 013579/034617 will be read" + self.legiNow = False + time.sleep(5) + return (int('013579'),False) + else: + print "Fake LegiReader: waiting 1 second until button 1 is pressed" + self.legiNow = True + time.sleep(1) + return (False,int('1')) + + def setFreeBeer(self,slots=(True,True,False,False)): + """Sets the Jumper so that a free beer can be released + Because of a bug slot 1+2 will always be active at the same time + + :param slots: Sets which slots will give a free beer/drink + """ + #Convert slots to jumper + # Jumper 1 = Fach 3 + # Jumper 2 = Fach 4 + # Jumper 3+4 = Fach 1+2 (somehow combined) + jumper = [0,0,0,0] + if slots[0]: + jumper[2] += 2 + jumper[3] += 2 + elif slots[1]: + jumper[2] += 2 + jumper[3] += 2 + if slots[2]: + jumper[0] += 2 + if slots[3]: + jumper[1] += 2 + + #Activate LEDs, send Jumpers to Automat + led = '' + for slot in slots: + if slot: + led += 'F' + else: + led += '0' + out = '@s'+str(jumper[0])+str(jumper[1])+str(jumper[2])+str(jumper[3])+led+str(0) + self.ser.write( out+'\r' ) + + #print "Activated FreeBeers, now the button must be pressed in 5 seconds" diff --git a/templates/beer.html b/templates/beer.html new file mode 100644 index 0000000..443a5d1 --- /dev/null +++ b/templates/beer.html @@ -0,0 +1,163 @@ + + + +Bierautomat 2.0: Frontend Prototype + + + + + + + + + +
+ + + + + +
+ +
+

AMIV Bierautomat 2.0

+

+ Status: Du hast noch ein Freibier!
+ Bitte Knopf drücken. +

+

+ + Status: Leider schon gebraucht!
+ Musst du halt bis morgen warten... ;-) +

+

+ Status: Deine Legi ist leider nicht registriert!
+ Die Registrierung kannst du gleich selbst vornehmen. +

+ +

+ Status: Es ist alles OK!
+ Für Bierbezug bitte die Legi an den Leser halten. +

+

+
+ +
+ +
+ + + + + + + + + + + + + diff --git a/templates/hello.html b/templates/hello.html new file mode 100644 index 0000000..e4fa63a --- /dev/null +++ b/templates/hello.html @@ -0,0 +1,10 @@ + +Are you getting a beer? + + + + diff --git a/templates/valid.html b/templates/valid.html new file mode 100644 index 0000000..392e0ba --- /dev/null +++ b/templates/valid.html @@ -0,0 +1,4 @@ + +Are you getting a beer? +

Blubberli

+ diff --git a/visid.py b/visid.py new file mode 100644 index 0000000..11ccacb --- /dev/null +++ b/visid.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +""" +Connects to the AMIV REST Server +""" + +import ConfigParser +import random +import hashlib, hmac +import datetime +import urllib +import simplejson +from operator import itemgetter +import sys + +class VisID: + + def __init__(self,baseurl,apikey=None,secretkey=None): + """Prepares a connection to VisID + + :param apikey: Shared Secret string + :param baseurl: Optional, the URL of the REST service + """ + self.baseurl = baseurl + self.apikey = apikey + self.secretkey = secretkey + + def __sign(self,item,request): + """Computes the correct signature, sorting the request-array + + :param item: string which is searched for + :param request: list of tuples (param, value) + :returns: string which can be put in the GET-request + """ + sortedRequest = sorted(request, key=itemgetter(0)) + encodeRequest = '%s?%s'%(item,urllib.urlencode(request)) + signature = hmac.new(self.secretkey,encodeRequest,hashlib.sha256).hexdigest() + request.append(('signature', signature)) + finalRequest = '%s?%s'%(item,urllib.urlencode(request)) + return finalRequest + + def getUser(self,rfid): + """Gets a User-Dict based on a rfid-code + + :param rfid: 6-Digit RFID number from Legi + :returns: dict with user-infos + """ + #Create request + request = [('format', 'json')] + + #Add query & signature + finalRequest = "rfid/%06d?%s"%(rfid,urllib.urlencode(request)) + + try: + userDict = simplejson.load(urllib.urlopen(self.baseurl+finalRequest)) + return userDict + except ValueError as e: + return None + + + def getBeer(self,username): + """Gets the Infos if a user may get a beer (and how many) + + :param username: n.ethz or amiv.ethz.ch username + :returns: True if he gets a beer, False otherwise + """ + #Create request + request = [('format', 'json')] + + #Add query & signature + finalRequest = "status/%s?%s"%(username,urllib.urlencode(request)) + try: + beerDict = simplejson.load(urllib.urlopen(self.baseurl+finalRequest)) + return beerDict + except ValueError: + return None diff --git a/visualWeb.py b/visualWeb.py new file mode 100755 index 0000000..d06f1dc --- /dev/null +++ b/visualWeb.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +from flask import Flask, jsonify, render_template, request +import sys + +app = Flask(__name__) +getBeer = 1 +@app.route('/') +def index(): + return 'Index Page' + +@app.route('/beer/') +@app.route('/beer/') +def beer(beer=None): + return render_template('beer.html', beer=beer) + +@app.route('/valid') +def valid(): + return render_template('valid.html') + +@app.route('/ajax') +def ajax(): + return render_template('ajax.html') + +@app.route('/hello') +def hello(): + return render_template('hello.html') + +@app.route('/_checkLegi', methods=['POST','GET']) +def checkLegi(): + + global getBeer + getBeer = request.args.get('getBeer', 0, type=int) + print >>sys.stderr,getBeer + return "" + +@app.route('/_updateLegi') +def updateLegi(): + global getBeer + if (getBeer > 0): + releaseBeer = getBeer + else: + releaseBeer = getBeer + return jsonify(result=releaseBeer) + + +if __name__ == '__main__': + app.run('127.0.0.1',debug=True)