(self, new_state = None)
Roomba progresses through states (phases), current identified states
        are:
        ""              : program started up, no state yet
        "run"           : running on a Cleaning Mission
        "hmUsrDock"     : returning to Dock
        "hmMidMsn"      : need to recharge
        "hmPostMsn"     : mission completed
        "charge"        : charging
        "stuck"         : Roomba is stuck
        "stop"          : Stopped
        "pause"         : paused
        "evac"          : emptying bin
        "chargingerror" : charging base is unplugged

        available states:
        states = {"charge"          : "Charging",
                  "new"             : "New Mission",
                  "run"             : "Running",
                  "resume"          : "Running",
                  "hmMidMsn"        : "Docking",
                  "recharge"        : "Recharging",
                  "stuck"           : "Stuck",
                  "hmUsrDock"       : "User Docking",
                  "completed"       : "Mission Completed",
                  "cancelled"       : "Cancelled",
                  "stop"            : "Stopped",
                  "pause"           : "Paused",
                  "evac"            : "Emptying",
                  "hmPostMsn"       : "Docking - End Mission",
                  "chargingerror"   : "Base Unplugged",
                  ""                :  None}

        Normal Sequence is "" -> charge -> run -> hmPostMsn -> charge
        Mid mission recharge is "" -> charge -> run -> hmMidMsn -> charge
                                   -> run -> hmPostMsn -> charge
        Stuck is "" -> charge -> run -> hmPostMsn -> stuck
                    -> run/charge/stop/hmUsrDock -> charge
        Start program during run is "" -> run -> hmPostMsn -> charge
        Note: Braava M6 goes run -> hmPostMsn -> run -> charge when docking
        Note: S9+ goes run -> hmPostMsn -> charge -> run -> charge on a training mission (ie cleanMissionStatus_cycle = 'train')
        Note: The first 3 "pose" (x, y) co-ordinate in the first 10 seconds during undocking at mission start seem to be wrong
              for example, during undocking:
              {"x": 0, "y": 0},
              {"x": -49, "y": 0},
              {"x": -47, "y": 0},
              {"x": -75, "y": -11}... then suddenly becomes normal co-ordinates
              {"x": -22, "y": 131}
              {"x": -91, "y": 211}
              also during "hmPostMsn","hmMidMsn", "hmUsrDock" the co-ordinates system also seems to change to bogus values
              For example, in "run" phase, co-ordinates are reported as:
              {"x": -324, "y": 824},
              {"x": -324, "y": 826} ... etc, then some time after hmMidMsn (for example) they change to:
              {"x": 417, "y": -787}, which continues for a while
              {"x": 498, "y": -679}, and then suddenly changes back to normal co-ordinates
              {"x": -348, "y": 787},
              {"x": -161, "y": 181},
              {"x": 0, "y": 0}
              
              For now use self.distance_betwwen() to ignore large changes in position

        Need to identify a new mission to initialize map, and end of mission to
        finalise map.
        mission goes from 'none' to 'clean' (or another mission name) at start of mission (init map)
        mission goes from 'clean' (or other mission) to 'none' at end of missions (finalize map)
        Anything else = continue with existing map
Roomba progresses through states (phases), current identified states
        are:
        ""              : program started up, no state yet
        "run"           : running on a Cleaning Mission
        "hmUsrDock"     : returning to Dock
        "hmMidMsn"      : need to recharge
        "hmPostMsn"     : mission completed
        "charge"        : charging
        "stuck"         : Roomba is stuck
        "stop"          : Stopped
        "pause"         : paused
        "evac"          : emptying bin
        "chargingerror" : charging base is unplugged
Roomba progresses through states ( phases ) current identified states are : : program started up no state yet run : running on a Cleaning Mission hmUsrDock : returning to Dock hmMidMsn : need to recharge hmPostMsn : mission completed charge : charging stuck : Roomba is stuck stop : Stopped pause : paused evac : emptying bin chargingerror : charging base is unplugged
def update_state_machine(self, new_state = None):
        '''
        Roomba progresses through states (phases), current identified states
        are:
        ""              : program started up, no state yet
        "run"           : running on a Cleaning Mission
        "hmUsrDock"     : returning to Dock
        "hmMidMsn"      : need to recharge
        "hmPostMsn"     : mission completed
        "charge"        : charging
        "stuck"         : Roomba is stuck
        "stop"          : Stopped
        "pause"         : paused
        "evac"          : emptying bin
        "chargingerror" : charging base is unplugged

        available states:
        states = {"charge"          : "Charging",
                  "new"             : "New Mission",
                  "run"             : "Running",
                  "resume"          : "Running",
                  "hmMidMsn"        : "Docking",
                  "recharge"        : "Recharging",
                  "stuck"           : "Stuck",
                  "hmUsrDock"       : "User Docking",
                  "completed"       : "Mission Completed",
                  "cancelled"       : "Cancelled",
                  "stop"            : "Stopped",
                  "pause"           : "Paused",
                  "evac"            : "Emptying",
                  "hmPostMsn"       : "Docking - End Mission",
                  "chargingerror"   : "Base Unplugged",
                  ""                :  None}

        Normal Sequence is "" -> charge -> run -> hmPostMsn -> charge
        Mid mission recharge is "" -> charge -> run -> hmMidMsn -> charge
                                   -> run -> hmPostMsn -> charge
        Stuck is "" -> charge -> run -> hmPostMsn -> stuck
                    -> run/charge/stop/hmUsrDock -> charge
        Start program during run is "" -> run -> hmPostMsn -> charge
        Note: Braava M6 goes run -> hmPostMsn -> run -> charge when docking
        Note: S9+ goes run -> hmPostMsn -> charge -> run -> charge on a training mission (ie cleanMissionStatus_cycle = 'train')
        Note: The first 3 "pose" (x, y) co-ordinate in the first 10 seconds during undocking at mission start seem to be wrong
              for example, during undocking:
              {"x": 0, "y": 0},
              {"x": -49, "y": 0},
              {"x": -47, "y": 0},
              {"x": -75, "y": -11}... then suddenly becomes normal co-ordinates
              {"x": -22, "y": 131}
              {"x": -91, "y": 211}
              also during "hmPostMsn","hmMidMsn", "hmUsrDock" the co-ordinates system also seems to change to bogus values
              For example, in "run" phase, co-ordinates are reported as:
              {"x": -324, "y": 824},
              {"x": -324, "y": 826} ... etc, then some time after hmMidMsn (for example) they change to:
              {"x": 417, "y": -787}, which continues for a while
              {"x": 498, "y": -679}, and then suddenly changes back to normal co-ordinates
              {"x": -348, "y": 787},
              {"x": -161, "y": 181},
              {"x": 0, "y": 0}
              
              For now use self.distance_betwwen() to ignore large changes in position

        Need to identify a new mission to initialize map, and end of mission to
        finalise map.
        mission goes from 'none' to 'clean' (or another mission name) at start of mission (init map)
        mission goes from 'clean' (or other mission) to 'none' at end of missions (finalize map)
        Anything else = continue with existing map
        '''
        if new_state is not None:
            self.current_state = self.states[new_state]
            self.log.info("set current state to: {}".format(self.current_state))
            self.draw_map(True)
            return    
            
        self.publish_error_message()                #publish error messages
        self.update_precent_complete()
        mission = self.update_history("cycle")      #mission
        phase = self.update_history("phase")        #mission phase
        self.update_history("pose")                 #update co-ordinates
        
        if self.cb is not None:                     #call callback if set
            self.cb(self.master_state)
        
        if phase is None or mission is None:
            return
        
        current_mission = self.current_state
        
        if self.debug:
            self.timer('ignore_coordinates')
            current_mission = None  #force update of map

        self.log.info('current_state: {}, current phase: {}, mission: {}, mission_min: {}, recharge_min: {}, co-ords changed: {}'.format(self.current_state,
                                                                                                                    phase,
                                                                                                                    mission,
                                                                                                                    self.mssnM,
                                                                                                                    self.rechrgM,
                                                                                                                    self.changed('pose')))

        if phase == "charge":
            #self.set_history('pose', self.zero_pose())
            current_mission = None
            
        if self.current_state == self.states["new"] and phase != 'run':
            self.log.info('waiting for run state for New Missions')
            if time.time() - self.timers['start'] >= 20:
                self.log.warning('Timeout waiting for run state')
                self.current_state = self.states[phase]

        elif phase == "run" and (self.is_set('ignore_run') or mission == 'none'):
            self.log.info('Ignoring bogus run state')
            
        elif phase == "charge" and mission == 'none' and self.is_set('ignore_run'):
            self.log.info('Ignoring bogus charge/mission state')
            self.update_history("cycle", self.previous('cycle'))
            
        elif phase in ["hmPostMsn","hmMidMsn", "hmUsrDock"]:
            self.timer('ignore_run', True, 10)
            self.current_state = self.states[phase]
            
        elif self.changed('cycle'): #if mission has changed
            if mission != 'none':
                self.current_state = self.states["new"]
                self.timers['start'] = time.time()
                if isinstance(self.sku, str) and self.sku[0].lower() in ['i', 's', 'm']:
                    #self.timer('ignore_coordinates', True, 30)  #ignore updates for 30 seconds at start of new mission
                    pass
            else:
                self.timers.pop('start', None)
                if self.bin_full:
                    self.current_state = self.states["cancelled"]
                else:
                    self.current_state = self.states["completed"]
                self.timer('ignore_run', True, 5)  #still get bogus 'run' states after mission complete.
            
        elif phase == "charge" and self.rechrgM:
            if self.bin_full:
                self.current_state = self.states["pause"]
            else:
                self.current_state = self.states["recharge"]
            
        else:
            try:
                self.current_state = self.states[phase]
            except KeyError:
                self.log.warning('phase: {} not found in self.states'.format(phase))

        if self.current_state != current_mission:
            self.log.info("updated state to: {}".format(self.current_state))

        self.publish("state", self.current_state)
        
        if self.is_set('ignore_coordinates') and self.current_state != self.states["new"]:
            self.log.info('Ignoring co-ordinate updates')
        else:
            self.draw_map(current_mission != self.current_state)
def update_state_machine ( self , new_state = None ) : if new_state is not None : self . current_state = self . states [ new_state ] self . log . info ( "set current state to: {}" . format ( self . current_state ) ) self . draw_map ( True ) return self . publish_error_message ( ) #publish error messages self . update_precent_complete ( ) mission = self . update_history ( "cycle" ) #mission phase = self . update_history ( "phase" ) #mission phase self . update_history ( "pose" ) #update co-ordinates if self . cb is not None : #call callback if set self . cb ( self . master_state ) if phase is None or mission is None : return current_mission = self . current_state if self . debug : self . timer ( 'ignore_coordinates' ) current_mission = None #force update of map self . log . info ( 'current_state: {}, current phase: {}, mission: {}, mission_min: {}, recharge_min: {}, co-ords changed: {}' . format ( self . current_state , phase , mission , self . mssnM , self . rechrgM , self . changed ( 'pose' ) ) ) if phase == "charge" : #self.set_history('pose', self.zero_pose()) current_mission = None if self . current_state == self . states [ "new" ] and phase != 'run' : self . log . info ( 'waiting for run state for New Missions' ) if time . time ( ) - self . timers [ 'start' ] >= 20 : self . log . warning ( 'Timeout waiting for run state' ) self . current_state = self . states [ phase ] elif phase == "run" and ( self . is_set ( 'ignore_run' ) or mission == 'none' ) : self . log . info ( 'Ignoring bogus run state' ) elif phase == "charge" and mission == 'none' and self . is_set ( 'ignore_run' ) : self . log . info ( 'Ignoring bogus charge/mission state' ) self . update_history ( "cycle" , self . previous ( 'cycle' ) ) elif phase in [ "hmPostMsn" , "hmMidMsn" , "hmUsrDock" ] : self . timer ( 'ignore_run' , True , 10 ) self . current_state = self . states [ phase ] elif self . changed ( 'cycle' ) : #if mission has changed if mission != 'none' : self . current_state = self . states [ "new" ] self . timers [ 'start' ] = time . time ( ) if isinstance ( self . sku , str ) and self . sku [ 0 ] . lower ( ) in [ 'i' , 's' , 'm' ] : #self.timer('ignore_coordinates', True, 30)  #ignore updates for 30 seconds at start of new mission pass else : self . timers . pop ( 'start' , None ) if self . bin_full : self . current_state = self . states [ "cancelled" ] else : self . current_state = self . states [ "completed" ] self . timer ( 'ignore_run' , True , 5 ) #still get bogus 'run' states after mission complete. elif phase == "charge" and self . rechrgM : if self . bin_full : self . current_state = self . states [ "pause" ] else : self . current_state = self . states [ "recharge" ] else : try : self . current_state = self . states [ phase ] except KeyError : self . log . warning ( 'phase: {} not found in self.states' . format ( phase ) ) if self . current_state != current_mission : self . log . info ( "updated state to: {}" . format ( self . current_state ) ) self . publish ( "state" , self . current_state ) if self . is_set ( 'ignore_coordinates' ) and self . current_state != self . states [ "new" ] : self . log . info ( 'Ignoring co-ordinate updates' ) else : self . draw_map ( current_mission != self . current_state )