def execute_cmd(self, dst_dev_uuid, method, namespace, payload, callback=None, timeout=SHORT_TIMEOUT): # If the underlying mqttclient is not connected, let's fast-fail. # Otherwise, if it's connected but not yet subscribed to relevant topics, it's still OK to # queue messages: the client will dispatch them as soon it subscribes to the relevant topics. if not self._mqtt_client.is_connected(): l.error("The underlying mqtt client is not connected.") raise ConnectionError() 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 _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: raise InvalidSignatureException( 'The signature did not match!', expected_signature=expected_signature, provided_signature=header['sign'], data=message) # 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 InvalidSignatureException as e: l.error( "Invalid signature received. Expecting: %s, received %s. Message: %s" % (e.expected_signature, e.provided_signature, json.dumps(e.data))) except Exception: l.exception("Failed to process message.")