예제 #1
0
    def __init__(self, width, height, manager, owner):

        Screen.__init__(self, manager, owner, Screens.PRODUCTENTRY)
        ProductEntryGUI.__init__(self, width, height, self)

        self.states = Enum("BARCODE", "DESCRIPTION", "PRICE", "ADDING",
                           "WARNING")
        self.events = Enum("GUIEVENT", "SCAN", "BADSCAN", "KEYPRESS",
                           "TIMEOUT")

        self.barcode = ""
        self.price = 0
        self.description = ""

        self.sm = SimpleStateMachine(
            self.states.BARCODE,  #pylint: disable=C0103
            [
                (self.states.BARCODE, self.events.GUIEVENT,
                 self._on_gui_event),
                (self.states.BARCODE, self.events.SCAN, self._got_barcode),
                (self.states.BARCODE, self.events.BADSCAN,
                 self._got_new_barcode),
                (self.states.BARCODE, self.events.KEYPRESS,
                 lambda: self.states.BARCODE),
                (self.states.DESCRIPTION, self.events.GUIEVENT,
                 self._on_gui_event),
                (self.states.DESCRIPTION, self.events.KEYPRESS,
                 self._got_new_desc_char),
                (self.states.DESCRIPTION, self.events.SCAN,
                 lambda: self.states.DESCRIPTION),
                (self.states.DESCRIPTION, self.events.BADSCAN,
                 lambda: self.states.DESCRIPTION),
                (self.states.PRICE, self.events.GUIEVENT, self._on_gui_event),
                (self.states.PRICE, self.events.KEYPRESS,
                 self._got_new_price_char),
                (self.states.PRICE, self.events.SCAN,
                 lambda: self.states.DESCRIPTION),
                (self.states.PRICE, self.events.BADSCAN,
                 lambda: self.states.DESCRIPTION),
                (self.states.ADDING, self.events.TIMEOUT, self._exit),
                (self.states.ADDING, self.events.GUIEVENT,
                 lambda: self.states.ADDING),
                (self.states.ADDING, self.events.KEYPRESS,
                 lambda: self.states.ADDING),
                (self.states.ADDING, self.events.SCAN,
                 lambda: self.states.ADDING),
                (self.states.ADDING, self.events.BADSCAN,
                 lambda: self.states.ADDING),
                (self.states.WARNING, self.events.TIMEOUT,
                 lambda: self.states.BARCODE),
                (self.states.WARNING, self.events.GUIEVENT,
                 lambda: self.states.WARNING),
                (self.states.WARNING, self.events.KEYPRESS,
                 lambda: self.states.WARNING),
                (self.states.WARNING, self.events.SCAN,
                 lambda: self.states.WARNING),
                (self.states.WARNING, self.events.BADSCAN,
                 lambda: self.states.WARNING),
            ])
예제 #2
0
    def __init__(self, width, height, manager, owner):
        Screen.__init__(self, manager, owner, Screens.MAINSCREEN)
        MainScreenGUI.__init__(self, width, height, self)
        
        self.states = Enum("IDLE", "CHARGING", "PAYMENTMSG", "WARNING")
        self.events = Enum("GUIEVENT", "SCAN", "BADSCAN", "RFID", "BADRFID", "CHARGED", "BANNERTIMEOUT")
        
        self.sm = SimpleStateMachine(self.states.IDLE, #pylint: disable=C0103
        [
                [self.states.IDLE,          self.events.GUIEVENT,       self._on_idle_gui_event],
                [self.states.IDLE,          self.events.SCAN,           self._on_idle_scan_event],
                [self.states.IDLE,          self.events.BADSCAN,        self._on_idle_bad_scan_event],
                [self.states.IDLE,          self.events.RFID,           self._on_rfid_event],
                [self.states.IDLE,          self.events.BADRFID,        self._on_bad_rfid_event],

                [self.states.PAYMENTMSG,    self.events.BANNERTIMEOUT,  self._return_to_intro],
                [self.states.PAYMENTMSG,    self.events.GUIEVENT,       lambda: self.states.PAYMENTMSG],
                [self.states.PAYMENTMSG,    self.events.SCAN,           lambda: self.states.PAYMENTMSG],
                [self.states.PAYMENTMSG,    self.events.BADSCAN,        lambda: self.states.PAYMENTMSG],
                [self.states.PAYMENTMSG,    self.events.RFID,           lambda: self.states.PAYMENTMSG],
                [self.states.PAYMENTMSG,    self.events.BADRFID,        lambda: self.states.PAYMENTMSG],
                
                [self.states.CHARGING,      self.events.CHARGED,        lambda: self.states.PAYMENTMSG],
                [self.states.CHARGING,      self.events.GUIEVENT,       lambda: self.states.CHARGING],
                [self.states.CHARGING,      self.events.SCAN,           lambda: self.states.CHARGING],
                [self.states.CHARGING,      self.events.BADSCAN,        lambda: self.states.CHARGING],
                [self.states.CHARGING,      self.events.RFID,           lambda: self.states.CHARGING],
                [self.states.CHARGING,      self.events.BADRFID,        lambda: self.states.CHARGING],
                
                [self.states.WARNING,       self.events.BANNERTIMEOUT,  self._remove_warning],
                [self.states.WARNING,       self.events.SCAN,           self._on_idle_scan_event],
                [self.states.WARNING,       self.events.BADSCAN,        self._update_barcode_warning],
                [self.states.WARNING,       self.events.RFID,           self._on_rfid_event],
                [self.states.WARNING,       self.events.BADRFID,        self._on_bad_rfid_event],
                [self.states.WARNING,       self.events.GUIEVENT,       self._remove_warning],

        ])

        self.logger = logging.getLogger("MainScreen")

        self.new_product = None
        self.badcode = ""
예제 #3
0
    def __init__(self, width, height, manager, owner):

        Screen.__init__(self, manager, owner, Screens.PRODUCTENTRY)
        ProductEntryGUI.__init__(self, width, height, self)

        self.states = Enum("BARCODE", "DESCRIPTION", "PRICE", "ADDING", "WARNING")
        self.events = Enum("GUIEVENT", "SCAN", "BADSCAN", "KEYPRESS", "TIMEOUT")

        self.barcode = ""
        self.price = 0
        self.description = ""
       
        self.sm = SimpleStateMachine(self.states.BARCODE, #pylint: disable=C0103
        [
            (self.states.BARCODE, self.events.GUIEVENT,      self._on_gui_event),
            (self.states.BARCODE, self.events.SCAN,          self._got_barcode),
            (self.states.BARCODE, self.events.BADSCAN,       self._got_new_barcode),
            (self.states.BARCODE, self.events.KEYPRESS,      lambda: self.states.BARCODE),

            (self.states.DESCRIPTION, self.events.GUIEVENT,  self._on_gui_event),
            (self.states.DESCRIPTION, self.events.KEYPRESS,  self._got_new_desc_char),
            (self.states.DESCRIPTION, self.events.SCAN,      lambda: self.states.DESCRIPTION),
            (self.states.DESCRIPTION, self.events.BADSCAN,   lambda: self.states.DESCRIPTION),

            (self.states.PRICE, self.events.GUIEVENT,        self._on_gui_event),
            (self.states.PRICE, self.events.KEYPRESS,        self._got_new_price_char),
            (self.states.PRICE, self.events.SCAN,            lambda: self.states.DESCRIPTION),
            (self.states.PRICE, self.events.BADSCAN,         lambda: self.states.DESCRIPTION),

            (self.states.ADDING, self.events.TIMEOUT,        self._exit),
            (self.states.ADDING, self.events.GUIEVENT,       lambda: self.states.ADDING),
            (self.states.ADDING, self.events.KEYPRESS,       lambda: self.states.ADDING),
            (self.states.ADDING, self.events.SCAN,           lambda: self.states.ADDING),
            (self.states.ADDING, self.events.BADSCAN,        lambda: self.states.ADDING),
            
            (self.states.WARNING, self.events.TIMEOUT,       lambda: self.states.BARCODE),
            (self.states.WARNING, self.events.GUIEVENT,       lambda: self.states.WARNING),
            (self.states.WARNING, self.events.KEYPRESS,       lambda: self.states.WARNING),
            (self.states.WARNING, self.events.SCAN,           lambda: self.states.WARNING),
            (self.states.WARNING, self.events.BADSCAN,        lambda: self.states.WARNING),
        ])
예제 #4
0
class ProductEntry(Screen, ProductEntryGUI): #pylint: disable=R0902

    """ Implements the product entry screen
    pylint R0902 disabled as one 1 more attribute than recommened.
    Alternatives make code more complex than it needs to be for such a simple class"""
    
    def __init__(self, width, height, manager, owner):

        Screen.__init__(self, manager, owner, Screens.PRODUCTENTRY)
        ProductEntryGUI.__init__(self, width, height, self)

        self.states = Enum("BARCODE", "DESCRIPTION", "PRICE", "ADDING", "WARNING")
        self.events = Enum("GUIEVENT", "SCAN", "BADSCAN", "KEYPRESS", "TIMEOUT")

        self.barcode = ""
        self.price = 0
        self.description = ""
       
        self.sm = SimpleStateMachine(self.states.BARCODE, #pylint: disable=C0103
        [
            (self.states.BARCODE, self.events.GUIEVENT,      self._on_gui_event),
            (self.states.BARCODE, self.events.SCAN,          self._got_barcode),
            (self.states.BARCODE, self.events.BADSCAN,       self._got_new_barcode),
            (self.states.BARCODE, self.events.KEYPRESS,      lambda: self.states.BARCODE),

            (self.states.DESCRIPTION, self.events.GUIEVENT,  self._on_gui_event),
            (self.states.DESCRIPTION, self.events.KEYPRESS,  self._got_new_desc_char),
            (self.states.DESCRIPTION, self.events.SCAN,      lambda: self.states.DESCRIPTION),
            (self.states.DESCRIPTION, self.events.BADSCAN,   lambda: self.states.DESCRIPTION),

            (self.states.PRICE, self.events.GUIEVENT,        self._on_gui_event),
            (self.states.PRICE, self.events.KEYPRESS,        self._got_new_price_char),
            (self.states.PRICE, self.events.SCAN,            lambda: self.states.DESCRIPTION),
            (self.states.PRICE, self.events.BADSCAN,         lambda: self.states.DESCRIPTION),

            (self.states.ADDING, self.events.TIMEOUT,        self._exit),
            (self.states.ADDING, self.events.GUIEVENT,       lambda: self.states.ADDING),
            (self.states.ADDING, self.events.KEYPRESS,       lambda: self.states.ADDING),
            (self.states.ADDING, self.events.SCAN,           lambda: self.states.ADDING),
            (self.states.ADDING, self.events.BADSCAN,        lambda: self.states.ADDING),
            
            (self.states.WARNING, self.events.TIMEOUT,       lambda: self.states.BARCODE),
            (self.states.WARNING, self.events.GUIEVENT,       lambda: self.states.WARNING),
            (self.states.WARNING, self.events.KEYPRESS,       lambda: self.states.WARNING),
            (self.states.WARNING, self.events.SCAN,           lambda: self.states.WARNING),
            (self.states.WARNING, self.events.BADSCAN,        lambda: self.states.WARNING),
        ])

    def on_key_event(self, char):
        self.last_keypress = char
        self.sm.on_state_event(self.events.KEYPRESS)

    def on_gui_event(self, pos):
        self.last_gui_position = pos
        self.sm.on_state_event(self.events.GUIEVENT)

    def on_bad_scan(self, barcode):
        self.barcode = barcode
        self.sm.on_state_event(self.events.BADSCAN)

    def on_scan(self, __product):
        self.sm.on_state_event(self.events.SCAN)

    def _update_on_active(self):
        """ No need to change anything on active state """
        pass
    
    def on_rfid(self):
        """ Do nothing on RFID scans """
        pass
    
    def on_bad_rfid(self):
        """ Do nothing on RFID scans """
        pass
    
    def _on_gui_event(self):

        """ Move focus and state to pressed object """    
        pos = self.last_gui_position
        button = self.get_object_id(pos)
        next_state = self.sm.state 
        
        if button == self.buttons.BARCODE:
            self.barcode = ""
            self.set_active_entry(self.buttons.BARCODE)
            self._request_redraw()
            next_state = self.states.BARCODE

        if button == self.buttons.DESCRIPTION:
            self.description = ""
            self.set_active_entry(self.buttons.DESCRIPTION)
            self._request_redraw()
            next_state = self.states.DESCRIPTION

        if button == self.buttons.PRICE:
            self.price = 0
            self.set_active_entry(self.buttons.PRICE)
            self._request_redraw()
            next_state = self.states.PRICE

        if button == self.buttons.DONE:
            if self.data_ready():
                self.add_product()
                next_state = self.states.ADDING
            else:
                self.set_banner_with_timeout("One or more entries not valid!", 4, Colours.WARN, self._banner_timeout)
                self._request_redraw()
                next_state = self.states.WARNING

        if button == self.buttons.CANCEL:
            self._exit()
            next_state = self.states.BARCODE

        #No GUI object hit:
        return next_state

    def _got_new_desc_char(self):
        """ Update the description field on keypress """
        
        allow_desc_chars = " !$%^&*(){}[]:@~;'#<>?,./\\"
                
        if self.last_keypress.isalnum() or (self.last_keypress in allow_desc_chars):
            self.description += self.last_keypress
        elif self.last_keypress ==  "\b":
            self.description = self.description[:-1]

        self.change_description(self.description)
        self._request_redraw()
        return self.states.DESCRIPTION

    def _got_new_price_char(self):
        """ Update the price field on keypress """
        if self.last_keypress.isdigit():
            self.price *= 10
            self.price += int(self.last_keypress)
        elif self.last_keypress == "\b":
            self.price = int(self.price / 10)

        self.change_price(self.price)
        self._request_redraw()
        return self.states.PRICE

    def data_ready(self):
        """ Determine if all data is set """
        data_ready = len(self.barcode) > 0
        data_ready &= self.price > 0
        data_ready &= len(self.description) > 0
        return data_ready

    def add_product(self):
        """ Try to add the new product to the database """
        self.owner.new_product(self.barcode, self.description, self.price, self._add_product_callback)
    
    def _add_product_callback(self, barcode, description, result):
        """ Callback from database when adding product """
        if result:
            self.set_banner_with_timeout("New product %s added!" % description, 3, Colours.INFO, self._banner_timeout)
        else:
            self.set_banner_with_timeout("New product %s was not added!" % description, 3, Colours.WARN, self._banner_timeout)

        self._request_redraw()

        return self.states.ADDING
    
    def _got_new_barcode(self):
        """ On a new barcode scan, update the barcode field """
        self.change_barcode(self.barcode)
        self._request_redraw()
        return self.states.BARCODE

    def _got_barcode(self):
        """ If the barcode scanned exists, warn the user """
        self.set_banner_with_timeout("Barcode already exists!", 4, Colours.WARN, self._banner_timeout)
        self._request_redraw()
        return self.states.WARNING

    def _banner_timeout(self):
        """ Callback from GUI indicating banner has timed out """
        self.hide_banner()
        self._request_redraw()
        self.sm.on_state_event(self.events.TIMEOUT)

    def _request_redraw(self):
        """ Push a request for this screen to be drawn again """
        self.screen_manager.req(Screens.PRODUCTENTRY)

    def _exit(self):
        """ Request a return to the intro screen """
        self.reset_entry(self.buttons.BARCODE)
        self.reset_entry(self.buttons.PRICE)
        self.reset_entry(self.buttons.DESCRIPTION)
        
        self.screen_manager.req(Screens.INTROSCREEN)
        return self.states.BARCODE
예제 #5
0
class MainScreen(Screen, MainScreenGUI):

    """ Implements the main checkout screen """
    
    def __init__(self, width, height, manager, owner):
        Screen.__init__(self, manager, owner, Screens.MAINSCREEN)
        MainScreenGUI.__init__(self, width, height, self)
        
        self.states = Enum("IDLE", "CHARGING", "PAYMENTMSG", "WARNING")
        self.events = Enum("GUIEVENT", "SCAN", "BADSCAN", "RFID", "BADRFID", "CHARGED", "BANNERTIMEOUT")
        
        self.sm = SimpleStateMachine(self.states.IDLE, #pylint: disable=C0103
        [
                [self.states.IDLE,          self.events.GUIEVENT,       self._on_idle_gui_event],
                [self.states.IDLE,          self.events.SCAN,           self._on_idle_scan_event],
                [self.states.IDLE,          self.events.BADSCAN,        self._on_idle_bad_scan_event],
                [self.states.IDLE,          self.events.RFID,           self._on_rfid_event],
                [self.states.IDLE,          self.events.BADRFID,        self._on_bad_rfid_event],

                [self.states.PAYMENTMSG,    self.events.BANNERTIMEOUT,  self._return_to_intro],
                [self.states.PAYMENTMSG,    self.events.GUIEVENT,       lambda: self.states.PAYMENTMSG],
                [self.states.PAYMENTMSG,    self.events.SCAN,           lambda: self.states.PAYMENTMSG],
                [self.states.PAYMENTMSG,    self.events.BADSCAN,        lambda: self.states.PAYMENTMSG],
                [self.states.PAYMENTMSG,    self.events.RFID,           lambda: self.states.PAYMENTMSG],
                [self.states.PAYMENTMSG,    self.events.BADRFID,        lambda: self.states.PAYMENTMSG],
                
                [self.states.CHARGING,      self.events.CHARGED,        lambda: self.states.PAYMENTMSG],
                [self.states.CHARGING,      self.events.GUIEVENT,       lambda: self.states.CHARGING],
                [self.states.CHARGING,      self.events.SCAN,           lambda: self.states.CHARGING],
                [self.states.CHARGING,      self.events.BADSCAN,        lambda: self.states.CHARGING],
                [self.states.CHARGING,      self.events.RFID,           lambda: self.states.CHARGING],
                [self.states.CHARGING,      self.events.BADRFID,        lambda: self.states.CHARGING],
                
                [self.states.WARNING,       self.events.BANNERTIMEOUT,  self._remove_warning],
                [self.states.WARNING,       self.events.SCAN,           self._on_idle_scan_event],
                [self.states.WARNING,       self.events.BADSCAN,        self._update_barcode_warning],
                [self.states.WARNING,       self.events.RFID,           self._on_rfid_event],
                [self.states.WARNING,       self.events.BADRFID,        self._on_bad_rfid_event],
                [self.states.WARNING,       self.events.GUIEVENT,       self._remove_warning],

        ])

        self.logger = logging.getLogger("MainScreen")

        self.new_product = None
        self.badcode = ""
    
    
    def on_gui_event(self, pos):
        """ Called from owner to trigger a GUI press """
        if self.active:
            self.last_gui_position = pos
            self.sm.on_state_event(self.events.GUIEVENT)
            
    def clear_all(self):
        pass
    
    def on_scan(self, product):
        """ Called from owner to trigger a known product scan """
        self.new_product = product
        if self.active:
            self.sm.on_state_event(self.events.SCAN)

    def on_bad_scan(self, badcode):
        """ Called from owner to trigger an unknown product scan """
        if self.active:
            self.badcode = badcode
            self.sm.on_state_event(self.events.BADSCAN)

    def on_key_event(self, key):
        """ Called from owner to trigger keyboard press """
        pass

    def on_rfid(self):
        """ Called from owner to trigger a known RFID scan """
        if self.active:
            self.sm.on_state_event(self.events.RFID)

    def on_bad_rfid(self):
        """ Called from owner to trigger an unknown RFID scan """
        if self.active:
            self.sm.on_state_event(self.events.BADRFID)

    def user_allow_credit(self):
        """ Is the user allow to add credit to their account? """
        try:
            return self.user.creditAllowed()
        except AttributeError:
            return False

    def user_added_credit(self):
        """ Has the user added credit to their account? """
        return (self.user.Credit > 0)
    
    def total_price(self):
        """ Abstraction for total price """
        return self.owner.total_price()
    
    def _update_on_active(self):
        """ When active state changes, this is called to update screen """
        if self.user:
            self.set_user(self.user.name, self.user.balance, self.user.credit)
        else:
            self.set_unknown_user()

        for product in self.owner.products:
            self.on_scan(product)



    def _on_idle_gui_event(self):

        """ State machine function
        Runs when a GUI event has been triggered in idle
        """
        pos = self.last_gui_position
        button = self.get_object_id(pos)

        # Default to staying in same state
        next_state = self.sm.state

        if button > -1:
            self.play_sound()

        if (button == self.ids.DONE):
            next_state = self._charge_user()

        if (button == self.ids.CANCEL):
            next_state = self._return_to_intro()

        if (button == self.ids.PAY):
            self.screen_manager.req(Screens.NUMERICENTRY)

        if (button == self.ids.DOWN):
            self.product_displays.move_down()
            self._request_redraw()

        if (button == self.ids.UP):
            self.product_displays.move_up()
            self._request_redraw()

        if (button == self.ids.REMOVE):

            product = self.product_displays.get_at_position(pos)
            self.logger.info("Removing product %s" % product.barcode)

            if self.owner.remove_product(product) == 0:
                # No products of this type left in list
                self.product_displays.remove(product)

            self._request_redraw()

        return next_state

    def _on_idle_scan_event(self):
        """ State machine function
        Runs when a barcode scan has been triggered in idle
        """
        self.logger.info("Got barcode %s" % self.new_product.barcode)

        # Ensure that the warning banner goes away
        self.hide_banner()

        next_state = self.states.IDLE

        if self.user is not None:
            trans_allowed_state = self.user.transaction_allowed(self.new_product.price_in_pence)

            if trans_allowed_state == self.user.XACTION_ALLOWED:
                ## Add product, nothing else to do
                self.product_displays.add(self.new_product)
            elif trans_allowed_state == self.user.XACTION_OVERLIMIT:
                ## Add product, but also warn about being over credit limit
                self.product_displays.add(self.new_product)
                self.set_banner_with_timeout("Warning: you have reached your credit limit!", 4, Colours.WARN, self._banner_timeout)
                next_state = self.states.WARNING
            elif trans_allowed_state == self.user.XACTION_DENIED:
                ## Do not add the product to screen. Request removal from product list and warn user
                self.set_banner_with_timeout("Sorry, you have reached your credit limit!", 4, Colours.ERR, self._banner_timeout)
                self.owner.RemoveProduct(self.new_product)
                next_state = self.states.WARNING
        else:
            #Assume that the user is allowed to buy this
            self.product_displays.add(self.new_product)

        self._request_redraw()
        self.new_product = None

        return next_state

    def _on_idle_bad_scan_event(self):
        """ State machine function
        Runs when a bad barcode has been scanned in idle
        """
        self.logger.info("Got unrecognised barcode %s" % self.badcode)
        self.set_banner_with_timeout("Unknown barcode: '%s'" % self.badcode, 4, Colours.ERR, self._banner_timeout)
        self._request_redraw()
        self.badcode = ""

        return self.states.WARNING

    def _on_rfid_event(self):
        """ State machine function
        Runs when a known RFID card has been scanned in idle
        """
        self.logger.info("Got user %s" % self.user.name)
        self.hide_banner()
        self.set_user(self.user.name, self.user.balance, self.user.credit)
        self._request_redraw()

        return self.sm.state

    def _on_bad_rfid_event(self):
        """ State machine function
        Runs when an unknown RFID card has been scanned in idle
        """
        self.set_banner_with_timeout("Unknown RFID card!", 4, Colours.ERR, self._banner_timeout)
        self._request_redraw()
        return self.states.WARNING

    def _banner_timeout(self):
        """ Callback from GUI indicating banner has timed out """
        self.sm.on_state_event(self.events.BANNERTIMEOUT)

    def _update_barcode_warning(self):
        """ Show a warning about an unknown barcode """ 
        self.logger.info("Got unrecognised barcode %s" % self.badcode)
        self.set_banner_with_timeout("Unknown barcode: '%s'" % self.badcode, 4, Colours.ERR, self._banner_timeout)
        self._request_redraw()
        return self.states.WARNING

    def _remove_warning(self):
        """ Get rid of any banner """
        self.hide_banner()
        self._request_redraw()
        return self.states.IDLE

    def _return_to_intro(self):
        """ Request a return to the intro screen """
        self.hide_banner()
        
        ## Forget everything about the user and products
        self.clear_products()
        self.set_unknown_user()
        self.owner.forget_user()
        self.owner.forget_products()
        
        self.screen_manager.req(Screens.INTROSCREEN)
        return self.states.IDLE

    def _charge_user(self):

        """Request to charge the user and wait for reply """
        self.set_banner_with_timeout("Charging...", 0, Colours.INFO, None)
        
        self.owner.charge_all(self._charge_user_callback)
        self._request_redraw()
        return self.states.CHARGING
            
    def _charge_user_callback(self, total_price, credit, success):
        
        """ Show appropriate banner """
        
        amountinpounds = total_price / 100
        creditinpounds = credit / 100
        banner_width = 0.6
        
        if success:
            text = u"Thank you! You have been charged \xA3%.2f" % amountinpounds
            if creditinpounds > 0:
                text += u" and credited \xA3%.2f" % creditinpounds
                banner_width = 0.8

            self.set_banner_with_timeout(text, 8, Colours.INFO, self._banner_timeout)
            self.set_banner_width_fraction(banner_width)
        else:
            self.set_banner_with_timeout("An error occurred and has been logged.", 10, Colours.ERR, self._banner_timeout)
            self.logger.error("Failed to charge user %s %d pence")

        self._request_redraw()

        self.sm.on_state_event(self.events.CHARGED)
    
    def _request_redraw(self):
        """ Push a request for this screen to be drawn again """
        self.screen_manager.req(self.screen_id)
        
    @property
    def user(self):
        """ Easy access to the user """
        return self.owner.user
예제 #6
0
    def __init__(self, width, height, manager, owner):
        Screen.__init__(self, manager, owner, Screens.MAINSCREEN)
        MainScreenGUI.__init__(self, width, height, self)

        self.states = Enum("IDLE", "CHARGING", "PAYMENTMSG", "WARNING")
        self.events = Enum("GUIEVENT", "SCAN", "BADSCAN", "RFID", "BADRFID",
                           "CHARGED", "BANNERTIMEOUT")

        self.sm = SimpleStateMachine(
            self.states.IDLE,  #pylint: disable=C0103
            [
                [
                    self.states.IDLE, self.events.GUIEVENT,
                    self._on_idle_gui_event
                ],
                [self.states.IDLE, self.events.SCAN, self._on_idle_scan_event],
                [
                    self.states.IDLE, self.events.BADSCAN,
                    self._on_idle_bad_scan_event
                ],
                [self.states.IDLE, self.events.RFID, self._on_rfid_event],
                [
                    self.states.IDLE, self.events.BADRFID,
                    self._on_bad_rfid_event
                ],
                [
                    self.states.PAYMENTMSG, self.events.BANNERTIMEOUT,
                    self._return_to_intro
                ],
                [
                    self.states.PAYMENTMSG, self.events.GUIEVENT,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.PAYMENTMSG, self.events.SCAN,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.PAYMENTMSG, self.events.BADSCAN,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.PAYMENTMSG, self.events.RFID,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.PAYMENTMSG, self.events.BADRFID,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.CHARGING, self.events.CHARGED,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.CHARGING, self.events.GUIEVENT,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.CHARGING, self.events.SCAN,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.CHARGING, self.events.BADSCAN,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.CHARGING, self.events.RFID,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.CHARGING, self.events.BADRFID,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.WARNING, self.events.BANNERTIMEOUT,
                    self._remove_warning
                ],
                [
                    self.states.WARNING, self.events.SCAN,
                    self._on_idle_scan_event
                ],
                [
                    self.states.WARNING, self.events.BADSCAN,
                    self._update_barcode_warning
                ],
                [self.states.WARNING, self.events.RFID, self._on_rfid_event],
                [
                    self.states.WARNING, self.events.BADRFID,
                    self._on_bad_rfid_event
                ],
                [
                    self.states.WARNING, self.events.GUIEVENT,
                    self._remove_warning
                ],
            ])

        self.logger = logging.getLogger("MainScreen")

        self.new_product = None
        self.badcode = ""
예제 #7
0
class MainScreen(Screen, MainScreenGUI):
    """ Implements the main checkout screen """
    def __init__(self, width, height, manager, owner):
        Screen.__init__(self, manager, owner, Screens.MAINSCREEN)
        MainScreenGUI.__init__(self, width, height, self)

        self.states = Enum("IDLE", "CHARGING", "PAYMENTMSG", "WARNING")
        self.events = Enum("GUIEVENT", "SCAN", "BADSCAN", "RFID", "BADRFID",
                           "CHARGED", "BANNERTIMEOUT")

        self.sm = SimpleStateMachine(
            self.states.IDLE,  #pylint: disable=C0103
            [
                [
                    self.states.IDLE, self.events.GUIEVENT,
                    self._on_idle_gui_event
                ],
                [self.states.IDLE, self.events.SCAN, self._on_idle_scan_event],
                [
                    self.states.IDLE, self.events.BADSCAN,
                    self._on_idle_bad_scan_event
                ],
                [self.states.IDLE, self.events.RFID, self._on_rfid_event],
                [
                    self.states.IDLE, self.events.BADRFID,
                    self._on_bad_rfid_event
                ],
                [
                    self.states.PAYMENTMSG, self.events.BANNERTIMEOUT,
                    self._return_to_intro
                ],
                [
                    self.states.PAYMENTMSG, self.events.GUIEVENT,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.PAYMENTMSG, self.events.SCAN,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.PAYMENTMSG, self.events.BADSCAN,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.PAYMENTMSG, self.events.RFID,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.PAYMENTMSG, self.events.BADRFID,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.CHARGING, self.events.CHARGED,
                    lambda: self.states.PAYMENTMSG
                ],
                [
                    self.states.CHARGING, self.events.GUIEVENT,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.CHARGING, self.events.SCAN,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.CHARGING, self.events.BADSCAN,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.CHARGING, self.events.RFID,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.CHARGING, self.events.BADRFID,
                    lambda: self.states.CHARGING
                ],
                [
                    self.states.WARNING, self.events.BANNERTIMEOUT,
                    self._remove_warning
                ],
                [
                    self.states.WARNING, self.events.SCAN,
                    self._on_idle_scan_event
                ],
                [
                    self.states.WARNING, self.events.BADSCAN,
                    self._update_barcode_warning
                ],
                [self.states.WARNING, self.events.RFID, self._on_rfid_event],
                [
                    self.states.WARNING, self.events.BADRFID,
                    self._on_bad_rfid_event
                ],
                [
                    self.states.WARNING, self.events.GUIEVENT,
                    self._remove_warning
                ],
            ])

        self.logger = logging.getLogger("MainScreen")

        self.new_product = None
        self.badcode = ""

    def on_gui_event(self, pos):
        """ Called from owner to trigger a GUI press """
        if self.active:
            self.last_gui_position = pos
            self.sm.on_state_event(self.events.GUIEVENT)

    def clear_all(self):
        pass

    def on_scan(self, product):
        """ Called from owner to trigger a known product scan """
        self.new_product = product
        if self.active:
            self.sm.on_state_event(self.events.SCAN)

    def on_bad_scan(self, badcode):
        """ Called from owner to trigger an unknown product scan """
        if self.active:
            self.badcode = badcode
            self.sm.on_state_event(self.events.BADSCAN)

    def on_key_event(self, key):
        """ Called from owner to trigger keyboard press """
        pass

    def on_rfid(self):
        """ Called from owner to trigger a known RFID scan """
        if self.active:
            self.sm.on_state_event(self.events.RFID)

    def on_bad_rfid(self):
        """ Called from owner to trigger an unknown RFID scan """
        if self.active:
            self.sm.on_state_event(self.events.BADRFID)

    def user_allow_credit(self):
        """ Is the user allow to add credit to their account? """
        try:
            return self.user.creditAllowed()
        except AttributeError:
            return False

    def user_added_credit(self):
        """ Has the user added credit to their account? """
        return (self.user.Credit > 0)

    def total_price(self):
        """ Abstraction for total price """
        return self.owner.total_price()

    def _update_on_active(self):
        """ When active state changes, this is called to update screen """
        if self.user:
            self.set_user(self.user.name, self.user.balance, self.user.credit)
        else:
            self.set_unknown_user()

        for product in self.owner.products:
            self.on_scan(product)

    def _on_idle_gui_event(self):
        """ State machine function
        Runs when a GUI event has been triggered in idle
        """
        pos = self.last_gui_position
        button = self.get_object_id(pos)

        # Default to staying in same state
        next_state = self.sm.state

        if button > -1:
            self.play_sound()

        if (button == self.ids.DONE):
            next_state = self._charge_user()

        if (button == self.ids.CANCEL):
            next_state = self._return_to_intro()

        if (button == self.ids.PAY):
            self.screen_manager.req(Screens.NUMERICENTRY)

        if (button == self.ids.DOWN):
            self.product_displays.move_down()
            self._request_redraw()

        if (button == self.ids.UP):
            self.product_displays.move_up()
            self._request_redraw()

        if (button == self.ids.REMOVE):

            product = self.product_displays.get_at_position(pos)
            self.logger.info("Removing product %s" % product.barcode)

            if self.owner.remove_product(product) == 0:
                # No products of this type left in list
                self.product_displays.remove(product)

            self._request_redraw()

        return next_state

    def _on_idle_scan_event(self):
        """ State machine function
        Runs when a barcode scan has been triggered in idle
        """
        self.logger.info("Got barcode %s" % self.new_product.barcode)

        # Ensure that the warning banner goes away
        self.hide_banner()

        next_state = self.states.IDLE

        if self.user is not None:
            trans_allowed_state = self.user.transaction_allowed(
                self.new_product.price_in_pence)

            if trans_allowed_state == self.user.XACTION_ALLOWED:
                ## Add product, nothing else to do
                self.product_displays.add(self.new_product)
            elif trans_allowed_state == self.user.XACTION_OVERLIMIT:
                ## Add product, but also warn about being over credit limit
                self.product_displays.add(self.new_product)
                self.set_banner_with_timeout(
                    "Warning: you have reached your credit limit!", 4,
                    Colours.WARN, self._banner_timeout)
                next_state = self.states.WARNING
            elif trans_allowed_state == self.user.XACTION_DENIED:
                ## Do not add the product to screen. Request removal from product list and warn user
                self.set_banner_with_timeout(
                    "Sorry, you have reached your credit limit!", 4,
                    Colours.ERR, self._banner_timeout)
                self.owner.RemoveProduct(self.new_product)
                next_state = self.states.WARNING
        else:
            #Assume that the user is allowed to buy this
            self.product_displays.add(self.new_product)

        self._request_redraw()
        self.new_product = None

        return next_state

    def _on_idle_bad_scan_event(self):
        """ State machine function
        Runs when a bad barcode has been scanned in idle
        """
        self.logger.info("Got unrecognised barcode %s" % self.badcode)
        self.set_banner_with_timeout("Unknown barcode: '%s'" % self.badcode, 4,
                                     Colours.ERR, self._banner_timeout)
        self._request_redraw()
        self.badcode = ""

        return self.states.WARNING

    def _on_rfid_event(self):
        """ State machine function
        Runs when a known RFID card has been scanned in idle
        """
        self.logger.info("Got user %s" % self.user.name)
        self.hide_banner()
        self.set_user(self.user.name, self.user.balance, self.user.credit)
        self._request_redraw()

        return self.sm.state

    def _on_bad_rfid_event(self):
        """ State machine function
        Runs when an unknown RFID card has been scanned in idle
        """
        self.set_banner_with_timeout("Unknown RFID card!", 4, Colours.ERR,
                                     self._banner_timeout)
        self._request_redraw()
        return self.states.WARNING

    def _banner_timeout(self):
        """ Callback from GUI indicating banner has timed out """
        self.sm.on_state_event(self.events.BANNERTIMEOUT)

    def _update_barcode_warning(self):
        """ Show a warning about an unknown barcode """
        self.logger.info("Got unrecognised barcode %s" % self.badcode)
        self.set_banner_with_timeout("Unknown barcode: '%s'" % self.badcode, 4,
                                     Colours.ERR, self._banner_timeout)
        self._request_redraw()
        return self.states.WARNING

    def _remove_warning(self):
        """ Get rid of any banner """
        self.hide_banner()
        self._request_redraw()
        return self.states.IDLE

    def _return_to_intro(self):
        """ Request a return to the intro screen """
        self.hide_banner()

        ## Forget everything about the user and products
        self.clear_products()
        self.set_unknown_user()
        self.owner.forget_user()
        self.owner.forget_products()

        self.screen_manager.req(Screens.INTROSCREEN)
        return self.states.IDLE

    def _charge_user(self):
        """Request to charge the user and wait for reply """
        self.set_banner_with_timeout("Charging...", 0, Colours.INFO, None)

        self.owner.charge_all(self._charge_user_callback)
        self._request_redraw()
        return self.states.CHARGING

    def _charge_user_callback(self, total_price, credit, success):
        """ Show appropriate banner """

        amountinpounds = total_price / 100
        creditinpounds = credit / 100
        banner_width = 0.6

        if success:
            text = u"Thank you! You have been charged \xA3%.2f" % amountinpounds
            if creditinpounds > 0:
                text += u" and credited \xA3%.2f" % creditinpounds
                banner_width = 0.8

            self.set_banner_with_timeout(text, 8, Colours.INFO,
                                         self._banner_timeout)
            self.set_banner_width_fraction(banner_width)
        else:
            self.set_banner_with_timeout(
                "An error occurred and has been logged.", 10, Colours.ERR,
                self._banner_timeout)
            self.logger.error("Failed to charge user %s %d pence")

        self._request_redraw()

        self.sm.on_state_event(self.events.CHARGED)

    def _request_redraw(self):
        """ Push a request for this screen to be drawn again """
        self.screen_manager.req(self.screen_id)

    @property
    def user(self):
        """ Easy access to the user """
        return self.owner.user
예제 #8
0
class ProductEntry(Screen, ProductEntryGUI):  #pylint: disable=R0902
    """ Implements the product entry screen
    pylint R0902 disabled as one 1 more attribute than recommened.
    Alternatives make code more complex than it needs to be for such a simple class"""
    def __init__(self, width, height, manager, owner):

        Screen.__init__(self, manager, owner, Screens.PRODUCTENTRY)
        ProductEntryGUI.__init__(self, width, height, self)

        self.states = Enum("BARCODE", "DESCRIPTION", "PRICE", "ADDING",
                           "WARNING")
        self.events = Enum("GUIEVENT", "SCAN", "BADSCAN", "KEYPRESS",
                           "TIMEOUT")

        self.barcode = ""
        self.price = 0
        self.description = ""

        self.sm = SimpleStateMachine(
            self.states.BARCODE,  #pylint: disable=C0103
            [
                (self.states.BARCODE, self.events.GUIEVENT,
                 self._on_gui_event),
                (self.states.BARCODE, self.events.SCAN, self._got_barcode),
                (self.states.BARCODE, self.events.BADSCAN,
                 self._got_new_barcode),
                (self.states.BARCODE, self.events.KEYPRESS,
                 lambda: self.states.BARCODE),
                (self.states.DESCRIPTION, self.events.GUIEVENT,
                 self._on_gui_event),
                (self.states.DESCRIPTION, self.events.KEYPRESS,
                 self._got_new_desc_char),
                (self.states.DESCRIPTION, self.events.SCAN,
                 lambda: self.states.DESCRIPTION),
                (self.states.DESCRIPTION, self.events.BADSCAN,
                 lambda: self.states.DESCRIPTION),
                (self.states.PRICE, self.events.GUIEVENT, self._on_gui_event),
                (self.states.PRICE, self.events.KEYPRESS,
                 self._got_new_price_char),
                (self.states.PRICE, self.events.SCAN,
                 lambda: self.states.DESCRIPTION),
                (self.states.PRICE, self.events.BADSCAN,
                 lambda: self.states.DESCRIPTION),
                (self.states.ADDING, self.events.TIMEOUT, self._exit),
                (self.states.ADDING, self.events.GUIEVENT,
                 lambda: self.states.ADDING),
                (self.states.ADDING, self.events.KEYPRESS,
                 lambda: self.states.ADDING),
                (self.states.ADDING, self.events.SCAN,
                 lambda: self.states.ADDING),
                (self.states.ADDING, self.events.BADSCAN,
                 lambda: self.states.ADDING),
                (self.states.WARNING, self.events.TIMEOUT,
                 lambda: self.states.BARCODE),
                (self.states.WARNING, self.events.GUIEVENT,
                 lambda: self.states.WARNING),
                (self.states.WARNING, self.events.KEYPRESS,
                 lambda: self.states.WARNING),
                (self.states.WARNING, self.events.SCAN,
                 lambda: self.states.WARNING),
                (self.states.WARNING, self.events.BADSCAN,
                 lambda: self.states.WARNING),
            ])

    def on_key_event(self, char):
        self.last_keypress = char
        self.sm.on_state_event(self.events.KEYPRESS)

    def on_gui_event(self, pos):
        self.last_gui_position = pos
        self.sm.on_state_event(self.events.GUIEVENT)

    def on_bad_scan(self, barcode):
        self.barcode = barcode
        self.sm.on_state_event(self.events.BADSCAN)

    def on_scan(self, __product):
        self.sm.on_state_event(self.events.SCAN)

    def _update_on_active(self):
        """ No need to change anything on active state """
        pass

    def on_rfid(self):
        """ Do nothing on RFID scans """
        pass

    def on_bad_rfid(self):
        """ Do nothing on RFID scans """
        pass

    def _on_gui_event(self):
        """ Move focus and state to pressed object """
        pos = self.last_gui_position
        button = self.get_object_id(pos)
        next_state = self.sm.state

        if button == self.buttons.BARCODE:
            self.barcode = ""
            self.set_active_entry(self.buttons.BARCODE)
            self._request_redraw()
            next_state = self.states.BARCODE

        if button == self.buttons.DESCRIPTION:
            self.description = ""
            self.set_active_entry(self.buttons.DESCRIPTION)
            self._request_redraw()
            next_state = self.states.DESCRIPTION

        if button == self.buttons.PRICE:
            self.price = 0
            self.set_active_entry(self.buttons.PRICE)
            self._request_redraw()
            next_state = self.states.PRICE

        if button == self.buttons.DONE:
            if self.data_ready():
                self.add_product()
                next_state = self.states.ADDING
            else:
                self.set_banner_with_timeout("One or more entries not valid!",
                                             4, Colours.WARN,
                                             self._banner_timeout)
                self._request_redraw()
                next_state = self.states.WARNING

        if button == self.buttons.CANCEL:
            self._exit()
            next_state = self.states.BARCODE

        #No GUI object hit:
        return next_state

    def _got_new_desc_char(self):
        """ Update the description field on keypress """

        allow_desc_chars = " !$%^&*(){}[]:@~;'#<>?,./\\"

        if self.last_keypress.isalnum() or (self.last_keypress
                                            in allow_desc_chars):
            self.description += self.last_keypress
        elif self.last_keypress == "\b":
            self.description = self.description[:-1]

        self.change_description(self.description)
        self._request_redraw()
        return self.states.DESCRIPTION

    def _got_new_price_char(self):
        """ Update the price field on keypress """
        if self.last_keypress.isdigit():
            self.price *= 10
            self.price += int(self.last_keypress)
        elif self.last_keypress == "\b":
            self.price = int(self.price / 10)

        self.change_price(self.price)
        self._request_redraw()
        return self.states.PRICE

    def data_ready(self):
        """ Determine if all data is set """
        data_ready = len(self.barcode) > 0
        data_ready &= self.price > 0
        data_ready &= len(self.description) > 0
        return data_ready

    def add_product(self):
        """ Try to add the new product to the database """
        self.owner.new_product(self.barcode, self.description, self.price,
                               self._add_product_callback)

    def _add_product_callback(self, barcode, description, result):
        """ Callback from database when adding product """
        if result:
            self.set_banner_with_timeout("New product %s added!" % description,
                                         3, Colours.INFO, self._banner_timeout)
        else:
            self.set_banner_with_timeout(
                "New product %s was not added!" % description, 3, Colours.WARN,
                self._banner_timeout)

        self._request_redraw()

        return self.states.ADDING

    def _got_new_barcode(self):
        """ On a new barcode scan, update the barcode field """
        self.change_barcode(self.barcode)
        self._request_redraw()
        return self.states.BARCODE

    def _got_barcode(self):
        """ If the barcode scanned exists, warn the user """
        self.set_banner_with_timeout("Barcode already exists!", 4,
                                     Colours.WARN, self._banner_timeout)
        self._request_redraw()
        return self.states.WARNING

    def _banner_timeout(self):
        """ Callback from GUI indicating banner has timed out """
        self.hide_banner()
        self._request_redraw()
        self.sm.on_state_event(self.events.TIMEOUT)

    def _request_redraw(self):
        """ Push a request for this screen to be drawn again """
        self.screen_manager.req(Screens.PRODUCTENTRY)

    def _exit(self):
        """ Request a return to the intro screen """
        self.reset_entry(self.buttons.BARCODE)
        self.reset_entry(self.buttons.PRICE)
        self.reset_entry(self.buttons.DESCRIPTION)

        self.screen_manager.req(Screens.INTROSCREEN)
        return self.states.BARCODE