def _fire_connection_event(self, connection_status): evt = ClientConnectionEvent(current_status=connection_status) for c in self._connection_event_callbacks: try: c(evt) except: l.exception( "Unhandled error occurred while executing event handler")
def notify_message_received(self, error=None, response=None): self._response = copy.deepcopy(response) self._error = error if self._event is not None: self._event.set() elif self._callback is not None: try: self._callback(self._error, self._response) except: l.exception("Unhandled error occurred while executing the callback")
def _on_connect(self, client, userdata, rc, other): l.debug("Connected with result code %s" % str(rc)) self.connection_status.update_status(ClientStatus.CONNECTED) self._client_response_topic = "/app/%s-%s/subscribe" % (self._cloud_creds.user_id, self._app_id) self._user_topic = "/app/%s/subscribe" % self._cloud_creds.user_id # Subscribe to the relevant topics l.debug("Subscribing to topics...") client.subscribe(self._user_topic) client.subscribe(self._client_response_topic)
def _on_disconnect(self, client, userdata, rc): l.info("Disconnection detected. Reason: %s" % str(rc)) # When the mqtt connection is dropped, we need to reset the subscription counter. self._subscription_count = AtomicCounter(0) self.connection_status.update_status(ClientStatus.CONNECTION_DROPPED) # TODO: should we handle disconnection in some way at this level? if rc == mqtt.MQTT_ERR_SUCCESS: pass else: client.loop_stop(True)
def _on_message(self, client, userdata, msg): """ This handler is called when a message is received from the MQTT broker, on the subscribed topics. The current implementation checks the validity of the message itself, by verifying its signature. :param client: is the MQTT client reference, useful to respond back :param userdata: metadata about the received data :param msg: message that was received :return: nothing, it simply handles the message accordingly. """ networkl.debug(msg.topic + " --> " + str(msg.payload)) try: message = json.loads(str(msg.payload, "utf8")) header = message['header'] message_hash = md5() strtohash = "%s%s%s" % (header['messageId'], self._cloud_creds.key, header['timestamp']) message_hash.update(strtohash.encode("utf8")) expected_signature = message_hash.hexdigest().lower() if header['sign'] != expected_signature: # TODO: custom exception for invalid signature raise Exception('The signature did not match!') # Check if there is any thread waiting for this message or if there is a callback that we need to invoke. # If so, do it here. handle = None with self._pending_responses_lock: msg_id = header['messageId'] handle = self._pending_response_messages.get(msg_id) from_myself = False if handle is not None: # There was a handle for this message-id. It means it is a response message to some # request performed by the library itself. from_myself = True try: l.debug("Calling handle event handler for message %s" % msg_id) # Call the handler handle.notify_message_received(error=None, response=message) l.debug("Done handler for message %s" % msg_id) # Remove the message from the pending queue with self._pending_responses_lock: del self._pending_response_messages[msg_id] except: l.exception( "Error occurred while invoking message handler") # Let's also catch all the "PUSH" notifications and dispatch them to the push_notification_callback. if self._push_message_callback is not None and header[ 'method'] == "PUSH" and 'namespace' in header: self._push_message_callback(message, from_myself=from_myself) except Exception: l.exception("Failed to process message.")
def close(self): l.info("Closing the MQTT connection...") self._mqtt_client.disconnect() l.debug("Waiting for the client to disconnect...") self.connection_status.wait_for_status(ClientStatus.CONNECTION_DROPPED) # Starts a new thread that handles mqtt protocol and calls us back via callbacks l.debug("Stopping the MQTT looper.") self._mqtt_client.loop_stop(True) l.info("Client has been fully disconnected.")
def execute_cmd(self, dst_dev_uuid, method, namespace, payload, callback=None, timeout=SHORT_TIMEOUT): start = time.time() # Build the mqtt message we will send to the broker message, message_id = self._build_mqtt_message(method, namespace, payload) # Register the waiting handler for that message handle = PendingMessageResponse(message_id=message_id, callback=callback) with self._pending_responses_lock: self._pending_response_messages[message_id] = handle # Send the message to the broker l.debug("Executing message-id %s, %s on %s command for device %s" % (message_id, method, namespace, dst_dev_uuid)) self._mqtt_client.publish(topic=build_client_request_topic(dst_dev_uuid), payload=message) # If the caller has specified a callback, we don't need to actively wait for the message ACK. So we can # immediately return. if callback is not None: return None # Otherwise, we need to wait until the message is received. l.debug("Waiting for response to message-id %s" % message_id) success, resp = handle.wait_for_response(timeout=timeout) if not success: raise CommandTimeoutException("A timeout occurred while waiting for the ACK: %d" % timeout) elapsed = time.time() - start l.debug("Message-id: %s, command %s-%s command for device %s took %s" % (message_id, method, namespace, dst_dev_uuid, str(elapsed))) return resp['payload']
def connect(self): """ Starts the connection to the MQTT broker :return: """ l.info("Initializing the MQTT connection...") self._mqtt_client.connect(self._domain, self._port, keepalive=30) self.connection_status.update_status(ClientStatus.CONNECTING) # Starts a new thread that handles mqtt protocol and calls us back via callbacks l.debug("(Re)Starting the MQTT looper.") self._mqtt_client.loop_stop(True) self._mqtt_client.loop_start() l.debug("Waiting for the client to connect...") self.connection_status.wait_for_status(ClientStatus.SUBSCRIBED) l.info("Client connected to MQTT broker and subscribed to relevant topics.")
def _on_disconnect(self, client, userdata, rc): l.info("Disconnection detected. Reason: %s" % str(rc)) # When the mqtt connection is dropped, we need to reset the subscription counter. self._subscription_count = AtomicCounter(0) self.connection_status.update_status(ClientStatus.CONNECTION_DROPPED) # If the client disconnected explicitly, the mqtt library handles thred stop autonomously if rc == mqtt.MQTT_ERR_SUCCESS: pass else: # Otherwise, if the disconnection was not intentional, we probably had a connection drop. # In this case, we only stop the loop thread if auto_reconnect is not set. In fact, the loop will # handle reconnection autonomously on connection drops. if not self._auto_reconnect: l.info("Stopping mqtt loop on connection drop") client.loop_stop(True) else: l.warning( "Client has been disconnected, however auto_reconnect flag is set. " "Won't stop the looping thread, as it will retry to connect." )
def _on_subscribe(self, client, userdata, mid, granted_qos): l.debug("Succesfully subscribed to topic. Subscription count: %d" % self._subscription_count.get()) if self._subscription_count.inc() == 2: self.connection_status.update_status(ClientStatus.SUBSCRIBED)
def _on_unsubscribe(self): l.debug("Unsubscribed from topic") self._subscription_count.dec()
def unregister_connection_event_callback(self, callback): with self._connection_event_callbacks_lock: if callback in self._connection_event_callbacks: self._connection_event_callbacks.remove(callback) else: l.debug("Callback was present: nothing to unregister.")
def register_connection_event_callback(self, callback): with self._connection_event_callbacks_lock: if callback not in self._connection_event_callbacks: self._connection_event_callbacks.append(callback) else: l.debug("Callback was already registered.")