def liquid_level_test(self, dispenser, threshold): if app.globals.get_state() == fsm.STATE_ERROR: return if not app.options.use_liquid_level_sensors: return log.info("Start liquid level test: (disp %s thres: %d)" % (dispenser, threshold)) if not self.driver.update_liquid_levels(): raise BartendroBrokenError("Failed to update liquid levels") sleep(.01) level = self.driver.get_liquid_level(dispenser) log.info("initial reading: %d" % level) if level <= threshold: log.info("liquid is out before starting: %d" % level) return last = -1 self.driver.start(dispenser) while level > threshold: if not self.driver.update_liquid_levels(): raise BartendroBrokenError("Failed to update liquid levels") return sleep(.01) level = self.driver.get_liquid_level(dispenser) if level != last: log.info(" %d" % level) last = level self.driver.stop(dispenser) log.info("Stopped at level: %d" % level) sleep(.1) level = self.driver.get_liquid_level(dispenser) log.info("motor stopped at level: %d" % level)
def _state_pre_shot(self): if not app.options.use_liquid_level_sensors: return fsm.EVENT_LL_OK try: ll = self._check_liquid_levels() except BartendroLiquidLevelReadError: raise BartendroBrokenError("Failed to read liquid levels") booze_id = self.recipe.data.keys()[0] dispensers = db.session.query(Dispenser).order_by(Dispenser.id).all() for i, disp in enumerate(dispensers): if disp.booze_id == booze_id: if disp.out == LL_OUT: if ll == LL_OK: app.globals.set_state(fsm.STATE_OK) elif ll == LL_LOW: app.globals.set_state(fsm.STATE_LOW) elif ll == LL_OUT: app.globals.set_state(fsm.STATE_OUT) else: app.globals.set_state(fsm.STATE_HARD_OUT) raise BartendroCantPourError( "Cannot make drink: Dispenser %d is out of booze." % (i + 1)) break return fsm.EVENT_LL_OK
def sync(self, state): if self.software_only: return try: if (state): self._write_byte_with_retry(ROUTER_ADDRESS, ROUTER_CMD_SYNC_ON) else: self._write_byte_with_retry(ROUTER_ADDRESS, ROUTER_CMD_SYNC_OFF) except IOError: app.globals.set_state(fsm.STATE_ERROR) raise BartendroBrokenError("Cannot write to I2C interface.")
def _write_byte_with_retry(self, address, byte): try: self.router.write_byte(address, byte) except IOError, e: # if we get an error, try again, just once try: log.error("*** router send: error while sending. Retrying. " + repr(e)) self.router.write_byte(address, byte) except IOError: app.globals.set_state(fsm.STATE_ERROR) raise BartendroBrokenError("Cannot write to I2C interface.")
def open(self): '''Open the i2c connection to the router''' if self.software_only: return if smbus_missing: log.error("You must install the smbus module!") sys.exit(-1) log.info("Opening I2C bus to router") try: self.router = smbus.SMBus(ROUTER_BUS) except IOError: app.globals.set_state(fsm.STATE_ERROR) raise BartendroBrokenError("Cannot open I2C interface.") log.info("Done.")
def _dispense_recipe(self, recipe, always_fast=False): active_disp = [] for disp in recipe: if not recipe[disp]: continue ticks = int(recipe[disp] * TICKS_PER_ML) if recipe[disp] < SLOW_DISPENSE_THRESHOLD and not always_fast: speed = HALF_SPEED else: speed = FULL_SPEED self.driver.set_motor_direction(disp, MOTOR_DIRECTION_FORWARD) # todo: create dispense_ml, why do we care about 'ticks'? #if not self.driver.dispense_ticks(disp, ticks, speed): if not self.driver.dispense_ml(disp, ticks, speed): raise BartendroBrokenError( "Dispense error. Dispense %d ml, speed %d on dispenser %d failed." % (recipe[disp], speed, disp + 1)) active_disp.append(disp) sleep(.01) for disp in active_disp: while True: (is_dispensing, over_current) = app.driver.is_dispensing(disp) log.debug("is_disp %d, over_cur %d" % (is_dispensing, over_current)) # If we get errors here, try again. Running motors can cause noisy comm lines if is_dispensing < 0 or over_current < 0: log.error( "Is dispensing test on dispenser %d failed. Ignoring." % (disp + 1)) sleep(.2) continue if over_current: raise BartendroCurrentSenseError( "One of the pumps did not operate properly. Your drink is broken. Sorry. :(" ) if is_dispensing == 0: break sleep(.1)
def _state_pre_pour(self): try: ll = self._check_liquid_levels() except BartendroLiquidLevelReadError: raise BartendroBrokenError("Failed to read liquid levels") # update the list of drinks we can make drinks = self.get_available_drink_list() if len(drinks) == 0: raise BartendroCantPourError("Cannot make this drink now.") if ll == LL_OK: return fsm.EVENT_LL_OK if ll == LL_LOW: return fsm.EVENT_LL_LOW return LL_OUT
class Mixer(object): '''The mixer object is the heart of Bartendro. This is where the state of the bot is managed, checked if drinks can be made, and actually make drinks. Everything else in Bartendro lives for *this* *code*. :) ''' def __init__(self, driver, mc): if driver: self.driver = driver self.mc = mc self.disp_count = self.driver.count() self.do_event(fsm.EVENT_START) self.err = "" def check_levels(self): with BartendroLock(app.globals): self.do_event(fsm.EVENT_CHECK_LEVELS) def dispense_shot(self, dispenser, ml): r = Recipe() r.data = {dispenser.booze.id: ml} r.booze = dispenser.booze self.recipe = r with BartendroLock(app.globals): self.do_event(fsm.EVENT_MAKE_SHOT) t = int(time()) slog = ShotLog(dispenser.booze.id, t, ml) db.session.add(slog) db.session.commit() def dispense_ml(self, dispenser, ml): r = Recipe() r.data = {dispenser.booze.id: ml} r.booze = dispenser.booze self.recipe = r with BartendroLock(app.globals): self.do_event(fsm.EVENT_TEST_DISPENSE) def make_drink(self, drink, recipe): r = Recipe() r.data = recipe r.drink = drink self.recipe = r log.info("make_drink drink: %r reciple: %r " % (drink, recipe)) with BartendroLock(app.globals): self.do_event(fsm.EVENT_MAKE_DRINK) if drink and drink.id: size = 0 for k in recipe.keys(): size += recipe[k] t = int(time()) dlog = DrinkLog(drink.id, t, size) db.session.add(dlog) db.session.commit() def do_event(self, event): cur_state = app.globals.get_state() log.info("do_event event: %r " % (event)) while True: next_state = None for t_state, t_event, t_next_state in fsm.transition_table: if t_state == cur_state and event == t_event: next_state = t_next_state log.info("do_event next_state: %r " % (next_state)) break if not next_state: log.error("Current state %d, event %d. No next state." % (cur_state, event)) raise BartendroBrokenError( "Bartendro is unable to pour drinks right now. Sorry.") #print "cur state: %d event: %d next state: %d" % (cur_state, event, next_state) try: if next_state == fsm.STATE_PRE_POUR: event = self._state_pre_pour() elif next_state == fsm.STATE_CHECK: event = self._state_check() elif next_state == fsm.STATE_PRE_SHOT: event = self._state_pre_shot() elif next_state == fsm.STATE_READY: event = self._state_ready() elif next_state == fsm.STATE_LOW: event = self._state_low() elif next_state == fsm.STATE_OUT: event = self._state_out() elif next_state == fsm.STATE_HARD_OUT: event = self._state_hard_out() elif next_state == fsm.STATE_POURING or next_state == fsm.STATE_POUR_SHOT: event = self._state_pouring() elif next_state == fsm.STATE_POUR_DONE: event = self._state_pour_done() elif next_state == fsm.STATE_CURRENT_SENSE: event = self._state_current_sense() elif next_state == fsm.STATE_ERROR: event = self._state_error() elif next_state == fsm.STATE_TEST_DISPENSE: event = self._state_test_dispense() else: self._state_error() app.globals.set_state(fsm.STATE_ERROR) log.error( "Current state: %d, event %d. Can't find next state." % (cur_state, event)) raise BartendroBrokenError( "Internal error. Bartendro has had one too many.") except BartendroBrokenError, err: exc_type, exc_value, exc_traceback = sys.exc_info() # traceback.print_tb(exc_traceback) self._state_error() app.globals.set_state(fsm.STATE_ERROR) raise except BartendroCantPourError, err: exc_type, exc_value, exc_traceback = sys.exc_info() # traceback.print_tb(exc_traceback) raise except BartendroCurrentSenseError, err: exc_type, exc_value, exc_traceback = sys.exc_info() # traceback.print_tb(exc_traceback) raise BartendroBrokenError(err)
def do_event(self, event): cur_state = app.globals.get_state() log.info("do_event event: %r " % (event)) while True: next_state = None for t_state, t_event, t_next_state in fsm.transition_table: if t_state == cur_state and event == t_event: next_state = t_next_state log.info("do_event next_state: %r " % (next_state)) break if not next_state: log.error("Current state %d, event %d. No next state." % (cur_state, event)) raise BartendroBrokenError( "Bartendro is unable to pour drinks right now. Sorry.") #print "cur state: %d event: %d next state: %d" % (cur_state, event, next_state) try: if next_state == fsm.STATE_PRE_POUR: event = self._state_pre_pour() elif next_state == fsm.STATE_CHECK: event = self._state_check() elif next_state == fsm.STATE_PRE_SHOT: event = self._state_pre_shot() elif next_state == fsm.STATE_READY: event = self._state_ready() elif next_state == fsm.STATE_LOW: event = self._state_low() elif next_state == fsm.STATE_OUT: event = self._state_out() elif next_state == fsm.STATE_HARD_OUT: event = self._state_hard_out() elif next_state == fsm.STATE_POURING or next_state == fsm.STATE_POUR_SHOT: event = self._state_pouring() elif next_state == fsm.STATE_POUR_DONE: event = self._state_pour_done() elif next_state == fsm.STATE_CURRENT_SENSE: event = self._state_current_sense() elif next_state == fsm.STATE_ERROR: event = self._state_error() elif next_state == fsm.STATE_TEST_DISPENSE: event = self._state_test_dispense() else: self._state_error() app.globals.set_state(fsm.STATE_ERROR) log.error( "Current state: %d, event %d. Can't find next state." % (cur_state, event)) raise BartendroBrokenError( "Internal error. Bartendro has had one too many.") except BartendroBrokenError, err: exc_type, exc_value, exc_traceback = sys.exc_info() # traceback.print_tb(exc_traceback) self._state_error() app.globals.set_state(fsm.STATE_ERROR) raise except BartendroCantPourError, err: exc_type, exc_value, exc_traceback = sys.exc_info() # traceback.print_tb(exc_traceback) raise