Esempio n. 1
0
class DiffDriveManipulatorRobot(object):
    """
    DiffDrive main robot class. Launching a few threads to handle mechanics
    and odometry.
    """

    __slots__ = [
        '_urgentStop',
        'timeStep',
        'logger',
        '_current_V',
        'baseMovementLock',
        'armMovementLock',
        'powerSupply',
        'eventLoop',
        'eventLoopExecutors',
        'eventLoopArmExecutors',
        'eventLoopMainExecutors',
        'eventLoopBaseExecutors',
        'sensors',
        'gyroSensorConfiguration',
        'robotConfiguration',
        'arm',
        'base',
        'mqtt_topics',
        'mqtt_connect_mid',
        'threadOdometry',
        'eventLoopThread',
        'threadMQTT',
        'mqttMoveQueue',
        'mqttClient',
        'exceptionQueue',
        'asyncVoltageCheckLoopTask',
        'asyncOdometryReportingTask',
        'M0e',
        'Toe_grip',
        'Tbc',
        'Tcb',
        'asyncImplMoveLoopTask'
    ]

    def __init__(self,
                 robot_config_file,
                 initRobotConfig,
                 timeStep,
                 odometryTimer=0.1,
                 defaultLogging=30):
        """
        Creates the main bot class.
        """
        if not os.path.exists(robot_config_file):
            raise ValueError(f'Cannot find configuration file '
                             f'"{robot_config_file}"')

        with open(robot_config_file, 'r') as f:
            self.robotConfiguration = json.load(f)

        # Adjust JSON just read
        self.__adjustConfigDict(self.robotConfiguration)

        # Exception Queue for inter threads exception communications
        self.exceptionQueue = queue.Queue()
        self.mqttMoveQueue = queue.Queue()
        self.baseMovementLock = asyncio.locks.Lock()
        self.timeStep = timeStep
        self.powerSupply = PowerSupply()

        # region sensor Init
        self.sensors = {}
        for sensor in self.robotConfiguration["sensors"]:
            sensorDict = self.robotConfiguration["sensors"][sensor]
            legoPort = LegoPort(sensorDict["input"])
            # Applies to both I2C and SDK controled sensors
            if legoPort.mode != sensorDict["mode"]:
                legoPort.mode = sensorDict["mode"]
            # Only applies to sensors controled by the python SDK
            if "set_device" in sensorDict:
                legoPort.set_device = sensorDict["set_device"]
                time.sleep(1)
            address = sensorDict["input"]
            self.sensors[sensor] = sensorDict["sensorType"](address=address)
        # endregion

        # base Init
        self.base = DiffDriveBase(self.robotConfiguration["base"],
                                  initRobotConfig, odometryTimer)

        # arm/endeffector Init
        self.arm = Arm(armLinkDefinitions=self.robotConfiguration["arm"])

        # Other matrix init
        self.M0e = mr.RpToTrans(
            R=np.array(
                self.robotConfiguration["arm"]["arm1"]["manipulatorConfiguration"]["M0e"]["rotationMatrix"]).T,   # noqa F501
            p=self.robotConfiguration["arm"]["arm1"]["manipulatorConfiguration"]["M0e"]["p"])   # noqa F501
        self.Toe_grip = mr.RpToTrans(
            R=np.array(
                self.robotConfiguration["arm"]["arm1"]["manipulatorConfiguration"]["Toe_grip"]["rotationMatrix"]).T,   # noqa F501
            p=self.robotConfiguration["arm"]["arm1"]["manipulatorConfiguration"]["Toe_grip"]["p"])    # noqa F501
        self.Tbc = mr.RpToTrans(
            R=np.array(
                self.robotConfiguration["cameraConfiguration"]["Tbc"]["rotationMatrix"]).T,   # noqa F501
            p=self.robotConfiguration["cameraConfiguration"]["Tbc"]["p"])
        self.Tcb = mr.TransInv(self.Tbc)

        # Reset all motors at init
        self.urgentStop = False
        log.info(LOGGER_DDR_MAIN, 'Done main robot init.')
        # log.setLevel(LOGGER_DDR_MAIN)

    def __del__(self):
        """ Destructor """
        loggerName = 'ddrmain'
        log.info(loggerName,
                 msg=f'Deleting robot ; opening gripper '
                     f'and shutting down motors.')
        # Open the gripper
        self.arm.openEndEffector()
        # Shut down the motors
        self.urgentStop = True
        # Clear the event loop and shut it down
        self.asyncVoltageCheckLoopTask.cancel()
        self.asyncImplMoveLoopTask.cancel()
        # Cancel running tasks in the event loop
        if self.eventLoop.is_running():
            self.eventLoop.stop()
        # Stop threads
        self.base.stopThreadOdometry()
        # Will release motors that are on hold before shutting down
        self.base.resetBodyConfiguration()
        # Stop the mqtt client loop
        self.mqttClient.loop_stop()
        self.arm.killThread = True
        self.arm.openEndEffector()
        # Unload the drivers for sensors
        for sensor in self.robotConfiguration["sensors"]:
            LegoPort(
                self.robotConfiguration["sensors"][sensor]['input']).mode = \
                'none'
        log.debug(loggerName, 'Done the __del__ function')
        # Unload the sensor drivers

    # region properties
    @property
    def currentVoltage(self):
        return self.powerSupply.measured_volts

    @property
    def urgentStop(self):
        return self._urgentStop

    @urgentStop.setter
    def urgentStop(self, value):
        if value is True:
            log.info(LOGGER_DDR_MAIN, "Need to immediately stop the bot.")
            self.mqttMoveQueue = queue.Queue()
        self._urgentStop = value
        if self.base:
            self.base.urgentStop = value
        if self.arm:
            self.arm.urgentStop = value

    @property           # should stay on main robot
    def currentConfigVector(self):
        return np.r_[self.base.currentConfigVector,
                     self.arm.currentConfigVector,
                     self.arm.endEffectorStatus]
    # endregion

    def run(self):
        """
        run the main event loop for the robot and the required threads
        """
        try:
            # region Start Base Odometry Thread Init
            self.base.run()
            # endregion

            # region MQTTConnect Thread Init
            self.threadMQTT = threading.Thread(
                target=self.threadImplMQTT,
                name="threadImplMQTT")
            self.threadMQTT.start()
            # endregion

            # Launch main event loop in a separate thread
            self.eventLoopThread = threading.Thread(
                target=self.threadImplEventLoop,
                name="threadImplEventLoop")
            self.eventLoopThread.start()
            # Error handling for threads
            while True:
                try:
                    exc = self.exceptionQueue.get(block=True)
                except queue.Empty:
                    pass
                else:
                    log.error(LOGGER_DDR_RUN,
                              msg=f'Exception handled in one of the '
                                  f'spawned process : {exc}')
                    raise exc
                self.eventLoopThread.join(0.2)
                if self.eventLoopThread.isAlive():
                    continue
                else:
                    break
        except SystemExit:
            # raise the exception up the stack
            raise
        except:
            log.error(LOGGER_DDR_RUN, f'Error : {traceback.print_exc()}')

    def threadImplEventLoop(self):
        """
        Main event asyncio eventloop launched in a thread
        """
        try:
            log.warning(LOGGER_DDR_THREADIMPLEVENTLOOP,
                        msg=f'Launching main event loop.')
            self.eventLoop = asyncio.new_event_loop()
            self.eventLoopArmExecutors = \
                concurrent.futures.ThreadPoolExecutor(
                    max_workers=MAX_WORKER_THREADS,
                    thread_name_prefix='ArmThreadPool')
            self.eventLoopMainExecutors = \
                concurrent.futures.ThreadPoolExecutor(
                    max_workers=MAX_WORKER_THREADS,
                    thread_name_prefix='MainThreadPool')
            self.eventLoopBaseExecutors = \
                concurrent.futures.ThreadPoolExecutor(
                    max_workers=MAX_WORKER_THREADS,
                    thread_name_prefix='BaseThreadPool')
            self.asyncImplMoveLoopTask = \
                self.eventLoop.create_task(
                    self.asyncProcessMQTTMessages(0.25))
            self.asyncVoltageCheckLoopTask = \
                self.eventLoop.create_task(self.asyncVoltageCheckLoop(10))
            self.asyncOdometryReportingTask = \
                self.eventLoop.create_task(self.asyncOdometryReporting(1))
            self.eventLoop.run_in_executor(
                self.eventLoopMainExecutors,
                self.asyncExecutorImplKillSwitch)
            self.eventLoop.run_forever()
        except:
            log.error(LOGGER_DDR_THREADIMPLEVENTLOOP,
                      msg=f'Error : {traceback.print_exc()}')
        finally:
            self.eventLoop.stop()
            self.eventLoop.close()

    async def asyncProcessMQTTMessages(self, loopDelay):
        """
        This function receives the messages from MQTT to move the robot.
        It is implemented in another thread so that the killswitch can kill
        the thread without knowing which specific
        """
        while True:
            try:
                await asyncio.sleep(loopDelay)
                if self.mqttMoveQueue.empty() is False:
                    try:
                        if self.currentVoltage < LOW_VOLTAGE_THRESHOLD:
                            raise LowVoltageException
                    except LowVoltageException:
                        log.warning(LOGGER_DDR_THREADIMPLMQTT,
                                    msg=f'Low voltage threshold '
                                        f'{float(LOW_VOLTAGE_THRESHOLD):.2} '
                                        f'reached. Current voltage = '
                                        f'{self.currentVoltage:.2f} .'
                                        f'Robot will not move, but MQTT '
                                        f'message will attempt to process. '
                                        f'Consider charging the battery, '
                                        f'or find some kind of power supply')
                    # Remove the first in the list, will pause until there
                    # is something
                    currentMQTTMoveMessage = self.mqttMoveQueue.get()
                    # Decode message received
                    msgdict = json.loads(
                        currentMQTTMoveMessage.payload.decode('utf-8'))
                    # Verify if need to urgently overried current movement
                    # and queue
                    if 'override' in msgdict:
                        if msgdict['override']:
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Overriding all motion - calling '
                                          f'killswitch to empty queue')
                            # Urgently stop the current movement (and empty
                            # movement queue - done in killSwitch)
                            self.killSwitch()

                    # Default motion configuration
                    pid = False if 'pid' not in msgdict else msgdict["pid"]
                    velocity_factor = 0.1 if 'velocity_factor' not \
                        in msgdict else msgdict["velocity_factor"]
                    dryrun = False if 'dryrun' not in msgdict else \
                        msgdict["dryrun"]
                    inter_wheel_distance = None if 'inter_wheel_distance' not\
                        in msgdict else msgdict["inter_wheel_distance"]
                    wheel_radius = None if 'wheel_radius' not in msgdict \
                        else msgdict["wheel_radius"]
                    time_scaling_method = MOVEMENT_TIME_SCALING_METHOD_DEFAULT

                    if currentMQTTMoveMessage.topic == 'bot/killSwitch':
                        self.killSwitch()

                    elif currentMQTTMoveMessage.topic == \
                            'bot/base/reset/position':
                        log.warning(
                            LOGGER_DDR_IMPLMOVELOOP,
                            msg=f'Invoking reset body configuration')
                        self.base.resetBodyConfiguration()

                    elif currentMQTTMoveMessage.topic == \
                            'bot/base/move/xyphi':
                        self.eventLoop.run_in_executor(
                            self.eventLoopBaseExecutors,
                            functools.partial(self.base.moveBaseXYPhi,
                                              endBaseConfig=(msgdict["x"],
                                                             msgdict["y"],
                                                             msgdict["phi"]),
                                              pid=pid,
                                              velocity_factor=velocity_factor,
                                              dryrun=dryrun))

                    elif currentMQTTMoveMessage.topic == \
                            'bot/base/move/x':
                        self.eventLoop.run_in_executor(
                            self.eventLoopBaseExecutors,
                            functools.partial(self.base.moveBaseX,
                                              distance=msgdict["distance"],
                                              pid=pid,
                                              velocity_factor=velocity_factor,
                                              wheel_radius=wheel_radius,
                                              dryrun=dryrun))

                    elif currentMQTTMoveMessage.topic == \
                            'bot/base/turn/phi':
                        self.eventLoop.run_in_executor(
                            self.eventLoopBaseExecutors,
                            functools.partial(
                                self.base.rotateBasePhi,
                                phi=msgdict["phi"],
                                pid=pid,
                                velocity_factor=velocity_factor,
                                inter_wheel_distance=inter_wheel_distance,
                                dryrun=dryrun))

                    elif currentMQTTMoveMessage.topic == \
                            'bot/base/move/home':
                        # Calculate where to go from there (not avoiding
                        # obstacles...)
                        _, (x, y, _) = mr.TransToRp(
                            mr.TransInv(self.base.Tsb))
                        # pi - self.base.currentPhi
                        phi = - 1 * self.base.currentPhi
                        self.base.moveBaseXYPhi(
                            endBaseConfig=(x, y, phi),
                            pid=pid,
                            velocity_factor=velocity_factor,
                            time_scaling_method=time_scaling_method,
                            dryrun=dryrun)

                    elif currentMQTTMoveMessage.topic == \
                            'bot/arm/move/rest':
                        self.eventLoop.run_in_executor(
                            self.eventLoopArmExecutors,
                            functools.partial(
                                self.arm.moveToAngle,
                                armConfigVector=self.arm.armRestPosition,
                                dryrun=dryrun))

                    elif currentMQTTMoveMessage.topic == \
                            'bot/arm/move/zero':
                        self.eventLoop.run_in_executor(
                            self.eventLoopArmExecutors,
                            functools.partial(
                                self.arm.moveToAngle,
                                armConfigVector=self.arm.armZeroPosition,
                                dryrun=dryrun))

                    elif currentMQTTMoveMessage.topic == \
                            'bot/arm/move/theta':
                        self.eventLoop.run_in_executor(
                            self.eventLoopArmExecutors,
                            functools.partial(
                                self.arm.moveToAngle,
                                armConfigVector=[msgdict["theta1"],
                                                 msgdict["theta2"]],
                                dryrun=dryrun))

                    elif currentMQTTMoveMessage.topic == \
                            'bot/gripper/open':
                        # Already an async call (non blocking)
                        self.arm.openEndEffector()

                    elif currentMQTTMoveMessage.topic == \
                            'bot/gripper/close':
                        # Already an async call (non blocking)
                        self.arm.closeEndEffector()

                    elif currentMQTTMoveMessage.topic == \
                            'bot/gripper/position':
                        # Call function to move this thing
                        Pco = np.array(msgdict["P"])
                        angle = msgdict["grip_angle"]
                        Toe_grip = mr.RpToTrans(
                            R=np.array([[cos(angle), 0, sin(angle)],
                                        [0, 1, 0],
                                        [-sin(angle), 0, cos(angle)]]),
                            p=[0, 0, 0])
                        Rco, _ = mr.TransToRp(Toe_grip)  # Rco=camera-object
                        # Rco, _ = mr.TransToRp(self.Toe_grip)
                        Tco_desired = mr.RpToTrans(R=Rco, p=Pco)
                        Tbo_desired = self.Tbc @ Tco_desired
                        # Initial guess - based on the angle received
                        thetalist0 = (0, 0, pi / 8, pi / 4)   # pi / 4, pi / 2)

                        log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                  msg=f'Running inverse kinematics to get '
                                      f'required joint angles.')
                        thetalist, success = mr.IKinBody(
                            Blist=self.arm.bList,
                            M=self.M0e,
                            T=Tbo_desired,
                            thetalist0=thetalist0,
                            eomg=0.02,   # 0.08 rad = 5 deg uncerainty
                            ev=0.025)     # 4cm error?
                        if success:
                            thetalist = thetalist.round(6)
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'IK Solved with joint thetas '
                                          f'found {thetalist}')
                            # 0. Open gripper
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Opening or ensuring end effector '
                                          f'is opened.')
                            self.arm.openEndEffector()
                            # 1. Move arm to 0 position - launch async.
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Moving arm to driving'
                                          f'position')
                            self.arm.moveToAngle(
                                armConfigVector=self.arm.armDrivePosition,
                                dryrun=dryrun)
                            # 2. Move base by X first
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Moving body by X '
                                          f'{thetalist[0]:.2f}')
                            self.base.moveBaseX(
                                distance=thetalist[0],
                                pid=pid,
                                velocity_factor=velocity_factor,
                                dryrun=dryrun)
                            # 3. Rotate base by Phi
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Rotating base by phi '
                                          f'{thetalist[1]:.2f}')
                            self.base.rotateBasePhi(
                                phi=thetalist[1],
                                pid=pid,
                                velocity_factor=velocity_factor,
                                dryrun=dryrun)
                            # 4a. Move to standoff position
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Moving arm joints to standoff '
                                          f'position')
                            # 4. Move other joint in position - need to block
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Moving arm joints into position '
                                          f'for picking up object j3 = '
                                          f'{degrees(thetalist[2]):.2f}, '
                                          f'j4 = {degrees(thetalist[3]):.2f}')
                            self.arm.moveToAngle(armConfigVector=thetalist[2:],
                                                 dryrun=dryrun)
                            # 5. Grab object
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Closing end effector')
                            self.arm.closeEndEffector()
                            # Rest b/c the previous call is non blocking,
                            # and blocking causes issues on the wait
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Async Sleep for 1 second...')
                            await asyncio.sleep(1)
                            # 6. Set to driving position
                            log.debug(LOGGER_DDR_IMPLMOVELOOP,
                                      msg=f'Moving arm to driving position.')
                            self.arm.moveToAngle(
                                armConfigVector=self.arm.armDrivePosition,
                                dryrun=dryrun)
                            log.debug(LOGGER_DDR_IMPLMOVELOOP, f'Done!')
                        else:
                            log.warning(LOGGER_DDR_IMPLMOVELOOP,
                                        msg=f'Unable to calculate joint and '
                                            f'wheel angles to achieve the '
                                            f'required position.')

                    elif currentMQTTMoveMessage.topic == \
                            'bot/logger':
                        # Changing the logging level on the fly...
                        log.setLevel(msgdict['logger'], lvl=msgdict['level'])
                    elif currentMQTTMoveMessage.topic == \
                            'bot/logger/multiple':
                        # Changing the logging level on the fly for multiple loggers at a time
                        for logger, level in msgdict.items():
                            log.setLevel(logger, level)
                    else:
                        raise NotImplementedError
            except NotImplementedError:
                log.warning(LOGGER_DDR_IMPLMOVELOOP,
                            msg=f'MQTT message not implemented.')
            except asyncio.futures.CancelledError:
                log.warning(LOGGER_DDR_IMPLMOVELOOP,
                            msg=f'Cancelled the MQTT dequeing task.')
            except:
                log.error(LOGGER_DDR_IMPLMOVELOOP,
                          msg=f'Error : {traceback.print_exc()}')

    async def asyncVoltageCheckLoop(self, loopDelay):
        """
        run the voltage loop check
        """
        while True:
            try:
                await asyncio.sleep(loopDelay)
                if self.currentVoltage < LOW_VOLTAGE_THRESHOLD:
                    raise LowVoltageException
            except asyncio.futures.CancelledError:
                log.warning(LOGGER_DDR_VOLTAGECHECK,
                            msg=f'Cancelled the Voltage Check task.')
            except LowVoltageException:
                log.critical(LOGGER_DDR_VOLTAGECHECK,
                             msg=f'Voltage low on device '
                                 f'({self.currentVoltage:.2f}V - '
                                 f'threshold  = {LOW_VOLTAGE_THRESHOLD:.2f}V)'
                                 f' - consider charging battery or shutting '
                                 f'down.')

    async def asyncOdometryReporting(self, loopDelay):
        """
        run the odometry loop and printout
        """
        try:
            while True:
                await asyncio.sleep(loopDelay)
                log.info(LOGGER_DDR_ODOMETRY,
                         msg=f'Joint theta (degrees) = {self.arm}')
                log.info(LOGGER_DDR_ODOMETRY,
                         msg=f'Latest Vb6 = {self.base.Vb6} ; '
                             f'Phi(degrees) = '
                             f'{degrees(self.base.currentPhi):.2f}')
                log.info(LOGGER_DDR_ODOMETRY,
                         msg=f'Latest Base SE3 (self.base.Tsb) = \n'
                             f'{self.base.Tsb}')
                # Don't read actual position, there's a race condition
                left = self.base.oldLeftPhi
                right = self.base.oldRightPhi
                log.debug(LOGGER_DDR_ODOMETRY,
                          msg=f'Left/Right Position:{left:.2f}/{right:.2f}')
                theoretical_dist = radians((left + right) / 2) * \
                    self.base.wheelRadius
                log.debug(LOGGER_DDR_ODOMETRY,
                          msg=f'Theoretical distance:{theoretical_dist:.4f}')

        except asyncio.futures.CancelledError:
            log.warning(LOGGER_DDR_ODOMETRY,
                        msg=f'Cancelled the Voltage Check task.')

    def asyncExecutorImplKillSwitch(self):
        """ Detects click and double clicks"""
        try:
            button = self.sensors["killSwitch"]
            log.info(LOGGER_DDR_EXECUTORKILLSWITCH,
                     msg=f'Initialized async process for kill switch.')
            while True:
                firstClick = time.time()
                button.wait_for_bump()
                secondClick = time.time()
                if secondClick - firstClick < DOUBLECLICK:
                    log.warning(LOGGER_DDR_EXECUTORKILLSWITCH,
                                msg=f'DoubleClick caught.')
                    self.exceptionQueue.put(SystemExit)
                    # Return is there to end this thread and return it
                    # to the pool (run_in_executor).
                    return
                else:
                    self.killSwitch()
                log.warning(LOGGER_DDR_EXECUTORKILLSWITCH,
                            msg=f'Button pressed. Urgent Stop switch '
                                f'flipped. Threads using motors should '
                                f'stop.')
        except:
            log.warning(LOGGER_DDR_EXECUTORKILLSWITCH,
                        msg=f'Error in the kill switch routing : '
                            f'{traceback.print_exc()}')

    def __adjustConfigDict(self, confDict):
        '''
        adjustConfigDict :
        param:confDict:parameters read in the config file for the robot
        param:confDict:type:parameter dictionary
        returns : modified confDict
        '''
        for key, value in confDict.items():
            if not isinstance(value, dict):
                if not isinstance(value, list):
                    if value in LEGO_ROBOT_DEFINITIONS:
                        confDict[key] = LEGO_ROBOT_DEFINITIONS[value]
            else:
                confDict[key] = self.__adjustConfigDict(confDict[key])
        return confDict

    def killSwitch(self):
        # Empty movement queue
        try:
            log.warning(LOGGER_DDR_KILLSWITCH, 'Killing all motor motion.')
            self.urgentStop = True
            time.sleep(1)
            self.urgentStop = False
        except:
            pass

    def threadImplMQTT(self):
        """
        MQTT Thread launching the loop and subscripbing to the right topics
        """
        mqtt_default_qos = 2
        self.mqtt_topics = [
            (topic, mqtt_default_qos) for topic in
            self.robotConfiguration["mqtt"]["subscribedTopics"]]

        def on_connect(client, userdata, flags, rc):
            log.warning(LOGGER_DDR_THREADIMPLMQTT,
                        msg=f'Connected to MQTT broker. '
                            f'Result code {rc}')
            mqtt_connect_result, self.mqtt_connect_mid = \
                client.subscribe(self.mqtt_topics)
            if mqtt_connect_result == mqtt.MQTT_ERR_SUCCESS:
                log.warning(LOGGER_DDR_THREADIMPLMQTT,
                            msg=f'Successfully subscribed '
                                f'to {self.mqtt_topics}')
            else:
                log.error(LOGGER_DDR_THREADIMPLMQTT,
                          msg=f'MQTT Broker subscription problem.')

        def on_message(client, userdata, message):
            """ callback function used for the mqtt client (called when
            a new message is publisehd to one of the queues we subscribe to)
            """
            log.info(LOGGER_DDR_THREADIMPLMQTT,
                     msg=f'Received MID {message.mid} : '
                         f'{str(message.payload)} on topic '
                         f'{message.topic} with QoS {message.qos}')
            self.mqttMoveQueue.put_nowait(message)

        def on_disconnect(client, userdata, rc=0):
            """callback for handling disconnects
            """
            log.info(LOGGER_DDR_THREADIMPLMQTT,
                     msg=f'Disconnected MQTT result code = {rc}.'
                         f'Should automatically re-connect to broker')

        def on_subscribe(client, userdata, mid, granted_qos):
            if mid == self.mqtt_connect_mid:
                log.warning(LOGGER_DDR_THREADIMPLMQTT,
                            msg=f'Subscribed to topics. Granted QOS '
                                f'= {granted_qos}')
            else:
                log.error(LOGGER_DDR_THREADIMPLMQTT,
                          msg=f'Strange... MID doesn\'t match '
                              f'self.mqtt_connect_mid')

        self.mqttClient = mqtt.Client(
            client_id="mybot",
            clean_session=True,
            transport=self.robotConfiguration["mqtt"]["brokerProto"])
        self.mqttClient.enable_logger(
            logger=RoboLogger.getSpecificLogger(LOGGER_DDR_THREADIMPLMQTT))
        self.mqttClient.on_subscribe = on_subscribe
        self.mqttClient.on_connect = on_connect
        self.mqttClient.on_disconnect = on_disconnect
        self.mqttClient.on_message = on_message
        self.mqttClient.connect(
            host=self.robotConfiguration["mqtt"]["brokerIP"],
            port=self.robotConfiguration["mqtt"]["brokerPort"])
        self.mqttClient.loop_start()