From: Mark Tearle Date: Sat, 8 Nov 2014 14:05:44 +0000 (+0800) Subject: Move VendServer code up a directory wholesale and rename X-Git-Tag: HACK-SLAH-BURN-ENDS~1 X-Git-Url: https://git.ucc.asn.au/?p=uccvend-vendserver.git;a=commitdiff_plain;h=6c130dc942392ccc8b7ffa0ec2ede5f9327f99e6 Move VendServer code up a directory wholesale and rename --- diff --git a/VendServer/.cvsignore b/VendServer/.cvsignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/VendServer/.cvsignore @@ -0,0 +1 @@ +*.pyc diff --git a/VendServer/CRC.py b/VendServer/CRC.py new file mode 100644 index 0000000..512a7bd --- /dev/null +++ b/VendServer/CRC.py @@ -0,0 +1,16 @@ +crctab = [] +CRC16 = 0x1021 + +def do_crc(message, crc = 0): + for i in range(0,len(message)): + crc = ((crc << 8) ^ (crctab[(crc >> 8) ^ ord(message[i])])) & 0xffff + return crc + +# Generate crctab +for val in range(0,256): + crc = val << 8; + for i in range(0,8): + crc = crc << 1 + if (crc & 0x10000): + crc = crc ^ CRC16 + crctab.append(crc & 0xffff) diff --git a/VendServer/DoorClient.py b/VendServer/DoorClient.py new file mode 100755 index 0000000..d46cebe --- /dev/null +++ b/VendServer/DoorClient.py @@ -0,0 +1,30 @@ +#!/usr/bin/python + +from LATClient import LATClient +from select import select +import signal +import sys + +def check_door_service(service, test_string="got wombles?"): + latclient = LATClient(service=sys.argv[1]) + latclient.sock.send(test_string) + rfh, wfh = latclient.get_fh() + wfh.write(test_string) + wfh.flush() + rr, wr, er = select([rfh], [], [], 10.0) + if rfh not in rr: return "open" + recv = rfh.read(len(test_string)) + if recv <> test_string: return "error" + return "closed" + +if __name__ == '__main__': + result_codes = { 'open' : 0, 'closed' : 1, 'error' : 2, 'invalid args' : 3} + def return_result(result): + print result + sys.exit(result_codes[result]) + def timeout(signum, frame): + return_result("error") + if len(sys.argv) <> 2: return_result('invalid args') + signal.signal(signal.SIGALRM, timeout) + signal.alarm(15) + return_result(check_door_service(sys.argv[1])) diff --git a/VendServer/HorizScroll.py b/VendServer/HorizScroll.py new file mode 100644 index 0000000..d27f7dc --- /dev/null +++ b/VendServer/HorizScroll.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +import string +import sys +import time + +class HorizScroll: + def __init__(self, text): + self.text = text + pass + + def expand(self, padding=None, paddingchar=" ", dir=None, wraparound=False): + if len(self.text) <= 10: + return [text] + + if padding == None: + padding = len(self.text) / 2 + 1 + + format = "%-" + str(padding) + "." + str(padding) + "s" + pad = string.replace(format % " "," ",paddingchar) + padtext = self.text + pad + if not wraparound: + numiters = len(self.text) - 10 + else: + numiters = len(padtext) + + expansion = [] + + for x in range(0,numiters): + expansion.append("%-10.10s" % (padtext[x:] + padtext[:x])) + + if dir == -1: + expansion.reverse() + + return expansion + +if __name__ == '__main__': + h = HorizScroll("hello cruel world") + eh = h.expand() + while 1: + for x in eh: + sys.stdout.write("\r") + print "%-10.10s" % x, + sys.stdout.flush() + time.sleep(0.1) + diff --git a/VendServer/Idler.py b/VendServer/Idler.py new file mode 100755 index 0000000..5b66455 --- /dev/null +++ b/VendServer/Idler.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python + +import string, time, os +from subprocess import Popen, PIPE +from random import random +from MessageKeeper import MessageKeeper + +orderings = None + +IDLER_TEXT_SPEED=1.8 + +class Idler: + def __init__(self, v): + self.v = v + + def next(self): + """Displays next stage of the idler. Returns time to the next step""" + return 1 + + def reset(self): + """Resets the idler to a known intial state""" + pass + + def finished(self): + """Returns True if the idler is considered finished""" + return False + + def affinity(self): + """How much we want this idler to be the next one chosen""" + return 1 + +class GreetingIdler(Idler): + def __init__(self, v, secs_to_greeting = None): + self.v = v + self.secs_to_greeting = secs_to_greeting + self.message_displayed = False + + def next(self): + if not self.secs_to_greeting is None: + x = self.secs_to_greeting + self.secs_to_greeting = None + return x + + self.v.display('UCC SNACKS') + self.message_displayed = True + return 5 + + def reset(self): + self.message_displayed = False + self.secs_to_greeting = None + + def finished(self): + return self.message_displayed + + def affinity(self): + return 0 + +class TrainIdler(Idler): + def __init__(self, v): + self.idle_state = 0 + self.v = v + + def put_shark(self, s, l): + if self.s[l] == ' ': + self.s[l] = s + elif self.s[l] == 'X': + self.s[l] = '*' + else: + self.s[l] = 'X' + + def next(self): + # does the next stage of a dance + self.s = [' ']*10 + shark1 = self.idle_state % 18 + if shark1 < 9: + self.put_shark('^', shark1) + else: + self.put_shark('^', 18-shark1) + + shark2 = ((self.idle_state+4) % 36)/2 + if shark2 < 9: + self.put_shark('<', shark2) + else: + self.put_shark('<', 18-shark2) + + shark3 = ((self.idle_state+7) % 54)/3 + if shark3 < 9: + self.put_shark('>', 9-shark3) + else: + self.put_shark('>', 9-(18-shark3)) + + train1 = ((self.idle_state%(18*36))) + train1_start = 122 + if train1 > train1_start and train1 < train1_start+(10*2): + for i in range(5): + ptr = i+train1-train1_start-5 + if ptr >= 0 and ptr < 10: self.s[ptr] = '#' + + train2 = ((self.idle_state%(18*36))) + train2_start = 400 + if train2 > train2_start and train2 < train2_start+(10*2): + for i in range(5): + ptr = i+train2-train2_start-5 + if ptr >= 0 and ptr < 10: self.s[9-ptr] = '#' + + train3 = ((self.idle_state%(18*36))) + train3_start = 230 + if train3 > train3_start and train3 < train3_start+(10*2): + for i in range(10): + ptr = i+train3-train3_start-10 + if ptr >= 0 and ptr < 10: self.s[ptr] = '-' + + self.v.display(string.join(self.s, '')) + self.idle_state += 1 + self.idle_state %= 18*36*54 + + def reset(self): + self.idle_state = 0 + +class OrderMaker: + def __init__(self, n=8): + self.n = n + self.make_factorials(n) + + def make_factorials(self, n): + self.factorial = [] + a = 1 + for i in range(1,n+1): + self.factorial.append(a) + a *= i + + def order(self, index): + used = [] + for i in range(0,self.n): + used.append(i) + i = self.n-1 + j = 0 + res = [] + while i >= 0: + a = index/self.factorial[i] + index %= self.factorial[i] + res.append(a+1) + i -= 1 + j += 1 + for i in range(0,self.n): + tmp = used[res[i]-1] + for j in range(res[i],self.n): + used[j-1] = used[j] + res[i] = tmp + return res + + def __getitem__(self, i): + return self.order(i) + +class GrayIdler(Idler): + def __init__(self, v, one=None, zero=None, reorder=0): + self.bits = 8 + self.size = 1 << self.bits + self.i = 0 + self.grayCode = 0 + self.v = v + self.one = one + self.zero = zero + self.reorder = reorder + global orderings + if not orderings: + orderings = OrderMaker() + + def next(self): + output = self.do_next_state() + # does the next stage of a dance + if self.zero: + output = string.replace(output, "0", self.zero) + if self.one: + output = string.replace(output, "1", self.one) + if self.reorder: + global orderings + newoutput = "" + for i in range(0,8): + newoutput += output[orderings[self.reorder][i]] + output = newoutput + self.v.display(" %8.8s " % (output)) + self.i = (self.i + 1) % self.size + + def do_next_state(self): + self.grayCode = self.i ^ (self.i >> 1) + output = self.dec2bin(self.grayCode) + + return "0"*(self.bits-len(output))+output + + + def dec2bin(self,num): + """Convert long/integer number to binary string. + + E.g. dec2bin(12) ==> '1100'. + + from http://starship.python.net/~gherman/playground/decbingray/decbingray.py""" + + assert num >= 0, "Decimal number must be >= 0!" + + # Gracefully handle degenerate case. + # (Not really needed, but anyway.) + if num == 0: + return '0' + + # Find highest value bit. + val, j = 1L, 1L + while val < num: + val, j = val*2L, j+1L + + # Convert. + bin = '' + i = j - 1 + while i + 1L: + k = pow(2L, i) + if num >= k: + bin = bin + '1' + num = num - k + else: + if len(bin) > 0: + bin = bin + '0' + i = i - 1L + + return bin + + def reset(self): + self.i = 0 + self.grayCode = 0 + if self.reorder: + self.reorder = int(random()*40319)+1 + + +class StringIdler(Idler): + def __init__(self, v, text="Hello Cruel World! ",repeat=True): + self.v = v + self.mk = MessageKeeper(v) + self.text = self.clean_text(text) + " " + + msg = [("",False, None),(self.text, repeat, IDLER_TEXT_SPEED)] + self.mk.set_messages(msg) + + def clean_text(self, text): + # nothing like a bit of good clean text :) + valid = string.digits \ + + string.letters \ + + string.punctuation \ + + " " + # uppercase it + text = string.upper(text) + clean = "" + for char in text: + if char in valid: + clean = clean + char + else: + clean = clean + " " + return clean + + def next(self): + self.mk.update_display() + + def finished(self): + return self.mk.done() + +class ClockIdler(Idler): + def __init__(self, v): + self.v = v + self.last = None + + def next(self): + colonchar = ':' + if int(time.time()*2) & 1: colonchar = ' ' + output = time.strftime("%%H%c%%M%c%%S"%(colonchar,colonchar)) + if output != self.last: + self.v.display(" %8.8s " % (output)) + self.last = output + + def affinity(self): + return 3 + +class FortuneIdler(StringIdler): + def __init__(self, v): + fortune = "/usr/games/fortune" + text = "I broke my wookie...." + if os.access(fortune,os.F_OK|os.X_OK): + (lines, unused) = Popen((fortune,), close_fds=True, stdout=PIPE).communicate() + text = string.join(lines) + StringIdler.__init__(self, v, text,repeat=False) + + def affinity(self): + return 20 + +class PipeIdler(StringIdler): + def __init__(self, v, command, args): + text = "I ate my cookie...." + if os.access(command,os.F_OK|os.X_OK): + (lines, unused) = Popen([command,] + args.split(), close_fds=True, stdout=PIPE).communicate() + text = string.join(lines) + StringIdler.__init__(self, v, text,repeat=False) + + def affinity(self): + return 20 + +class FileIdler(StringIdler): + def __init__(self, v, thefile=None, repeat=False, affinity=8): + text = "I broke my wookie...." + self._affinity = affinity + + if file and os.access(thefile,os.F_OK|os.R_OK): + f = file(thefile,'r') + text = string.join(f.readlines()) + f.close() + StringIdler.__init__(self, v, text,repeat=repeat) + + def affinity(self): + return self._affinity diff --git a/VendServer/LATClient.py b/VendServer/LATClient.py new file mode 100644 index 0000000..638d20b --- /dev/null +++ b/VendServer/LATClient.py @@ -0,0 +1,157 @@ +from socket import * +from select import select +from os import popen4 +from time import sleep +import logging + +LATCP_SOCKET = '/var/run/latlogin' + +LAT_VERSION = '1.22' +LAT_VERSION = '1.24' # for running on Mermaid. [DAA] 20071107 +LATCP_CMD_VERSION = 8 +LATCP_CMD_TERMINALSESSION = 26 +LATCP_CMD_ERRORMSG = 99 + +class LATClientException(Exception): pass + +def read_for_a_bit(rfh): + message = '' + while 1: + r = select([rfh], [], [], 5.0)[0] + if r: + try: + ch = rfh.read(1) + except socket.error: + ch = '' + if ch == '': + break + message = message + ch + else: + break + logging.debug("Received message: ", repr(message)) + return message + +def write_and_get_response(rfh, wfh, message, expect_echo=True): + logging.debug("Writing message:", repr(message)) + wfh.write(message+'\r\n') + wfh.flush() + logging.debug(" --> Sent") + response = read_for_a_bit(rfh) + if response.find(message) == -1 and expect_echo: + raise LATClientException("Talking to DEC server, expected to find original message in echo but didn't") + return response + +class LATClient: + def __init__(self, service = None, node = None, port = None, + localport = None, password = None, is_queued = False, + server_name = '', connect_password='', priv_password=''): + + self.server_name = server_name + self.connect_password = connect_password + self.priv_password = priv_password + + self.sock = socket(AF_UNIX, SOCK_STREAM, 0); + self.sock.connect(LATCP_SOCKET) + self.send_msg(LATCP_CMD_VERSION, LAT_VERSION+'\000') + (cmd, msg) = self.read_reply() + if service == None: service = '' + if node == None: node = '' + if port == None: port = '' + if localport == None: localport = '' + if password == None: password = '' + if is_queued == True: + is_queued = 1 + else: + is_queued = 0 + self.send_msg(LATCP_CMD_TERMINALSESSION, '%c%c%s%c%s%c%s%c%s%c%s' % \ + (is_queued, + len(service), service, + len(node), node, + len(port), port, + len(localport), localport, + len(password), password + )) + (cmd, msg) = self.read_reply() + if ord(cmd) == LATCP_CMD_ERRORMSG: + raise LATClientException(msg) + + self.rfh = self.sock.makefile('r') + self.wfh = self.sock.makefile('w') + + r = select([self.rfh], [], [], 2.0)[0] + if r: + l = self.rfh.readline() + if l.find('Service in use') >= 0: + logging.warning("Service in use, apparently: restarting DEC server") + self.reboot_server() + + def __del__(self): + try: + self.sock.close() + self.sock.shutdown(2) + except: + pass + del self.sock + + def send_msg(self, cmd, msg): + self.sock.send('%c%c%c%s'%(cmd, len(msg)/256, len(msg)%256, msg)) + + def reboot_server(self): + self.sock.shutdown(2) + self.sock.close() + + logging.info('Logging into DEC server') + mopw, mopr = popen4('/usr/sbin/moprc '+self.server_name) + write_and_get_response(mopr, mopw, '') + + logging.info('Sending password') + r = write_and_get_response(mopr, mopw, self.connect_password, False) + if r.find('Enter username> ') == -1: + logging.warning("Expected username prompt, got " + repr(r)) + raise LATClientException('failed to reboot server') + + logging.info('Sending username') + r = write_and_get_response(mopr, mopw, 'grim reaper') + if r.find('Local> ') == -1: + logging.warning("Expected DEC server prompt, got " + repr(r)) + raise LATClientException('failed to reboot server') + + logging.info('Requesting privileges') + r = write_and_get_response(mopr, mopw, 'set priv') + if r.find('Password> ') == -1: + logging.warning("Expected priv password prompt, got " + repr(r)) + raise LATClientException('failed to reboot server') + + logging.info('Sending password') + r = write_and_get_response(mopr, mopw, self.priv_password, False) + if r.find('Local> ') == -1: + logging.warning("Expected DEC server prompt, got " + repr(r)) + raise LATClientException('failed to reboot server') + + logging.info('Sending reboot request') + r = write_and_get_response(mopr, mopw, 'init del 0') + if r.find('Target does not respond') == -1: + logging.warning("Expected DEC server to die, got " + repr(r)) + raise LATClientException('failed to reboot server') + + logging.info('Closed connection to server') + mopr.close() + mopw.close() + logging.info("Waiting 10 seconds for DEC server to come back to life...") + sleep(10) + logging.info("Rightyo, back to vending!") + raise LATClientException('needed to reboot server') + + def read_reply(self): + head = self.sock.recv(3) + if len(head) != 3: + raise LATClientException('Short LAT packet') + cmd = head[0] + length = ord(head[1])*256 + ord(head[2]) + msg = self.sock.recv(length) + if cmd == LATCP_CMD_ERRORMSG: + raise LATClientException('Received LAT error: %s'%msg) + return (cmd, msg) + + def get_fh(self): + return (self.rfh, self.wfh) diff --git a/VendServer/LDAPConnector.py b/VendServer/LDAPConnector.py new file mode 100644 index 0000000..8699fd2 --- /dev/null +++ b/VendServer/LDAPConnector.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python2.4 + +import ldap +import ldap.filter + +LDAP_TIMEOUT = 10 + +def get_ldap_connection(): + ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/ssl/UCC-CA.crt') + ldap.set_option(ldap.OPT_X_TLS,1) + ldap.set_option(ldap.OPT_X_TLS_ALLOW,1) + #ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) + conn = ldap.initialize('ldaps://mussel.ucc.gu.uwa.edu.au/') + + binddn = 'cn=mifareagent,ou=profile,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' + passfile = open('/etc/dispense2/ldap.passwd') + password = passfile.readline().strip() + passfile.close() + + conn.simple_bind_s(binddn, password) + return conn + +def get_uid(card_id): + ldapconn = get_ldap_connection() + + basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' + filter = ldap.filter.filter_format('(uccDispenseMIFARE=%s)', (card_id, )) + attrs = ('uidNumber',) + + results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) + + ldapconn.unbind() + + if len(results) != 1: + raise ValueError, "no UID found for card ID" + + return results[0][1]['uidNumber'][0] + +def get_uname(uid): + ldapconn = get_ldap_connection() + + basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' + filter = ldap.filter.filter_format('(uidNumber=%s)', (uid, )) + attrs = ('uid',) + + results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) + + ldapconn.unbind() + + if len(results) != 1: + raise ValueError, "no username found for user id" + + return results[0][1]['uid'][0] + +def set_card_id(uidNumber, card_id): + ldapconn = get_ldap_connection() + + # fix uidNumber for three/four digit uids + uidNumber = str(int(uidNumber)) + basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' + filter = ldap.filter.filter_format('(uidNumber=%s)', (uidNumber, )) + attrs = ('objectClass', ) + + results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) + + if len(results) != 1: + raise "ValueError", 'error in uidNumber' + + user_dn = results[0][0] + + mod_attrs = [] + + # Does it have the correct object class? + if 'uccDispenseAccount' not in results[0][1]['objectClass']: + # Add uccDispenseAccount objectclass + mod_attrs.append((ldap.MOD_ADD, 'objectClass', 'uccDispenseAccount')) + + # Add MIFARE Card ID + mod_attrs.append((ldap.MOD_ADD, 'uccDispenseMIFARE', card_id)) + + # Use a double-try here to work around something that's fixed in Python 2.5 + try: + try: + ldapconn.modify_s(user_dn, mod_attrs) + except ldap.TYPE_OR_VALUE_EXISTS, e: + pass + finally: + ldapconn.unbind() + +if __name__ == '__main__': + set_card_id('11126', '\x01\x02\x03\x04\x05\x06') + print get_uid('\x01\x02\x03\x04\x05\x06') diff --git a/VendServer/MIFAREClient.py b/VendServer/MIFAREClient.py new file mode 100644 index 0000000..e0b6a6c --- /dev/null +++ b/VendServer/MIFAREClient.py @@ -0,0 +1,44 @@ +from MIFAREDriver import MIFAREReader, MIFAREException +from serial import Serial +from LDAPConnector import get_uid, set_card_id + +class MIFAREClient: + def __init__(self): + self.port = Serial('/dev/ttyS2', baudrate = 19200) + self.reader = MIFAREReader(self.port) + self.reader.set_led(red = False, green = True) + self.reader.beep(100) + + def get_card_id(self): + self.reader.set_led(red = True, green = False) + try: + card_id, capacity = self.reader.select_card() + except MIFAREException: + self.reader.set_led(red = False, green = True) + return None + else: + self.reader.set_led(red = False, green = True) + self.reader.beep(100) + return card_id + + def get_card_uid(self): + card_id = self.get_card_id() + if card_id == None: + return None + else: + return get_uid(card_id) + + def add_card(self, uid): + self.reader.set_led(red = True, green = False) + for attempt in range(5): + self.reader.beep(50) + try: + card_id, capacity = self.reader.select_card() + except MIFAREException: + pass + else: + set_card_id(uid, card_id) + self.reader.set_led(red = False, green = True) + return True + self.reader.set_led(red = False, green = True) + return False diff --git a/VendServer/MIFAREDriver.py b/VendServer/MIFAREDriver.py new file mode 100644 index 0000000..6248f60 --- /dev/null +++ b/VendServer/MIFAREDriver.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python2.5 + +'''mifare - a library for interacting with MIFARE readers. +Written by David Adam +Requires Python 2.5. + +Licensed under an MIT-style license: see LICENSE file for details. +''' + +import serial, logging + +xor = lambda x, y: x ^ y +def checksum(string): + return chr(reduce(xor, [ord(i) for i in string])) + + +class MIFAREException(Exception): + pass + + +class MIFARECommunicationException(MIFAREException): + pass + + +class MIFAREAuthenticationException(MIFAREException): + pass + + +class MIFAREReader: + '''An interface to a particular MIFARE reader.''' + + def __init__(self, io): + '''Returns an interface to a MIFARE reader given a file-like object. + The file-like object is generally a pyserial Serial object.''' + self.io = io + if isinstance(self.io, serial.Serial): + self.io.setTimeout(2) + self.address = '\x00\x00' + + def get_absolute_block(self, vector): + if vector[0] < 32: + return vector[0] * 4 + vector[1] + else: + # Sectors below 32 are 4 blocks + # Sectors above are 16 blocks + # Thus, sector 32 starts at block 128, 33 at 144, and so on + return 128 + (vector[0] - 32) * 16 + vector[1] + + def send_packet(self, data): + '''Constructs a packet for the supplied data string, sends it to the + MIFARE reader, then returns the response (if any) to the commmand.''' + + # Occasionally the reader inserts extra trailing characters into its + # responses, so flush the buffers if possible beforehand. + if isinstance(self.io, serial.Serial): + self.io.flushInput() + self.io.flushOutput() + + # XXX - Needs more error checking. + data = '\x00' + self.address + data + packet = '\xAA\xBB' + chr(len(data)) + data + checksum(data) + self.io.write(packet) + response = '' + header = self.io.read(2) + if header == '\xaa\xbb': + length = ord(self.io.read(1)) + data = self.io.read(length) + packet_xsum = self.io.read(1) + if checksum(data) == packet_xsum and len(data) == length: + # Strip off separator and address header + return data[3:] + else: + raise MIFARECommunicationException, "Invalid response received" + + def set_antenna(self, state = True): + """Turn the card reader's antenna on or off (no return value)""" + command = '\x0C\x01' + chr(int(state)) + response = self.send_packet(command) + if response == '\x0c\x01\x00': + return None + else: + raise MIFAREException, 'command failed: set_antenna (%s)' % state + + def select_card(self, include_halted = False): + """Selects a card and returns a tuple of (serial number, capacity). + + If include_halted is set, may select a card that halt() has previously + been called on.""" + + # Request type of card available + command = command = '\x01\x02' + if include_halted: + command += '\x52' + else: + command += '\x26' + + card_type_response = self.send_packet(command) + + if card_type_response == None or card_type_response[2] == '\x14': + raise MIFAREException, "select_card: no card available" + card_type = card_type_response[3:5] + + if card_type == '\x44\x00': # MIFARE UltraLight + raise NotImplementedError, "UltraLight card selected - no functions available" + + else: + # Otherwise, must be a standard MIFARE card. + # Anticollision + command = '\x02\x02\x04' + # No error handling on this command + serial = self.send_packet(command)[3:] + + # Select the card for use + try: + select_response = self.send_packet('\x03\x02' + serial) + capacity = ord(select_response[3]) + except IndexError: + logging.warning('Tried to select card but failed: card_type %s, serial %s, select_response %s' % (card_type.__repr__(), serial.__repr__(), select_response.__repr__())) + capacity = 0 + return (serial, capacity) + + def sector_login(self, blockvect, key, keytype=0): + """Log in to a block using the six-byte key. + + Use a keytype of 1 to use key B.""" + sector = self.get_absolute_block((blockvect[0], 0)) + + if len(key) != 6: + raise ValueError, 'key must be a six-byte string' + + keytype = 96 + keytype + + data = chr(keytype) + chr(sector) + key + + result = self.send_packet('\x07\x02' + data) + if ord(result[2]) == 22: + raise MIFAREAuthenticationException, "incorrect key provided" + + return + + def read_block(self, blockvect): + "Read the 16-byte block at vector (sector, block)." + block = self.get_absolute_block(blockvect) + + result = self.send_packet('\x08\x02' + chr(block)) + return result[3:19] + + def write_block(self, blockvect, data): + """Write the 16 bytes in data to the block at vector (sector, block).""" + block = self.get_absolute_block(blockvect) + if len(data) != 16: + raise ValueError, "invalid data length - must be 16 bytes" + + result = self.send_packet('\x09\x02' + chr(block) + data) + return + + def write_key(self, key): + pass + + def value_block_increment(self, blocknum, increment): + pass + + def value_block_decrement(self, blocknum, decrement): + pass + + def copy_block(self, source, dest): + pass + + def halt(self): + """Halt the current card - no further transactions will be performed with it.""" + self.send_packet('\x04\x02') + + def set_led(self, red = False, green = False): + led_state = 0 + if red: + led_state += 1 + if green: + led_state += 2 + self.send_packet('\x07\x01' + chr(led_state)) + + def beep(self, length): + '''Beep for a specified length of milliseconds.''' + length = int(round(length / 10.)) + if length > 255: + length = 255 + self.send_packet('\x06\x01' + chr(length)) + + def reset(self): + pass diff --git a/VendServer/MessageKeeper.py b/VendServer/MessageKeeper.py new file mode 100755 index 0000000..60d2b9e --- /dev/null +++ b/VendServer/MessageKeeper.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +# vim:ts=4 + +import sys, os, string, re, pwd, signal +from HorizScroll import HorizScroll +from random import random, seed +from time import time, sleep + +class MessageKeeper: + def __init__(self, vendie): + # Each element of scrolling_message should be a 3-tuple of + # ('message', True/False if it is to be repeated, time to display) + self.scrolling_message = [] + self.v = vendie + self.next_update = None + + def set_message(self, string): + self.scrolling_message = [(string, False, None)] + self.update_display(True) + + def set_messages(self, strings): + self.scrolling_message = strings + self.update_display(True) + + def update_display(self, forced = False, padding = 0): + if not forced and self.next_update != None and time() < self.next_update: + return + if len(self.scrolling_message) > 0: + if len(self.scrolling_message[0][0]) > 10: + (m, r, t) = self.scrolling_message[0] + a = [] + exp = HorizScroll(m).expand(padding, wraparound = r) + if t == None: + t = 0.1 + else: + t = t / len(exp) + for x in exp: + a.append((x, r, t)) + del self.scrolling_message[0] + self.scrolling_message = a + self.scrolling_message + newmsg = self.scrolling_message[0] + if newmsg[2] != None: + self.next_update = time() + newmsg[2] + else: + self.next_update = None + self.v.display(self.scrolling_message[0][0]) + if self.scrolling_message[0][1]: + self.scrolling_message.append(self.scrolling_message[0]) + del self.scrolling_message[0] + + def done(self): + return len(self.scrolling_message) == 0 + diff --git a/VendServer/SerialClient.py b/VendServer/SerialClient.py new file mode 100644 index 0000000..e0a9a7e --- /dev/null +++ b/VendServer/SerialClient.py @@ -0,0 +1,41 @@ +import os, termios +from time import sleep +import logging +from serial import * + +class SerialClientException(Exception): pass + +class SerialClient: + def __init__(self, port = '/dev/ttyS1', baud = 9600): + self.ser = Serial( + port = port, + baudrate = baud, + bytesize=EIGHTBITS, #number of databits + parity=PARITY_NONE, #enable parity checking + stopbits=STOPBITS_ONE, #number of stopbits + timeout=10, #set a timeout value, None for waiting forever, return on read + xonxoff=0, #enable software flow control + rtscts=0, #enable RTS/CTS flow control + ) + + + self.rfh = self.ser + self.wfh = self.ser + self.wfh.write('B\n') + + def get_fh(self): + return (self.rfh, self.wfh) + + def __del__(self): + pass + + +if __name__ == '__main__': + s = SerialClient("/dev/ttyS1", 9600) + + (rfh, wfh) = s.get_fh() + + wfh.write('B\n') + print rfh.read() + + diff --git a/VendServer/SnackConfig.py b/VendServer/SnackConfig.py new file mode 100755 index 0000000..2314074 --- /dev/null +++ b/VendServer/SnackConfig.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +class VendingException( Exception ): pass + +import subprocess +import os, re + +def get_snack( slot ): + + if slot == "--": + return (0, 'nothing', 'Nothing') + cmd = 'dispense iteminfo snack:%s' % slot +# print 'cmd = %s' % cmd + try: +# info = subprocess.check_output(["dispense","iteminfo",'snack:%s'%slot]) + raw = os.popen(cmd) + info = raw.read() + raw.close() +# print 'cmd (2) = %s' % cmd +# print 'info = "%s"' % info + m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info) + val = ( int(m.group(1))*100 + int(m.group(2)), m.group(3), m.group(3) ) +# print 'Price: %i, Name: %s' % (val[0], val[1]) + except BaseException as e: + print "BaseException" + print e + val = (0, 'error', 'Error') + except: + print "Unknown exception" + val = (0, 'error', 'Error') + return val + +def get_price( slot ): + p, sn, n = get_snacks( slot ) + return p + +def get_name( slot ): + p, sn, n = get_snacks( slot ) + return n + +def get_short_name( slot ): + p, sn, n = get_snacks( slot ) + return sn + +if __name__ == '__main__': + print "Don't run this" diff --git a/VendServer/VendServer.py b/VendServer/VendServer.py new file mode 100755 index 0000000..2a9a55a --- /dev/null +++ b/VendServer/VendServer.py @@ -0,0 +1,1187 @@ +#!/usr/bin/python +# vim:ts=4 + +USE_DB = 0 +USE_MIFARE = 1 + +import ConfigParser +import sys, os, string, re, pwd, signal, math, syslog +import logging, logging.handlers +from traceback import format_tb +if USE_DB: import pg +from time import time, sleep, mktime, localtime +from subprocess import Popen, PIPE +from LATClient import LATClient, LATClientException +from SerialClient import SerialClient, SerialClientException +from VendingMachine import VendingMachine, VendingException +from MessageKeeper import MessageKeeper +from HorizScroll import HorizScroll +from random import random, seed +from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler +from SnackConfig import get_snack#, get_snacks +import socket +from posix import geteuid +from LDAPConnector import get_uid,get_uname, set_card_id + +CREDITS=""" +This vending machine software brought to you by: +Bernard Blackham +Mark Tearle +Nick Bannon +Cameron Patrick +and a collective of hungry alpacas. + + + +For a good time call +61 8 6488 3901 + + + +""" + +PIN_LENGTH = 4 + +DOOR = 1 +SWITCH = 2 +KEY = 3 +TICK = 4 +MIFARE = 5 + + +( +STATE_IDLE, +STATE_DOOR_OPENING, +STATE_DOOR_CLOSING, +STATE_GETTING_UID, +STATE_GETTING_PIN, +STATE_GET_SELECTION, +STATE_GRANDFATHER_CLOCK, +) = range(1,8) + +TEXT_SPEED = 0.8 +IDLE_SPEED = 0.05 + +class DispenseDatabaseException(Exception): pass + +class DispenseDatabase: + def __init__(self, vending_machine, host, name, user, password): + self.vending_machine = vending_machine + self.db = pg.DB(dbname = name, host = host, user = user, passwd = password) + self.db.query('LISTEN vend_requests') + + def process_requests(self): + logging.debug('database processing') + query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false' + try: + outstanding = self.db.query(query).getresult() + except (pg.error,), db_err: + raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip())) + for (id, slot) in outstanding: + (worked, code, string) = self.vending_machine.vend(slot) + logging.debug (str((worked, code, string))) + if worked: + query = 'SELECT vend_success(%s)'%id + self.db.query(query).getresult() + else: + query = 'SELECT vend_failed(%s)'%id + self.db.query(query).getresult() + + def handle_events(self): + notifier = self.db.getnotify() + while notifier is not None: + self.process_requests() + notify = self.db.getnotify() + +def scroll_options(username, mk, welcome = False): + if welcome: + # Balance checking + acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate() + # this is fucking appalling + balance = acct[acct.find("$")+1:acct.find("(")].strip() + + msg = [(center('WELCOME'), False, TEXT_SPEED), + (center(username), False, TEXT_SPEED), + (center(balance), False, TEXT_SPEED),] + else: + msg = [] + choices = ' '*10+'CHOICES: ' + + # Get coke contents + cokes = [] + for i in range(0, 7): + args = ('dispense', 'iteminfo', 'coke:%i' % i) + info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate() + m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info) + cents = int(m.group(1))*100 + int(m.group(2)) + cokes.append('%i %i %s' % (i, cents, m.group(3))); + + for c in cokes: + c = c.strip() + (slot_num, price, slot_name) = c.split(' ', 2) + if slot_name == 'dead': continue + choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num) + +# we don't want to print snacks for now since it'll be too large +# and there's physical bits of paper in the machine anyway - matt +# try: +# snacks = get_snacks() +# except: +# snacks = {} +# +# for slot, ( name, price ) in snacks.items(): +# choices += '%s8-%s (%sc) ' % ( slot, name, price ) + + choices += '55-DOOR ' + choices += 'OR ANOTHER SNACK. ' + choices += '99 TO READ AGAIN. ' + choices += 'CHOICE? ' + msg.append((choices, False, None)) + mk.set_messages(msg) + +_pin_uid = 0 +_pin_uname = 'root' +_pin_pin = '----' + +def _check_pin(uid, pin): + global _pin_uid + global _pin_uname + global _pin_pin + print "_check_pin('",uid,"',---)" + if uid != _pin_uid: + try: + info = pwd.getpwuid(uid) + except KeyError: + logging.info('getting pin for uid %d: user not in password file'%uid) + return None + if info.pw_dir == None: return False + pinfile = os.path.join(info.pw_dir, '.pin') + try: + s = os.stat(pinfile) + except OSError: + logging.info('getting pin for uid %d: .pin not found in home directory'%uid) + return None + if s.st_mode & 077: + logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid) + os.chmod(pinfile, 0600) + try: + f = file(pinfile) + except IOError: + logging.info('getting pin for uid %d: I cannot read pin file'%uid) + return None + pinstr = f.readline() + f.close() + if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr): + logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr))) + return None + _pin_uid = uid + _pin_pin = pinstr + _pin_uname = info.pw_name + else: + pinstr = _pin_pin + if pin == int(pinstr): + logging.info("Pin correct for %d",uid) + else: + logging.info("Pin incorrect for %d",uid) + return pin == int(pinstr) + +def acct_is_disabled(name=None): + global _pin_uname + if name == None: + name = _pin_uname + acct, unused = Popen(['dispense', 'acct', _pin_uname], close_fds=True, stdout=PIPE).communicate() + # this is fucking appalling + flags = acct[acct.find("(")+1:acct.find(")")].strip() + if 'disabled' in flags: + return True + if 'internal' in flags: + return True + return False + +def has_good_pin(uid): + return _check_pin(uid, None) != None + +def verify_user_pin(uid, pin, skip_pin_check=False): + if skip_pin_check or _check_pin(uid, pin) == True: + info = pwd.getpwuid(uid) + if skip_pin_check: + if acct_is_disabled(info.pw_name): + logging.info('refused mifare for disabled acct uid %d (%s)'%(uid,info.pw_name)) + return '-disabled-' + logging.info('accepted mifare for uid %d (%s)'%(uid,info.pw_name)) + else: + logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name)) + return info.pw_name + else: + logging.info('refused pin for uid %d'%(uid)) + return None + + +def cookie(v): + seed(time()) + messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY'] + choice = int(random()*len(messages)) + msg = messages[choice] + left = range(len(msg)) + for i in range(len(msg)): + if msg[i] == ' ': left.remove(i) + reveal = 1 + while left: + s = '' + for i in range(0, len(msg)): + if i in left: + if reveal == 0: + left.remove(i) + s += msg[i] + else: + s += chr(int(random()*26)+ord('A')) + reveal += 1 + reveal %= 17 + else: + s += msg[i] + v.display(s) + +def center(str): + LEN = 10 + return ' '*((LEN-len(str))/2)+str + + + +idlers = [] +idler = None + +def setup_idlers(v): + global idlers, idler + idlers = [ + GrayIdler(v), + StringIdler(v, text="Kill 'em all", repeat=False), + GrayIdler(v,one="*",zero="-"), + StringIdler(v, text=CREDITS), + GrayIdler(v,one="/",zero="\\"), + ClockIdler(v), + GrayIdler(v,one="X",zero="O"), + FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2), + GrayIdler(v,one="*",zero="-",reorder=1), + StringIdler(v, text=str(math.pi) + " "), + ClockIdler(v), + GrayIdler(v,one="/",zero="\\",reorder=1), + StringIdler(v, text=str(math.e) + " "), + GrayIdler(v,one="X",zero="O",reorder=1), + StringIdler(v, text=" I want some pizza - please call Pizza Hut Shenton Park on +61 8 9381 9979 [now closed? - MSH] - and order as Quinn - I am getting really hungry", repeat=False), + PipeIdler(v, "/usr/bin/getent", "passwd"), + FortuneIdler(v), + ClockIdler(v), + StringIdler(v), + TrainIdler(v), + # "Hello World" in brainfuck + StringIdler(v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."), + ] + disabled = [ + ] + +def reset_idler(v, vstatus, t = None): + global idlers, idler + idler = GreetingIdler(v, t) + vstatus.time_of_next_idlestep = time()+idler.next() + vstatus.time_of_next_idler = None + vstatus.time_to_autologout = None + vstatus.change_state(STATE_IDLE, 1) + +def choose_idler(): + global idlers, idler + iiindex = 0 + average_affinity = 10 # guessing here... + + if idler and idler.__class__ != GreetingIdler: + iiindex = idlers.index(idler) + + iilen = len(idlers) + + move = int(random()*len(idlers)*average_affinity) + 1 + + while move >= 0: + iiindex += 1 + iiindex %= iilen + idler = idlers[iiindex] + move -= idler.affinity() + + idler.reset() + +def idle_step(vstatus): + global idler + if idler.finished(): + choose_idler() + vstatus.time_of_next_idler = time() + 30 + nextidle = idler.next() + if nextidle is None: + nextidle = IDLE_SPEED + vstatus.time_of_next_idlestep = time()+nextidle + +class VendState: + def __init__(self,v): + self.state_table = {} + self.state = STATE_IDLE + self.counter = 0 + + self.mk = MessageKeeper(v) + self.cur_user = '' + self.cur_pin = '' + self.username = '' + self.cur_selection = '' + self.time_to_autologout = None + + self.last_timeout_refresh = None + + def change_state(self,newstate,newcounter=None): + if self.state != newstate: + #print "Changing state from: ", + #print self.state, + #print " to ", + #print newstate + self.state = newstate + + if newcounter is not None and self.counter != newcounter: + #print "Changing counter from: ", + #print self.counter, + #print " to ", + #print newcounter + self.counter = newcounter + + + +def handle_tick_event(event, params, v, vstatus): + # don't care right now. + pass + +def handle_switch_event(event, params, v, vstatus): + # don't care right now. + pass + + +def do_nothing(state, event, params, v, vstatus): + print "doing nothing (s,e,p)", state, " ", event, " ", params + pass + +def handle_getting_uid_idle(state, event, params, v, vstatus): + # don't care right now. + pass + +def handle_getting_pin_idle(state, event, params, v, vstatus): + # don't care right now. + pass + +def handle_get_selection_idle(state, event, params, v, vstatus): + # don't care right now. + ### + ### State logging out .. + if vstatus.time_to_autologout != None: + time_left = vstatus.time_to_autologout - time() + if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left): + vstatus.mk.set_message('LOGOUT: '+str(int(time_left))) + vstatus.last_timeout_refresh = int(time_left) + vstatus.cur_selection = '' + + if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0: + vstatus.time_to_autologout = None + vstatus.cur_user = '' + vstatus.cur_pin = '' + vstatus.cur_selection = '' + + reset_idler(v, vstatus) + + ### State fully logged out ... reset variables + if vstatus.time_to_autologout and not vstatus.mk.done(): + vstatus.time_to_autologout = None + if vstatus.cur_user == '' and vstatus.time_to_autologout: + vstatus.time_to_autologout = None + + ### State logged in + if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None: + # start autologout + vstatus.time_to_autologout = time() + 15 + vstatus.last_timeout_refresh = None + + ## FIXME - this may need to be elsewhere..... + # need to check + vstatus.mk.update_display() + + + +def handle_get_selection_key(state, event, params, v, vstatus): + key = params + if len(vstatus.cur_selection) == 0: + if key == 11: + vstatus.cur_pin = '' + vstatus.cur_user = '' + vstatus.cur_selection = '' + + vstatus.mk.set_messages([(center('BYE!'), False, 1.5)]) + reset_idler(v, vstatus, 2) + return + vstatus.cur_selection += chr(key + ord('0')) + vstatus.mk.set_message('SELECT: '+vstatus.cur_selection) + vstatus.time_to_autologout = None + elif len(vstatus.cur_selection) == 1: + if key == 11: + vstatus.cur_selection = '' + vstatus.time_to_autologout = None + scroll_options(vstatus.username, vstatus.mk) + return + else: + vstatus.cur_selection += chr(key + ord('0')) + if vstatus.cur_user: + make_selection(v,vstatus) + vstatus.cur_selection = '' + vstatus.time_to_autologout = time() + 8 + vstatus.last_timeout_refresh = None + else: + # Price check mode. + price_check(v,vstatus) + vstatus.cur_selection = '' + vstatus.time_to_autologout = None + vstatus.last_timeout_refresh = None + +def make_selection(v, vstatus): + # should use sudo here + if vstatus.cur_selection == '55': + vstatus.mk.set_message('OPENSESAME') + logging.info('dispensing a door for %s'%vstatus.username) + if geteuid() == 0: + #ret = os.system('su - "%s" -c "dispense door"'%vstatus.username) + ret = os.system('dispense -u "%s" door'%vstatus.username) + else: + ret = os.system('dispense door') + if ret == 0: + logging.info('door opened') + vstatus.mk.set_message(center('DOOR OPEN')) + else: + logging.warning('user %s tried to dispense a bad door'%vstatus.username) + vstatus.mk.set_message(center('BAD DOOR')) + sleep(1) + elif vstatus.cur_selection == '81': + cookie(v) + elif vstatus.cur_selection == '99': + scroll_options(vstatus.username, vstatus.mk) + vstatus.cur_selection = '' + return + elif vstatus.cur_selection[1] == '8': + v.display('GOT DRINK?') + if ((os.system('dispense -u "%s" coke:%s'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0): + v.display('SEEMS NOT') + else: + v.display('GOT DRINK!') + else: + # first see if it's a named slot + try: + price, shortname, name = get_snack( vstatus.cur_selection ) + except: + price, shortname, name = get_snack( '--' ) + dollarprice = "$%.2f" % ( price / 100.0 ) + v.display(vstatus.cur_selection+' - %s'%dollarprice) +# exitcode = os.system('dispense -u "%s" give \>snacksales %d "%s"'%(vstatus.username, price, name)) >> 8 +# exitcode = os.system('dispense -u "%s" give \>sales\:snack %d "%s"'%(vstatus.username, price, name)) >> 8 + exitcode = os.system('dispense -u "%s" snack:%s'%(vstatus.username, vstatus.cur_selection)) >> 8 + if (exitcode == 0): + # magic dispense syslog service + syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "vended %s (slot %s) for %s" % (name, vstatus.cur_selection, vstatus.username)) + (worked, code, string) = v.vend(vstatus.cur_selection) + if worked: + v.display('THANK YOU') + else: + print "Vend Failed:", code, string + v.display('VEND FAIL') + elif (exitcode == 5): # RV_BALANCE + v.display('NO MONEY?') + elif (exitcode == 4): # RV_ARGUMENTS (zero give causes arguments) + v.display('EMPTY SLOT') + elif (exitcode == 1): # RV_BADITEM (Dead slot) + v.display('EMPTY SLOT') + else: + syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "failed vending %s (slot %s) for %s (code %d)" % (name, vstatus.cur_selection, vstatus.username, exitcode)) + v.display('UNK ERROR') + sleep(1) + + +def price_check(v, vstatus): + if vstatus.cur_selection[1] == '8': + args = ('dispense', 'iteminfo', 'coke:' + vstatus.cur_selection[0]) + info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate() + dollarprice = re.match("\s*[a-z]+:\d+\s+(\d+\.\d\d)\s+([^\n]+)", info).group(1) + else: + # first see if it's a named slot + try: + price, shortname, name = get_snack( vstatus.cur_selection ) + except: + price, shortname, name = get_snack( '--' ) + dollarprice = "$%.2f" % ( price / 100.0 ) + v.display(vstatus.cur_selection+' - %s'%dollarprice) + + +def handle_getting_pin_key(state, event, params, v, vstatus): + #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params + key = params + if len(vstatus.cur_pin) < PIN_LENGTH: + if key == 11: + if vstatus.cur_pin == '': + vstatus.cur_user = '' + reset_idler(v, vstatus) + + return + vstatus.cur_pin = '' + vstatus.mk.set_message('PIN: ') + return + vstatus.cur_pin += chr(key + ord('0')) + vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin)) + if len(vstatus.cur_pin) == PIN_LENGTH: + vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin)) + if vstatus.username: + v.beep(0, False) + vstatus.cur_selection = '' + vstatus.change_state(STATE_GET_SELECTION) + scroll_options(vstatus.username, vstatus.mk, True) + return + else: + v.beep(40, False) + vstatus.mk.set_messages( + [(center('BAD PIN'), False, 1.0), + (center('SORRY'), False, 0.5)]) + vstatus.cur_user = '' + vstatus.cur_pin = '' + + reset_idler(v, vstatus, 2) + + return + + +def handle_getting_uid_key(state, event, params, v, vstatus): + #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params + key = params + + # complicated key handling here: + + if len(vstatus.cur_user) == 0 and key == 9: + vstatus.cur_selection = '' + vstatus.time_to_autologout = None + vstatus.mk.set_message('PRICECHECK') + sleep(0.5) + scroll_options('', vstatus.mk) + vstatus.change_state(STATE_GET_SELECTION) + return + + if len(vstatus.cur_user) <8: + if key == 11: + vstatus.cur_user = '' + + reset_idler(v, vstatus) + return + vstatus.cur_user += chr(key + ord('0')) + #logging.info('dob: '+vstatus.cur_user) + if len(vstatus.cur_user) > 5: + vstatus.mk.set_message('>'+vstatus.cur_user) + else: + vstatus.mk.set_message('UID: '+vstatus.cur_user) + + if len(vstatus.cur_user) == 5: + uid = int(vstatus.cur_user) + + if uid == 0: + logging.info('user '+vstatus.cur_user+' has a bad PIN') + pfalken=""" +CARRIER DETECTED + +CONNECT 128000 + +Welcome to Picklevision Sytems, Sunnyvale, CA + +Greetings Professor Falken. + + + + +Shall we play a game? + + +Please choose from the following menu: + +1. Tic-Tac-Toe +2. Chess +3. Checkers +4. Backgammon +5. Poker +6. Toxic and Biochemical Warfare +7. Global Thermonuclear War + +7 [ENTER] + +Wouldn't you prefer a nice game of chess? + +""".replace('\n',' ') + vstatus.mk.set_messages([(pfalken, False, 10)]) + vstatus.cur_user = '' + vstatus.cur_pin = '' + + reset_idler(v, vstatus, 10) + + return + + if not has_good_pin(uid): + logging.info('user '+vstatus.cur_user+' has a bad PIN') + vstatus.mk.set_messages( + [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)]) + vstatus.cur_user = '' + vstatus.cur_pin = '' + + reset_idler(v, vstatus, 3) + + return + + if acct_is_disabled(): + logging.info('user '+vstatus.cur_user+' is disabled') + vstatus.mk.set_messages( + [(' '*11+'ACCOUNT DISABLED'+' '*11, False, 3)]) + vstatus.cur_user = '' + vstatus.cur_pin = '' + + reset_idler(v, vstatus, 3) + return + + + vstatus.cur_pin = '' + vstatus.mk.set_message('PIN: ') + logging.info('need pin for user %s'%vstatus.cur_user) + vstatus.change_state(STATE_GETTING_PIN) + return + + +def handle_idle_key(state, event, params, v, vstatus): + #print "handle_idle_key (s,e,p)", state, " ", event, " ", params + + key = params + + if key == 11: + vstatus.cur_user = '' + reset_idler(v, vstatus) + return + + vstatus.change_state(STATE_GETTING_UID) + run_handler(event, key, v, vstatus) + + +def handle_idle_tick(state, event, params, v, vstatus): + ### State idling + if vstatus.mk.done(): + idle_step(vstatus) + + if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler: + vstatus.time_of_next_idler = time() + 30 + choose_idler() + + ### + + vstatus.mk.update_display() + + vstatus.change_state(STATE_GRANDFATHER_CLOCK) + run_handler(event, params, v, vstatus) + sleep(0.05) + +def beep_on(when, before=0): + start = int(when - before) + end = int(when) + now = int(time()) + + if now >= start and now <= end: + return 1 + return 0 + +def handle_idle_grandfather_tick(state, event, params, v, vstatus): + ### check for interesting times + now = localtime() + + quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]]) + halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]]) + threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]]) + fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]]) + + hourfromnow = localtime(time() + 3600) + + #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]]) + onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \ + 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]]) + + ## check for X seconds to the hour + ## if case, update counter to 2 + if beep_on(onthehour,15) \ + or beep_on(halfhour,0) \ + or beep_on(quarterhour,0) \ + or beep_on(threequarterhour,0) \ + or beep_on(fivetothehour,0): + vstatus.change_state(STATE_GRANDFATHER_CLOCK,2) + run_handler(event, params, v, vstatus) + else: + vstatus.change_state(STATE_IDLE) + +def handle_grandfather_tick(state, event, params, v, vstatus): + go_idle = 1 + + msg = [] + ### we live in interesting times + now = localtime() + + quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]]) + halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]]) + threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]]) + fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]]) + + hourfromnow = localtime(time() + 3600) + +# onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]]) + onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \ + 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]]) + + + #print "when it fashionable to wear a onion on your hip" + + if beep_on(onthehour,15): + go_idle = 0 + next_hour=((hourfromnow[3] + 11) % 12) + 1 + if onthehour - time() < next_hour and onthehour - time() > 0: + v.beep(0, False) + + t = int(time()) + if (t % 2) == 0: + msg.append(("DING!", False, None)) + else: + msg.append((" DING!", False, None)) + elif int(onthehour - time()) == 0: + v.beep(255, False) + msg.append((" BONG!", False, None)) + msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4)) + elif beep_on(halfhour,0): + go_idle = 0 + v.beep(0, False) + msg.append((" HALFHOUR ", False, 50)) + elif beep_on(quarterhour,0): + go_idle = 0 + v.beep(0, False) + msg.append((" QTR HOUR ", False, 50)) + elif beep_on(threequarterhour,0): + go_idle = 0 + v.beep(0, False) + msg.append((" 3 QTR HR ", False, 50)) + elif beep_on(fivetothehour,0): + go_idle = 0 + v.beep(0, False) + msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4)) + else: + go_idle = 1 + + ## check for X seconds to the hour + + if len(msg): + vstatus.mk.set_messages(msg) + sleep(1) + + vstatus.mk.update_display() + ## if no longer case, return to idle + + ## change idler to be clock + if go_idle and vstatus.mk.done(): + vstatus.change_state(STATE_IDLE,1) + +def handle_door_idle(state, event, params, v, vstatus): + def twiddle(clock,v,wise = 2): + if (clock % 4 == 0): + v.display("-FEED ME-") + elif (clock % 4 == 1+wise): + v.display("\\FEED ME/") + elif (clock % 4 == 2): + v.display("-FEED ME-") + elif (clock % 4 == 3-wise): + v.display("/FEED ME\\") + + # don't care right now. + now = int(time()) + + if ((now % 60 % 2) == 0): + twiddle(now, v) + else: + twiddle(now, v, wise=0) + + +def handle_door_event(state, event, params, v, vstatus): + if params == 0: #door open + vstatus.change_state(STATE_DOOR_OPENING) + logging.warning("Entering open door mode") + v.display("-FEED ME-") + #door_open_mode(v); + vstatus.cur_user = '' + vstatus.cur_pin = '' + elif params == 1: #door closed + vstatus.change_state(STATE_DOOR_CLOSING) + reset_idler(v, vstatus, 3) + + logging.warning('Leaving open door mode') + v.display("-YUM YUM!-") + +def handle_mifare_event(state, event, params, v, vstatus): + card_id = params + # Translate card_id into uid. + if card_id == None: + return + + try: + vstatus.cur_user = get_uid(card_id) + logging.info('Mapped card id to uid %s'%vstatus.cur_user) + vstatus.username = get_uname(vstatus.cur_user) + if acct_is_disabled(vstatus.username): + vstatus.username = '-disabled-' + except ValueError: + vstatus.username = None + if vstatus.username == '-disabled-': + v.beep(40, False) + vstatus.mk.set_messages( + [(center('ACCT DISABLED'), False, 1.0), + (center('SORRY'), False, 0.5)]) + vstatus.cur_user = '' + vstatus.cur_pin = '' + vstatus.username = None + + reset_idler(v, vstatus, 2) + return + elif vstatus.username: + v.beep(0, False) + vstatus.cur_selection = '' + vstatus.change_state(STATE_GET_SELECTION) + scroll_options(vstatus.username, vstatus.mk, True) + return + else: + v.beep(40, False) + vstatus.mk.set_messages( + [(center('BAD CARD'), False, 1.0), + (center('SORRY'), False, 0.5)]) + vstatus.cur_user = '' + vstatus.cur_pin = '' + + reset_idler(v, vstatus, 2) + return + +def handle_mifare_add_user_event(state, event, params, v, vstatus): + card_id = params + + # Translate card_id into uid. + if card_id == None: + return + + try: + if get_uid(card_id) != None: + vstatus.mk.set_messages( + [(center('ALREADY'), False, 0.5), + (center('ENROLLED'), False, 0.5)]) + + # scroll_options(vstatus.username, vstatus.mk) + return + except ValueError: + pass + + logging.info('Enrolling card %s to uid %s (%s)'%(card_id, vstatus.cur_user, vstatus.username)) + set_card_id(vstatus.cur_user, card_id) + vstatus.mk.set_messages( + [(center('CARD'), False, 0.5), + (center('ENROLLED'), False, 0.5)]) + + # scroll_options(vstatus.username, vstatus.mk) + +def return_to_idle(state,event,params,v,vstatus): + reset_idler(v, vstatus) + +def create_state_table(vstatus): + vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick + vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key + vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event + vstatus.state_table[(STATE_IDLE,MIFARE,1)] = handle_mifare_event + + vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle + vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event + vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing + vstatus.state_table[(STATE_DOOR_OPENING,MIFARE,1)] = do_nothing + + vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle + vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event + vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing + vstatus.state_table[(STATE_DOOR_CLOSING,MIFARE,1)] = do_nothing + + vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle + vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing + vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key + vstatus.state_table[(STATE_GETTING_UID,MIFARE,1)] = handle_mifare_event + + vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle + vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing + vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key + vstatus.state_table[(STATE_GETTING_PIN,MIFARE,1)] = handle_mifare_event + + vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle + vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing + vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key + vstatus.state_table[(STATE_GET_SELECTION,MIFARE,1)] = handle_mifare_add_user_event + + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = handle_mifare_event + +def get_state_table_handler(vstatus, state, event, counter): + return vstatus.state_table[(state,event,counter)] + +def time_to_next_update(vstatus): + idle_update = vstatus.time_of_next_idlestep - time() + if not vstatus.mk.done() and vstatus.mk.next_update is not None: + mk_update = vstatus.mk.next_update - time() + if mk_update < idle_update: + idle_update = mk_update + return idle_update + +def run_forever(rfh, wfh, options, cf): + v = VendingMachine(rfh, wfh, USE_MIFARE) + vstatus = VendState(v) + create_state_table(vstatus) + + logging.debug('PING is ' + str(v.ping())) + + if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword) + + setup_idlers(v) + reset_idler(v, vstatus) + + # This main loop was hideous and the work of the devil. + # This has now been fixed (mostly) - mtearle + # + # + # notes for later surgery + # (event, counter, ' ') + # V + # d[ ] = (method) + # + # ( return state - not currently implemented ) + + while True: + if USE_DB: + try: + db.handle_events() + except DispenseDatabaseException, e: + logging.error('Database error: '+str(e)) + + timeout = time_to_next_update(vstatus) + e = v.next_event(timeout) + (event, params) = e + + run_handler(event, params, v, vstatus) + +# logging.debug('Got event: ' + repr(e)) + + +def run_handler(event, params, v, vstatus): + handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter) + if handler: + handler(vstatus.state, event, params, v, vstatus) + +def connect_to_vend(options, cf): + + if options.use_lat: + logging.info('Connecting to vending machine using LAT') + latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword) + rfh, wfh = latclient.get_fh() + elif options.use_serial: + # Open vending machine via serial. + logging.info('Connecting to vending machine using serial') + serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600) + rfh,wfh = serialclient.get_fh() + else: + #(rfh, wfh) = popen2('../../virtualvend/vvend.py') + logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port)) + import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + sock.connect((options.host, options.port)) + rfh = sock.makefile('r') + wfh = sock.makefile('w') + global USE_MIFARE + USE_MIFARE = 0 + + return rfh, wfh + +def parse_args(): + from optparse import OptionParser + + op = OptionParser(usage="%prog [OPTION]...") + op.add_option('-f', '--config-file', default='/etc/dispense2/servers.conf', metavar='FILE', dest='config_file', help='use the specified config file instead of /etc/dispense/servers.conf') + op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port') + op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT') + op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT') + op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)') + op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)') + op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file') + op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility') + op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon') + op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output') + op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors') + op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file') + options, args = op.parse_args() + + if len(args) != 0: + op.error('extra command line arguments: ' + ' '.join(args)) + + return options + +config_options = { + 'DBServer': ('Database', 'Server'), + 'DBName': ('Database', 'Name'), + 'DBUser': ('VendingMachine', 'DBUser'), + 'DBPassword': ('VendingMachine', 'DBPassword'), + + 'ServiceName': ('VendingMachine', 'ServiceName'), + 'ServicePassword': ('VendingMachine', 'Password'), + + 'ServerName': ('DecServer', 'Name'), + 'ConnectPassword': ('DecServer', 'ConnectPassword'), + 'PrivPassword': ('DecServer', 'PrivPassword'), + } + +class VendConfigFile: + def __init__(self, config_file, options): + try: + cp = ConfigParser.ConfigParser() + cp.read(config_file) + + for option in options: + section, name = options[option] + value = cp.get(section, name) + self.__dict__[option] = value + + except ConfigParser.Error, e: + raise SystemExit("Error reading config file "+config_file+": " + str(e)) + +def create_pid_file(name): + try: + pid_file = file(name, 'w') + pid_file.write('%d\n'%os.getpid()) + pid_file.close() + except IOError, e: + logging.warning('unable to write to pid file '+name+': '+str(e)) + +def set_stuff_up(): + def do_nothing(signum, stack): + signal.signal(signum, do_nothing) + def stop_server(signum, stack): raise KeyboardInterrupt + signal.signal(signal.SIGHUP, do_nothing) + signal.signal(signal.SIGTERM, stop_server) + signal.signal(signal.SIGINT, stop_server) + + options = parse_args() + config_opts = VendConfigFile(options.config_file, config_options) + if options.daemon: become_daemon() + set_up_logging(options) + if options.pid_file != '': create_pid_file(options.pid_file) + + return options, config_opts + +def clean_up_nicely(options, config_opts): + if options.pid_file != '': + try: + os.unlink(options.pid_file) + logging.debug('Removed pid file '+options.pid_file) + except OSError: pass # if we can't delete it, meh + +def set_up_logging(options): + logger = logging.getLogger() + + if not options.daemon: + stderr_logger = logging.StreamHandler(sys.stderr) + stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) + logger.addHandler(stderr_logger) + + if options.log_file != '': + try: + file_logger = logging.FileHandler(options.log_file) + file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s')) + logger.addHandler(file_logger) + except IOError, e: + logger.warning('unable to write to log file '+options.log_file+': '+str(e)) + + if options.syslog != None: + sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog) + sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s')) + logger.addHandler(sys_logger) + + if options.quiet: + logger.setLevel(logging.WARNING) + elif options.verbose: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + +def become_daemon(): + dev_null = file('/dev/null') + fd = dev_null.fileno() + os.dup2(fd, 0) + os.dup2(fd, 1) + os.dup2(fd, 2) + try: + if os.fork() != 0: + sys.exit(0) + os.setsid() + except OSError, e: + raise SystemExit('failed to fork: '+str(e)) + +def do_vend_server(options, config_opts): + while True: + try: + rfh, wfh = connect_to_vend(options, config_opts) + except (SerialClientException, socket.error), e: + (exc_type, exc_value, exc_traceback) = sys.exc_info() + del exc_traceback + logging.error("Connection error: "+str(exc_type)+" "+str(e)) + logging.info("Trying again in 5 seconds.") + sleep(5) + continue + +# run_forever(rfh, wfh, options, config_opts) + + try: + run_forever(rfh, wfh, options, config_opts) + except VendingException: + logging.error("Connection died, trying again...") + logging.info("Trying again in 5 seconds.") + sleep(5) + +if __name__ == '__main__': + options, config_opts = set_stuff_up() + while True: + try: + logging.warning('Starting Vend Server') + do_vend_server(options, config_opts) + logging.error('Vend Server finished unexpectedly, restarting') + except KeyboardInterrupt: + logging.info("Killed by signal, cleaning up") + clean_up_nicely(options, config_opts) + logging.warning("Vend Server stopped") + break + except SystemExit: + break + except: + (exc_type, exc_value, exc_traceback) = sys.exc_info() + tb = format_tb(exc_traceback, 20) + del exc_traceback + + logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception") + logging.critical("Message: " + str(exc_value)) + logging.critical("Traceback:") + for event in tb: + for line in event.split('\n'): + logging.critical(' '+line) + logging.critical("This message should be considered a bug in the Vend Server.") + logging.critical("Please report this to someone who can fix it.") + sleep(10) + logging.warning("Trying again anyway (might not help, but hey...)") + diff --git a/VendServer/VendingMachine.py b/VendServer/VendingMachine.py new file mode 100644 index 0000000..e5a0251 --- /dev/null +++ b/VendServer/VendingMachine.py @@ -0,0 +1,204 @@ +# vim:ts=4 +import re +from CRC import do_crc +from select import select +import socket, logging +from time import time, sleep +from MIFAREClient import MIFAREClient + +asynchronous_responses = [ '400', '401', # door open/closed + '610', # switches changed + '200', '201', '202', '203', '204', '205', '206', + '207', '208', '209', '211', # key presses + ] +DOOR = 1 +SWITCH = 2 +KEY = 3 +TICK = 4 +MIFARE = 5 + +class VendingException(Exception): pass + +class VendingMachine: + def __init__(self, rfh, wfh, use_mifare): + self.events = [] + # Secret + self.secret = 'SN4CKZ0RZZZZZZZZ' + self.rfh = rfh + self.wfh = wfh + self.challenge = None + # Initialise ourselves into a known state + self.wfh.write('\n') + self.await_prompt() + self.wfh.write('echo off\n') + self.await_prompt() + self.wfh.write('PING\n') + code = '' + while code != '000': + code = self.get_response()[0] + self.get_switches() + if use_mifare: + self.mifare = MIFAREClient() + self.mifare_timeout = 0 + else: + self.mifare = None + + def await_prompt(self): + self.wfh.flush() + state = 1 + timeout = 0.5 + prefix = '' + s = '' + # mtearle - vending machine was dying wait for a response from + # the hardware, suspect it was missing characters + # + # fixed by migration to pyserial - but future good place to start + while True: + try: + s = self.rfh.read(1) + except socket.error: + raise VendingException('failed to read input from vending machine') + if s == '': raise VendingException('nothing read!') + if (s != '#' and s != '%') and state == 1: prefix += s + if s == '\n' or s == '\r': + state = 1 + prefix = '' + if (s == '#' or s == '%') and state == 1: state = 2 + if s == ' ' and state == 2: + if prefix == '': + self.challenge = None + return + if re.search('^[0-9a-fA-F]{4}$', prefix): + self.challenge = int(prefix, 16) + return + + def get_response(self, async = False): + self.wfh.flush() + while True: + s = '' + while s == '': + s = self.rfh.readline() + if s == '': + raise VendingException('Input socket has closed!') + s = s.strip('\r\n') + code = s[0:3] + text = s[4:] + if code in asynchronous_responses: + self.handle_event(code, text) + if async: return None + else: + self.await_prompt() + return (code, text) + + def get_switches(self): + self.wfh.write('S\n') + (code, text) = self.get_response() + if code != '600': + return (False, code, text) + self.interpret_switches(text) + return (True, code, text) + + def interpret_switches(self, text): + self.switches = (int(text[0:2], 16) << 8) | int(text[3:5], 16) + + def handle_event(self, code, text): + if code == '400': + self.events.append((DOOR, 1)) + elif code == '401': + self.events.append((DOOR, 0)) + elif code == '610': + # NOP this. Nothing handles this yet. + #self.events.append((SWITCH, None)) + self.interpret_switches(text) + elif code[0] == '2': + self.events.append((KEY, int(code[1:3]))) + else: + logging.warning('Unhandled event! (%s %s)\n'%(code,text)) + + def authed_message(self, message): + print 'self.challenge = %04x' % self.challenge + if self.challenge == None: + return message + crc = do_crc('%c%c'%(self.challenge >> 8, self.challenge & 0xff)) + crc = do_crc(self.secret, crc) + crc = do_crc(message, crc) + print 'output = "%s|%04x"' % (message, crc) + return message+'|'+('%04x'%crc) + + def ping(self): + self.wfh.write('PING\n') + (code, string) = self.get_response() + return (code == '000', code, string) + + def vend(self, item): + if not re.search('^[0-9][0-9]$', item): + return (False, 'Invalid item requested (%s)'%item) + self.wfh.write(self.authed_message(('V%s'%item))+'\n') + (code, string) = self.get_response() + return (code == '100', code, string) + + def beep(self, duration = None, synchronous = True): + msg = 'B' + if synchronous: msg += 'S' + if duration != None: + if duration > 255: duration = 255 + if duration < 1: duration = 1 + msg += '%02x'%duration + self.wfh.write(msg+'\n') + (code, string) = self.get_response() + return (code == '500', code, string) + + def silence(self, duration = None, synchronous = True): + msg = 'C' + if synchronous: msg += 'S' + if duration != None: + if duration > 255: duration = 255 + if duration < 1: duration = 1 + msg += '%02x'%duration + self.wfh.write(msg+'\n') + (code, string) = self.get_response() + return (code == '501', code, string) + + def display(self, string): + if len(string) > 10: + string = string[0:10] + string = re.sub('(.)\.', lambda match: '.'+match.group(1), string) + self.wfh.write('D'+string+'\n') + (code, string) = self.get_response() + return (code == '300', code, string) + + def next_event(self, timeout = None): + # we don't want to buffer in the serial port, so we get all the events + # we can ASAP. + + # Never have no timeout... + if timeout == None: timeout = 60*60*24*365 + + # Make sure we go through the loop at least once. + if timeout < 0: timeout = 0 + + while timeout >= 0: + this_timeout = min(timeout, 0.2) + timeout -= this_timeout + + (r, _, _) = select([self.rfh], [], [], this_timeout) + if r: + self.get_response(async = True) + timeout = 0 + + if self.mifare: + now = time() + if now > self.mifare_timeout: + self.mifare_timeout = now + 0.5 + mifare_uid = self.mifare.get_card_id() + if mifare_uid != None: + logging.info('Got MIFARE card id %s'%(repr(mifare_uid))) + self.events.append((MIFARE, mifare_uid)) + timeout = 0 + if timeout == 0: + break + + if len(self.events) == 0: return (TICK, time()) + ret = self.events[0] + del self.events[0] + return ret diff --git a/VendServer/servers.conf b/VendServer/servers.conf new file mode 100644 index 0000000..5919d22 --- /dev/null +++ b/VendServer/servers.conf @@ -0,0 +1,15 @@ + +[Database] +Server = localhost +Name = dispense + +[VendingMachine] +DBUser = cameron +DBPassword = blah +ServiceName = blah +Password = foo + +[DecServer] +Name = outside +ConnectPassword = foo +PrivPassword = bar diff --git a/VendServer/snacks.conf b/VendServer/snacks.conf new file mode 100644 index 0000000..4f036ed --- /dev/null +++ b/VendServer/snacks.conf @@ -0,0 +1,14 @@ +# format: +# all are whitespace seperated, name can have whitespace. +# price - in cents +# slots - comma seperated list of slots with this item. +# - don't put any spaces in this +# - the magically named '--' slot is the default for +# unadded slots +# name - the name, spaces are OK. + +# price slots dispensename name +# eg +# 550 88,00 5out $5 withdrawal + 550 88,00 5out $5 withdrawal + 110 -- snack tasty snack diff --git a/sql-edition/servers/.cvsignore b/sql-edition/servers/.cvsignore deleted file mode 100644 index 0d20b64..0000000 --- a/sql-edition/servers/.cvsignore +++ /dev/null @@ -1 +0,0 @@ -*.pyc diff --git a/sql-edition/servers/CRC.py b/sql-edition/servers/CRC.py deleted file mode 100644 index 512a7bd..0000000 --- a/sql-edition/servers/CRC.py +++ /dev/null @@ -1,16 +0,0 @@ -crctab = [] -CRC16 = 0x1021 - -def do_crc(message, crc = 0): - for i in range(0,len(message)): - crc = ((crc << 8) ^ (crctab[(crc >> 8) ^ ord(message[i])])) & 0xffff - return crc - -# Generate crctab -for val in range(0,256): - crc = val << 8; - for i in range(0,8): - crc = crc << 1 - if (crc & 0x10000): - crc = crc ^ CRC16 - crctab.append(crc & 0xffff) diff --git a/sql-edition/servers/DoorClient.py b/sql-edition/servers/DoorClient.py deleted file mode 100755 index d46cebe..0000000 --- a/sql-edition/servers/DoorClient.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/python - -from LATClient import LATClient -from select import select -import signal -import sys - -def check_door_service(service, test_string="got wombles?"): - latclient = LATClient(service=sys.argv[1]) - latclient.sock.send(test_string) - rfh, wfh = latclient.get_fh() - wfh.write(test_string) - wfh.flush() - rr, wr, er = select([rfh], [], [], 10.0) - if rfh not in rr: return "open" - recv = rfh.read(len(test_string)) - if recv <> test_string: return "error" - return "closed" - -if __name__ == '__main__': - result_codes = { 'open' : 0, 'closed' : 1, 'error' : 2, 'invalid args' : 3} - def return_result(result): - print result - sys.exit(result_codes[result]) - def timeout(signum, frame): - return_result("error") - if len(sys.argv) <> 2: return_result('invalid args') - signal.signal(signal.SIGALRM, timeout) - signal.alarm(15) - return_result(check_door_service(sys.argv[1])) diff --git a/sql-edition/servers/HorizScroll.py b/sql-edition/servers/HorizScroll.py deleted file mode 100644 index d27f7dc..0000000 --- a/sql-edition/servers/HorizScroll.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python - -import string -import sys -import time - -class HorizScroll: - def __init__(self, text): - self.text = text - pass - - def expand(self, padding=None, paddingchar=" ", dir=None, wraparound=False): - if len(self.text) <= 10: - return [text] - - if padding == None: - padding = len(self.text) / 2 + 1 - - format = "%-" + str(padding) + "." + str(padding) + "s" - pad = string.replace(format % " "," ",paddingchar) - padtext = self.text + pad - if not wraparound: - numiters = len(self.text) - 10 - else: - numiters = len(padtext) - - expansion = [] - - for x in range(0,numiters): - expansion.append("%-10.10s" % (padtext[x:] + padtext[:x])) - - if dir == -1: - expansion.reverse() - - return expansion - -if __name__ == '__main__': - h = HorizScroll("hello cruel world") - eh = h.expand() - while 1: - for x in eh: - sys.stdout.write("\r") - print "%-10.10s" % x, - sys.stdout.flush() - time.sleep(0.1) - diff --git a/sql-edition/servers/Idler.py b/sql-edition/servers/Idler.py deleted file mode 100755 index 5b66455..0000000 --- a/sql-edition/servers/Idler.py +++ /dev/null @@ -1,315 +0,0 @@ -#!/usr/bin/env python - -import string, time, os -from subprocess import Popen, PIPE -from random import random -from MessageKeeper import MessageKeeper - -orderings = None - -IDLER_TEXT_SPEED=1.8 - -class Idler: - def __init__(self, v): - self.v = v - - def next(self): - """Displays next stage of the idler. Returns time to the next step""" - return 1 - - def reset(self): - """Resets the idler to a known intial state""" - pass - - def finished(self): - """Returns True if the idler is considered finished""" - return False - - def affinity(self): - """How much we want this idler to be the next one chosen""" - return 1 - -class GreetingIdler(Idler): - def __init__(self, v, secs_to_greeting = None): - self.v = v - self.secs_to_greeting = secs_to_greeting - self.message_displayed = False - - def next(self): - if not self.secs_to_greeting is None: - x = self.secs_to_greeting - self.secs_to_greeting = None - return x - - self.v.display('UCC SNACKS') - self.message_displayed = True - return 5 - - def reset(self): - self.message_displayed = False - self.secs_to_greeting = None - - def finished(self): - return self.message_displayed - - def affinity(self): - return 0 - -class TrainIdler(Idler): - def __init__(self, v): - self.idle_state = 0 - self.v = v - - def put_shark(self, s, l): - if self.s[l] == ' ': - self.s[l] = s - elif self.s[l] == 'X': - self.s[l] = '*' - else: - self.s[l] = 'X' - - def next(self): - # does the next stage of a dance - self.s = [' ']*10 - shark1 = self.idle_state % 18 - if shark1 < 9: - self.put_shark('^', shark1) - else: - self.put_shark('^', 18-shark1) - - shark2 = ((self.idle_state+4) % 36)/2 - if shark2 < 9: - self.put_shark('<', shark2) - else: - self.put_shark('<', 18-shark2) - - shark3 = ((self.idle_state+7) % 54)/3 - if shark3 < 9: - self.put_shark('>', 9-shark3) - else: - self.put_shark('>', 9-(18-shark3)) - - train1 = ((self.idle_state%(18*36))) - train1_start = 122 - if train1 > train1_start and train1 < train1_start+(10*2): - for i in range(5): - ptr = i+train1-train1_start-5 - if ptr >= 0 and ptr < 10: self.s[ptr] = '#' - - train2 = ((self.idle_state%(18*36))) - train2_start = 400 - if train2 > train2_start and train2 < train2_start+(10*2): - for i in range(5): - ptr = i+train2-train2_start-5 - if ptr >= 0 and ptr < 10: self.s[9-ptr] = '#' - - train3 = ((self.idle_state%(18*36))) - train3_start = 230 - if train3 > train3_start and train3 < train3_start+(10*2): - for i in range(10): - ptr = i+train3-train3_start-10 - if ptr >= 0 and ptr < 10: self.s[ptr] = '-' - - self.v.display(string.join(self.s, '')) - self.idle_state += 1 - self.idle_state %= 18*36*54 - - def reset(self): - self.idle_state = 0 - -class OrderMaker: - def __init__(self, n=8): - self.n = n - self.make_factorials(n) - - def make_factorials(self, n): - self.factorial = [] - a = 1 - for i in range(1,n+1): - self.factorial.append(a) - a *= i - - def order(self, index): - used = [] - for i in range(0,self.n): - used.append(i) - i = self.n-1 - j = 0 - res = [] - while i >= 0: - a = index/self.factorial[i] - index %= self.factorial[i] - res.append(a+1) - i -= 1 - j += 1 - for i in range(0,self.n): - tmp = used[res[i]-1] - for j in range(res[i],self.n): - used[j-1] = used[j] - res[i] = tmp - return res - - def __getitem__(self, i): - return self.order(i) - -class GrayIdler(Idler): - def __init__(self, v, one=None, zero=None, reorder=0): - self.bits = 8 - self.size = 1 << self.bits - self.i = 0 - self.grayCode = 0 - self.v = v - self.one = one - self.zero = zero - self.reorder = reorder - global orderings - if not orderings: - orderings = OrderMaker() - - def next(self): - output = self.do_next_state() - # does the next stage of a dance - if self.zero: - output = string.replace(output, "0", self.zero) - if self.one: - output = string.replace(output, "1", self.one) - if self.reorder: - global orderings - newoutput = "" - for i in range(0,8): - newoutput += output[orderings[self.reorder][i]] - output = newoutput - self.v.display(" %8.8s " % (output)) - self.i = (self.i + 1) % self.size - - def do_next_state(self): - self.grayCode = self.i ^ (self.i >> 1) - output = self.dec2bin(self.grayCode) - - return "0"*(self.bits-len(output))+output - - - def dec2bin(self,num): - """Convert long/integer number to binary string. - - E.g. dec2bin(12) ==> '1100'. - - from http://starship.python.net/~gherman/playground/decbingray/decbingray.py""" - - assert num >= 0, "Decimal number must be >= 0!" - - # Gracefully handle degenerate case. - # (Not really needed, but anyway.) - if num == 0: - return '0' - - # Find highest value bit. - val, j = 1L, 1L - while val < num: - val, j = val*2L, j+1L - - # Convert. - bin = '' - i = j - 1 - while i + 1L: - k = pow(2L, i) - if num >= k: - bin = bin + '1' - num = num - k - else: - if len(bin) > 0: - bin = bin + '0' - i = i - 1L - - return bin - - def reset(self): - self.i = 0 - self.grayCode = 0 - if self.reorder: - self.reorder = int(random()*40319)+1 - - -class StringIdler(Idler): - def __init__(self, v, text="Hello Cruel World! ",repeat=True): - self.v = v - self.mk = MessageKeeper(v) - self.text = self.clean_text(text) + " " - - msg = [("",False, None),(self.text, repeat, IDLER_TEXT_SPEED)] - self.mk.set_messages(msg) - - def clean_text(self, text): - # nothing like a bit of good clean text :) - valid = string.digits \ - + string.letters \ - + string.punctuation \ - + " " - # uppercase it - text = string.upper(text) - clean = "" - for char in text: - if char in valid: - clean = clean + char - else: - clean = clean + " " - return clean - - def next(self): - self.mk.update_display() - - def finished(self): - return self.mk.done() - -class ClockIdler(Idler): - def __init__(self, v): - self.v = v - self.last = None - - def next(self): - colonchar = ':' - if int(time.time()*2) & 1: colonchar = ' ' - output = time.strftime("%%H%c%%M%c%%S"%(colonchar,colonchar)) - if output != self.last: - self.v.display(" %8.8s " % (output)) - self.last = output - - def affinity(self): - return 3 - -class FortuneIdler(StringIdler): - def __init__(self, v): - fortune = "/usr/games/fortune" - text = "I broke my wookie...." - if os.access(fortune,os.F_OK|os.X_OK): - (lines, unused) = Popen((fortune,), close_fds=True, stdout=PIPE).communicate() - text = string.join(lines) - StringIdler.__init__(self, v, text,repeat=False) - - def affinity(self): - return 20 - -class PipeIdler(StringIdler): - def __init__(self, v, command, args): - text = "I ate my cookie...." - if os.access(command,os.F_OK|os.X_OK): - (lines, unused) = Popen([command,] + args.split(), close_fds=True, stdout=PIPE).communicate() - text = string.join(lines) - StringIdler.__init__(self, v, text,repeat=False) - - def affinity(self): - return 20 - -class FileIdler(StringIdler): - def __init__(self, v, thefile=None, repeat=False, affinity=8): - text = "I broke my wookie...." - self._affinity = affinity - - if file and os.access(thefile,os.F_OK|os.R_OK): - f = file(thefile,'r') - text = string.join(f.readlines()) - f.close() - StringIdler.__init__(self, v, text,repeat=repeat) - - def affinity(self): - return self._affinity diff --git a/sql-edition/servers/LATClient.py b/sql-edition/servers/LATClient.py deleted file mode 100644 index 638d20b..0000000 --- a/sql-edition/servers/LATClient.py +++ /dev/null @@ -1,157 +0,0 @@ -from socket import * -from select import select -from os import popen4 -from time import sleep -import logging - -LATCP_SOCKET = '/var/run/latlogin' - -LAT_VERSION = '1.22' -LAT_VERSION = '1.24' # for running on Mermaid. [DAA] 20071107 -LATCP_CMD_VERSION = 8 -LATCP_CMD_TERMINALSESSION = 26 -LATCP_CMD_ERRORMSG = 99 - -class LATClientException(Exception): pass - -def read_for_a_bit(rfh): - message = '' - while 1: - r = select([rfh], [], [], 5.0)[0] - if r: - try: - ch = rfh.read(1) - except socket.error: - ch = '' - if ch == '': - break - message = message + ch - else: - break - logging.debug("Received message: ", repr(message)) - return message - -def write_and_get_response(rfh, wfh, message, expect_echo=True): - logging.debug("Writing message:", repr(message)) - wfh.write(message+'\r\n') - wfh.flush() - logging.debug(" --> Sent") - response = read_for_a_bit(rfh) - if response.find(message) == -1 and expect_echo: - raise LATClientException("Talking to DEC server, expected to find original message in echo but didn't") - return response - -class LATClient: - def __init__(self, service = None, node = None, port = None, - localport = None, password = None, is_queued = False, - server_name = '', connect_password='', priv_password=''): - - self.server_name = server_name - self.connect_password = connect_password - self.priv_password = priv_password - - self.sock = socket(AF_UNIX, SOCK_STREAM, 0); - self.sock.connect(LATCP_SOCKET) - self.send_msg(LATCP_CMD_VERSION, LAT_VERSION+'\000') - (cmd, msg) = self.read_reply() - if service == None: service = '' - if node == None: node = '' - if port == None: port = '' - if localport == None: localport = '' - if password == None: password = '' - if is_queued == True: - is_queued = 1 - else: - is_queued = 0 - self.send_msg(LATCP_CMD_TERMINALSESSION, '%c%c%s%c%s%c%s%c%s%c%s' % \ - (is_queued, - len(service), service, - len(node), node, - len(port), port, - len(localport), localport, - len(password), password - )) - (cmd, msg) = self.read_reply() - if ord(cmd) == LATCP_CMD_ERRORMSG: - raise LATClientException(msg) - - self.rfh = self.sock.makefile('r') - self.wfh = self.sock.makefile('w') - - r = select([self.rfh], [], [], 2.0)[0] - if r: - l = self.rfh.readline() - if l.find('Service in use') >= 0: - logging.warning("Service in use, apparently: restarting DEC server") - self.reboot_server() - - def __del__(self): - try: - self.sock.close() - self.sock.shutdown(2) - except: - pass - del self.sock - - def send_msg(self, cmd, msg): - self.sock.send('%c%c%c%s'%(cmd, len(msg)/256, len(msg)%256, msg)) - - def reboot_server(self): - self.sock.shutdown(2) - self.sock.close() - - logging.info('Logging into DEC server') - mopw, mopr = popen4('/usr/sbin/moprc '+self.server_name) - write_and_get_response(mopr, mopw, '') - - logging.info('Sending password') - r = write_and_get_response(mopr, mopw, self.connect_password, False) - if r.find('Enter username> ') == -1: - logging.warning("Expected username prompt, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Sending username') - r = write_and_get_response(mopr, mopw, 'grim reaper') - if r.find('Local> ') == -1: - logging.warning("Expected DEC server prompt, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Requesting privileges') - r = write_and_get_response(mopr, mopw, 'set priv') - if r.find('Password> ') == -1: - logging.warning("Expected priv password prompt, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Sending password') - r = write_and_get_response(mopr, mopw, self.priv_password, False) - if r.find('Local> ') == -1: - logging.warning("Expected DEC server prompt, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Sending reboot request') - r = write_and_get_response(mopr, mopw, 'init del 0') - if r.find('Target does not respond') == -1: - logging.warning("Expected DEC server to die, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Closed connection to server') - mopr.close() - mopw.close() - logging.info("Waiting 10 seconds for DEC server to come back to life...") - sleep(10) - logging.info("Rightyo, back to vending!") - raise LATClientException('needed to reboot server') - - def read_reply(self): - head = self.sock.recv(3) - if len(head) != 3: - raise LATClientException('Short LAT packet') - cmd = head[0] - length = ord(head[1])*256 + ord(head[2]) - msg = self.sock.recv(length) - if cmd == LATCP_CMD_ERRORMSG: - raise LATClientException('Received LAT error: %s'%msg) - return (cmd, msg) - - def get_fh(self): - return (self.rfh, self.wfh) diff --git a/sql-edition/servers/LDAPConnector.py b/sql-edition/servers/LDAPConnector.py deleted file mode 100644 index 8699fd2..0000000 --- a/sql-edition/servers/LDAPConnector.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python2.4 - -import ldap -import ldap.filter - -LDAP_TIMEOUT = 10 - -def get_ldap_connection(): - ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/ssl/UCC-CA.crt') - ldap.set_option(ldap.OPT_X_TLS,1) - ldap.set_option(ldap.OPT_X_TLS_ALLOW,1) - #ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) - conn = ldap.initialize('ldaps://mussel.ucc.gu.uwa.edu.au/') - - binddn = 'cn=mifareagent,ou=profile,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' - passfile = open('/etc/dispense2/ldap.passwd') - password = passfile.readline().strip() - passfile.close() - - conn.simple_bind_s(binddn, password) - return conn - -def get_uid(card_id): - ldapconn = get_ldap_connection() - - basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' - filter = ldap.filter.filter_format('(uccDispenseMIFARE=%s)', (card_id, )) - attrs = ('uidNumber',) - - results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) - - ldapconn.unbind() - - if len(results) != 1: - raise ValueError, "no UID found for card ID" - - return results[0][1]['uidNumber'][0] - -def get_uname(uid): - ldapconn = get_ldap_connection() - - basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' - filter = ldap.filter.filter_format('(uidNumber=%s)', (uid, )) - attrs = ('uid',) - - results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) - - ldapconn.unbind() - - if len(results) != 1: - raise ValueError, "no username found for user id" - - return results[0][1]['uid'][0] - -def set_card_id(uidNumber, card_id): - ldapconn = get_ldap_connection() - - # fix uidNumber for three/four digit uids - uidNumber = str(int(uidNumber)) - basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' - filter = ldap.filter.filter_format('(uidNumber=%s)', (uidNumber, )) - attrs = ('objectClass', ) - - results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) - - if len(results) != 1: - raise "ValueError", 'error in uidNumber' - - user_dn = results[0][0] - - mod_attrs = [] - - # Does it have the correct object class? - if 'uccDispenseAccount' not in results[0][1]['objectClass']: - # Add uccDispenseAccount objectclass - mod_attrs.append((ldap.MOD_ADD, 'objectClass', 'uccDispenseAccount')) - - # Add MIFARE Card ID - mod_attrs.append((ldap.MOD_ADD, 'uccDispenseMIFARE', card_id)) - - # Use a double-try here to work around something that's fixed in Python 2.5 - try: - try: - ldapconn.modify_s(user_dn, mod_attrs) - except ldap.TYPE_OR_VALUE_EXISTS, e: - pass - finally: - ldapconn.unbind() - -if __name__ == '__main__': - set_card_id('11126', '\x01\x02\x03\x04\x05\x06') - print get_uid('\x01\x02\x03\x04\x05\x06') diff --git a/sql-edition/servers/MIFAREClient.py b/sql-edition/servers/MIFAREClient.py deleted file mode 100644 index e0b6a6c..0000000 --- a/sql-edition/servers/MIFAREClient.py +++ /dev/null @@ -1,44 +0,0 @@ -from MIFAREDriver import MIFAREReader, MIFAREException -from serial import Serial -from LDAPConnector import get_uid, set_card_id - -class MIFAREClient: - def __init__(self): - self.port = Serial('/dev/ttyS2', baudrate = 19200) - self.reader = MIFAREReader(self.port) - self.reader.set_led(red = False, green = True) - self.reader.beep(100) - - def get_card_id(self): - self.reader.set_led(red = True, green = False) - try: - card_id, capacity = self.reader.select_card() - except MIFAREException: - self.reader.set_led(red = False, green = True) - return None - else: - self.reader.set_led(red = False, green = True) - self.reader.beep(100) - return card_id - - def get_card_uid(self): - card_id = self.get_card_id() - if card_id == None: - return None - else: - return get_uid(card_id) - - def add_card(self, uid): - self.reader.set_led(red = True, green = False) - for attempt in range(5): - self.reader.beep(50) - try: - card_id, capacity = self.reader.select_card() - except MIFAREException: - pass - else: - set_card_id(uid, card_id) - self.reader.set_led(red = False, green = True) - return True - self.reader.set_led(red = False, green = True) - return False diff --git a/sql-edition/servers/MIFAREDriver.py b/sql-edition/servers/MIFAREDriver.py deleted file mode 100644 index 6248f60..0000000 --- a/sql-edition/servers/MIFAREDriver.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python2.5 - -'''mifare - a library for interacting with MIFARE readers. -Written by David Adam -Requires Python 2.5. - -Licensed under an MIT-style license: see LICENSE file for details. -''' - -import serial, logging - -xor = lambda x, y: x ^ y -def checksum(string): - return chr(reduce(xor, [ord(i) for i in string])) - - -class MIFAREException(Exception): - pass - - -class MIFARECommunicationException(MIFAREException): - pass - - -class MIFAREAuthenticationException(MIFAREException): - pass - - -class MIFAREReader: - '''An interface to a particular MIFARE reader.''' - - def __init__(self, io): - '''Returns an interface to a MIFARE reader given a file-like object. - The file-like object is generally a pyserial Serial object.''' - self.io = io - if isinstance(self.io, serial.Serial): - self.io.setTimeout(2) - self.address = '\x00\x00' - - def get_absolute_block(self, vector): - if vector[0] < 32: - return vector[0] * 4 + vector[1] - else: - # Sectors below 32 are 4 blocks - # Sectors above are 16 blocks - # Thus, sector 32 starts at block 128, 33 at 144, and so on - return 128 + (vector[0] - 32) * 16 + vector[1] - - def send_packet(self, data): - '''Constructs a packet for the supplied data string, sends it to the - MIFARE reader, then returns the response (if any) to the commmand.''' - - # Occasionally the reader inserts extra trailing characters into its - # responses, so flush the buffers if possible beforehand. - if isinstance(self.io, serial.Serial): - self.io.flushInput() - self.io.flushOutput() - - # XXX - Needs more error checking. - data = '\x00' + self.address + data - packet = '\xAA\xBB' + chr(len(data)) + data + checksum(data) - self.io.write(packet) - response = '' - header = self.io.read(2) - if header == '\xaa\xbb': - length = ord(self.io.read(1)) - data = self.io.read(length) - packet_xsum = self.io.read(1) - if checksum(data) == packet_xsum and len(data) == length: - # Strip off separator and address header - return data[3:] - else: - raise MIFARECommunicationException, "Invalid response received" - - def set_antenna(self, state = True): - """Turn the card reader's antenna on or off (no return value)""" - command = '\x0C\x01' + chr(int(state)) - response = self.send_packet(command) - if response == '\x0c\x01\x00': - return None - else: - raise MIFAREException, 'command failed: set_antenna (%s)' % state - - def select_card(self, include_halted = False): - """Selects a card and returns a tuple of (serial number, capacity). - - If include_halted is set, may select a card that halt() has previously - been called on.""" - - # Request type of card available - command = command = '\x01\x02' - if include_halted: - command += '\x52' - else: - command += '\x26' - - card_type_response = self.send_packet(command) - - if card_type_response == None or card_type_response[2] == '\x14': - raise MIFAREException, "select_card: no card available" - card_type = card_type_response[3:5] - - if card_type == '\x44\x00': # MIFARE UltraLight - raise NotImplementedError, "UltraLight card selected - no functions available" - - else: - # Otherwise, must be a standard MIFARE card. - # Anticollision - command = '\x02\x02\x04' - # No error handling on this command - serial = self.send_packet(command)[3:] - - # Select the card for use - try: - select_response = self.send_packet('\x03\x02' + serial) - capacity = ord(select_response[3]) - except IndexError: - logging.warning('Tried to select card but failed: card_type %s, serial %s, select_response %s' % (card_type.__repr__(), serial.__repr__(), select_response.__repr__())) - capacity = 0 - return (serial, capacity) - - def sector_login(self, blockvect, key, keytype=0): - """Log in to a block using the six-byte key. - - Use a keytype of 1 to use key B.""" - sector = self.get_absolute_block((blockvect[0], 0)) - - if len(key) != 6: - raise ValueError, 'key must be a six-byte string' - - keytype = 96 + keytype - - data = chr(keytype) + chr(sector) + key - - result = self.send_packet('\x07\x02' + data) - if ord(result[2]) == 22: - raise MIFAREAuthenticationException, "incorrect key provided" - - return - - def read_block(self, blockvect): - "Read the 16-byte block at vector (sector, block)." - block = self.get_absolute_block(blockvect) - - result = self.send_packet('\x08\x02' + chr(block)) - return result[3:19] - - def write_block(self, blockvect, data): - """Write the 16 bytes in data to the block at vector (sector, block).""" - block = self.get_absolute_block(blockvect) - if len(data) != 16: - raise ValueError, "invalid data length - must be 16 bytes" - - result = self.send_packet('\x09\x02' + chr(block) + data) - return - - def write_key(self, key): - pass - - def value_block_increment(self, blocknum, increment): - pass - - def value_block_decrement(self, blocknum, decrement): - pass - - def copy_block(self, source, dest): - pass - - def halt(self): - """Halt the current card - no further transactions will be performed with it.""" - self.send_packet('\x04\x02') - - def set_led(self, red = False, green = False): - led_state = 0 - if red: - led_state += 1 - if green: - led_state += 2 - self.send_packet('\x07\x01' + chr(led_state)) - - def beep(self, length): - '''Beep for a specified length of milliseconds.''' - length = int(round(length / 10.)) - if length > 255: - length = 255 - self.send_packet('\x06\x01' + chr(length)) - - def reset(self): - pass diff --git a/sql-edition/servers/MessageKeeper.py b/sql-edition/servers/MessageKeeper.py deleted file mode 100755 index 60d2b9e..0000000 --- a/sql-edition/servers/MessageKeeper.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/python -# vim:ts=4 - -import sys, os, string, re, pwd, signal -from HorizScroll import HorizScroll -from random import random, seed -from time import time, sleep - -class MessageKeeper: - def __init__(self, vendie): - # Each element of scrolling_message should be a 3-tuple of - # ('message', True/False if it is to be repeated, time to display) - self.scrolling_message = [] - self.v = vendie - self.next_update = None - - def set_message(self, string): - self.scrolling_message = [(string, False, None)] - self.update_display(True) - - def set_messages(self, strings): - self.scrolling_message = strings - self.update_display(True) - - def update_display(self, forced = False, padding = 0): - if not forced and self.next_update != None and time() < self.next_update: - return - if len(self.scrolling_message) > 0: - if len(self.scrolling_message[0][0]) > 10: - (m, r, t) = self.scrolling_message[0] - a = [] - exp = HorizScroll(m).expand(padding, wraparound = r) - if t == None: - t = 0.1 - else: - t = t / len(exp) - for x in exp: - a.append((x, r, t)) - del self.scrolling_message[0] - self.scrolling_message = a + self.scrolling_message - newmsg = self.scrolling_message[0] - if newmsg[2] != None: - self.next_update = time() + newmsg[2] - else: - self.next_update = None - self.v.display(self.scrolling_message[0][0]) - if self.scrolling_message[0][1]: - self.scrolling_message.append(self.scrolling_message[0]) - del self.scrolling_message[0] - - def done(self): - return len(self.scrolling_message) == 0 - diff --git a/sql-edition/servers/SerialClient.py b/sql-edition/servers/SerialClient.py deleted file mode 100644 index e0a9a7e..0000000 --- a/sql-edition/servers/SerialClient.py +++ /dev/null @@ -1,41 +0,0 @@ -import os, termios -from time import sleep -import logging -from serial import * - -class SerialClientException(Exception): pass - -class SerialClient: - def __init__(self, port = '/dev/ttyS1', baud = 9600): - self.ser = Serial( - port = port, - baudrate = baud, - bytesize=EIGHTBITS, #number of databits - parity=PARITY_NONE, #enable parity checking - stopbits=STOPBITS_ONE, #number of stopbits - timeout=10, #set a timeout value, None for waiting forever, return on read - xonxoff=0, #enable software flow control - rtscts=0, #enable RTS/CTS flow control - ) - - - self.rfh = self.ser - self.wfh = self.ser - self.wfh.write('B\n') - - def get_fh(self): - return (self.rfh, self.wfh) - - def __del__(self): - pass - - -if __name__ == '__main__': - s = SerialClient("/dev/ttyS1", 9600) - - (rfh, wfh) = s.get_fh() - - wfh.write('B\n') - print rfh.read() - - diff --git a/sql-edition/servers/SnackConfig.py b/sql-edition/servers/SnackConfig.py deleted file mode 100755 index 2314074..0000000 --- a/sql-edition/servers/SnackConfig.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python - -class VendingException( Exception ): pass - -import subprocess -import os, re - -def get_snack( slot ): - - if slot == "--": - return (0, 'nothing', 'Nothing') - cmd = 'dispense iteminfo snack:%s' % slot -# print 'cmd = %s' % cmd - try: -# info = subprocess.check_output(["dispense","iteminfo",'snack:%s'%slot]) - raw = os.popen(cmd) - info = raw.read() - raw.close() -# print 'cmd (2) = %s' % cmd -# print 'info = "%s"' % info - m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info) - val = ( int(m.group(1))*100 + int(m.group(2)), m.group(3), m.group(3) ) -# print 'Price: %i, Name: %s' % (val[0], val[1]) - except BaseException as e: - print "BaseException" - print e - val = (0, 'error', 'Error') - except: - print "Unknown exception" - val = (0, 'error', 'Error') - return val - -def get_price( slot ): - p, sn, n = get_snacks( slot ) - return p - -def get_name( slot ): - p, sn, n = get_snacks( slot ) - return n - -def get_short_name( slot ): - p, sn, n = get_snacks( slot ) - return sn - -if __name__ == '__main__': - print "Don't run this" diff --git a/sql-edition/servers/VendServer.py b/sql-edition/servers/VendServer.py deleted file mode 100755 index 2a9a55a..0000000 --- a/sql-edition/servers/VendServer.py +++ /dev/null @@ -1,1187 +0,0 @@ -#!/usr/bin/python -# vim:ts=4 - -USE_DB = 0 -USE_MIFARE = 1 - -import ConfigParser -import sys, os, string, re, pwd, signal, math, syslog -import logging, logging.handlers -from traceback import format_tb -if USE_DB: import pg -from time import time, sleep, mktime, localtime -from subprocess import Popen, PIPE -from LATClient import LATClient, LATClientException -from SerialClient import SerialClient, SerialClientException -from VendingMachine import VendingMachine, VendingException -from MessageKeeper import MessageKeeper -from HorizScroll import HorizScroll -from random import random, seed -from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler -from SnackConfig import get_snack#, get_snacks -import socket -from posix import geteuid -from LDAPConnector import get_uid,get_uname, set_card_id - -CREDITS=""" -This vending machine software brought to you by: -Bernard Blackham -Mark Tearle -Nick Bannon -Cameron Patrick -and a collective of hungry alpacas. - - - -For a good time call +61 8 6488 3901 - - - -""" - -PIN_LENGTH = 4 - -DOOR = 1 -SWITCH = 2 -KEY = 3 -TICK = 4 -MIFARE = 5 - - -( -STATE_IDLE, -STATE_DOOR_OPENING, -STATE_DOOR_CLOSING, -STATE_GETTING_UID, -STATE_GETTING_PIN, -STATE_GET_SELECTION, -STATE_GRANDFATHER_CLOCK, -) = range(1,8) - -TEXT_SPEED = 0.8 -IDLE_SPEED = 0.05 - -class DispenseDatabaseException(Exception): pass - -class DispenseDatabase: - def __init__(self, vending_machine, host, name, user, password): - self.vending_machine = vending_machine - self.db = pg.DB(dbname = name, host = host, user = user, passwd = password) - self.db.query('LISTEN vend_requests') - - def process_requests(self): - logging.debug('database processing') - query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false' - try: - outstanding = self.db.query(query).getresult() - except (pg.error,), db_err: - raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip())) - for (id, slot) in outstanding: - (worked, code, string) = self.vending_machine.vend(slot) - logging.debug (str((worked, code, string))) - if worked: - query = 'SELECT vend_success(%s)'%id - self.db.query(query).getresult() - else: - query = 'SELECT vend_failed(%s)'%id - self.db.query(query).getresult() - - def handle_events(self): - notifier = self.db.getnotify() - while notifier is not None: - self.process_requests() - notify = self.db.getnotify() - -def scroll_options(username, mk, welcome = False): - if welcome: - # Balance checking - acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate() - # this is fucking appalling - balance = acct[acct.find("$")+1:acct.find("(")].strip() - - msg = [(center('WELCOME'), False, TEXT_SPEED), - (center(username), False, TEXT_SPEED), - (center(balance), False, TEXT_SPEED),] - else: - msg = [] - choices = ' '*10+'CHOICES: ' - - # Get coke contents - cokes = [] - for i in range(0, 7): - args = ('dispense', 'iteminfo', 'coke:%i' % i) - info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate() - m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info) - cents = int(m.group(1))*100 + int(m.group(2)) - cokes.append('%i %i %s' % (i, cents, m.group(3))); - - for c in cokes: - c = c.strip() - (slot_num, price, slot_name) = c.split(' ', 2) - if slot_name == 'dead': continue - choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num) - -# we don't want to print snacks for now since it'll be too large -# and there's physical bits of paper in the machine anyway - matt -# try: -# snacks = get_snacks() -# except: -# snacks = {} -# -# for slot, ( name, price ) in snacks.items(): -# choices += '%s8-%s (%sc) ' % ( slot, name, price ) - - choices += '55-DOOR ' - choices += 'OR ANOTHER SNACK. ' - choices += '99 TO READ AGAIN. ' - choices += 'CHOICE? ' - msg.append((choices, False, None)) - mk.set_messages(msg) - -_pin_uid = 0 -_pin_uname = 'root' -_pin_pin = '----' - -def _check_pin(uid, pin): - global _pin_uid - global _pin_uname - global _pin_pin - print "_check_pin('",uid,"',---)" - if uid != _pin_uid: - try: - info = pwd.getpwuid(uid) - except KeyError: - logging.info('getting pin for uid %d: user not in password file'%uid) - return None - if info.pw_dir == None: return False - pinfile = os.path.join(info.pw_dir, '.pin') - try: - s = os.stat(pinfile) - except OSError: - logging.info('getting pin for uid %d: .pin not found in home directory'%uid) - return None - if s.st_mode & 077: - logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid) - os.chmod(pinfile, 0600) - try: - f = file(pinfile) - except IOError: - logging.info('getting pin for uid %d: I cannot read pin file'%uid) - return None - pinstr = f.readline() - f.close() - if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr): - logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr))) - return None - _pin_uid = uid - _pin_pin = pinstr - _pin_uname = info.pw_name - else: - pinstr = _pin_pin - if pin == int(pinstr): - logging.info("Pin correct for %d",uid) - else: - logging.info("Pin incorrect for %d",uid) - return pin == int(pinstr) - -def acct_is_disabled(name=None): - global _pin_uname - if name == None: - name = _pin_uname - acct, unused = Popen(['dispense', 'acct', _pin_uname], close_fds=True, stdout=PIPE).communicate() - # this is fucking appalling - flags = acct[acct.find("(")+1:acct.find(")")].strip() - if 'disabled' in flags: - return True - if 'internal' in flags: - return True - return False - -def has_good_pin(uid): - return _check_pin(uid, None) != None - -def verify_user_pin(uid, pin, skip_pin_check=False): - if skip_pin_check or _check_pin(uid, pin) == True: - info = pwd.getpwuid(uid) - if skip_pin_check: - if acct_is_disabled(info.pw_name): - logging.info('refused mifare for disabled acct uid %d (%s)'%(uid,info.pw_name)) - return '-disabled-' - logging.info('accepted mifare for uid %d (%s)'%(uid,info.pw_name)) - else: - logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name)) - return info.pw_name - else: - logging.info('refused pin for uid %d'%(uid)) - return None - - -def cookie(v): - seed(time()) - messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY'] - choice = int(random()*len(messages)) - msg = messages[choice] - left = range(len(msg)) - for i in range(len(msg)): - if msg[i] == ' ': left.remove(i) - reveal = 1 - while left: - s = '' - for i in range(0, len(msg)): - if i in left: - if reveal == 0: - left.remove(i) - s += msg[i] - else: - s += chr(int(random()*26)+ord('A')) - reveal += 1 - reveal %= 17 - else: - s += msg[i] - v.display(s) - -def center(str): - LEN = 10 - return ' '*((LEN-len(str))/2)+str - - - -idlers = [] -idler = None - -def setup_idlers(v): - global idlers, idler - idlers = [ - GrayIdler(v), - StringIdler(v, text="Kill 'em all", repeat=False), - GrayIdler(v,one="*",zero="-"), - StringIdler(v, text=CREDITS), - GrayIdler(v,one="/",zero="\\"), - ClockIdler(v), - GrayIdler(v,one="X",zero="O"), - FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2), - GrayIdler(v,one="*",zero="-",reorder=1), - StringIdler(v, text=str(math.pi) + " "), - ClockIdler(v), - GrayIdler(v,one="/",zero="\\",reorder=1), - StringIdler(v, text=str(math.e) + " "), - GrayIdler(v,one="X",zero="O",reorder=1), - StringIdler(v, text=" I want some pizza - please call Pizza Hut Shenton Park on +61 8 9381 9979 [now closed? - MSH] - and order as Quinn - I am getting really hungry", repeat=False), - PipeIdler(v, "/usr/bin/getent", "passwd"), - FortuneIdler(v), - ClockIdler(v), - StringIdler(v), - TrainIdler(v), - # "Hello World" in brainfuck - StringIdler(v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."), - ] - disabled = [ - ] - -def reset_idler(v, vstatus, t = None): - global idlers, idler - idler = GreetingIdler(v, t) - vstatus.time_of_next_idlestep = time()+idler.next() - vstatus.time_of_next_idler = None - vstatus.time_to_autologout = None - vstatus.change_state(STATE_IDLE, 1) - -def choose_idler(): - global idlers, idler - iiindex = 0 - average_affinity = 10 # guessing here... - - if idler and idler.__class__ != GreetingIdler: - iiindex = idlers.index(idler) - - iilen = len(idlers) - - move = int(random()*len(idlers)*average_affinity) + 1 - - while move >= 0: - iiindex += 1 - iiindex %= iilen - idler = idlers[iiindex] - move -= idler.affinity() - - idler.reset() - -def idle_step(vstatus): - global idler - if idler.finished(): - choose_idler() - vstatus.time_of_next_idler = time() + 30 - nextidle = idler.next() - if nextidle is None: - nextidle = IDLE_SPEED - vstatus.time_of_next_idlestep = time()+nextidle - -class VendState: - def __init__(self,v): - self.state_table = {} - self.state = STATE_IDLE - self.counter = 0 - - self.mk = MessageKeeper(v) - self.cur_user = '' - self.cur_pin = '' - self.username = '' - self.cur_selection = '' - self.time_to_autologout = None - - self.last_timeout_refresh = None - - def change_state(self,newstate,newcounter=None): - if self.state != newstate: - #print "Changing state from: ", - #print self.state, - #print " to ", - #print newstate - self.state = newstate - - if newcounter is not None and self.counter != newcounter: - #print "Changing counter from: ", - #print self.counter, - #print " to ", - #print newcounter - self.counter = newcounter - - - -def handle_tick_event(event, params, v, vstatus): - # don't care right now. - pass - -def handle_switch_event(event, params, v, vstatus): - # don't care right now. - pass - - -def do_nothing(state, event, params, v, vstatus): - print "doing nothing (s,e,p)", state, " ", event, " ", params - pass - -def handle_getting_uid_idle(state, event, params, v, vstatus): - # don't care right now. - pass - -def handle_getting_pin_idle(state, event, params, v, vstatus): - # don't care right now. - pass - -def handle_get_selection_idle(state, event, params, v, vstatus): - # don't care right now. - ### - ### State logging out .. - if vstatus.time_to_autologout != None: - time_left = vstatus.time_to_autologout - time() - if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left): - vstatus.mk.set_message('LOGOUT: '+str(int(time_left))) - vstatus.last_timeout_refresh = int(time_left) - vstatus.cur_selection = '' - - if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0: - vstatus.time_to_autologout = None - vstatus.cur_user = '' - vstatus.cur_pin = '' - vstatus.cur_selection = '' - - reset_idler(v, vstatus) - - ### State fully logged out ... reset variables - if vstatus.time_to_autologout and not vstatus.mk.done(): - vstatus.time_to_autologout = None - if vstatus.cur_user == '' and vstatus.time_to_autologout: - vstatus.time_to_autologout = None - - ### State logged in - if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None: - # start autologout - vstatus.time_to_autologout = time() + 15 - vstatus.last_timeout_refresh = None - - ## FIXME - this may need to be elsewhere..... - # need to check - vstatus.mk.update_display() - - - -def handle_get_selection_key(state, event, params, v, vstatus): - key = params - if len(vstatus.cur_selection) == 0: - if key == 11: - vstatus.cur_pin = '' - vstatus.cur_user = '' - vstatus.cur_selection = '' - - vstatus.mk.set_messages([(center('BYE!'), False, 1.5)]) - reset_idler(v, vstatus, 2) - return - vstatus.cur_selection += chr(key + ord('0')) - vstatus.mk.set_message('SELECT: '+vstatus.cur_selection) - vstatus.time_to_autologout = None - elif len(vstatus.cur_selection) == 1: - if key == 11: - vstatus.cur_selection = '' - vstatus.time_to_autologout = None - scroll_options(vstatus.username, vstatus.mk) - return - else: - vstatus.cur_selection += chr(key + ord('0')) - if vstatus.cur_user: - make_selection(v,vstatus) - vstatus.cur_selection = '' - vstatus.time_to_autologout = time() + 8 - vstatus.last_timeout_refresh = None - else: - # Price check mode. - price_check(v,vstatus) - vstatus.cur_selection = '' - vstatus.time_to_autologout = None - vstatus.last_timeout_refresh = None - -def make_selection(v, vstatus): - # should use sudo here - if vstatus.cur_selection == '55': - vstatus.mk.set_message('OPENSESAME') - logging.info('dispensing a door for %s'%vstatus.username) - if geteuid() == 0: - #ret = os.system('su - "%s" -c "dispense door"'%vstatus.username) - ret = os.system('dispense -u "%s" door'%vstatus.username) - else: - ret = os.system('dispense door') - if ret == 0: - logging.info('door opened') - vstatus.mk.set_message(center('DOOR OPEN')) - else: - logging.warning('user %s tried to dispense a bad door'%vstatus.username) - vstatus.mk.set_message(center('BAD DOOR')) - sleep(1) - elif vstatus.cur_selection == '81': - cookie(v) - elif vstatus.cur_selection == '99': - scroll_options(vstatus.username, vstatus.mk) - vstatus.cur_selection = '' - return - elif vstatus.cur_selection[1] == '8': - v.display('GOT DRINK?') - if ((os.system('dispense -u "%s" coke:%s'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0): - v.display('SEEMS NOT') - else: - v.display('GOT DRINK!') - else: - # first see if it's a named slot - try: - price, shortname, name = get_snack( vstatus.cur_selection ) - except: - price, shortname, name = get_snack( '--' ) - dollarprice = "$%.2f" % ( price / 100.0 ) - v.display(vstatus.cur_selection+' - %s'%dollarprice) -# exitcode = os.system('dispense -u "%s" give \>snacksales %d "%s"'%(vstatus.username, price, name)) >> 8 -# exitcode = os.system('dispense -u "%s" give \>sales\:snack %d "%s"'%(vstatus.username, price, name)) >> 8 - exitcode = os.system('dispense -u "%s" snack:%s'%(vstatus.username, vstatus.cur_selection)) >> 8 - if (exitcode == 0): - # magic dispense syslog service - syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "vended %s (slot %s) for %s" % (name, vstatus.cur_selection, vstatus.username)) - (worked, code, string) = v.vend(vstatus.cur_selection) - if worked: - v.display('THANK YOU') - else: - print "Vend Failed:", code, string - v.display('VEND FAIL') - elif (exitcode == 5): # RV_BALANCE - v.display('NO MONEY?') - elif (exitcode == 4): # RV_ARGUMENTS (zero give causes arguments) - v.display('EMPTY SLOT') - elif (exitcode == 1): # RV_BADITEM (Dead slot) - v.display('EMPTY SLOT') - else: - syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "failed vending %s (slot %s) for %s (code %d)" % (name, vstatus.cur_selection, vstatus.username, exitcode)) - v.display('UNK ERROR') - sleep(1) - - -def price_check(v, vstatus): - if vstatus.cur_selection[1] == '8': - args = ('dispense', 'iteminfo', 'coke:' + vstatus.cur_selection[0]) - info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate() - dollarprice = re.match("\s*[a-z]+:\d+\s+(\d+\.\d\d)\s+([^\n]+)", info).group(1) - else: - # first see if it's a named slot - try: - price, shortname, name = get_snack( vstatus.cur_selection ) - except: - price, shortname, name = get_snack( '--' ) - dollarprice = "$%.2f" % ( price / 100.0 ) - v.display(vstatus.cur_selection+' - %s'%dollarprice) - - -def handle_getting_pin_key(state, event, params, v, vstatus): - #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params - key = params - if len(vstatus.cur_pin) < PIN_LENGTH: - if key == 11: - if vstatus.cur_pin == '': - vstatus.cur_user = '' - reset_idler(v, vstatus) - - return - vstatus.cur_pin = '' - vstatus.mk.set_message('PIN: ') - return - vstatus.cur_pin += chr(key + ord('0')) - vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin)) - if len(vstatus.cur_pin) == PIN_LENGTH: - vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin)) - if vstatus.username: - v.beep(0, False) - vstatus.cur_selection = '' - vstatus.change_state(STATE_GET_SELECTION) - scroll_options(vstatus.username, vstatus.mk, True) - return - else: - v.beep(40, False) - vstatus.mk.set_messages( - [(center('BAD PIN'), False, 1.0), - (center('SORRY'), False, 0.5)]) - vstatus.cur_user = '' - vstatus.cur_pin = '' - - reset_idler(v, vstatus, 2) - - return - - -def handle_getting_uid_key(state, event, params, v, vstatus): - #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params - key = params - - # complicated key handling here: - - if len(vstatus.cur_user) == 0 and key == 9: - vstatus.cur_selection = '' - vstatus.time_to_autologout = None - vstatus.mk.set_message('PRICECHECK') - sleep(0.5) - scroll_options('', vstatus.mk) - vstatus.change_state(STATE_GET_SELECTION) - return - - if len(vstatus.cur_user) <8: - if key == 11: - vstatus.cur_user = '' - - reset_idler(v, vstatus) - return - vstatus.cur_user += chr(key + ord('0')) - #logging.info('dob: '+vstatus.cur_user) - if len(vstatus.cur_user) > 5: - vstatus.mk.set_message('>'+vstatus.cur_user) - else: - vstatus.mk.set_message('UID: '+vstatus.cur_user) - - if len(vstatus.cur_user) == 5: - uid = int(vstatus.cur_user) - - if uid == 0: - logging.info('user '+vstatus.cur_user+' has a bad PIN') - pfalken=""" -CARRIER DETECTED - -CONNECT 128000 - -Welcome to Picklevision Sytems, Sunnyvale, CA - -Greetings Professor Falken. - - - - -Shall we play a game? - - -Please choose from the following menu: - -1. Tic-Tac-Toe -2. Chess -3. Checkers -4. Backgammon -5. Poker -6. Toxic and Biochemical Warfare -7. Global Thermonuclear War - -7 [ENTER] - -Wouldn't you prefer a nice game of chess? - -""".replace('\n',' ') - vstatus.mk.set_messages([(pfalken, False, 10)]) - vstatus.cur_user = '' - vstatus.cur_pin = '' - - reset_idler(v, vstatus, 10) - - return - - if not has_good_pin(uid): - logging.info('user '+vstatus.cur_user+' has a bad PIN') - vstatus.mk.set_messages( - [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)]) - vstatus.cur_user = '' - vstatus.cur_pin = '' - - reset_idler(v, vstatus, 3) - - return - - if acct_is_disabled(): - logging.info('user '+vstatus.cur_user+' is disabled') - vstatus.mk.set_messages( - [(' '*11+'ACCOUNT DISABLED'+' '*11, False, 3)]) - vstatus.cur_user = '' - vstatus.cur_pin = '' - - reset_idler(v, vstatus, 3) - return - - - vstatus.cur_pin = '' - vstatus.mk.set_message('PIN: ') - logging.info('need pin for user %s'%vstatus.cur_user) - vstatus.change_state(STATE_GETTING_PIN) - return - - -def handle_idle_key(state, event, params, v, vstatus): - #print "handle_idle_key (s,e,p)", state, " ", event, " ", params - - key = params - - if key == 11: - vstatus.cur_user = '' - reset_idler(v, vstatus) - return - - vstatus.change_state(STATE_GETTING_UID) - run_handler(event, key, v, vstatus) - - -def handle_idle_tick(state, event, params, v, vstatus): - ### State idling - if vstatus.mk.done(): - idle_step(vstatus) - - if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler: - vstatus.time_of_next_idler = time() + 30 - choose_idler() - - ### - - vstatus.mk.update_display() - - vstatus.change_state(STATE_GRANDFATHER_CLOCK) - run_handler(event, params, v, vstatus) - sleep(0.05) - -def beep_on(when, before=0): - start = int(when - before) - end = int(when) - now = int(time()) - - if now >= start and now <= end: - return 1 - return 0 - -def handle_idle_grandfather_tick(state, event, params, v, vstatus): - ### check for interesting times - now = localtime() - - quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]]) - halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]]) - threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]]) - fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]]) - - hourfromnow = localtime(time() + 3600) - - #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]]) - onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \ - 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]]) - - ## check for X seconds to the hour - ## if case, update counter to 2 - if beep_on(onthehour,15) \ - or beep_on(halfhour,0) \ - or beep_on(quarterhour,0) \ - or beep_on(threequarterhour,0) \ - or beep_on(fivetothehour,0): - vstatus.change_state(STATE_GRANDFATHER_CLOCK,2) - run_handler(event, params, v, vstatus) - else: - vstatus.change_state(STATE_IDLE) - -def handle_grandfather_tick(state, event, params, v, vstatus): - go_idle = 1 - - msg = [] - ### we live in interesting times - now = localtime() - - quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]]) - halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]]) - threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]]) - fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]]) - - hourfromnow = localtime(time() + 3600) - -# onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]]) - onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \ - 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]]) - - - #print "when it fashionable to wear a onion on your hip" - - if beep_on(onthehour,15): - go_idle = 0 - next_hour=((hourfromnow[3] + 11) % 12) + 1 - if onthehour - time() < next_hour and onthehour - time() > 0: - v.beep(0, False) - - t = int(time()) - if (t % 2) == 0: - msg.append(("DING!", False, None)) - else: - msg.append((" DING!", False, None)) - elif int(onthehour - time()) == 0: - v.beep(255, False) - msg.append((" BONG!", False, None)) - msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4)) - elif beep_on(halfhour,0): - go_idle = 0 - v.beep(0, False) - msg.append((" HALFHOUR ", False, 50)) - elif beep_on(quarterhour,0): - go_idle = 0 - v.beep(0, False) - msg.append((" QTR HOUR ", False, 50)) - elif beep_on(threequarterhour,0): - go_idle = 0 - v.beep(0, False) - msg.append((" 3 QTR HR ", False, 50)) - elif beep_on(fivetothehour,0): - go_idle = 0 - v.beep(0, False) - msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4)) - else: - go_idle = 1 - - ## check for X seconds to the hour - - if len(msg): - vstatus.mk.set_messages(msg) - sleep(1) - - vstatus.mk.update_display() - ## if no longer case, return to idle - - ## change idler to be clock - if go_idle and vstatus.mk.done(): - vstatus.change_state(STATE_IDLE,1) - -def handle_door_idle(state, event, params, v, vstatus): - def twiddle(clock,v,wise = 2): - if (clock % 4 == 0): - v.display("-FEED ME-") - elif (clock % 4 == 1+wise): - v.display("\\FEED ME/") - elif (clock % 4 == 2): - v.display("-FEED ME-") - elif (clock % 4 == 3-wise): - v.display("/FEED ME\\") - - # don't care right now. - now = int(time()) - - if ((now % 60 % 2) == 0): - twiddle(now, v) - else: - twiddle(now, v, wise=0) - - -def handle_door_event(state, event, params, v, vstatus): - if params == 0: #door open - vstatus.change_state(STATE_DOOR_OPENING) - logging.warning("Entering open door mode") - v.display("-FEED ME-") - #door_open_mode(v); - vstatus.cur_user = '' - vstatus.cur_pin = '' - elif params == 1: #door closed - vstatus.change_state(STATE_DOOR_CLOSING) - reset_idler(v, vstatus, 3) - - logging.warning('Leaving open door mode') - v.display("-YUM YUM!-") - -def handle_mifare_event(state, event, params, v, vstatus): - card_id = params - # Translate card_id into uid. - if card_id == None: - return - - try: - vstatus.cur_user = get_uid(card_id) - logging.info('Mapped card id to uid %s'%vstatus.cur_user) - vstatus.username = get_uname(vstatus.cur_user) - if acct_is_disabled(vstatus.username): - vstatus.username = '-disabled-' - except ValueError: - vstatus.username = None - if vstatus.username == '-disabled-': - v.beep(40, False) - vstatus.mk.set_messages( - [(center('ACCT DISABLED'), False, 1.0), - (center('SORRY'), False, 0.5)]) - vstatus.cur_user = '' - vstatus.cur_pin = '' - vstatus.username = None - - reset_idler(v, vstatus, 2) - return - elif vstatus.username: - v.beep(0, False) - vstatus.cur_selection = '' - vstatus.change_state(STATE_GET_SELECTION) - scroll_options(vstatus.username, vstatus.mk, True) - return - else: - v.beep(40, False) - vstatus.mk.set_messages( - [(center('BAD CARD'), False, 1.0), - (center('SORRY'), False, 0.5)]) - vstatus.cur_user = '' - vstatus.cur_pin = '' - - reset_idler(v, vstatus, 2) - return - -def handle_mifare_add_user_event(state, event, params, v, vstatus): - card_id = params - - # Translate card_id into uid. - if card_id == None: - return - - try: - if get_uid(card_id) != None: - vstatus.mk.set_messages( - [(center('ALREADY'), False, 0.5), - (center('ENROLLED'), False, 0.5)]) - - # scroll_options(vstatus.username, vstatus.mk) - return - except ValueError: - pass - - logging.info('Enrolling card %s to uid %s (%s)'%(card_id, vstatus.cur_user, vstatus.username)) - set_card_id(vstatus.cur_user, card_id) - vstatus.mk.set_messages( - [(center('CARD'), False, 0.5), - (center('ENROLLED'), False, 0.5)]) - - # scroll_options(vstatus.username, vstatus.mk) - -def return_to_idle(state,event,params,v,vstatus): - reset_idler(v, vstatus) - -def create_state_table(vstatus): - vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick - vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key - vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event - vstatus.state_table[(STATE_IDLE,MIFARE,1)] = handle_mifare_event - - vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle - vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event - vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing - vstatus.state_table[(STATE_DOOR_OPENING,MIFARE,1)] = do_nothing - - vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle - vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event - vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing - vstatus.state_table[(STATE_DOOR_CLOSING,MIFARE,1)] = do_nothing - - vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle - vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing - vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key - vstatus.state_table[(STATE_GETTING_UID,MIFARE,1)] = handle_mifare_event - - vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle - vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing - vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key - vstatus.state_table[(STATE_GETTING_PIN,MIFARE,1)] = handle_mifare_event - - vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle - vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing - vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key - vstatus.state_table[(STATE_GET_SELECTION,MIFARE,1)] = handle_mifare_add_user_event - - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = handle_mifare_event - -def get_state_table_handler(vstatus, state, event, counter): - return vstatus.state_table[(state,event,counter)] - -def time_to_next_update(vstatus): - idle_update = vstatus.time_of_next_idlestep - time() - if not vstatus.mk.done() and vstatus.mk.next_update is not None: - mk_update = vstatus.mk.next_update - time() - if mk_update < idle_update: - idle_update = mk_update - return idle_update - -def run_forever(rfh, wfh, options, cf): - v = VendingMachine(rfh, wfh, USE_MIFARE) - vstatus = VendState(v) - create_state_table(vstatus) - - logging.debug('PING is ' + str(v.ping())) - - if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword) - - setup_idlers(v) - reset_idler(v, vstatus) - - # This main loop was hideous and the work of the devil. - # This has now been fixed (mostly) - mtearle - # - # - # notes for later surgery - # (event, counter, ' ') - # V - # d[ ] = (method) - # - # ( return state - not currently implemented ) - - while True: - if USE_DB: - try: - db.handle_events() - except DispenseDatabaseException, e: - logging.error('Database error: '+str(e)) - - timeout = time_to_next_update(vstatus) - e = v.next_event(timeout) - (event, params) = e - - run_handler(event, params, v, vstatus) - -# logging.debug('Got event: ' + repr(e)) - - -def run_handler(event, params, v, vstatus): - handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter) - if handler: - handler(vstatus.state, event, params, v, vstatus) - -def connect_to_vend(options, cf): - - if options.use_lat: - logging.info('Connecting to vending machine using LAT') - latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword) - rfh, wfh = latclient.get_fh() - elif options.use_serial: - # Open vending machine via serial. - logging.info('Connecting to vending machine using serial') - serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600) - rfh,wfh = serialclient.get_fh() - else: - #(rfh, wfh) = popen2('../../virtualvend/vvend.py') - logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port)) - import socket - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - sock.connect((options.host, options.port)) - rfh = sock.makefile('r') - wfh = sock.makefile('w') - global USE_MIFARE - USE_MIFARE = 0 - - return rfh, wfh - -def parse_args(): - from optparse import OptionParser - - op = OptionParser(usage="%prog [OPTION]...") - op.add_option('-f', '--config-file', default='/etc/dispense2/servers.conf', metavar='FILE', dest='config_file', help='use the specified config file instead of /etc/dispense/servers.conf') - op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port') - op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT') - op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT') - op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)') - op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)') - op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file') - op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility') - op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon') - op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output') - op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors') - op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file') - options, args = op.parse_args() - - if len(args) != 0: - op.error('extra command line arguments: ' + ' '.join(args)) - - return options - -config_options = { - 'DBServer': ('Database', 'Server'), - 'DBName': ('Database', 'Name'), - 'DBUser': ('VendingMachine', 'DBUser'), - 'DBPassword': ('VendingMachine', 'DBPassword'), - - 'ServiceName': ('VendingMachine', 'ServiceName'), - 'ServicePassword': ('VendingMachine', 'Password'), - - 'ServerName': ('DecServer', 'Name'), - 'ConnectPassword': ('DecServer', 'ConnectPassword'), - 'PrivPassword': ('DecServer', 'PrivPassword'), - } - -class VendConfigFile: - def __init__(self, config_file, options): - try: - cp = ConfigParser.ConfigParser() - cp.read(config_file) - - for option in options: - section, name = options[option] - value = cp.get(section, name) - self.__dict__[option] = value - - except ConfigParser.Error, e: - raise SystemExit("Error reading config file "+config_file+": " + str(e)) - -def create_pid_file(name): - try: - pid_file = file(name, 'w') - pid_file.write('%d\n'%os.getpid()) - pid_file.close() - except IOError, e: - logging.warning('unable to write to pid file '+name+': '+str(e)) - -def set_stuff_up(): - def do_nothing(signum, stack): - signal.signal(signum, do_nothing) - def stop_server(signum, stack): raise KeyboardInterrupt - signal.signal(signal.SIGHUP, do_nothing) - signal.signal(signal.SIGTERM, stop_server) - signal.signal(signal.SIGINT, stop_server) - - options = parse_args() - config_opts = VendConfigFile(options.config_file, config_options) - if options.daemon: become_daemon() - set_up_logging(options) - if options.pid_file != '': create_pid_file(options.pid_file) - - return options, config_opts - -def clean_up_nicely(options, config_opts): - if options.pid_file != '': - try: - os.unlink(options.pid_file) - logging.debug('Removed pid file '+options.pid_file) - except OSError: pass # if we can't delete it, meh - -def set_up_logging(options): - logger = logging.getLogger() - - if not options.daemon: - stderr_logger = logging.StreamHandler(sys.stderr) - stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) - logger.addHandler(stderr_logger) - - if options.log_file != '': - try: - file_logger = logging.FileHandler(options.log_file) - file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s')) - logger.addHandler(file_logger) - except IOError, e: - logger.warning('unable to write to log file '+options.log_file+': '+str(e)) - - if options.syslog != None: - sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog) - sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s')) - logger.addHandler(sys_logger) - - if options.quiet: - logger.setLevel(logging.WARNING) - elif options.verbose: - logger.setLevel(logging.DEBUG) - else: - logger.setLevel(logging.INFO) - -def become_daemon(): - dev_null = file('/dev/null') - fd = dev_null.fileno() - os.dup2(fd, 0) - os.dup2(fd, 1) - os.dup2(fd, 2) - try: - if os.fork() != 0: - sys.exit(0) - os.setsid() - except OSError, e: - raise SystemExit('failed to fork: '+str(e)) - -def do_vend_server(options, config_opts): - while True: - try: - rfh, wfh = connect_to_vend(options, config_opts) - except (SerialClientException, socket.error), e: - (exc_type, exc_value, exc_traceback) = sys.exc_info() - del exc_traceback - logging.error("Connection error: "+str(exc_type)+" "+str(e)) - logging.info("Trying again in 5 seconds.") - sleep(5) - continue - -# run_forever(rfh, wfh, options, config_opts) - - try: - run_forever(rfh, wfh, options, config_opts) - except VendingException: - logging.error("Connection died, trying again...") - logging.info("Trying again in 5 seconds.") - sleep(5) - -if __name__ == '__main__': - options, config_opts = set_stuff_up() - while True: - try: - logging.warning('Starting Vend Server') - do_vend_server(options, config_opts) - logging.error('Vend Server finished unexpectedly, restarting') - except KeyboardInterrupt: - logging.info("Killed by signal, cleaning up") - clean_up_nicely(options, config_opts) - logging.warning("Vend Server stopped") - break - except SystemExit: - break - except: - (exc_type, exc_value, exc_traceback) = sys.exc_info() - tb = format_tb(exc_traceback, 20) - del exc_traceback - - logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception") - logging.critical("Message: " + str(exc_value)) - logging.critical("Traceback:") - for event in tb: - for line in event.split('\n'): - logging.critical(' '+line) - logging.critical("This message should be considered a bug in the Vend Server.") - logging.critical("Please report this to someone who can fix it.") - sleep(10) - logging.warning("Trying again anyway (might not help, but hey...)") - diff --git a/sql-edition/servers/VendingMachine.py b/sql-edition/servers/VendingMachine.py deleted file mode 100644 index e5a0251..0000000 --- a/sql-edition/servers/VendingMachine.py +++ /dev/null @@ -1,204 +0,0 @@ -# vim:ts=4 -import re -from CRC import do_crc -from select import select -import socket, logging -from time import time, sleep -from MIFAREClient import MIFAREClient - -asynchronous_responses = [ '400', '401', # door open/closed - '610', # switches changed - '200', '201', '202', '203', '204', '205', '206', - '207', '208', '209', '211', # key presses - ] -DOOR = 1 -SWITCH = 2 -KEY = 3 -TICK = 4 -MIFARE = 5 - -class VendingException(Exception): pass - -class VendingMachine: - def __init__(self, rfh, wfh, use_mifare): - self.events = [] - # Secret - self.secret = 'SN4CKZ0RZZZZZZZZ' - self.rfh = rfh - self.wfh = wfh - self.challenge = None - # Initialise ourselves into a known state - self.wfh.write('\n') - self.await_prompt() - self.wfh.write('echo off\n') - self.await_prompt() - self.wfh.write('PING\n') - code = '' - while code != '000': - code = self.get_response()[0] - self.get_switches() - if use_mifare: - self.mifare = MIFAREClient() - self.mifare_timeout = 0 - else: - self.mifare = None - - def await_prompt(self): - self.wfh.flush() - state = 1 - timeout = 0.5 - prefix = '' - s = '' - # mtearle - vending machine was dying wait for a response from - # the hardware, suspect it was missing characters - # - # fixed by migration to pyserial - but future good place to start - while True: - try: - s = self.rfh.read(1) - except socket.error: - raise VendingException('failed to read input from vending machine') - if s == '': raise VendingException('nothing read!') - if (s != '#' and s != '%') and state == 1: prefix += s - if s == '\n' or s == '\r': - state = 1 - prefix = '' - if (s == '#' or s == '%') and state == 1: state = 2 - if s == ' ' and state == 2: - if prefix == '': - self.challenge = None - return - if re.search('^[0-9a-fA-F]{4}$', prefix): - self.challenge = int(prefix, 16) - return - - def get_response(self, async = False): - self.wfh.flush() - while True: - s = '' - while s == '': - s = self.rfh.readline() - if s == '': - raise VendingException('Input socket has closed!') - s = s.strip('\r\n') - code = s[0:3] - text = s[4:] - if code in asynchronous_responses: - self.handle_event(code, text) - if async: return None - else: - self.await_prompt() - return (code, text) - - def get_switches(self): - self.wfh.write('S\n') - (code, text) = self.get_response() - if code != '600': - return (False, code, text) - self.interpret_switches(text) - return (True, code, text) - - def interpret_switches(self, text): - self.switches = (int(text[0:2], 16) << 8) | int(text[3:5], 16) - - def handle_event(self, code, text): - if code == '400': - self.events.append((DOOR, 1)) - elif code == '401': - self.events.append((DOOR, 0)) - elif code == '610': - # NOP this. Nothing handles this yet. - #self.events.append((SWITCH, None)) - self.interpret_switches(text) - elif code[0] == '2': - self.events.append((KEY, int(code[1:3]))) - else: - logging.warning('Unhandled event! (%s %s)\n'%(code,text)) - - def authed_message(self, message): - print 'self.challenge = %04x' % self.challenge - if self.challenge == None: - return message - crc = do_crc('%c%c'%(self.challenge >> 8, self.challenge & 0xff)) - crc = do_crc(self.secret, crc) - crc = do_crc(message, crc) - print 'output = "%s|%04x"' % (message, crc) - return message+'|'+('%04x'%crc) - - def ping(self): - self.wfh.write('PING\n') - (code, string) = self.get_response() - return (code == '000', code, string) - - def vend(self, item): - if not re.search('^[0-9][0-9]$', item): - return (False, 'Invalid item requested (%s)'%item) - self.wfh.write(self.authed_message(('V%s'%item))+'\n') - (code, string) = self.get_response() - return (code == '100', code, string) - - def beep(self, duration = None, synchronous = True): - msg = 'B' - if synchronous: msg += 'S' - if duration != None: - if duration > 255: duration = 255 - if duration < 1: duration = 1 - msg += '%02x'%duration - self.wfh.write(msg+'\n') - (code, string) = self.get_response() - return (code == '500', code, string) - - def silence(self, duration = None, synchronous = True): - msg = 'C' - if synchronous: msg += 'S' - if duration != None: - if duration > 255: duration = 255 - if duration < 1: duration = 1 - msg += '%02x'%duration - self.wfh.write(msg+'\n') - (code, string) = self.get_response() - return (code == '501', code, string) - - def display(self, string): - if len(string) > 10: - string = string[0:10] - string = re.sub('(.)\.', lambda match: '.'+match.group(1), string) - self.wfh.write('D'+string+'\n') - (code, string) = self.get_response() - return (code == '300', code, string) - - def next_event(self, timeout = None): - # we don't want to buffer in the serial port, so we get all the events - # we can ASAP. - - # Never have no timeout... - if timeout == None: timeout = 60*60*24*365 - - # Make sure we go through the loop at least once. - if timeout < 0: timeout = 0 - - while timeout >= 0: - this_timeout = min(timeout, 0.2) - timeout -= this_timeout - - (r, _, _) = select([self.rfh], [], [], this_timeout) - if r: - self.get_response(async = True) - timeout = 0 - - if self.mifare: - now = time() - if now > self.mifare_timeout: - self.mifare_timeout = now + 0.5 - mifare_uid = self.mifare.get_card_id() - if mifare_uid != None: - logging.info('Got MIFARE card id %s'%(repr(mifare_uid))) - self.events.append((MIFARE, mifare_uid)) - timeout = 0 - if timeout == 0: - break - - if len(self.events) == 0: return (TICK, time()) - ret = self.events[0] - del self.events[0] - return ret diff --git a/sql-edition/servers/servers.conf b/sql-edition/servers/servers.conf deleted file mode 100644 index 5919d22..0000000 --- a/sql-edition/servers/servers.conf +++ /dev/null @@ -1,15 +0,0 @@ - -[Database] -Server = localhost -Name = dispense - -[VendingMachine] -DBUser = cameron -DBPassword = blah -ServiceName = blah -Password = foo - -[DecServer] -Name = outside -ConnectPassword = foo -PrivPassword = bar diff --git a/sql-edition/servers/snacks.conf b/sql-edition/servers/snacks.conf deleted file mode 100644 index 4f036ed..0000000 --- a/sql-edition/servers/snacks.conf +++ /dev/null @@ -1,14 +0,0 @@ -# format: -# all are whitespace seperated, name can have whitespace. -# price - in cents -# slots - comma seperated list of slots with this item. -# - don't put any spaces in this -# - the magically named '--' slot is the default for -# unadded slots -# name - the name, spaces are OK. - -# price slots dispensename name -# eg -# 550 88,00 5out $5 withdrawal - 550 88,00 5out $5 withdrawal - 110 -- snack tasty snack