import socket
from posix import geteuid
from LDAPConnector import get_uid,get_uname, set_card_id
+from OpenDispense import OpenDispense as Dispense
CREDITS="""
This vending machine software brought to you by:
Cameron Patrick
and a collective of hungry alpacas.
+The MIFARE card reader bought to you by:
+David Adam
+Bug Hunting and hardware maintenance by:
+Mitchell Pomery
For a good time call +61 8 6488 3901
TEXT_SPEED = 0.8
IDLE_SPEED = 0.05
+_pin_uid = 0
+_pin_uname = 'root'
+_pin_pin = '----'
+
+_last_card_id = -1
+
+idlers = []
+idler = None
+
+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))
+
class DispenseDatabaseException(Exception): pass
class DispenseDatabase:
while notifier is not None:
self.process_requests()
notify = self.db.getnotify()
+"""
+This class manages the current state of the vending machine.
+"""
+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:
+ self.state = newstate
+
+ if newcounter is not None and self.counter != newcounter:
+ self.counter = newcounter
+"""
+Show information to the user as to what can be dispensed.
+"""
def scroll_options(username, mk, welcome = False):
+ # If the user has just logged in, show them their balance
if welcome:
# Balance checking
acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate()
msg = []
choices = ' '*10+'CHOICES: '
- # Get coke contents
+ # Show what is in the coke machine
cokes = []
for i in range(0, 7):
args = ('dispense', 'iteminfo', 'coke:%i' % i)
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 )
-
+ # Show the final few options
choices += '55-DOOR '
choices += 'OR ANOTHER SNACK. '
choices += '99 TO READ AGAIN. '
choices += 'CHOICE? '
msg.append((choices, False, None))
+ # Send it to the display
mk.set_messages(msg)
-_pin_uid = 0
-_pin_uname = 'root'
-_pin_pin = '----'
+"""
+Verify the users pin
+"""
def _check_pin(uid, pin):
global _pin_uid
global _pin_uname
logging.info("Pin incorrect for %d",uid)
return pin == int(pinstr)
+"""
+Check if the users account has been disabled
+"""
def acct_is_disabled(name=None):
global _pin_uname
if name == None:
return True
return False
+"""
+Check that the user has a valid pin set
+"""
def has_good_pin(uid):
return _check_pin(uid, None) != None
+"""
+Verify the users pin.
+"""
def verify_user_pin(uid, pin, skip_pin_check=False):
if skip_pin_check or _check_pin(uid, pin) == True:
info = pwd.getpwuid(uid)
logging.info('refused pin for uid %d'%(uid))
return None
-
+"""
+In here just for fun.
+"""
def cookie(v):
seed(time())
messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
s += msg[i]
v.display(s)
+"""
+Format text so it will appear centered on the screen.
+"""
def center(str):
LEN = 10
return ' '*((LEN-len(str))/2)+str
-
-
-idlers = []
-idler = None
-
+"""
+Configure the things that will appear on screen whil the machine is idling.
+"""
def setup_idlers(v):
global idlers, idler
idlers = [
- GrayIdler(v),
+ #
+ GrayIdler(v),
+ GrayIdler(v,one="*",zero="-"),
+ GrayIdler(v,one="/",zero="\\"),
+ GrayIdler(v,one="X",zero="O"),
+ GrayIdler(v,one="*",zero="-",reorder=1),
+ GrayIdler(v,one="/",zero="\\",reorder=1),
+ GrayIdler(v,one="X",zero="O",reorder=1),
+ #
+ ClockIdler(v),
+ ClockIdler(v),
+ ClockIdler(v),
+ #
+ StringIdler(v), # Hello Cruel World
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),
+ StringIdler(v, text=" I want some pizza - please call Broadway Pizza on +61 8 9389 8500 - and order as Quinn - I am getting really hungry", repeat=False),
# "Hello World" in brainfuck
StringIdler(v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."),
+ #
+ TrainIdler(v),
+ #
+ FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
+ #
+ PipeIdler(v, "/usr/bin/getent", "passwd"),
+ FortuneIdler(v,affinity=20),
]
disabled = [
]
+"""
+Go back to the default idler.
+"""
def reset_idler(v, vstatus, t = None):
global idlers, idler
idler = GreetingIdler(v, t)
vstatus.time_to_autologout = None
vstatus.change_state(STATE_IDLE, 1)
+"""
+Change to a random idler.
+"""
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()
+ # Implementation of the King Of the Hill algorithm from;
+ # http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/
+
+ #def weighted_choice_king(weights):
+ # total = 0
+ # winner = 0
+ # for i, w in enumerate(weights):
+ # total += w
+ # if random.random() * total < w:
+ # winner = i
+ # return winner
+ #
+
+ total = 0
+ winner = None
+
+ for choice in idlers:
+ weight = choice.affinity()
+ total += weight
+ if random() * total < weight:
+ winner = choice
- idler.reset()
+ idler = winner
+ if idler:
+ idler.reset()
+"""
+Run every step while the machine is idling.
+"""
def idle_step(vstatus):
global idler
if idler.finished():
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
-
-
-
+"""
+These next two events trigger no response in the code.
+"""
def handle_tick_event(event, params, v, vstatus):
# don't care right now.
pass
# don't care right now.
pass
-
+"""
+Don't do anything for this event.
+"""
def do_nothing(state, event, params, v, vstatus):
print "doing nothing (s,e,p)", state, " ", event, " ", params
pass
+"""
+These next few entrie tell us to do nothing while we are idling
+"""
def handle_getting_uid_idle(state, event, params, v, vstatus):
# don't care right now.
pass
# don't care right now.
pass
+"""
+While logged in and waiting for user input, slowly get closer to logging out.
+"""
def handle_get_selection_idle(state, event, params, v, vstatus):
+ global _last_card_id
# don't care right now.
###
### State logging out ..
vstatus.cur_user = ''
vstatus.cur_pin = ''
vstatus.cur_selection = ''
-
+ _last_card_id = -1
reset_idler(v, vstatus)
### State fully logged out ... reset variables
# need to check
vstatus.mk.update_display()
-
-
+"""
+Triggered on user input while logged in.
+"""
def handle_get_selection_key(state, event, params, v, vstatus):
+ global _last_card_id
key = params
if len(vstatus.cur_selection) == 0:
if key == 11:
vstatus.cur_pin = ''
vstatus.cur_user = ''
vstatus.cur_selection = ''
-
+ _last_card_id = -1
vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
reset_idler(v, vstatus, 2)
return
vstatus.time_to_autologout = None
vstatus.last_timeout_refresh = None
+"""
+Triggered when the user has entered the id of something they would like to purchase.
+"""
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')
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
v.display('UNK ERROR')
sleep(1)
-
+"""
+Find the price of an item.
+"""
def price_check(v, vstatus):
if vstatus.cur_selection[1] == '8':
args = ('dispense', 'iteminfo', 'coke:' + vstatus.cur_selection[0])
dollarprice = "$%.2f" % ( price / 100.0 )
v.display(vstatus.cur_selection+' - %s'%dollarprice)
-
+"""
+Triggered when the user presses a button while entering their pin.
+"""
def handle_getting_pin_key(state, event, params, v, vstatus):
#print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
key = params
return
-
+"""
+Triggered when the user presses a button while entering their user id.
+"""
def handle_getting_uid_key(state, event, params, v, vstatus):
#print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
key = params
vstatus.change_state(STATE_GETTING_PIN)
return
-
+"""
+Triggered when a key is pressed and the machine is idling.
+"""
def handle_idle_key(state, event, params, v, vstatus):
#print "handle_idle_key (s,e,p)", state, " ", event, " ", params
vstatus.change_state(STATE_GETTING_UID)
run_handler(event, key, v, vstatus)
-
+"""
+What to do when there is nothing to do.
+"""
def handle_idle_tick(state, event, params, v, vstatus):
### State idling
if vstatus.mk.done():
run_handler(event, params, v, vstatus)
sleep(0.05)
+"""
+Manages the beeps for the grandfather clock
+"""
def beep_on(when, before=0):
start = int(when - before)
end = int(when)
if go_idle and vstatus.mk.done():
vstatus.change_state(STATE_IDLE,1)
+"""
+What to do when the door is open.
+"""
def handle_door_idle(state, event, params, v, vstatus):
def twiddle(clock,v,wise = 2):
if (clock % 4 == 0):
else:
twiddle(now, v, wise=0)
-
+"""
+What to do when the door is opened or closed.
+"""
def handle_door_event(state, event, params, v, vstatus):
if params == 0: #door open
vstatus.change_state(STATE_DOOR_OPENING)
logging.warning('Leaving open door mode')
v.display("-YUM YUM!-")
+"""
+Triggered when a user swipes their caed, and the machine is logged out.
+"""
def handle_mifare_event(state, event, params, v, vstatus):
+ global _last_card_id
card_id = params
# Translate card_id into uid.
- if card_id == None:
+ if card_id == None or card_id == _last_card_id:
return
+ _last_card_id = card_id
+
try:
vstatus.cur_user = get_uid(card_id)
logging.info('Mapped card id to uid %s'%vstatus.cur_user)
(center('SORRY'), False, 0.5)])
vstatus.cur_user = ''
vstatus.cur_pin = ''
+ _last_card_id = -1
reset_idler(v, vstatus, 2)
return
+"""
+Triggered when a user swipes their card and the machine is logged in.
+"""
def handle_mifare_add_user_event(state, event, params, v, vstatus):
- card_id = params
+ global _last_card_id
+ card_id = params
# Translate card_id into uid.
- if card_id == None:
+ if card_id == None or card_id == _last_card_id:
return
+ _last_card_id = card_id
+
try:
if get_uid(card_id) != None:
vstatus.mk.set_messages(
def return_to_idle(state,event,params,v,vstatus):
reset_idler(v, vstatus)
+"""
+Maps what to do when the state changes.
+"""
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_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,DOOR,1)] = handle_door_event
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,DOOR,1)] = handle_door_event
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,DOOR,1)] = handle_door_event
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,DOOR,1)] = handle_door_event
+ vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = handle_door_event
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
+"""
+Get what to do on a state change.
+"""
def get_state_table_handler(vstatus, state, event, counter):
return vstatus.state_table[(state,event,counter)]
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:
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)
+"""
+Connect to the machine.
+"""
def connect_to_vend(options, cf):
if options.use_lat:
return rfh, wfh
+"""
+Parse arguments from the command line
+"""
def parse_args():
from optparse import OptionParser
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')
logging.info("Trying again in 5 seconds.")
sleep(5)
-if __name__ == '__main__':
+
+def main(argv=None):
options, config_opts = set_stuff_up()
while True:
try:
sleep(10)
logging.warning("Trying again anyway (might not help, but hey...)")
+if __name__ == '__main__':
+ sys.exit(main())