--- /dev/null
+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)
--- /dev/null
+#!/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]))
--- /dev/null
+#!/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)
+
--- /dev/null
+#!/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
--- /dev/null
+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)
--- /dev/null
+#!/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')
--- /dev/null
+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
--- /dev/null
+#!/usr/bin/env python2.5
+
+'''mifare - a library for interacting with MIFARE readers.
+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
--- /dev/null
+#!/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
+
--- /dev/null
+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()
+
+
--- /dev/null
+#!/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"
--- /dev/null
+#!/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...)")
+
--- /dev/null
+# 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
--- /dev/null
+
+[Database]
+Server = localhost
+Name = dispense
+
+[VendingMachine]
+DBUser = cameron
+DBPassword = blah
+ServiceName = blah
+Password = foo
+
+[DecServer]
+Name = outside
+ConnectPassword = foo
+PrivPassword = bar
--- /dev/null
+# 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
+++ /dev/null
-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)
+++ /dev/null
-#!/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]))
+++ /dev/null
-#!/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)
-
+++ /dev/null
-#!/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
+++ /dev/null
-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)
+++ /dev/null
-#!/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')
+++ /dev/null
-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
+++ /dev/null
-#!/usr/bin/env python2.5
-
-'''mifare - a library for interacting with MIFARE readers.
-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
+++ /dev/null
-#!/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
-
+++ /dev/null
-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()
-
-
+++ /dev/null
-#!/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"
+++ /dev/null
-#!/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...)")
-
+++ /dev/null
-# 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
+++ /dev/null
-
-[Database]
-Server = localhost
-Name = dispense
-
-[VendingMachine]
-DBUser = cameron
-DBPassword = blah
-ServiceName = blah
-Password = foo
-
-[DecServer]
-Name = outside
-ConnectPassword = foo
-PrivPassword = bar
+++ /dev/null
-# 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