Move VendServer code up a directory wholesale and rename
authorMark Tearle <[email protected]>
Sat, 8 Nov 2014 14:05:44 +0000 (22:05 +0800)
committerMark Tearle <[email protected]>
Sat, 8 Nov 2014 14:05:44 +0000 (22:05 +0800)
32 files changed:
VendServer/.cvsignore [new file with mode: 0644]
VendServer/CRC.py [new file with mode: 0644]
VendServer/DoorClient.py [new file with mode: 0755]
VendServer/HorizScroll.py [new file with mode: 0644]
VendServer/Idler.py [new file with mode: 0755]
VendServer/LATClient.py [new file with mode: 0644]
VendServer/LDAPConnector.py [new file with mode: 0644]
VendServer/MIFAREClient.py [new file with mode: 0644]
VendServer/MIFAREDriver.py [new file with mode: 0644]
VendServer/MessageKeeper.py [new file with mode: 0755]
VendServer/SerialClient.py [new file with mode: 0644]
VendServer/SnackConfig.py [new file with mode: 0755]
VendServer/VendServer.py [new file with mode: 0755]
VendServer/VendingMachine.py [new file with mode: 0644]
VendServer/servers.conf [new file with mode: 0644]
VendServer/snacks.conf [new file with mode: 0644]
sql-edition/servers/.cvsignore [deleted file]
sql-edition/servers/CRC.py [deleted file]
sql-edition/servers/DoorClient.py [deleted file]
sql-edition/servers/HorizScroll.py [deleted file]
sql-edition/servers/Idler.py [deleted file]
sql-edition/servers/LATClient.py [deleted file]
sql-edition/servers/LDAPConnector.py [deleted file]
sql-edition/servers/MIFAREClient.py [deleted file]
sql-edition/servers/MIFAREDriver.py [deleted file]
sql-edition/servers/MessageKeeper.py [deleted file]
sql-edition/servers/SerialClient.py [deleted file]
sql-edition/servers/SnackConfig.py [deleted file]
sql-edition/servers/VendServer.py [deleted file]
sql-edition/servers/VendingMachine.py [deleted file]
sql-edition/servers/servers.conf [deleted file]
sql-edition/servers/snacks.conf [deleted file]

diff --git a/VendServer/.cvsignore b/VendServer/.cvsignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/VendServer/CRC.py b/VendServer/CRC.py
new file mode 100644 (file)
index 0000000..512a7bd
--- /dev/null
@@ -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 (executable)
index 0000000..d46cebe
--- /dev/null
@@ -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 (file)
index 0000000..d27f7dc
--- /dev/null
@@ -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 (executable)
index 0000000..5b66455
--- /dev/null
@@ -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 (file)
index 0000000..638d20b
--- /dev/null
@@ -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 (file)
index 0000000..8699fd2
--- /dev/null
@@ -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 (file)
index 0000000..e0b6a6c
--- /dev/null
@@ -0,0 +1,44 @@
+from MIFAREDriver import MIFAREReader, MIFAREException\r
+from serial import Serial\r
+from LDAPConnector import get_uid, set_card_id\r
+\r
+class MIFAREClient:\r
+    def __init__(self):\r
+        self.port = Serial('/dev/ttyS2', baudrate = 19200)\r
+        self.reader = MIFAREReader(self.port)\r
+        self.reader.set_led(red = False, green = True)\r
+        self.reader.beep(100)\r
+    \r
+    def get_card_id(self):\r
+        self.reader.set_led(red = True, green = False)\r
+        try:\r
+            card_id, capacity = self.reader.select_card()\r
+        except MIFAREException:\r
+            self.reader.set_led(red = False, green = True)\r
+            return None\r
+        else:\r
+            self.reader.set_led(red = False, green = True)\r
+            self.reader.beep(100)\r
+            return card_id\r
+    \r
+    def get_card_uid(self):\r
+               card_id = self.get_card_id()\r
+               if card_id == None:\r
+                       return None\r
+               else:\r
+                       return get_uid(card_id)\r
+    \r
+    def add_card(self, uid):\r
+        self.reader.set_led(red = True, green = False)\r
+        for attempt in range(5):\r
+            self.reader.beep(50)\r
+            try:\r
+                card_id, capacity = self.reader.select_card()\r
+            except MIFAREException:\r
+                pass\r
+            else:\r
+                set_card_id(uid, card_id)\r
+                self.reader.set_led(red = False, green = True)\r
+                return True\r
+        self.reader.set_led(red = False, green = True)\r
+        return False\r
diff --git a/VendServer/MIFAREDriver.py b/VendServer/MIFAREDriver.py
new file mode 100644 (file)
index 0000000..6248f60
--- /dev/null
@@ -0,0 +1,189 @@
+#!/usr/bin/env python2.5
+
+'''mifare - a library for interacting with MIFARE readers.
+Written by David Adam <[email protected]>
+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 (executable)
index 0000000..60d2b9e
--- /dev/null
@@ -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 (file)
index 0000000..e0a9a7e
--- /dev/null
@@ -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 (executable)
index 0000000..2314074
--- /dev/null
@@ -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 (executable)
index 0000000..2a9a55a
--- /dev/null
@@ -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 (file)
index 0000000..e5a0251
--- /dev/null
@@ -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 (file)
index 0000000..5919d22
--- /dev/null
@@ -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 (file)
index 0000000..4f036ed
--- /dev/null
@@ -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 (file)
index 0d20b64..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.pyc
diff --git a/sql-edition/servers/CRC.py b/sql-edition/servers/CRC.py
deleted file mode 100644 (file)
index 512a7bd..0000000
+++ /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 (executable)
index d46cebe..0000000
+++ /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 (file)
index d27f7dc..0000000
+++ /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 (executable)
index 5b66455..0000000
+++ /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 (file)
index 638d20b..0000000
+++ /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 (file)
index 8699fd2..0000000
+++ /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 (file)
index e0b6a6c..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-from MIFAREDriver import MIFAREReader, MIFAREException\r
-from serial import Serial\r
-from LDAPConnector import get_uid, set_card_id\r
-\r
-class MIFAREClient:\r
-    def __init__(self):\r
-        self.port = Serial('/dev/ttyS2', baudrate = 19200)\r
-        self.reader = MIFAREReader(self.port)\r
-        self.reader.set_led(red = False, green = True)\r
-        self.reader.beep(100)\r
-    \r
-    def get_card_id(self):\r
-        self.reader.set_led(red = True, green = False)\r
-        try:\r
-            card_id, capacity = self.reader.select_card()\r
-        except MIFAREException:\r
-            self.reader.set_led(red = False, green = True)\r
-            return None\r
-        else:\r
-            self.reader.set_led(red = False, green = True)\r
-            self.reader.beep(100)\r
-            return card_id\r
-    \r
-    def get_card_uid(self):\r
-               card_id = self.get_card_id()\r
-               if card_id == None:\r
-                       return None\r
-               else:\r
-                       return get_uid(card_id)\r
-    \r
-    def add_card(self, uid):\r
-        self.reader.set_led(red = True, green = False)\r
-        for attempt in range(5):\r
-            self.reader.beep(50)\r
-            try:\r
-                card_id, capacity = self.reader.select_card()\r
-            except MIFAREException:\r
-                pass\r
-            else:\r
-                set_card_id(uid, card_id)\r
-                self.reader.set_led(red = False, green = True)\r
-                return True\r
-        self.reader.set_led(red = False, green = True)\r
-        return False\r
diff --git a/sql-edition/servers/MIFAREDriver.py b/sql-edition/servers/MIFAREDriver.py
deleted file mode 100644 (file)
index 6248f60..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/usr/bin/env python2.5
-
-'''mifare - a library for interacting with MIFARE readers.
-Written by David Adam <[email protected]>
-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 (executable)
index 60d2b9e..0000000
+++ /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 (file)
index e0a9a7e..0000000
+++ /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 (executable)
index 2314074..0000000
+++ /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 (executable)
index 2a9a55a..0000000
+++ /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 (file)
index e5a0251..0000000
+++ /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 (file)
index 5919d22..0000000
+++ /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 (file)
index 4f036ed..0000000
+++ /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

UCC git Repository :: git.ucc.asn.au