diff --git a/VendServer/DoorClient.py b/VendServer/DoorClient.py index d46cebe98f9e7ca055cf0c58d9279480930b1b0a..121b563892457c9460274c4a99cfc09df423206d 100755 --- a/VendServer/DoorClient.py +++ b/VendServer/DoorClient.py @@ -1,6 +1,6 @@ #!/usr/bin/python -from LATClient import LATClient +from .LATClient import LATClient from select import select import signal import sys @@ -14,17 +14,17 @@ def check_door_service(service, test_string="got wombles?"): 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" + 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 + print(result) sys.exit(result_codes[result]) def timeout(signum, frame): return_result("error") - if len(sys.argv) <> 2: return_result('invalid args') + if len(sys.argv) != 2: return_result('invalid args') signal.signal(signal.SIGALRM, timeout) signal.alarm(15) return_result(check_door_service(sys.argv[1])) diff --git a/VendServer/HorizScroll.py b/VendServer/HorizScroll.py index d27f7dc8e8150b72df36f6915bd55873ffca6c14..9687c8299e03b64c385fc1651724c644cef71d95 100644 --- a/VendServer/HorizScroll.py +++ b/VendServer/HorizScroll.py @@ -11,13 +11,12 @@ class HorizScroll: def expand(self, padding=None, paddingchar=" ", dir=None, wraparound=False): if len(self.text) <= 10: - return [text] + return [self.text] if padding == None: - padding = len(self.text) / 2 + 1 + padding = len(self.text) // 2 + 1 - format = "%-" + str(padding) + "." + str(padding) + "s" - pad = string.replace(format % " "," ",paddingchar) + pad = paddingchar * padding padtext = self.text + pad if not wraparound: numiters = len(self.text) - 10 @@ -27,7 +26,7 @@ class HorizScroll: expansion = [] for x in range(0,numiters): - expansion.append("%-10.10s" % (padtext[x:] + padtext[:x])) + expansion.append("%-10.10s" % (padtext[x:] + padtext[:x])) if dir == -1: expansion.reverse() @@ -40,7 +39,7 @@ if __name__ == '__main__': while 1: for x in eh: sys.stdout.write("\r") - print "%-10.10s" % x, + print("%-10.10s" % x, end=' ') sys.stdout.flush() time.sleep(0.1) diff --git a/VendServer/Idler.py b/VendServer/Idler.py index c2eedee233e8c2521dbe64f229e67d43cfa19afe..d3cab072a6f70c64ab7469b7cd319dd3a88c7456 100755 --- a/VendServer/Idler.py +++ b/VendServer/Idler.py @@ -3,307 +3,307 @@ import string, time, os from subprocess import Popen, PIPE from random import random -from MessageKeeper import MessageKeeper +from .MessageKeeper import MessageKeeper orderings = None IDLER_TEXT_SPEED=1.8 class Idler: - def __init__(self, v, affinity=None): - self.v = v - if affinity: - self._affinity = affinity - else: - self._affinity = 1 - - 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 self._affinity + def __init__(self, v, affinity=None): + self.v = v + if affinity: + self._affinity = affinity + else: + self._affinity = 1 + + 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 self._affinity class GreetingIdler(Idler): - def __init__(self, v, secs_to_greeting = None): - affinity = 0 - Idler.__init__(self, v, affinity = affinity) - self.secs_to_greeting = secs_to_greeting - self.message_displayed = False + def __init__(self, v, secs_to_greeting = None): + affinity = 0 + Idler.__init__(self, v, affinity = affinity) + 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 + 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 + self.v.display('UCC SNACKS') + self.message_displayed = True + return 5 - def reset(self): - self.message_displayed = False - self.secs_to_greeting = None + def reset(self): + self.message_displayed = False + self.secs_to_greeting = None - def finished(self): - return self.message_displayed + def finished(self): + return self.message_displayed class TrainIdler(Idler): - def __init__(self, v): - Idler.__init__(self, v) - self.idle_state = 0 - - 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 + def __init__(self, v): + Idler.__init__(self, v) + self.idle_state = 0 + + def put_shark(self, s: str, l: int): + 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(''.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) + 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): - Idler.__init__(self, v) - self.bits = 8 - self.size = 1 << self.bits - self.i = 0 - self.grayCode = 0 - 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 + def __init__(self, v, one=None, zero=None, reorder=0): + Idler.__init__(self, v) + self.bits = 8 + self.size = 1 << self.bits + self.i = 0 + self.grayCode = 0 + 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 = output.replace("0", self.zero) + if self.one: + output = output.replace("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 = 1, 1 + while val < num: + val, j = val*2, j+1 + + # Convert. + bin = '' + i = j - 1 + while i + 1: + k = pow(2, i) + if num >= k: + bin = bin + '1' + num = num - k + else: + if len(bin) > 0: + bin = bin + '0' + i = i - 1 + + 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, affinity=None): - Idler.__init__(self, v, affinity=affinity) - 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() + def __init__(self, v, text="Hello Cruel World! ",repeat=True, affinity=None): + Idler.__init__(self, v, affinity=affinity) + 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: str): + # nothing like a bit of good clean text :) + valid = string.digits \ + + string.ascii_letters \ + + string.punctuation \ + + " " + # uppercase it + text = text.upper() + 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): - affinity = 3 - Idler.__init__(self, v, affinity = affinity) - 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 __init__(self, v): + affinity = 3 + Idler.__init__(self, v, affinity = affinity) + 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 class FortuneIdler(StringIdler): - def __init__(self, v, affinity = 30): - 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 = lines.replace('\n', ' ').replace('\r', '') - StringIdler.__init__(self, v, text,repeat=False, affinity=affinity) + def __init__(self, v, affinity = 30): + 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 = lines.decode('utf-8').replace('\n', ' ').replace('\r', '') + StringIdler.__init__(self, v, text,repeat=False, affinity=affinity) - def reset(self): - self.__init__(self.v, affinity=self._affinity) + def reset(self): + self.__init__(self.v, affinity=self._affinity) class PipeIdler(StringIdler): - def __init__(self, v, command, args, affinity = 5): - 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 = lines.replace('\n', ' ').replace('\r', '') - StringIdler.__init__(self, v, text,repeat=False, affinity=affinity) + def __init__(self, v, command, args, affinity = 5): + 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 = lines.decode('utf-8').replace('\n', ' ').replace('\r', '') + StringIdler.__init__(self, v, text,repeat=False, affinity=affinity) class FileIdler(StringIdler): - def __init__(self, v, thefile=None, repeat=False, affinity=8): - text = "I broke my wookie...." - - 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=False, affinity=affinity) + def __init__(self, v, thefile=None, repeat=False, affinity=8): + text = "I broke my wookie...." + + if os.access(thefile,os.F_OK|os.R_OK): + f = open(thefile,'r') + text = "".join(f.readlines()) + f.close() + StringIdler.__init__(self, v, text,repeat=False, affinity=affinity) diff --git a/VendServer/LATClient.py b/VendServer/LATClient.py deleted file mode 100644 index 638d20b05bc1f3a136a93f2c5fd415f964c1048e..0000000000000000000000000000000000000000 --- a/VendServer/LATClient.py +++ /dev/null @@ -1,157 +0,0 @@ -from socket import * -from select import select -from os import popen4 -from time import sleep -import logging - -LATCP_SOCKET = '/var/run/latlogin' - -LAT_VERSION = '1.22' -LAT_VERSION = '1.24' # for running on Mermaid. [DAA] 20071107 -LATCP_CMD_VERSION = 8 -LATCP_CMD_TERMINALSESSION = 26 -LATCP_CMD_ERRORMSG = 99 - -class LATClientException(Exception): pass - -def read_for_a_bit(rfh): - message = '' - while 1: - r = select([rfh], [], [], 5.0)[0] - if r: - try: - ch = rfh.read(1) - except socket.error: - ch = '' - if ch == '': - break - message = message + ch - else: - break - logging.debug("Received message: ", repr(message)) - return message - -def write_and_get_response(rfh, wfh, message, expect_echo=True): - logging.debug("Writing message:", repr(message)) - wfh.write(message+'\r\n') - wfh.flush() - logging.debug(" --> Sent") - response = read_for_a_bit(rfh) - if response.find(message) == -1 and expect_echo: - raise LATClientException("Talking to DEC server, expected to find original message in echo but didn't") - return response - -class LATClient: - def __init__(self, service = None, node = None, port = None, - localport = None, password = None, is_queued = False, - server_name = '', connect_password='', priv_password=''): - - self.server_name = server_name - self.connect_password = connect_password - self.priv_password = priv_password - - self.sock = socket(AF_UNIX, SOCK_STREAM, 0); - self.sock.connect(LATCP_SOCKET) - self.send_msg(LATCP_CMD_VERSION, LAT_VERSION+'\000') - (cmd, msg) = self.read_reply() - if service == None: service = '' - if node == None: node = '' - if port == None: port = '' - if localport == None: localport = '' - if password == None: password = '' - if is_queued == True: - is_queued = 1 - else: - is_queued = 0 - self.send_msg(LATCP_CMD_TERMINALSESSION, '%c%c%s%c%s%c%s%c%s%c%s' % \ - (is_queued, - len(service), service, - len(node), node, - len(port), port, - len(localport), localport, - len(password), password - )) - (cmd, msg) = self.read_reply() - if ord(cmd) == LATCP_CMD_ERRORMSG: - raise LATClientException(msg) - - self.rfh = self.sock.makefile('r') - self.wfh = self.sock.makefile('w') - - r = select([self.rfh], [], [], 2.0)[0] - if r: - l = self.rfh.readline() - if l.find('Service in use') >= 0: - logging.warning("Service in use, apparently: restarting DEC server") - self.reboot_server() - - def __del__(self): - try: - self.sock.close() - self.sock.shutdown(2) - except: - pass - del self.sock - - def send_msg(self, cmd, msg): - self.sock.send('%c%c%c%s'%(cmd, len(msg)/256, len(msg)%256, msg)) - - def reboot_server(self): - self.sock.shutdown(2) - self.sock.close() - - logging.info('Logging into DEC server') - mopw, mopr = popen4('/usr/sbin/moprc '+self.server_name) - write_and_get_response(mopr, mopw, '') - - logging.info('Sending password') - r = write_and_get_response(mopr, mopw, self.connect_password, False) - if r.find('Enter username> ') == -1: - logging.warning("Expected username prompt, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Sending username') - r = write_and_get_response(mopr, mopw, 'grim reaper') - if r.find('Local> ') == -1: - logging.warning("Expected DEC server prompt, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Requesting privileges') - r = write_and_get_response(mopr, mopw, 'set priv') - if r.find('Password> ') == -1: - logging.warning("Expected priv password prompt, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Sending password') - r = write_and_get_response(mopr, mopw, self.priv_password, False) - if r.find('Local> ') == -1: - logging.warning("Expected DEC server prompt, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Sending reboot request') - r = write_and_get_response(mopr, mopw, 'init del 0') - if r.find('Target does not respond') == -1: - logging.warning("Expected DEC server to die, got " + repr(r)) - raise LATClientException('failed to reboot server') - - logging.info('Closed connection to server') - mopr.close() - mopw.close() - logging.info("Waiting 10 seconds for DEC server to come back to life...") - sleep(10) - logging.info("Rightyo, back to vending!") - raise LATClientException('needed to reboot server') - - def read_reply(self): - head = self.sock.recv(3) - if len(head) != 3: - raise LATClientException('Short LAT packet') - cmd = head[0] - length = ord(head[1])*256 + ord(head[2]) - msg = self.sock.recv(length) - if cmd == LATCP_CMD_ERRORMSG: - raise LATClientException('Received LAT error: %s'%msg) - return (cmd, msg) - - def get_fh(self): - return (self.rfh, self.wfh) diff --git a/VendServer/LDAPConnector.py b/VendServer/LDAPConnector.py deleted file mode 100644 index 8699fd2ba6f536d39efd434ca534c685bdad87e7..0000000000000000000000000000000000000000 --- a/VendServer/LDAPConnector.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python2.4 - -import ldap -import ldap.filter - -LDAP_TIMEOUT = 10 - -def get_ldap_connection(): - ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/ssl/UCC-CA.crt') - ldap.set_option(ldap.OPT_X_TLS,1) - ldap.set_option(ldap.OPT_X_TLS_ALLOW,1) - #ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) - conn = ldap.initialize('ldaps://mussel.ucc.gu.uwa.edu.au/') - - binddn = 'cn=mifareagent,ou=profile,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' - passfile = open('/etc/dispense2/ldap.passwd') - password = passfile.readline().strip() - passfile.close() - - conn.simple_bind_s(binddn, password) - return conn - -def get_uid(card_id): - ldapconn = get_ldap_connection() - - basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' - filter = ldap.filter.filter_format('(uccDispenseMIFARE=%s)', (card_id, )) - attrs = ('uidNumber',) - - results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) - - ldapconn.unbind() - - if len(results) != 1: - raise ValueError, "no UID found for card ID" - - return results[0][1]['uidNumber'][0] - -def get_uname(uid): - ldapconn = get_ldap_connection() - - basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' - filter = ldap.filter.filter_format('(uidNumber=%s)', (uid, )) - attrs = ('uid',) - - results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) - - ldapconn.unbind() - - if len(results) != 1: - raise ValueError, "no username found for user id" - - return results[0][1]['uid'][0] - -def set_card_id(uidNumber, card_id): - ldapconn = get_ldap_connection() - - # fix uidNumber for three/four digit uids - uidNumber = str(int(uidNumber)) - basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' - filter = ldap.filter.filter_format('(uidNumber=%s)', (uidNumber, )) - attrs = ('objectClass', ) - - results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) - - if len(results) != 1: - raise "ValueError", 'error in uidNumber' - - user_dn = results[0][0] - - mod_attrs = [] - - # Does it have the correct object class? - if 'uccDispenseAccount' not in results[0][1]['objectClass']: - # Add uccDispenseAccount objectclass - mod_attrs.append((ldap.MOD_ADD, 'objectClass', 'uccDispenseAccount')) - - # Add MIFARE Card ID - mod_attrs.append((ldap.MOD_ADD, 'uccDispenseMIFARE', card_id)) - - # Use a double-try here to work around something that's fixed in Python 2.5 - try: - try: - ldapconn.modify_s(user_dn, mod_attrs) - except ldap.TYPE_OR_VALUE_EXISTS, e: - pass - finally: - ldapconn.unbind() - -if __name__ == '__main__': - set_card_id('11126', '\x01\x02\x03\x04\x05\x06') - print get_uid('\x01\x02\x03\x04\x05\x06') diff --git a/VendServer/MIFAREClient.py b/VendServer/MIFAREClient.py index e0b6a6c83eed31f73bdbc6b1194b60adac73baee..8adf2336cc699bd8ec369d692e00af7a8da79ea3 100644 --- a/VendServer/MIFAREClient.py +++ b/VendServer/MIFAREClient.py @@ -1,44 +1,21 @@ -from MIFAREDriver import MIFAREReader, MIFAREException -from serial import Serial -from LDAPConnector import get_uid, set_card_id - -class MIFAREClient: - def __init__(self): - self.port = Serial('/dev/ttyS2', baudrate = 19200) - self.reader = MIFAREReader(self.port) - self.reader.set_led(red = False, green = True) - self.reader.beep(100) - - def get_card_id(self): - self.reader.set_led(red = True, green = False) - try: - card_id, capacity = self.reader.select_card() - except MIFAREException: - self.reader.set_led(red = False, green = True) - return None - else: - self.reader.set_led(red = False, green = True) - self.reader.beep(100) - return card_id - - def get_card_uid(self): - card_id = self.get_card_id() - if card_id == None: - return None - else: - return get_uid(card_id) - - def add_card(self, uid): - self.reader.set_led(red = True, green = False) - for attempt in range(5): - self.reader.beep(50) - try: - card_id, capacity = self.reader.select_card() - except MIFAREException: - pass - else: - set_card_id(uid, card_id) - self.reader.set_led(red = False, green = True) - return True - self.reader.set_led(red = False, green = True) - return False +from .MIFAREDriver import MIFAREReader, MIFAREException +from serial import Serial + +class MIFAREClient: + def __init__(self): + self.port = Serial('/dev/ttyS2', baudrate = 19200) + self.reader = MIFAREReader(self.port) + self.reader.set_led(red = False, green = True) + self.reader.beep(100) + + def get_card_id(self): + self.reader.set_led(red = True, green = False) + try: + card_id, capacity = self.reader.select_card() + except MIFAREException: + self.reader.set_led(red = False, green = True) + return None + else: + self.reader.set_led(red = False, green = True) + self.reader.beep(100) + return card_id diff --git a/VendServer/MIFAREDriver.py b/VendServer/MIFAREDriver.py index a52a89115883b8d18f6574f805625ee3d8515995..852a9056f9119c1e73953d6ae0b5d26d7af04d37 100644 --- a/VendServer/MIFAREDriver.py +++ b/VendServer/MIFAREDriver.py @@ -8,10 +8,11 @@ Licensed under an MIT-style license: see LICENSE file for details. ''' import serial, logging +from functools import reduce xor = lambda x, y: x ^ y -def checksum(string): - return chr(reduce(xor, [ord(i) for i in string])) +def checksum(data: bytes): + return bytes([reduce(xor, data)]) class MIFAREException(Exception): @@ -35,7 +36,7 @@ class MIFAREReader: self.io = io if isinstance(self.io, serial.Serial): self.io.setTimeout = 2 - self.address = '\x00\x00' + self.address = b'\x00\x00' def get_absolute_block(self, vector): if vector[0] < 32: @@ -46,7 +47,7 @@ class MIFAREReader: # 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): + def send_packet(self, data: bytes): '''Constructs a packet for the supplied data string, sends it to the MIFARE reader, then returns the response (if any) to the commmand.''' @@ -57,29 +58,30 @@ class MIFAREReader: self.io.flushOutput() # XXX - Needs more error checking. - data = '\x00' + self.address + data - packet = '\xAA\xBB' + chr(len(data)) + data + checksum(data) + data = b'\x00' + self.address + data + packet = bytes([0xAA, 0xBB, len(data)]) + data + checksum(data) + #print("send_packet: {!r}".format(packet)) self.io.write(packet) response = '' header = self.io.read(2) - if header == '\xaa\xbb': - length = ord(self.io.read(1)) + if header == b'\xaa\xbb': + length = self.io.read(1)[0] 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" + 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)) + command = bytes([0xc, 0x1, int(state)]) response = self.send_packet(command) - if response == '\x0c\x01\x00': + if response == b'\x0c\x01\x00': return None else: - raise MIFAREException, 'command failed: set_antenna (%s)' % state + 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). @@ -88,59 +90,59 @@ class MIFAREReader: been called on.""" # Request type of card available - command = command = '\x01\x02' + command = command = b'\x01\x02' if include_halted: - command += '\x52' + command += b'\x52' else: - command += '\x26' + command += b'\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" + if card_type_response == None or card_type_response[2] == 0x14: + raise MIFAREException("select_card: no card available") card_type = card_type_response[3:5] - if card_type == '\x44\x00': # MIFARE UltraLight + if card_type == b'\x44\x00': # MIFARE UltraLight #raise NotImplementedError, "UltraLight card selected - no functions available" - # HACK by JAH: The response format isn't fully known yet (official driver reads 7 bytes from offset +9 in the raw response) + # HACK by TPG: The response format isn't fully known yet (official driver reads 7 bytes from offset +9 in the raw response) # - This code effectively reads all bytes after +9 (AA, BB, len, and three skilled by `send_packet`, then three skipped here) # - Official driver has `AA` bytes followed by `00` (encoded/decoded) - command = '\x12\x02' + command = b'\x12\x02' serial = self.send_packet(command)[3:] capacity = 0 return (serial, capacity) else: # Otherwise, must be a standard MIFARE card. # Anticollision - command = '\x02\x02\x04' + command = b'\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]) + select_response = self.send_packet(b'\x03\x02' + serial) + capacity = 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): + def sector_login(self, blockvect, key: bytes, 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' + raise ValueError('key must be a six-byte string') keytype = 96 + keytype - data = chr(keytype) + chr(sector) + key + data = bytes([keytype, sector]) + key - result = self.send_packet('\x07\x02' + data) - if ord(result[2]) == 22: - raise MIFAREAuthenticationException, "incorrect key provided" + result = self.send_packet(b'\x07\x02' + data) + if result[2] == 22: + raise MIFAREAuthenticationException("incorrect key provided") return @@ -148,17 +150,17 @@ class MIFAREReader: "Read the 16-byte block at vector (sector, block)." block = self.get_absolute_block(blockvect) - result = self.send_packet('\x08\x02' + chr(block)) + result = self.send_packet( bytes([0x08,0x02, block]) ) return result[3:19] - def write_block(self, blockvect, data): + def write_block(self, blockvect, data: bytes): """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" + raise ValueError("invalid data length - must be 16 bytes") - result = self.send_packet('\x09\x02' + chr(block) + data) - return + result = self.send_packet( bytes([0x09,0x02,block]) + data ) + return #result[3:19] def write_key(self, key): pass @@ -174,7 +176,7 @@ class MIFAREReader: def halt(self): """Halt the current card - no further transactions will be performed with it.""" - self.send_packet('\x04\x02') + self.send_packet(b'\x04\x02') def set_led(self, red = False, green = False): led_state = 0 @@ -182,14 +184,14 @@ class MIFAREReader: led_state += 1 if green: led_state += 2 - self.send_packet('\x07\x01' + chr(led_state)) + self.send_packet(bytes([0x07,0x01, 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)) + self.send_packet(bytes([0x06,0x01, length])) def reset(self): pass diff --git a/VendServer/MessageKeeper.py b/VendServer/MessageKeeper.py index 60d2b9e2b331bee93bffe25f0edecedf3ddddb97..f14e05ac08f1c3cd51cba6a0c684ed80007c559a 100755 --- a/VendServer/MessageKeeper.py +++ b/VendServer/MessageKeeper.py @@ -2,7 +2,7 @@ # vim:ts=4 import sys, os, string, re, pwd, signal -from HorizScroll import HorizScroll +from .HorizScroll import HorizScroll from random import random, seed from time import time, sleep diff --git a/VendServer/OpenDispense.py b/VendServer/OpenDispense.py index 6b93058b19e0dac3bf1b9a921f8e5229ed736f05..c42f235d0db5d30b27de13109d07b065cebba510 100644 --- a/VendServer/OpenDispense.py +++ b/VendServer/OpenDispense.py @@ -7,18 +7,15 @@ This is so VendServer can easily operate regardless of the current accounting ba Documentation for this code can be found inder Dispence.DispenceInterface """ -from DispenseInterface import DispenseInterface +from .DispenseInterface import DispenseInterface import os import logging import re import pwd import base64 import socket -from subprocess import Popen, PIPE -from LDAPConnector import get_uid,get_uname, set_card_id DISPENSE_ENDPOINT = ("localhost", 11020) -DISPSRV_MIFARE = True # A list of cards that should never be registered, and should never log in # - Some of these might have been registered before we knew they were duplicates @@ -36,32 +33,52 @@ class OpenDispense(DispenseInterface): def __init__(self, username=None, secret=False): pass - def authUserIdPin(self, userId, pin): + def authUserIdPin(self, userId: str, pin: str): return self.authUserIdPin_db(userId, pin) #return self.authUserIdPin_file(userId, pin) - def authUserIdPin_db(self, userId, pin): + def _connect(self, authenticate:bool=True, set_euid:bool=False): + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + try: + sock.connect(DISPENSE_ENDPOINT) + except ConnectionRefusedError: + logging.error("Cannot connect to dispsrv on {}".format(DISPENSE_ENDPOINT,)) + return None + logging.debug('connected to dispsrv') + conn = Connection( sock.makefile('rw', encoding='utf-8') ) + if authenticate: + rsp = conn.send_command("AUTHIDENT") + if not "200" in rsp: + logging.info('Server said no to AUTHIDENT! - %r' % (rsp,)) + return None + logging.debug('authenticated') + if set_euid: + rsp = conn.send_command("SETEUSER %s" % (self._username,)) + if not "200" in rsp: + logging.info('Server said no to SETEUSER! - %r' % (rsp,)) + return None + + return conn + + def authUserIdPin_db(self, userId: str, pin: str): userId = int(userId) try: - # Get username (TODO: Store the user ID in the dispense database too) + # Get username (TODO: Store the user ID in the dispense database too, so the vending machine + # doesn't need LDAP/AD working) info = pwd.getpwuid(userId) except KeyError: logging.info('getting pin for uid %d: user not in password file'%userId) return False - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - sock.connect(DISPENSE_ENDPOINT) - logging.debug('connected to dispsrv') - sockf = sock.makefile() - sockf.write("AUTHIDENT\n"); sockf.flush() - rsp = sockf.readline() - assert "200" in rsp - logging.debug('authenticated') - sockf.write("PIN_CHECK %s %s\n" % (info.pw_name, pin)); sockf.flush() - rsp = sockf.readline() + conn = self._connect() + if conn is None: + logging.error("getting pin for uid {}: Unable to open connection".format(userId)) + return False + rsp = conn.send_command("PIN_CHECK %s %s" % (info.pw_name, pin,)) if not "200" in rsp: - logging.info('checking pin for uid %d: Server said no - %r' % (userId, rsp)) + logging.info('checking pin for uid %d: Server said no (PIN_CHECK) - %r' % (userId, rsp)) return False #Login Successful logging.info('accepted pin for uid %d \'%s\'' % (userId, info.pw_name)) @@ -71,7 +88,7 @@ class OpenDispense(DispenseInterface): self._username = info.pw_name return True - def authUserIdPin_file(self, userId, pin): + def authUserIdPin_file(self, userId: str, pin: str): userId = int(userId) try: @@ -88,11 +105,11 @@ class OpenDispense(DispenseInterface): except OSError: logging.info('getting pin for uid %d: .pin not found in home directory'%userId) return False - if s.st_mode & 077: + if s.st_mode & 0o77: logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%userId) - os.chmod(pinfile, 0600) + os.chmod(pinfile, 0o600) try: - f = file(pinfile) + f = open(pinfile) except IOError: logging.info('getting pin for uid %d: I cannot read pin file'%userId) return False @@ -116,94 +133,66 @@ class OpenDispense(DispenseInterface): def authMifareCard(self, cardId): self._loggedIn = False self._username = None - if DISPSRV_MIFARE: - card_base64 = base64.b64encode(cardId) + + card_base64 = base64.b64encode(cardId).decode('utf-8') - if card_base64 in CARD_BLACKLIST: - logging.info("Blacklisted card base64:%s" % (card_base64,)) - return False - - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - sock.connect(DISPENSE_ENDPOINT) - logging.debug('connected to dispsrv') - sockf = sock.makefile() - sockf.write("AUTHIDENT\n"); sockf.flush() - rsp = sockf.readline() - assert "200" in rsp - logging.debug('authenticated') - sockf.write("AUTHCARD %s\n" % (card_base64,)); sockf.flush() - rsp = sockf.readline() - if not "200" in rsp: - logging.info("Rejected card base64:%s" % (card_base64,)) - return False - username = rsp.split('=')[1].strip() - logging.info("Accepted card base64:%s for %s" % (card_base64,username,)) - - ## Check for thier username - #try: - # # Get info from the system (by username) - # info = pwd.getpwnam(username) - #except KeyError: - # logging.info('getting info for user \'%s\': user not in password file' % (username,)) - # return False - #self._userid = info.pw_uid - self._userid = None - self._username = username - else: - # Get the users ID - self._userid = get_uid(cardId) - - # Check for thier username - try: - # Get info from the system (by UID) - info = pwd.getpwuid(self._userid) - except KeyError: - logging.info('getting info for uid %d: user not in password file' % (self._userid,)) - return False - self._username = info.pw_name + if card_base64 in CARD_BLACKLIST: + logging.info("Blacklisted card base64:%s" % (card_base64,)) + return False + + conn = self._connect() + if conn is None: + logging.error("getting username for card {}: Unable to open connection".format(card_base64)) + return False + cmd = "AUTHCARD %s" % (card_base64,) + rsp = conn.send_command(cmd) + if not rsp.startswith("200 "): + logging.info("%s failed: Rejected card base64:%s: rsp %r" % (cmd, card_base64, rsp)) + return False + username = rsp.split('=')[1].strip() + logging.info("Accepted card base64:%s for %s" % (card_base64,username,)) + + ## Get UID for the username (not needed?) + #try: + # # Get info from the system (by username) + # info = pwd.getpwnam(username) + #except KeyError: + # logging.info('getting info for user \'%s\': user not in password file' % (username,)) + # return False + #self._userid = info.pw_uid + self._userid = None + self._username = username # If we get this far all is good self._loggedIn = True self._disabled = False return True - def logOut(self): - self._loggedIn = False - self._disabled = False - self._userId = None - self._username = None + def logOut(self): + self._loggedIn = False + self._disabled = False + self._userId = None + self._username = None def addCard(self, cardId): if not self.isLoggedIn(): return False - if DISPSRV_MIFARE: - card_base64 = base64.b64encode(cardId) - if card_base64 in CARD_BLACKLIST: - logging.info("Blacklisted card base64:%s" % (card_base64,)) - return False - logging.info('Enrolling card base64:%s to uid %s (%s)' % (card_base64, self._userId, self._username)) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - sock.connect(DISPENSE_ENDPOINT) - sockf = sock.makefile() - sockf.write("AUTHIDENT\n") - sockf.flush(); rsp = sockf.readline() - assert "200" in rsp - sockf.write("SETEUSER %s\n" % (self._username,)) - sockf.flush(); rsp = sockf.readline() - assert "200" in rsp - sockf.write("CARD_ADD %s\n" % (card_base64,)) - sockf.flush(); rsp = sockf.readline() - if "200" in rsp: - return True - else: - return False + + card_base64 = base64.b64encode(cardId) + if card_base64 in CARD_BLACKLIST: + logging.info("Blacklisted card base64:%s" % (card_base64,)) + return False + logging.info('Enrolling card base64:%s to uid %s (%s)' % (card_base64, self._userId, self._username)) + conn = self._connect(set_euid=True) + if conn is None: + logging.warn("Enrolling card failed: Unable to connect".format(rsp)) + return False + rsp = conn.send_command("CARD_ADD %s" % (card_base64,)) + if "200" in rsp: + return True else: - if get_uid(cardId) != None: - return False - else: - logging.info('Enrolling card %s to uid %s (%s)' % (cardId, self._userId, self._username)) - set_card_id(self._userId, cardId) - return True + logging.warn("Enrolling card failed: Response = {}".format(rsp)) + return False def isLoggedIn(self): return self._loggedIn @@ -213,41 +202,92 @@ class OpenDispense(DispenseInterface): def getBalance(self): # Balance checking - if self.isLoggedIn(): - acct, unused = Popen(['dispense', 'acct', self._username], close_fds=True, stdout=PIPE).communicate() - else: - return None - balance = acct[acct.find("$")+1:acct.find("(")].strip() - return balance + if not self.isLoggedIn(): + return "?" + + conn = self._connect(authenticate=False) + if conn is None: + return "?" + cmd = "USER_INFO {}".format(self._username) + rsp = conn.send_command(cmd) + try: + code,rest = rsp.split(" ", 1) + if code != "202": + raise ValueError("Code not 202") + _user,_name,balance,flags = rest.split(" ") + return "{:.2f}".format(int(balance) / 100) + except ValueError as e: + logging.warn("OpenDispense: {!r} response malformed ({!r}) - exception {}".format(cmd, rsp, e)) + return "?" - def getItemInfo(self, itemId): + def getItemInfo(self, itemId: str): logging.debug("getItemInfo(%s)" % (itemId,)) itemId = OpenDispenseMapping.vendingMachineToOpenDispense(itemId) - args = ('dispense', 'iteminfo', itemId) - info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate() - m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info) - if m == None: - return("dead", 0) - cents = int(m.group(1))*100 + int(m.group(2)) - # return (name, price in cents) - return (m.group(3), cents) + + conn = self._connect(authenticate=False) + if conn is None: + return ("dead", 0,) + cmd = "ITEM_INFO {}".format(itemId) + rsp = conn.send_command(cmd) + try: + code,rest = rsp.split(" ", 1) + if code != "202": + raise ValueError("Code not 202") + _item,itemid,status,price_cents,name = rest.split(" ", 4) + return (name, int(price_cents),) + except ValueError as e: + logging.warn("OpenDispense: {!r} response malformed ({!r}) - exception {}".format(cmd, rsp, e)) + return ("dead", 0,) def isDisabled(self): return self._disabled def dispenseItem(self, itemId): + logging.debug("getItemInfo(%s)" % (itemId,)) if not self.isLoggedIn() or self.getItemInfo(itemId)[0] == "dead": + return "999" + + conn = self._connect(set_euid=True) + if conn is None: + return "999" + + cmd = "DISPENSE {}".format( OpenDispenseMapping.vendingMachineToOpenDispense(itemId) ) + rsp = conn.send_command(cmd) + try: + code,rest = rsp.split(" ", 1) + return code + except ValueError as e: + logging.warn("OpenDispense: {!r} response malformed ({!r}) - exception {}".format(cmd, rsp, e)) + return 999 + def openDoor(self): + if not self.isLoggedIn(): return False - else: - print('dispense -u "%s" %s'%(self._username, itemId)) - #os.system('dispense -u "%s" %s'%(self._username, itemId)) - return True - def logOut(self): - self._username = "" - self._disabled = True - self._loggedIn = False - self._userId = None + conn = self._connect(set_euid=True) + if conn is None: + return False + + cmd = "DISPENSE door:0".format() + rsp = conn.send_command(cmd) + try: + code,rest = rsp.split(" ", 1) + if code == "200": + return True + if code == "402": # "Poor You" + return False + raise ValueError("Unknown code") + except ValueError as e: + logging.warn("OpenDispense: {!r} response malformed ({!r}) - exception {}".format(cmd, rsp, e)) + return False + +class Connection(object): + def __init__(self, sockf): + self.sockf = sockf + def send_command(self, command): + self.sockf.write(command) + self.sockf.write("\n") + self.sockf.flush() + return self.sockf.readline() """ This class abstracts the idea of item numbers. diff --git a/VendServer/SerialClient.py b/VendServer/SerialClient.py index e0a9a7e965e95fcd0b86c2eb24917b8a8f49c1e8..280109b8838f5628fd3ad4a4c088fc4de0a34d37 100644 --- a/VendServer/SerialClient.py +++ b/VendServer/SerialClient.py @@ -19,8 +19,8 @@ class SerialClient: ) - self.rfh = self.ser - self.wfh = self.ser + self.rfh = ReadWrapper(self.ser) + self.wfh = WriteWrapper(self.ser) self.wfh.write('B\n') def get_fh(self): @@ -28,6 +28,22 @@ class SerialClient: def __del__(self): pass +class WriteWrapper: + def __init__(self, fh): + self.fh = fh + def write(self, s: str): + return self.fh.write(s.encode('utf-8')) + def flush(self): + return self.fh.flush() +class ReadWrapper: + def __init__(self, fh): + self.fh = fh + def fileno(self): + return self.fh.fileno() + def read(self, count) -> str: + return self.fh.read(count).decode('utf-8') + def readline(self) -> str: + return self.fh.readline().decode('utf-8') if __name__ == '__main__': @@ -36,6 +52,6 @@ if __name__ == '__main__': (rfh, wfh) = s.get_fh() wfh.write('B\n') - print rfh.read() + print(rfh.read()) diff --git a/VendServer/SnackConfig.py b/VendServer/SnackConfig.py deleted file mode 100755 index 231407480171c5078e55d3aaedc1e361e9a24bb5..0000000000000000000000000000000000000000 --- a/VendServer/SnackConfig.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python - -class VendingException( Exception ): pass - -import subprocess -import os, re - -def get_snack( slot ): - - if slot == "--": - return (0, 'nothing', 'Nothing') - cmd = 'dispense iteminfo snack:%s' % slot -# print 'cmd = %s' % cmd - try: -# info = subprocess.check_output(["dispense","iteminfo",'snack:%s'%slot]) - raw = os.popen(cmd) - info = raw.read() - raw.close() -# print 'cmd (2) = %s' % cmd -# print 'info = "%s"' % info - m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info) - val = ( int(m.group(1))*100 + int(m.group(2)), m.group(3), m.group(3) ) -# print 'Price: %i, Name: %s' % (val[0], val[1]) - except BaseException as e: - print "BaseException" - print e - val = (0, 'error', 'Error') - except: - print "Unknown exception" - val = (0, 'error', 'Error') - return val - -def get_price( slot ): - p, sn, n = get_snacks( slot ) - return p - -def get_name( slot ): - p, sn, n = get_snacks( slot ) - return n - -def get_short_name( slot ): - p, sn, n = get_snacks( slot ) - return sn - -if __name__ == '__main__': - print "Don't run this" diff --git a/VendServer/VendServer.py b/VendServer/VendServer.py index cf9c8bbdbb689476b1c8fe584a16049691175b2f..f26c0e3c489c9a170b412e2ce32b8b7a88b35d40 100755 --- a/VendServer/VendServer.py +++ b/VendServer/VendServer.py @@ -3,23 +3,21 @@ USE_MIFARE = 1 -import ConfigParser +import configparser import sys, os, string, re, pwd, signal, math, syslog import logging, logging.handlers from traceback import format_tb 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 .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 +from .Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler import socket from posix import geteuid -from OpenDispense import OpenDispense as Dispense +from .OpenDispense import OpenDispense as Dispense import TracebackPrinter CREDITS=""" @@ -28,6 +26,7 @@ Bernard Blackham Mark Tearle Nick Bannon Cameron Patrick +John Hodge and a collective of hungry alpacas. The MIFARE card reader bought to you by: @@ -59,7 +58,7 @@ STATE_GETTING_UID, STATE_GETTING_PIN, STATE_GET_SELECTION, STATE_GRANDFATHER_CLOCK, -) = range(1,8) +) = list(range(1,8)) TEXT_SPEED = 0.8 IDLE_SPEED = 0.05 @@ -82,7 +81,7 @@ config_options = { class VendConfigFile: def __init__(self, config_file, options): try: - cp = ConfigParser.ConfigParser() + cp = configparser.ConfigParser() cp.read(config_file) for option in options: @@ -90,7 +89,7 @@ class VendConfigFile: value = cp.get(section, name) self.__dict__[option] = value - except ConfigParser.Error, e: + except configparser.Error as e: raise SystemExit("Error reading config file "+config_file+": " + str(e)) """ @@ -176,7 +175,7 @@ class VendServer(): messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY'] choice = int(random()*len(messages)) msg = messages[choice] - left = range(len(msg)) + left = list(range(len(msg))) for i in range(len(msg)): if msg[i] == ' ': left.remove(i) reveal = 1 @@ -198,9 +197,9 @@ class VendServer(): """ Format text so it will appear centered on the screen. """ - def center(self, str): + def center(self, str: str): LEN = 10 - return ' '*((LEN-len(str))/2)+str + return ' '*((LEN-len(str))//2)+str """ Configure the things that will appear on screen whil the machine is idling. @@ -244,7 +243,7 @@ class VendServer(): """ def reset_idler(self, t = None): self.idler = GreetingIdler(self.v, t) - self.vstatus.time_of_next_idlestep = time()+self.idler.next() + self.vstatus.time_of_next_idlestep = time()+next(self.idler) self.vstatus.time_of_next_idler = None self.vstatus.time_to_autologout = None self.vstatus.change_state(STATE_IDLE, 1) @@ -287,7 +286,7 @@ class VendServer(): if self.idler.finished(): self.choose_idler() self.vstatus.time_of_next_idler = time() + 30 - nextidle = self.idler.next() + nextidle = next(self.idler) if nextidle is None: nextidle = IDLE_SPEED self.vstatus.time_of_next_idlestep = time()+nextidle @@ -307,7 +306,7 @@ class VendServer(): Don't do anything for this event. """ def do_nothing(self, event, params): - print "doing nothing (s,e,p)", state, " ", event, " ", params + print("doing nothing (s,e,p)", self.state, " ", event, " ", params) pass """ @@ -412,11 +411,7 @@ class VendServer(): if self.vstatus.cur_selection == '55': self.vstatus.mk.set_message('OPENSESAME') logging.info('dispensing a door for %s'%self.vstatus.username) - if geteuid() == 0: - ret = os.system('dispense -u "%s" door'%self.vstatus.username) - else: - ret = os.system('dispense door') - if ret == 0: + if self.dispense.openDoor(): logging.info('door opened') self.vstatus.mk.set_message(self.center('DOOR OPEN')) else: @@ -430,38 +425,36 @@ class VendServer(): self.vstatus.cur_selection = '' return elif self.vstatus.cur_selection[1] == '8': + # Drinks: No need to vend, just print a funny message and ask the server to vend from coke + logging.info('dispensing drink {} for {}'.format(self.vstatus.cur_selection, self.vstatus.username)) self.v.display('GOT DRINK?') - if ((os.system('dispense -u "%s" coke:%s'%(self.vstatus.username, self.vstatus.cur_selection[0])) >> 8) != 0): + if self.dispense.dispenseItem(self.vstatus.cur_selection) == "200": self.v.display('SEEMS NOT') else: self.v.display('GOT DRINK!') else: - # first see if it's a named slot - try: - price, shortname, name = get_snack( self.vstatus.cur_selection ) - except: - price, shortname, name = get_snack( '--' ) + logging.info('dispensing snack {} for {}'.format(self.vstatus.cur_selection, self.vstatus.username)) + # Snacks: Show the name/price then dispense it + name, price = self.dispense.getItemInfo( self.vstatus.cur_selection ) dollarprice = "$%.2f" % ( price / 100.0 ) self.v.display(self.vstatus.cur_selection+' - %s'%dollarprice) - exitcode = os.system('dispense -u "%s" snack:%s'%(self.vstatus.username, self.vstatus.cur_selection)) >> 8 - if (exitcode == 0): + status = self.dispense.dispenseItem(self.vstatus.cur_selection) + if status == "200": # magic dispense syslog service (worked, code, string) = self.v.vend(self.vstatus.cur_selection) if worked: self.v.display('THANK YOU') syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "vended %s (slot %s) for %s" % (name, self.vstatus.cur_selection, self.vstatus.username)) else: - print "Vend Failed:", code, string + print("Vend Failed:", code, string) syslog.syslog(syslog.LOG_WARNING | syslog.LOG_LOCAL4, "vending %s (slot %s) for %s FAILED %r %r" % (name, self.vstatus.cur_selection, self.vstatus.username, code, string)) self.v.display('VEND FAIL') - elif (exitcode == 5): # RV_BALANCE + elif status == "402": # RV_BALANCE self.v.display('NO MONEY?') - elif (exitcode == 4): # RV_ARGUMENTS (zero give causes arguments) - self.v.display('EMPTY SLOT') - elif (exitcode == 1): # RV_BADITEM (Dead slot) + elif status == "406": # RV_BADITEM (Dead slot) self.v.display('EMPTY SLOT') else: - syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "failed vending %s (slot %s) for %s (code %d)" % (name, self.vstatus.cur_selection, self.vstatus.username, exitcode)) + syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "failed vending %s (slot %s) for %s (code %s)" % (name, self.vstatus.cur_selection, self.vstatus.username, status)) self.v.display('UNK ERROR') sleep(1) @@ -645,16 +638,16 @@ class VendServer(): ### 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]]) + 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]]) + 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 @@ -675,16 +668,16 @@ class VendServer(): ### 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]]) + 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]]) + 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" @@ -923,7 +916,7 @@ Connect to the machine. """ def connect_to_vend(options, cf): - if options.use_lat: + if False and 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() @@ -964,7 +957,8 @@ def parse_args(): 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') - op.add_option('--traceback-file', dest='traceback_file', default='', help='destination to print tracebacks when receiving SIGUSR1') + op.add_option('--traceback-file', dest='traceback_file', default='', help='destination to print tracebacks when receiving SIGUSR1') + op.add_option('--crash', action='store_true', help="Crash immediately instead of looping") options, args = op.parse_args() if len(args) != 0: @@ -974,10 +968,10 @@ def parse_args(): def create_pid_file(name): try: - pid_file = file(name, 'w') + pid_file = open(name, 'w') pid_file.write('%d\n'%os.getpid()) pid_file.close() - except IOError, e: + except IOError as e: logging.warning('unable to write to pid file '+name+': '+str(e)) def set_stuff_up(): @@ -1016,7 +1010,7 @@ def set_up_logging(options): 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: + except IOError as e: logger.warning('unable to write to log file '+options.log_file+': '+str(e)) if options.syslog != None: @@ -1032,7 +1026,7 @@ def set_up_logging(options): logger.setLevel(logging.INFO) def become_daemon(): - dev_null = file('/dev/null') + dev_null = open('/dev/null') fd = dev_null.fileno() os.dup2(fd, 0) os.dup2(fd, 1) @@ -1041,14 +1035,14 @@ def become_daemon(): if os.fork() != 0: sys.exit(0) os.setsid() - except OSError, e: + except OSError as 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: + except (SerialClientException, socket.error) as e: (exc_type, exc_value, exc_traceback) = sys.exc_info() del exc_traceback logging.error("Connection error: "+str(exc_type)+" "+str(e)) @@ -1076,12 +1070,23 @@ def main(argv=None): logging.error('Vend Server finished unexpectedly, restarting') except KeyboardInterrupt: logging.info("Killed by signal, cleaning up") + # NOTE: When debugging deadlocks, enable this + if options.crash: + (exc_type, exc_value, exc_traceback) = sys.exc_info() + tb = format_tb(exc_traceback, 20) + del exc_traceback + logging.info("Traceback:") + for event in tb: + for line in event.strip().split('\n'): + logging.critical(' '+line) clean_up_nicely(options, config_opts) logging.warning("Vend Server stopped") break except SystemExit: break except: + if options.crash: + raise (exc_type, exc_value, exc_traceback) = sys.exc_info() tb = format_tb(exc_traceback, 20) del exc_traceback @@ -1090,7 +1095,7 @@ def main(argv=None): logging.critical("Message: " + str(exc_value)) logging.critical("Traceback:") for event in tb: - for line in event.split('\n'): + for line in event.strip().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.") diff --git a/VendServer/VendingMachine.py b/VendServer/VendingMachine.py index 72cec3dce2c63ba5be54f2a0f880a7507fa4f2c7..4a5026836b3b17861e3a2f7cc6968a2bb43ddafa 100644 --- a/VendServer/VendingMachine.py +++ b/VendServer/VendingMachine.py @@ -1,10 +1,10 @@ # vim:ts=4 import re -from CRC import do_crc +from .CRC import do_crc from select import select import socket, logging from time import time, sleep -from MIFAREClient import MIFAREClient +from .MIFAREClient import MIFAREClient asynchronous_responses = [ '400', '401', # door open/closed '610', # switches changed @@ -72,7 +72,7 @@ class VendingMachine: self.challenge = int(prefix, 16) return - def get_response(self, async = False): + def get_response(self, is_async = False): self.wfh.flush() while True: s = '' @@ -85,7 +85,7 @@ class VendingMachine: text = s[4:] if code in asynchronous_responses: self.handle_event(code, text) - if async: return None + if is_async: return None else: self.await_prompt() return (code, text) @@ -115,14 +115,15 @@ class VendingMachine: else: logging.warning('Unhandled event! (%s %s)\n'%(code,text)) - def authed_message(self, message): - print 'self.challenge = %04x' % self.challenge + def authed_message(self, message: str): if self.challenge == None: + print('self.challenge = None') return message + print('self.challenge = %04x' % self.challenge) 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) + print('output = "%s|%04x"' % (message, crc)) return message+'|'+('%04x'%crc) def ping(self): @@ -182,7 +183,7 @@ class VendingMachine: (r, _, _) = select([self.rfh], [], [], this_timeout) if r: - self.get_response(async = True) + self.get_response(is_async = True) timeout = 0 if self.mifare: