def disconnect(self, wait_for_replies=False, timeout=0): """ Stop threads and shut down MQTT client """ current_time = datetime.utcnow() end_time = current_time + timedelta(seconds=timeout) # Publish any data that was queued before disconnecting if not self.publish_queue.empty(): self.queue_work(defs.Work(constants.WORK_PUBLISH, None)) # Wait for pending work that has not been dealt with self.logger.info("Disconnecting...") while ((timeout == 0 or current_time < end_time) and not self.work_queue.empty()): sleep(0.1) current_time = datetime.utcnow() # Optionally wait for any outstanding replies. if wait_for_replies and self.is_connected(): self.logger.info("Waiting for replies...") while ((timeout == 0 or current_time < end_time) and len(self.reply_tracker) != 0): sleep(0.1) current_time = datetime.utcnow() self.to_quit = True #TODO: Kill any hanging threads if threading.current_thread() not in self.worker_threads: if self.main_thread: self.main_thread.join() self.main_thread = None return constants.STATUS_SUCCESS
def main_loop(self): """ Main loop for MQTT to send and receive messages, as well as queue work for publishing and checking timeouts """ # Continuously loop while connected or connecting while not self.to_quit: # If disconnected, attempt to reestablish connection if self.state == constants.STATE_DISCONNECTED: max_time = self.config.keep_alive elapsed_time = (datetime.utcnow() - self.last_connected).total_seconds() if max_time == 0 or elapsed_time < max_time: try: result = self.mqtt.reconnect() if result == 0: self.logger.debug("Reconnecting...") self.state = constants.STATE_CONNECTING except Exception: sleep(self.config.loop_time) else: self.logger.error("No connection after %d seconds, " "exiting...", self.config.keep_alive) self.to_quit = True break self.mqtt.loop(timeout=self.config.loop_time) # Make a work item to publish anything that's pending if not self.publish_queue.empty(): self.queue_work(defs.Work(constants.WORK_PUBLISH, None)) # One last loop to send out any pending messages self.mqtt.loop(timeout=0.1) # Disconnect MQTT self.mqtt.disconnect() # Wait for worker threads to finish. for thread in self.worker_threads: thread.join() self.worker_threads = [] # On disconnect, show all messages that never received replies if len(self.reply_tracker) > 0: self.logger.error("These messages never received a reply:") for mid, message in self.reply_tracker.items(): self.logger.error(".... %s - %s", mid, message.description) return constants.STATUS_SUCCESS
def on_message(self, mqtt, userdata, msg): """ Callback when MQTT Client receives a message """ message = defs.Message(msg.topic, json.loads(msg.payload.decode())) self.logger.debug("Received message on topic \"%s\"\n%s", msg.topic, message) # Queue work to handle received message. Don't block main loop with this # task. work = defs.Work(constants.WORK_MESSAGE, message) self.queue_work(work)
def alarm_publish(self, alarm_name, state, message=None): """ Publish an alarm to the Cloud Parameters: alarm_name (string) Name of alarm to publish state (int) State of publish message (string) Optional message to accompany alarm Returns: STATUS_SUCCESS Alarm has been queued for publishing """ alarm = defs.PublishAlarm(alarm_name, state, message) self.handler.queue_publish(alarm) work = defs.Work(WORK_PUBLISH, None) return self.handler.queue_work(work)
def handle_message(self, mqtt_message): """ Handle messages received from Cloud """ status = constants.STATUS_NOT_SUPPORTED msg_json = mqtt_message.json if "notify/" in mqtt_message.topic: # Received a notification if mqtt_message.topic[len("notify/"):] == "mailbox_activity": # Mailbox activity, send a request to check the mailbox self.logger.info("Recevied notification of mailbox activity") mailbox_check = tr50.create_mailbox_check(auto_complete=False) to_send = defs.OutMessage(mailbox_check, "Mailbox Check") self.send(to_send) status = constants.STATUS_SUCCESS elif "reply/" in mqtt_message.topic: # Received a reply to a previous message topic_num = mqtt_message.topic[len("reply/"):] for command_num in msg_json: reply = msg_json[command_num] # Retrieve the sent message that this is a reply for, removing # it from being tracked self.lock.acquire() try: sent_message = self.reply_tracker.pop_message(topic_num, command_num) except KeyError as error: self.logger.error(error.message) continue finally: self.lock.release() sent_command_type = sent_message.command.get("command") # Log success status of reply if reply.get("success"): self.logger.info("Received success for %s-%s - %s", topic_num, command_num, sent_message) else: self.logger.error("Received failure for %s-%s - %s", topic_num, command_num, sent_message) self.logger.error(".... %s", str(reply)) # Check what kind of message this is a reply to if sent_command_type == TR50Command.file_get: # Recevied a reply for a file download request if reply.get("success"): file_id = reply["params"].get("fileId") file_checksum = reply["params"].get("crc32") file_transfer = sent_message.data file_transfer.file_id = file_id file_transfer.file_checksum = file_checksum work = defs.Work(constants.WORK_DOWNLOAD, file_transfer) self.queue_work(work) else: if -90008 in reply.get("errorCodes", []): sent_message.data.status = constants.STATUS_NOT_FOUND else: sent_message.data.status = constants.STATUS_FAILURE elif sent_command_type == TR50Command.file_put: # Received a reply for a file upload request if reply.get("success"): file_id = reply["params"].get("fileId") file_transfer = sent_message.data file_transfer.file_id = file_id work = defs.Work(constants.WORK_UPLOAD, file_transfer) self.queue_work(work) else: sent_message.data.status = constants.STATUS_FAILURE elif sent_command_type == TR50Command.mailbox_check: # Received a reply for a mailbox check if reply.get("success"): try: for mail in reply["params"]["messages"]: mail_command = mail.get("command") if mail_command == "method.exec": # Action execute request in mailbox mail_id = mail.get("id") action_name = mail["params"].get("method") action_params = mail["params"].get("params") action_request = defs.ActionRequest(mail_id, action_name, action_params) work = defs.Work(constants.WORK_ACTION, action_request) self.queue_work(work) except: pass elif sent_command_type == TR50Command.diag_time: # Recevied a reply for a ping request if reply.get("success"): mill = reply["params"].get("time") print (datetime.fromtimestamp(mill/1000.0)) else: if -90008 in reply.get("errorCodes", []): sent_message.data.status = constants.STATUS_NOT_FOUND else: sent_message.data.status = constants.STATUS_FAILURE elif sent_command_type == TR50Command.diag_ping: # Recevied a reply for a ping request if reply.get("success"): print ('*Connection Okay* \n') else: if -90008 in reply.get("errorCodes", []): sent_message.data.status = constants.STATUS_NOT_FOUND else: sent_message.data.status = constants.STATUS_FAILURE status = constants.STATUS_SUCCESS return status