def __init__( self, locationId, locationData, mqttclient: MqttClient, api: IotCloudApi ): self.locationId = locationId self.locationName = "" self.postalCode = 0 self.timeZone = "" self.devices: typing.Dict[str, Device] = {} self.devicesLock = Lock() self.timeZone = "" # Manage the location dinamic data like the weather or the sun schedule self.dataManager = LocationDataManager(locationId, api) # Location status check data self.offlineInitialTimestamp = 0 self.timeFilter = 50 # Seconds self.notificationSent = False self.api = api # Set location data self.setLocationData(locationData, mqttclient) # Set MQTT topics and handlers self.updatedDeviceTopic = f"v1/{self.locationId}/+/updatedDevice" mqttclient.message_callback_add( self.updatedDeviceTopic, self.onDeviceUpdated) logger.info( f"Created location: {self.locationName} with {len(self.devices)} devices")
def ensure_mqtt(host): mqtt_client = MQTTClient() mqtt_client.on_connect = mqtt_on_connect mqtt_client.message_callback_add('embrace/sensors', mqtt_on_message_sensor) mqtt_client.message_callback_add('embrace/topology', mqtt_on_message_topology) mqtt_client.connect(host, 1883, 60) return mqtt_client
def on_connect(mqttc: mqtt.Client, inlfuxc, flags, rc): logging.info(f"MQTT connection established ({rc})") # subscribe to match signal cbor messages topic_matched_cbor = "jonas-rpi-00001/radiotracking/matched/cbor" mqttc.subscribe(topic_matched_cbor) mqttc.message_callback_add(topic_matched_cbor, on_matched_cbor) logging.info(f"Subscribed to {topic_matched_cbor}")
class MQTTSnipsComponent(SnipsComponent): """A Snips component using the MQTT protocol directly. Attributes: snips (:class:`.SnipsConfig`): The Snips configuration. mqtt (`paho.mqtt.client.Client`_): The MQTT client object. .. _`paho.mqtt.client.Client`: https://www.eclipse.org/paho/clients/python/docs/#client """ def _connect(self): """Connect with the MQTT broker referenced in the Snips configuration file. """ self.mqtt = Client() self.mqtt.on_connect = self._subscribe_topics connect(self.mqtt, self.snips.mqtt) def _start(self): """Start the event loop to the MQTT broker so the component starts listening to MQTT topics and the callback methods are called. """ self.mqtt.loop_forever() def _subscribe_topics(self, client, userdata, flags, connection_result): """Subscribe to the MQTT topics we're interested in. Each method with an attribute set by a :func:`snipskit.decorators.mqtt.topic` decorator is registered as a callback for the corresponding topic. """ for name in dir(self): callable_name = getattr(self, name) if hasattr(callable_name, 'topic'): self.mqtt.subscribe(getattr(callable_name, 'topic')) self.mqtt.message_callback_add(getattr(callable_name, 'topic'), callable_name) def publish(self, topic, payload, json_encode=True): """Publish a payload on an MQTT topic on the MQTT broker of this object. Args: topic (str): The MQTT topic to publish the payload on. payload (str): The payload to publish. json_encode (bool, optional): Whether or not the payload is a dict that will be encoded as a JSON string. The default value is True. Set this to False if you want to publish a binary payload as-is. Returns: :class:`paho.mqtt.MQTTMessageInfo`: Information about the publication of the message. .. versionadded:: 0.5.0 """ if json_encode: payload = json.dumps(payload) return self.mqtt.publish(topic, payload)
def on_connect(mqttc: mqtt.Client, inlfuxc, flags, rc): schedule.run_pending() logging.info(f"MQTT connection established ({rc})") # subscribe to signal cbor messages topic_signal_cbor = "+/radiotracking/device/+/cbor" mqttc.subscribe(topic_signal_cbor) mqttc.message_callback_add(topic_signal_cbor, on_signal_cbor) logging.info(f"Subscribed to {topic_signal_cbor}") # subscribe to match signal cbor messages topic_matched_cbor = "+/radiotracking/matched/cbor" mqttc.subscribe(topic_matched_cbor) mqttc.message_callback_add(topic_matched_cbor, on_matched_cbor) logging.info(f"Subscribed to {topic_matched_cbor}") # subscribe to log csv messages topic_log_csv = "+/radiotracking/log/csv" mqttc.subscribe(topic_log_csv) mqttc.message_callback_add(topic_log_csv, on_log_csv) logging.info(f"Subscribed to {topic_log_csv}") # subscribe to util messages topic_mqtt_util = "+/mqttutil/#" mqttc.subscribe(topic_mqtt_util) mqttc.message_callback_add(topic_mqtt_util, on_mqtt_util) logging.info(f"Subscribed to {topic_mqtt_util}")
def __init__(self, client: mclient.Client, topic: str, callback, logger: logging.Logger, QOS=0): super().__init__() if not callable(callback): raise AttributeError("callback muss aufrufbar sein.") self.name = "MsgThr" self.setDaemon(False) self._cancel_new_when_running = False self._sleeping = True self._callback = callback self._message_queue = queue.Queue() self._logger = logger.getChild("mmThread-{}".format(topic)) self._mutex = thr.Lock() self.start() client.subscribe(topic, qos=QOS) client.message_callback_add(topic, self.__mqtt_callback) self._kill = False
def __init__(self, baseTopic: str, sensorId: str, metadata: typing.Dict, mqttclient: MqttClient, locationData: LocationDataManager) -> None: super().__init__(baseTopic, sensorId, metadata, mqttclient, locationData) self.state = False self.setSensorData(metadata, mqttclient) # Set up the relevant MQTT topics self.stateTopic = f"{baseTopic}{sensorId}/state" self.setStateTopic = f"{baseTopic}{sensorId}/setState" mqttclient.message_callback_add(self.stateTopic, self.onDeviceState) # Enable the retrieval of the sun schedule locationData.registerSunSchedule()
def _on_connect(self, client: Client, userdata, flags, rc) -> None: client.subscribe("ald/sample/temperature") client.message_callback_add('ald/sample/temperature', self._on_sample_temperature) client.subscribe("ald/flow/state") client.message_callback_add('ald/flow/state', self._on_flow_state) client.subscribe("ald/pressure/main") client.message_callback_add('ald/pressure/main', self._on_pressure_main) client.subscribe("ald/io/state") client.message_callback_add('ald/io/state', self._on_valves) for topic in self.TEMPERATURE_TOPICS: client.subscribe("ald/temperature/{}".format(topic)) client.message_callback_add("ald/temperature/{}".format(topic), self._on_temperature) debug_print("Connected.")
def callback_servidor(mqttc, userdata, msg): ''' maneja la conexión inicial con el servidor hasta que recibe permiso, y las desconexiones inesperadas del servidor desconectando a todos los jugadores ''' if msg.payload == b"SERVER_FAIL": print("SERVER_FAIL: se ha caido el servidor. Por favor, introduzca 0.") mqttc.disconnect() conectado.value = 0 elif msg.payload == b"SERVER_READY": sleep(random() * 10) mqttc.publish(choques + "/servidor/" + userdata[0], payload="CONNECT_REQUEST") elif msg.payload == b"CONNECT_ACCEPT": print("SERVIDOR ACTIVO") mqttc.unsubscribe(choques + "/servidor/exception") mqttc.unsubscribe(choques + "/servidor/" + userdata[0]) mqttc.publish(choques + "/solicitudes", payload=userdata[0]) elif msg.payload == b"USER_EXC": print("Usuario no válido") print("Prueba otro usuario que no este en uso") mqttc.disconnect() nombre_usuario = input("¿nombre usuario? ") sleep(1) mqttc = Client(userdata=[nombre_usuario, 0, 0]) #,clean_session=True) mqttc.message_callback_add(choques + "/servidor/#", callback_servidor) mqttc.message_callback_add(choques + "/partidas/#", callback_partidas) mqttc.message_callback_add(choques + "/jugadores/" + nombre_usuario, callback_jugadores) mqttc.will_set(choques + "/jugadores/" + nombre_usuario, payload="DISCONNECT") mqttc.connect(broker) mqttc.subscribe(choques + "/jugadores/" + nombre_usuario) mqttc.subscribe(choques + "/servidor") mqttc.subscribe(choques + "/servidor/" + nombre_usuario) mqttc.subscribe(choques + "/servidor/exception") mqttc.publish(choques + "/servidor/" + nombre_usuario, payload="CONNECT_REQUEST") mqttc.loop_start()
class CubeSerializingSocket(object): CUBE_TOPIC_NAME = 'RawCubes' def __init__(self, on_receive_cube_fn=None): self.on_receive_cube_fn = on_receive_cube_fn # https://www.infoq.com/articles/practical-mqtt-with-paho self.mqtt_client = MqttClient() if self.on_receive_cube_fn is not None: def on_connect(client, userdata, flags, rc): self.mqtt_client.subscribe( CubeSerializingSocket.CUBE_TOPIC_NAME) self.mqtt_client.on_connect = on_connect self.mqtt_client.message_callback_add( CubeSerializingSocket.CUBE_TOPIC_NAME, self.receive_zipped_cube) self.mqtt_client.connect('127.0.0.1') self.mqtt_client.loop_start() def send_zipped_cube(self, cube_dict): dict = cPickle.dumps(cube_dict, protocol=cPickle.HIGHEST_PROTOCOL) dict = zlib.compress(dict) logging.debug("SEND Cube: Topic: {}".format( CubeSerializingSocket.CUBE_TOPIC_NAME)) return self.mqtt_client.publish(CubeSerializingSocket.CUBE_TOPIC_NAME, bytearray(dict)) def receive_zipped_cube(self, client, userdata, msg): data = msg.payload pobj = zlib.decompress(data) pobj = cPickle.loads(pobj) self.on_receive_cube_fn(msg.topic, pobj)
def _on_connect(self, client: Client, userdata, flags, rc) -> None: client.subscribe("ald/sample/temperature") client.message_callback_add('ald/sample/temperature', self._on_sample_temperature) client.subscribe("ald/flow/state") client.message_callback_add('ald/flow/state', self._on_flow_state) client.subscribe("ald/pressure/main") client.message_callback_add('ald/pressure/main', self._on_pressure_main) debug_print("Connected.")
def __init__(self, baseTopic: str, sensorId: str, metadata: typing.Dict, mqttclient: MqttClient, locationData: LocationDataManager) -> None: super().__init__(baseTopic, sensorId, metadata, mqttclient, locationData) # Runtime variables self.state = False self.tempReferences: dict[str, float] = {} self.tempRefValues: dict[str, TempValue] = {} self.heating = False self.setHeatingMem = False self.alarm = False # Default settings self.startHeatingAt = int(time.time()) self.setpoint = 20.0 self.stateChanged = False self.hysteresisHigh = -0.1 self.hysteresisLow = -0.8 self.maxHeatingTime = 3600 * 8 # 8 hours self.tempReferenceMem = 0.0 self.progThermostatShutdownEnabled = False self.progThermostatShutdownTime = 0 self.progThermostatShutdownMem = False self.filterTime = 90 # seconds self.startHeatingfilterTime = 0 self.stopHeatingfilterTime = 0 self.setSensorData(metadata, mqttclient) # Set up the relevant MQTT topics self.stateTopic = f"{baseTopic}{sensorId}/state" self.auxTopic = f"{baseTopic}{sensorId}/aux/" self.heatingTopic = self.auxTopic + "heating" self.setpointTopic = self.auxTopic + "setpoint" self.ackAlarmTopic = self.auxTopic + "ackAlarm" self.setStateTopic = f"{baseTopic}{sensorId}/setState" mqttclient.message_callback_add(self.stateTopic, self.onDeviceState) mqttclient.message_callback_add(self.heatingTopic, self.onHeating) mqttclient.message_callback_add(self.setpointTopic, self.onSetpoint) mqttclient.message_callback_add(self.ackAlarmTopic, self.onAckAlarm) self.addTempReference(mqttclient, f"{baseTopic}{sensorId}/value", 2.0) # Enable the retrieval of the sun schedule locationData.registerSunSchedule()
class Mqtt(): """Main Mqtt class. :param app: flask application object :param connect_async: if True then connect_aync will be used to connect to MQTT broker :param mqtt_logging: if True then messages from MQTT client will be logged """ def __init__(self, app=None, connect_async=False, mqtt_logging=False): # type: (Flask, bool, bool) -> None self.app = app self._connect_async = connect_async # type: bool self._connect_handler = None # type: Optional[Callable] self._disconnect_handler = None # type: Optional[Callable] self.topics = {} # type: Dict[str, TopicQos] self.connected = False self.client = Client() if mqtt_logging: self.client.enable_logger(logger) if app is not None: self.init_app(app) def init_app(self, app): # type: (Flask) -> None """Init the Flask-MQTT addon.""" self.client_id = app.config.get("MQTT_CLIENT_ID", "") self.clean_session = app.config.get("MQTT_CLEAN_SESSION", True) if isinstance(self.client_id, unicode): self.client._client_id = self.client_id.encode('utf-8') else: self.client._client_id = self.client_id self.client._clean_session = self.clean_session self.client._transport = app.config.get("MQTT_TRANSPORT", "tcp").lower() self.client._protocol = app.config.get("MQTT_PROTOCOL_VERSION", MQTTv311) self.client.on_connect = self._handle_connect self.client.on_disconnect = self._handle_disconnect self.username = app.config.get("MQTT_USERNAME") self.password = app.config.get("MQTT_PASSWORD") self.broker_url = app.config.get("MQTT_BROKER_URL", "localhost") self.broker_port = app.config.get("MQTT_BROKER_PORT", 1883) self.tls_enabled = app.config.get("MQTT_TLS_ENABLED", False) self.keepalive = app.config.get("MQTT_KEEPALIVE", 60) self.last_will_topic = app.config.get("MQTT_LAST_WILL_TOPIC") self.last_will_message = app.config.get("MQTT_LAST_WILL_MESSAGE") self.last_will_qos = app.config.get("MQTT_LAST_WILL_QOS", 0) self.last_will_retain = app.config.get("MQTT_LAST_WILL_RETAIN", False) if self.tls_enabled: self.tls_ca_certs = app.config["MQTT_TLS_CA_CERTS"] self.tls_certfile = app.config.get("MQTT_TLS_CERTFILE") self.tls_keyfile = app.config.get("MQTT_TLS_KEYFILE") self.tls_cert_reqs = app.config.get("MQTT_TLS_CERT_REQS", ssl.CERT_REQUIRED) self.tls_version = app.config.get("MQTT_TLS_VERSION", ssl.PROTOCOL_TLSv1) self.tls_ciphers = app.config.get("MQTT_TLS_CIPHERS") self.tls_insecure = app.config.get("MQTT_TLS_INSECURE", False) # set last will message if self.last_will_topic is not None: self.client.will_set( self.last_will_topic, self.last_will_message, self.last_will_qos, self.last_will_retain, ) self._connect() def _connect(self): # type: () -> None if self.username is not None: self.client.username_pw_set(self.username, self.password) # security if self.tls_enabled: self.client.tls_set( ca_certs=self.tls_ca_certs, certfile=self.tls_certfile, keyfile=self.tls_keyfile, cert_reqs=self.tls_cert_reqs, tls_version=self.tls_version, ciphers=self.tls_ciphers, ) if self.tls_insecure: self.client.tls_insecure_set(self.tls_insecure) if self._connect_async: # if connect_async is used self.client.connect_async(self.broker_url, self.broker_port, keepalive=self.keepalive) else: res = self.client.connect(self.broker_url, self.broker_port, keepalive=self.keepalive) if res == 0: logger.debug("Connected client '{0}' to broker {1}:{2}".format( self.client_id, self.broker_url, self.broker_port)) else: logger.error( "Could not connect to MQTT Broker, Error Code: {0}".format( res)) self.client.loop_start() def _disconnect(self): # type: () -> None self.client.loop_stop() self.client.disconnect() logger.debug('Disconnected from Broker') def _handle_connect(self, client, userdata, flags, rc): # type: (Client, Any, Dict, int) -> None if rc == MQTT_ERR_SUCCESS: self.connected = True for key, item in self.topics.items(): self.client.subscribe(topic=item.topic, qos=item.qos) if self._connect_handler is not None: self._connect_handler(client, userdata, flags, rc) def _handle_disconnect(self, client, userdata, rc): # type: (str, Any, int) -> None self.connected = False if self._disconnect_handler is not None: self._disconnect_handler() def on_topic(self, topic): # type: (str) -> Callable """Decorator. Decorator to add a callback function that is called when a certain topic has been published. The callback function is expected to have the following form: `handle_topic(client, userdata, message)` :parameter topic: a string specifying the subscription topic to subscribe to The topic still needs to be subscribed via mqtt.subscribe() before the callback function can be used to handle a certain topic. This way it is possible to subscribe and unsubscribe during runtime. **Example usage:**:: app = Flask(__name__) mqtt = Mqtt(app) mqtt.subscribe('home/mytopic') @mqtt.on_topic('home/mytopic') def handle_mytopic(client, userdata, message): print('Received message on topic {}: {}' .format(message.topic, message.payload.decode())) """ def decorator(handler): # type: (Callable[[str], None]) -> Callable[[str], None] self.client.message_callback_add(topic, handler) return handler return decorator def subscribe(self, topic, qos=0): # type: (str, int) -> Tuple[int, int] """ Subscribe to a certain topic. :param topic: a string specifying the subscription topic to subscribe to. :param qos: the desired quality of service level for the subscription. Defaults to 0. :rtype: (int, int) :result: (result, mid) A topic is a UTF-8 string, which is used by the broker to filter messages for each connected client. A topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator). The function returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the client is not currently connected. mid is the message ID for the subscribe request. The mid value can be used to track the subscribe request by checking against the mid argument in the on_subscribe() callback if it is defined. **Topic example:** `myhome/groundfloor/livingroom/temperature` """ # TODO: add support for list of topics # don't subscribe if already subscribed # try to subscribe result, mid = self.client.subscribe(topic=topic, qos=qos) # if successful add to topics if result == MQTT_ERR_SUCCESS: self.topics[topic] = TopicQos(topic=topic, qos=qos) logger.debug('Subscribed to topic: {0}, qos: {1}'.format( topic, qos)) else: logger.error('Error {0} subscribing to topic: {1}'.format( result, topic)) return (result, mid) def unsubscribe(self, topic): # type: (str) -> Optional[Tuple[int, int]] """ Unsubscribe from a single topic. :param topic: a single string that is the subscription topic to unsubscribe from :rtype: (int, int) :result: (result, mid) Returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the client is not currently connected. mid is the message ID for the unsubscribe request. The mid value can be used to track the unsubscribe request by checking against the mid argument in the on_unsubscribe() callback if it is defined. """ # don't unsubscribe if not in topics if topic in self.topics: result, mid = self.client.unsubscribe(topic) if result == MQTT_ERR_SUCCESS: self.topics.pop(topic) logger.debug('Unsubscribed from topic: {0}'.format(topic)) else: logger.debug('Error {0} unsubscribing from topic: {1}'.format( result, topic)) # if successful remove from topics return result, mid return None def unsubscribe_all(self): # type: () -> None """Unsubscribe from all topics.""" topics = list(self.topics.keys()) for topic in topics: self.unsubscribe(topic) def publish(self, topic, payload=None, qos=0, retain=False): # type: (str, bytes, int, bool) -> Tuple[int, int] """ Send a message to the broker. :param topic: the topic that the message should be published on :param payload: the actual message to send. If not given, or set to None a zero length message will be used. Passing an int or float will result in the payload being converted to a string representing that number. If you wish to send a true int/float, use struct.pack() to create the payload you require. :param qos: the quality of service level to use :param retain: if set to True, the message will be set as the "last known good"/retained message for the topic :returns: Returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or MQTT_ERR_NO_CONN if the client is not currently connected. mid is the message ID for the publish request. """ if not self.connected: self.client.reconnect() result, mid = self.client.publish(topic, payload, qos, retain) if result == MQTT_ERR_SUCCESS: logger.debug('Published topic {0}: {1}'.format(topic, payload)) else: logger.error('Error {0} publishing topic {1}'.format( result, topic)) return (result, mid) def on_connect(self): # type: () -> Callable """Decorator. Decorator to handle the event when the broker responds to a connection request. Only the last decorated function will be called. """ def decorator(handler): # type: (Callable) -> Callable self._connect_handler = handler return handler return decorator def on_disconnect(self): # type: () -> Callable """Decorator. Decorator to handle the event when client disconnects from broker. Only the last decorated function will be called. """ def decorator(handler): # type: (Callable) -> Callable self._disconnect_handler = handler return handler return decorator def on_message(self): # type: () -> Callable """Decorator. Decorator to handle all messages that have been subscribed and that are not handled via the `on_message` decorator. **Note:** Unlike as written in the paho mqtt documentation this callback will not be called if there exists an topic-specific callback added by the `on_topic` decorator. **Example Usage:**:: @mqtt.on_message() def handle_messages(client, userdata, message): print('Received message on topic {}: {}' .format(message.topic, message.payload.decode())) """ def decorator(handler): # type: (Callable) -> Callable self.client.on_message = handler return handler return decorator def on_publish(self): # type: () -> Callable """Decorator. Decorator to handle all messages that have been published by the client. **Example Usage:**:: @mqtt.on_publish() def handle_publish(client, userdata, mid): print('Published message with mid {}.' .format(mid)) """ def decorator(handler): # type: (Callable) -> Callable self.client.on_publish = handler return handler return decorator def on_subscribe(self): # type: () -> Callable """Decorate a callback function to handle subscritions. **Usage:**:: @mqtt.on_subscribe() def handle_subscribe(client, userdata, mid, granted_qos): print('Subscription id {} granted with qos {}.' .format(mid, granted_qos)) """ def decorator(handler): # type: (Callable) -> Callable self.client.on_subscribe = handler return handler return decorator def on_unsubscribe(self): # type: () -> Callable """Decorate a callback funtion to handle unsubscribtions. **Usage:**:: @mqtt.unsubscribe() def handle_unsubscribe(client, userdata, mid) print('Unsubscribed from topic (id: {})' .format(mid)') """ def decorator(handler): # type: (Callable) -> Callable self.client.on_unsubscribe = handler return handler return decorator def on_log(self): # type: () -> Callable """Decorate a callback function to handle MQTT logging. **Example Usage:** :: @mqtt.on_log() def handle_logging(client, userdata, level, buf): print(client, userdata, level, buf) """ def decorator(handler): # type: (Callable) -> Callable self.client.on_log = handler return handler return decorator
class Mqtt(): def __init__(self, app=None): # type: (Flask) -> None self.app = app self.client = Client() self.client.on_connect = self._handle_connect self.client.on_disconnect = self._handle_disconnect self.topics = [] # type: List[str] self.connected = False if app is not None: self.init_app(app) def init_app(self, app): # type: (Flask) -> None self.username = app.config.get('MQTT_USERNAME') self.password = app.config.get('MQTT_PASSWORD') self.broker_url = app.config.get('MQTT_BROKER_URL', 'localhost') self.broker_port = app.config.get('MQTT_BROKER_PORT', 1883) self.tls_enabled = app.config.get('MQTT_TLS_ENABLED', False) self.keepalive = app.config.get('MQTT_KEEPALIVE', 60) self.last_will_topic = app.config.get('MQTT_LAST_WILL_TOPIC') self.last_will_message = app.config.get('MQTT_LAST_WILL_MESSAGE') self.last_will_qos = app.config.get('MQTT_LAST_WILL_QOS', 0) self.last_will_retain = app.config.get('MQTT_LAST_WILL_RETAIN', False) if self.tls_enabled: self.tls_ca_certs = app.config['MQTT_TLS_CA_CERTS'] self.tls_certfile = app.config.get('MQTT_TLS_CERTFILE') self.tls_keyfile = app.config.get('MQTT_TLS_KEYFILE') self.tls_cert_reqs = app.config.get('MQTT_TLS_CERT_REQS', ssl.CERT_REQUIRED) self.tls_version = app.config.get('MQTT_TLS_VERSION', ssl.PROTOCOL_TLSv1) self.tls_ciphers = app.config.get('MQTT_TLS_CIPHERS') self.tls_insecure = app.config.get('MQTT_TLS_INSECURE', False) # set last will message if self.last_will_topic is not None: self.client.will_set(self.last_will_topic, self.last_will_message, self.last_will_qos, self.last_will_retain) self.app = app self._connect() def _connect(self): # type: () -> None if self.username is not None: self.client.username_pw_set(self.username, self.password) # security if self.tls_enabled: if self.tls_insecure: self.client.tls_insecure_set(self.tls_insecure) self.client.tls_set( ca_certs=self.tls_ca_certs, certfile=self.tls_certfile, keyfile=self.tls_keyfile, cert_reqs=self.tls_cert_reqs, tls_version=self.tls_version, ciphers=self.tls_ciphers, ) self.client.loop_start() res = self.client.connect(self.broker_url, self.broker_port, keepalive=self.keepalive) def _disconnect(self): # type: () -> None self.client.loop_stop() self.client.disconnect() def _handle_connect(self, client, userdata, flags, rc): # type: (Client, Any, Dict, int) -> None if rc == MQTT_ERR_SUCCESS: self.connected = True for topic in self.topics: self.client.subscribe(topic) def _handle_disconnect(self, client, userdata, rc): # type: (str, Any, int) -> None self.connected = False def on_topic(self, topic): # type: (str) -> Callable """ Decorator to add a callback function that is called when a certain topic has been published. The callback function is expected to have the following form: `handle_topic(client, userdata, message)` :parameter topic: a string specifying the subscription topic to subscribe to The topic still needs to be subscribed via mqtt.subscribe() before the callback function can be used to handle a certain topic. This way it is possible to subscribe and unsubscribe during runtime. **Example usage:**:: app = Flask(__name__) mqtt = Mqtt(app) mqtt.subscribe('home/mytopic') @mqtt.on_topic('home/mytopic') def handle_mytopic(client, userdata, message): print('Received message on topic {}: {}' .format(message.topic, message.payload.decode())) """ def decorator(handler): # type: (Callable[[str], None]) -> Callable[[str], None] self.client.message_callback_add(topic, handler) return handler return decorator def subscribe(self, topic, qos=0): # type: (str, int) -> tuple(int, int) """ Subscribe to a certain topic. :param topic: a string specifying the subscription topic to subscribe to. :param qos: the desired quality of service level for the subscription. Defaults to 0. :rtype: (int, int) :result: (result, mid) A topic is a UTF-8 string, which is used by the broker to filter messages for each connected client. A topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator). The function returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the client is not currently connected. mid is the message ID for the subscribe request. The mid value can be used to track the subscribe request by checking against the mid argument in the on_subscribe() callback if it is defined. **Topic example:** `myhome/groundfloor/livingroom/temperature` """ # TODO: add support for list of topics # don't subscribe if already subscribed # try to subscribe result, mid = self.client.subscribe(topic, qos) # if successful add to topics if result == MQTT_ERR_SUCCESS: if topic not in self.topics: self.topics.append(topic) return (result, mid) def unsubscribe(self, topic): # type: (str) -> tuple(int, int) """ Unsubscribe from a single topic. :param topic: a single string that is the subscription topic to unsubscribe from :rtype: (int, int) :result: (result, mid) Returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the client is not currently connected. mid is the message ID for the unsubscribe request. The mid value can be used to track the unsubscribe request by checking against the mid argument in the on_unsubscribe() callback if it is defined. """ # don't unsubscribe if not in topics if topic not in self.topics: return result, mid = self.client.unsubscribe(topic) # if successful remove from topics if result == MQTT_ERR_SUCCESS: self.topics.remove(topic) return result, mid def unsubscribe_all(self): # type: () -> None """ Unsubscribe from all topics. """ topics = self.topics[:] for topic in topics: self.unsubscribe(topic) def publish(self, topic, payload=None, qos=0, retain=False): # type: (str, bytes, int, bool) -> Tuple[int, int] """ Send a message to the broker. :param topic: the topic that the message should be published on :param payload: the actual message to send. If not given, or set to None a zero length message will be used. Passing an int or float will result in the payload being converted to a string representing that number. If you wish to send a true int/float, use struct.pack() to create the payload you require. :param qos: the quality of service level to use :param retain: if set to True, the message will be set as the "last known good"/retained message for the topic :returns: Returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or MQTT_ERR_NO_CONN if the client is not currently connected. mid is the message ID for the publish request. """ if not self.connected: self.client.reconnect() return self.client.publish(topic, payload, qos, retain) def on_message(self): # type: () -> Callable """ Decorator to handle all messages that have been subscribed and that are not handled via the `on_message` decorator. **Note:** Unlike as written in the paho mqtt documentation this callback will not be called if there exists an topic-specific callback added by the `on_topic` decorator. **Example Usage:**:: @mqtt.on_message() def handle_messages(client, userdata, message): print('Received message on topic {}: {}' .format(message.topic, message.payload.decode())) """ def decorator(handler): # type: (Callable) -> Callable self.client.on_message = handler return handler return decorator def on_publish(self): """ Decorator to handle all messages that have been published by the client. **Example Usage:**:: @mqtt.on_publish() def handle_publish(client, userdata, mid): print('Published message with mid {}.' .format(mid)) """ def decorator(handler): self.client.on_publish = handler return handler return decorator def on_subscribe(self): """ Decorator to handle subscribe callbacks. **Usage:**:: @mqtt.on_subscribe() def handle_subscribe(client, userdata, mid, granted_qos): print('Subscription id {} granted with qos {}.' .format(mid, granted_qos)) """ def decorator(handler): self.client.on_subscribe = handler return handler return decorator def on_unsubscribe(self): """ Decorator to handle unsubscribe callbacks. **Usage:**:: @mqtt.unsubscribe() def handle_unsubscribe(client, userdata, mid) print('Unsubscribed from topic (id: {})' .format(mid)') """ def decorator(handler): self.client.on_unsubscribe = handler return handler return decorator def on_log(self): # type: () -> Callable """ Decorator to handle MQTT logging. **Example Usage:** :: @mqtt.on_log() def handle_logging(client, userdata, level, buf): print(client, userdata, level, buf) """ def decorator(handler): # type: (Callable) -> Callable self.client.on_log = handler return handler return decorator
def subscribe(self, client: MQTTClient.Client): logger.info("Subscribing for topic '{}'".format(self.sub_topic)) client.subscribe(self.sub_topic) client.message_callback_add(self.sub_topic, self.on_message)
class RhasspyMQTTClient: def __init__(self, host="", port=1883, username="", password="", tls=False, cacerts=None, recording=False, jsonfolder="", logger=None): """The __init__ function of custom MQTT Class. Args: host (str) : MQTT Server name or IP. port (int) : MQTT server TCP port. logger (class:logging.Logger): Logger object for logging messages. username (str)(option) : User name to connect MQTT server. password (str)(option) : Password to connect MQTT server. tls (bool) : Use TLS to connect to MQTT server. cacerts (str) : CA path to verify the MQTT server's TLS certificate, or None for the system's default CA system. recording (bool) : save all messages as json file (and wave). jsonfolder (str) : Folder where messages are saved """ ## Properties self.host = host self.port = port self.username = username self.password = password self.tls = tls self.cacerts = cacerts self.recording = recording self.jsonfolder = jsonfolder self.logger = logger self.__dateFileFormat = '%Y%m%d%H%M%S%f' ## Dict who contains pair key/value by site ## Key is site name / Value is array of wave bytes received self.__audioFrames = {} self.__playBytes = {} ## Paho Mqtt Client self.__mqtt = Client() self.__mqtt.message_callback_add("hermes/audioServer/#", self.on_audio) ## override some methods self.__mqtt.on_connect = self.on_cnx ### GRRRR self.__mqtt.on_message = self.on_msg def __saveJson(self, payload, topic, logTime): """Save each MQTT message (not audio) as a json file """ self.logger.debug('enter in show_message method.') strFileTime = logTime.strftime(self.__dateFileFormat) ## Get payloada nd save topic info in json payload.update({"topic": topic}) ## Dump the payload to json file with open(os.path.join(self.jsonfolder, strFileTime + ".json"), 'w') as outfile: json.dump(payload, outfile) self.logger.debug("payload saved in %s.json file", strFileTime) def __saveWave(self, siteId, logTime, arrBytesWav, flux): """save all audio messages as wave files """ self.logger.debug('enter in __saveWave private method.') strFileTime = logTime.strftime(self.__dateFileFormat) ## Generate the wave file name with name, siteId and flux wave_filename = os.path.join( self.jsonfolder, strFileTime + "_" + siteId + "_" + flux + ".wav") self.logger.debug("Saving array of waves in %s.wav ", strFileTime) ## open the wave file with write access output = wave.open(wave_filename, 'wb') try: ## set wave file parameters from first wave element in array with io.BytesIO(arrBytesWav[0]) as wav_buffer: with wave.open(wav_buffer, 'rb') as w: output.setparams(w.getparams()) try: ## Extract each frames from all wave elements in arrBytesWav ## to add them to output wave file for wav in arrBytesWav: with io.BytesIO(wav) as wav_buffer: with wave.open(wav_buffer, 'rb') as w: output.writeframes(w.readframes(w.getnframes())) output.close() self.logger.debug("%s.wav saved successfully ", strFileTime) self.on_saved_wav( strFileTime + "_" + siteId + "_" + flux + ".wav", siteId, flux, logTime) except: self.logger.warning( "ERROR : Failed to extract frames from array", strFileTime) output.close() except: self.logger.warning( "ERROR : Failed to get wave param in first frames ", strFileTime) output.close() def connect(self): """Connect to the MQTT broker defined in the configuration. """ self.logger.debug('enter in connect method.') if self.username != "": self.logger.debug('Setting username and password for MQTT broker.') self.__mqtt.username_pw_set(self.username, self.password) if self.tls: self.logger.debug('Setting TLS for MQTT broker.') self.__mqtt.tls_set(ca_certs=self.cacerts) self.logger.info('Connecting to MQTT broker %s:%s...', str(self.host), str(self.port)) self.__mqtt.connect(self.host, self.port) self.__mqtt.loop_forever() def translate_message(self, payload, topic, strLogTime, outputFormat): """ 2 possibilities (actually) for output text - In human readable text - In json text format with dump of payload This method returns text in desired format """ self.logger.debug('enter in translate_message method.') if (outputFormat == "raw"): text = "{0}".format(payload) logText = "[{0}] {1} - {2}".format(strLogTime, topic, text) else: text = self.get_humanText(payload, topic) logText = "[{0}] {1}".format(strLogTime, text) return logText def show_message(self, text, outputFile, noStandardOut): """ this methods is used to manage the MQTT message display - noStandardOut : If true, nothing write to stdout - outputFile : If not empty, all MQTT message will be saved inside this file """ self.logger.debug('enter in show_message method.') if not noStandardOut: print(text) if outputFile != "": with codecs.open(outputFile, 'a', encoding='utf8') as f: f.write(text + "\n") f.close() def get_humanText(self, payload, topic): """ This method translate Rhasspy/snips MQTT message as human readable text Maybe all topics are not processed. """ self.logger.debug('enter in get_humanText method.') self.logger.debug('Topic : {0}'.format(topic)) ######################## # HOTWORD # ######################## if "hermes/hotword/toggleOn" in topic: text = colored("[hotword]",'magenta') + \ " was asked to toggle itself 'on' on site {0}"\ .format (colored(payload['siteId'],'white',attrs=['bold'])) elif "hermes/hotword/toggleOff" in topic: text = colored("[hotword]",'magenta') + \ " was asked to toggle itself 'off' on site {0}"\ .format (colored(payload['siteId'],'white',attrs=['bold'])) elif ("hermes/hotword/" in topic) and ("/detected" in topic): text = colored("[hotword]",'yellow') + \ " detected on site {0}, for model {1}"\ .format(colored(payload['siteId'],'white',attrs=['bold']), payload['modelId']) ######################## # ASR # ######################## elif "hermes/asr/stopListening" in topic: text = colored("[Asr]",'magenta') + \ " was asked to stop listening on site {0}"\ .format (colored(payload['siteId'],'white',attrs=['bold'])) elif ("hermes/asr/startListening" in topic): text = colored("[Asr]",'magenta') + \ " was asked to listen on site {0}"\ .format (colored(payload['siteId'],'white',attrs=['bold'])) elif ("hermes/asr/textCaptured" in topic): text = colored("[Asr]",'yellow') + \ " captured text '{0}' in {1}s on site {2}"\ .format(colored(payload['text'],'green',attrs=['bold']), payload['seconds'], colored(payload['siteId'],'white',attrs=['bold'])) elif "hermes/asr/toggleOn" in topic: text = colored("[Asr]",'magenta') + \ " was asked to toggle itself 'on' on site {0}"\ .format (colored(payload['siteId'],'white',attrs=['bold'])) elif "hermes/asr/toggleOff" in topic: text = colored("[Asr]",'magenta') + \ " was asked to toggle itself 'off' on site {0}"\ .format (colored(payload['siteId'],'white',attrs=['bold'])) ######################## # DIALOGUE MANAGER # ######################## elif ("hermes/dialogueManager/sessionStarted" in topic): text = colored("[Dialogue]",'yellow') \ + " session with id {0} was started on site {1}."\ .format(payload['sessionId'], colored(payload['siteId'],'white',attrs=['bold'])) elif ("hermes/dialogueManager/sessionEnded" in topic): text = colored("[Dialogue]",'yellow') + \ " session with id {0} was ended on site {1}. Reason: {2}"\ .format(payload['sessionId'], colored(payload['siteId'],'white',attrs=['bold']), payload['termination']['reason']) if 'customData' in payload.keys(): if payload['customData'] is not None: text = text + "\n with customData : " text = text + "\n {0} "\ .format(colored(payload['customData'],'cyan', attrs=['bold'])) elif ("hermes/dialogueManager/endSession" in topic): text = colored("[Dialogue]",'magenta') + \ " was ask to end session with id {0} by saying '{1}'"\ .format(payload['sessionId'], (payload['text'])) elif ("hermes/dialogueManager/continueSession" in topic): text = colored("[Dialogue]",'magenta') + \ " was ask to continue session with id {0} by saying '{1}'"\ .format(payload['sessionId'], (payload['text'])) if 'customData' in payload.keys(): if payload['customData'] is not None: text = text + "\n with customData : " text = text + "\n {0}"\ .format(colored(payload['customData'],'cyan', attrs=['bold'])) elif ("hermes/dialogueManager/intentNotRecognized" in topic): text = colored("[Dialogue]",'red') + \ " Intent NOT recognized for session with id {0} by saying '{1}'"\ .format(payload['sessionId'], (payload['input'])) if 'customData' in payload.keys(): if payload['customData'] is not None: text = text + "\n with customData : " text = text + "\n {0}"\ .format(colored(payload['customData'],'cyan', attrs=['bold'])) ######################## # NLU # ######################## elif ("hermes/nlu/query" in topic): text = colored("[Nlu]",'magenta') + \ " was asked to parse input '{0}'"\ .format(payload['input']) elif ("hermes/nlu/intentNotRecognized" in topic): text = colored("[Nlu]",'yellow') + \ " Intent not recognized for {0}"\ .format(colored(payload['input'],'red', attrs=['bold'])) elif ("hermes/nlu/intentParsed" in topic): text = colored("[Nlu]",'yellow') + \ " Detected intent {0} with confidence score {1} for input '{2}'"\ .format(colored(payload["intent"]["intentName"],'green', attrs=['bold']), payload["intent"]["confidenceScore"], payload['input']) ######################## # INTENT # ######################## elif ("hermes/intent/" in topic): text = colored("[Nlu]",'yellow') + \ " Intent {0} with confidence score {1} on site {2} "\ .format(colored(payload["intent"]["intentName"],'green', attrs=['bold']), payload["intent"]["confidenceScore"], colored(payload['siteId'],'white',attrs=['bold'])) """ In snips, in slot, the word is "confidenceScore" In rhasspy, in slot, the word is "confidence" """ if len(payload['slots']) > 0: text = text + "\n with slots : " for slot in payload['slots']: confidence = "N/A" if "confidenceScore" in slot.keys(): confidence = slot['confidenceScore'] else: confidence = slot['confidence'] text = text + "\n {0} => {1} (confidenceScore={2})"\ .format(colored(slot['slotName'],'cyan', attrs=['bold']), slot['value']['value'], confidence) if 'customData' in payload.keys(): if payload['customData'] is not None: text = text + "\n with customData : " text = text + "\n {0} "\ .format(colored(payload['customData'],'cyan', attrs=['bold'])) ######################## # TTS # ######################## elif ("hermes/tts/say" == topic): text = colored("[Tts]",'yellow') + \ " was asked to say '{0}' in {1} on site {2}".\ format(colored(payload['text'],'green', attrs=['bold']), payload['lang'], colored(payload['siteId'],'white',attrs=['bold'])) elif ("hermes/tts/sayFinished" in topic): text = colored("[Tts]",'cyan') + \ " finished speaking with id '{0}'"\ .format(payload['sessionId']) ######################## # AUDIO SERVER # ######################## elif ("hermes/audioServer" in topic): text = colored("[audioServer]",'cyan') + \ " audio on topic {0}".format(topic) ######################## # UNKNOWN # ######################## else: self.logger.warning('Unknow topic : {0}'.format(topic)) text = colored("[UNKNOWN]",'red') + \ " message on topic {0}".format(topic) return text def search_message(self, datestart, datestop, siteId, jsonfolder, searchoutputFormat, outputFile): """ This method allow to query all MQTT messages saved as file """ self.logger.debug('enter in search_message method.') ## Get list of all json files sorted by name (so by datetime) allJsonFiles = os.listdir(jsonfolder) allJsonFiles.sort() ## For each file, check if it's between the start and stop search date for filename in allJsonFiles: ## Get extension and name of file filenameWithoutExt, extension = ( os.path.basename(filename)).split(".") self.logger.debug('filename %s - ext %s', filenameWithoutExt, extension) if extension == "wav": """ If extension is .wav, so in name, there are : myDate = date time when the wav was saved siteId = The site of Rhasspy/snips flux = if it's input or output wave file input : play on the siteId output : record from the siteId Ex wave filename : 20200429195055646804_bureau_play.wav """ ## Get date, siteId, flux strDate, siteId, flux = filenameWithoutExt.split("_") myDate = datetime.strptime(strDate, self.__dateFileFormat) ## We check if file date is between start and stop search date if datestart <= myDate <= datestop: self.logger.debug( 'WAV : strDate : %s - siteId : %s - flux : %s', strDate, siteId, flux) ## If yes, call the on_saved_wav self.on_saved_wav(filename, siteId, flux, myDate) elif extension == "json": ## Get date strDate = filenameWithoutExt.split("_")[0] myDate = datetime.strptime(strDate, self.__dateFileFormat) ## As script knows the format, it can retrieve the date as datetime type myDate = datetime.strptime(filenameWithoutExt, self.__dateFileFormat) ## We check if file date is between start and stop date search if datestart <= myDate <= datestop: ## The file is concerned by search. Script open it and load json with open(os.path.join(jsonfolder, filename)) as json_file: payload = json.load(json_file) ## Remove the 'topic' json element imported when json file was saved ## the 'topic' element is added to file only to retieve information topic = bytes(payload['topic'], 'utf-8') payload.pop('topic', None) ## Create a paho MQTT message myMQTTmessage = MQTTMessage(mid=0, topic=topic) myMQTTmessage.payload = json.dumps(payload).encode('utf-8') ## call on_message method and pass the MQTT message self.on_message(None, None, myMQTTmessage, myDate) def on_audio(self, client, userdata, msg): """ Specific method to intercept audio MQTT message and save data in a array of bytearray. The entire array will be dump in a wav file on specific MQTT message """ self.logger.debug('enter in on_audio method. (read audio stream)') currentTime = datetime.now() if self.recording: siteId = ((msg.topic).split("/"))[2] flux = ((msg.topic).split("/"))[3] ## If it's record stream if flux == "audioFrame": if siteId not in self.__audioFrames: self.__audioFrames[siteId] = [] ## Add this piece of wave in an array of bytes (self.__audioFrames[siteId]).append(bytearray(msg.payload)) ## If it's a play stream if flux == "playBytesStreaming": if siteId not in self.__playBytes: self.__playBytes[siteId] = [] (self.__playBytes[siteId]).append(bytearray(msg.payload)) ## when topic contains "playBytes" or "streamFinished", it means ## play stream has stopped on rhasspy. So the wav file can be saved. ## Use <IS_LAST_CHUNK> instead ? if (flux == "playBytes") or (flux == "streamFinished"): self.logger.debug("fin streaming on site %s", siteId) if (siteId not in self.__playBytes) or (len( self.__playBytes[siteId]) == 0): self.__playBytes[siteId] = [] (self.__playBytes[siteId]).append(bytearray(msg.payload)) count_item = len(self.__playBytes[siteId]) self.logger.debug("count of item in array %s", str(count_item)) if len(self.__playBytes[siteId]) > 0: self.__saveWave(siteId, currentTime, self.__playBytes[siteId], 'play') self.__playBytes[siteId] = [] def on_msg(self, client, userdata, msg): """The on_message callback of paho MQTT client is intercepted by this on_msg method before the propagation of MQTT message. Here, we can : - Save payload to json if porperty recording is True - Save output wave file when specific message is received - Save input wave file when specific message is received - propagate the MQTT message to on_message method of our custom MQTT class """ self.logger.debug('enter in on_msg method.') currentTime = datetime.now() ## If MQTT message has to be saved in file if self.recording: ## If message does not come from audioServer ## the message is saved as json file if "hermes/audioServer/" not in msg.topic: payload = json.loads(msg.payload.decode('utf8')) self.__saveJson(payload, msg.topic, currentTime) ## If "textCaptured" is in topic, it means ASR stop ## to record from Rhasspy. So the wav file can be saved. if "hermes/asr/textCaptured" in msg.topic: self.__saveWave(payload['siteId'], currentTime, self.__audioFrames[payload['siteId']], 'record') self.__audioFrames[payload['siteId']] = [] ## Propagate the message MQTT self.on_message(client, userdata, msg, currentTime) ## Not good enough in python to avoid this :/ def on_cnx(self, client, userdata, flags, result_code): self.on_connect(client=client, userdata=userdata, flags=flags, result_code=result_code) def subscribe(self, topic): """Method used to subscribe to private MQTT object topic """ self.__mqtt.subscribe(topic) def on_message(self, client, userdata, msg, logTime): """Event method """ def on_connect(self, client, userdata, flags, result_code): """Event method """ def on_saved_wav(self, filename, siteId, flux, logTime): """Event method """
class LampiApp(App): _updated = False _updatingUI = False _hue = NumericProperty() _saturation = NumericProperty() _brightness = NumericProperty() lamp_is_on = BooleanProperty() def _get_hue(self): return self._hue def _set_hue(self, value): self._hue = value def _get_saturation(self): return self._saturation def _set_saturation(self, value): self._saturation = value def _get_brightness(self): return self._brightness def _set_brightness(self, value): self._brightness = value hue = AliasProperty(_get_hue, _set_hue, bind=['_hue']) saturation = AliasProperty(_get_saturation, _set_saturation, bind=['_saturation']) brightness = AliasProperty(_get_brightness, _set_brightness, bind=['_brightness']) gpio17_pressed = BooleanProperty(False) device_associated = BooleanProperty(True) def on_start(self): self.keen = KeenEventRecorder(PROJECT_ID, WRITE_KEY, get_device_id()) self._publish_clock = None self.mqtt_broker_bridged = False self._associated = True self.association_code = None self.mqtt = Client(client_id=MQTT_CLIENT_ID) self.mqtt.will_set(client_state_topic(MQTT_CLIENT_ID), "0", qos=2, retain=True) self.mqtt.on_connect = self.on_connect self.mqtt.connect(MQTT_BROKER_HOST, port=MQTT_BROKER_PORT, keepalive=MQTT_BROKER_KEEP_ALIVE_SECS) self.mqtt.loop_start() self.set_up_GPIO_and_device_status_popup() self.associated_status_popup = self._build_associated_status_popup() self.associated_status_popup.bind(on_open=self.update_popup_associated) Clock.schedule_interval(self._poll_associated, 0.1) def _build_associated_status_popup(self): return Popup(title='Associate your Lamp', content=Label(text='Msg here', font_size='30sp'), size_hint=(1, 1), auto_dismiss=False) def on_hue(self, instance, value): if self._updatingUI: return evt = self._create_event_record('hue-slider', value) self.keen.record_event('ui', evt) if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_saturation(self, instance, value): if self._updatingUI: return evt = self._create_event_record('saturation-slider', value) self.keen.record_event('ui', evt) if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_brightness(self, instance, value): if self._updatingUI: return evt = self._create_event_record('brightness-slider', value) self.keen.record_event('ui', evt) if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_lamp_is_on(self, instance, value): if self._updatingUI: return evt = self._create_event_record('power', value) self.keen.record_event('ui', evt) if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def _create_event_record(self, element, value): return {'element': {'id': element, 'value': value}} def on_connect(self, client, userdata, flags, rc): self.mqtt.publish(client_state_topic(MQTT_CLIENT_ID), "1", qos=2, retain=True) self.mqtt.message_callback_add(TOPIC_LAMP_CHANGE_NOTIFICATION, self.receive_new_lamp_state) self.mqtt.message_callback_add(broker_bridge_connection_topic(), self.receive_bridge_connection_status) self.mqtt.message_callback_add(TOPIC_LAMP_ASSOCIATED, self.receive_associated) self.mqtt.subscribe(broker_bridge_connection_topic(), qos=1) self.mqtt.subscribe(TOPIC_LAMP_CHANGE_NOTIFICATION, qos=1) self.mqtt.subscribe(TOPIC_LAMP_ASSOCIATED, qos=2) def _poll_associated(self, dt): # this polling loop allows us to synchronize changes from the # MQTT callbacks (which happen in a different thread) to the # Kivy UI self.device_associated = self._associated def receive_associated(self, client, userdata, message): # this is called in MQTT event loop thread new_associated = json.loads(message.payload) if self._associated != new_associated['associated']: if not new_associated['associated']: self.association_code = new_associated['code'] else: self.association_code = None self._associated = new_associated['associated'] def on_device_associated(self, instance, value): if value: self.associated_status_popup.dismiss() else: self.associated_status_popup.open() def update_popup_associated(self, instance): code = self.association_code[0:6] instance.content.text = ("Please use the\n" "following code\n" "to associate\n" "your device\n" "on the Web\n{}".format(code)) def receive_bridge_connection_status(self, client, userdata, message): # monitor if the MQTT bridge to our cloud broker is up if message.payload == "1": self.mqtt_broker_bridged = True else: self.mqtt_broker_bridged = False def receive_new_lamp_state(self, client, userdata, message): new_state = json.loads(message.payload) Clock.schedule_once(lambda dt: self._update_ui(new_state), 0.01) def _update_ui(self, new_state): if self._updated and new_state['client'] == MQTT_CLIENT_ID: # ignore updates generated by this client, except the first to # make sure the UI is syncrhonized with the lamp_service return self._updatingUI = True try: if 'color' in new_state: self.hue = new_state['color']['h'] self.saturation = new_state['color']['s'] if 'brightness' in new_state: self.brightness = new_state['brightness'] if 'on' in new_state: self.lamp_is_on = new_state['on'] finally: self._updatingUI = False self._updated = True def _update_leds(self): msg = { 'color': { 'h': self._hue, 's': self._saturation }, 'brightness': self._brightness, 'on': self.lamp_is_on, 'client': MQTT_CLIENT_ID } self.mqtt.publish(TOPIC_SET_LAMP_CONFIG, json.dumps(msg), qos=1) self._publish_clock = None def set_up_GPIO_and_device_status_popup(self): self.pi = pigpio.pi() self.pi.set_mode(17, pigpio.INPUT) self.pi.set_pull_up_down(17, pigpio.PUD_UP) Clock.schedule_interval(self._poll_GPIO, 0.05) self.network_status_popup = self._build_network_status_popup() self.network_status_popup.bind(on_open=self.update_device_status_popup) def _build_network_status_popup(self): return Popup(title='Device Status', content=Label(text='IP ADDRESS WILL GO HERE'), size_hint=(1, 1), auto_dismiss=False) def update_device_status_popup(self, instance): interface = "wlan0" ipaddr = lampi_util.get_ip_address(interface) deviceid = lampi_util.get_device_id() msg = ("Version: {}\n" "{}: {}\n" "DeviceID: {}\n" "Broker Bridged: {}\n" "threaded").format(LAMPI_APP_VERSION, interface, ipaddr, deviceid, self.mqtt_broker_bridged) instance.content.text = msg def on_gpio17_pressed(self, instance, value): if value: self.network_status_popup.open() else: self.network_status_popup.dismiss() def _poll_GPIO(self, dt): # GPIO17 is the rightmost button when looking front of LAMPI self.gpio17_pressed = not self.pi.read(17)
class MqttClient(): default_config = { "host": "api.raptorbox.eu", "port": 1883, "reconnect_min_delay": 1, "reconnect_max_delay": 127, } def __init__(self, config): ''' Initialize information for the client provide configurations as parameter, if None provided the configurations would be defaults see MqttClient.default_config for configurations format ''' self.config = MqttClient.default_config if config: for k, v in config.iteritems(): self.config[k] = v self.mqtt_client = Client() self.mqtt_client.username_pw_set(config["username"], config["password"]) if "reconnect_min_delay" in config and "reconnect_max_delay" in config: self.mqtt_client.reconnect_delay_set( config["reconnect_min_delay"], config["reconnect_max_delay"], ) # self.connection_thread = None # self.mqtt_client.tls_set() def connect(self): #TODO consider srv field in dns ''' connect the client to mqtt server with configurations provided by constructor and starts listening for topics ''' self.mqtt_client.connect(self.config["host"], self.config["port"]) # self.mqtt_client.loop_forever() self.mqtt_client.loop_start() def subscribe(self, topic, callback): ''' register 'callback' to "topic". Every message will be passed to callback in order to be executed ''' logging.debug("subscribing to topic: {}".format(topic)) self.mqtt_client.subscribe(topic) self.mqtt_client.message_callback_add(topic, callback) def unsubscribe(self, topic): ''' unsubscribe to topic topic could be either a string or al list of strings containing to topics to unsubscribe to. Returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the client is not currently connected. mid is the message ID for the unsubscribe request. The mid value can be used to track the unsubscribe request by checking against the mid argument in the on_unsubscribe() callback if it is defined. ''' return self.mqtt_client.unsubscribe(topic) def disconnect(self): ''' Disconnects from the server ''' self.mqtt_client.disconnect() def reconnect(self): ''' Reconnects after a disconnection, this could be called after connection only ''' self.mqtt_client.reconnect()
class MQTTConnection(): client: Client _instance = None @classmethod def get_instance(cls) -> "MQTTConnection": if cls._instance is None: cls._instance = MQTTConnection() return cls._instance def __init__(self): self.client = Client( "pai" + os.urandom(8).hex(), protocol=protocol_map.get(str(cfg.MQTT_PROTOCOL), MQTTv311), transport=cfg.MQTT_TRANSPORT, ) self._last_pai_status = "unknown" self.pai_status_topic = "{}/{}/{}".format(cfg.MQTT_BASE_TOPIC, cfg.MQTT_INTERFACE_TOPIC, "pai_status") self.availability_topic = "{}/{}/{}".format(cfg.MQTT_BASE_TOPIC, cfg.MQTT_INTERFACE_TOPIC, "availability") self.client.on_connect = self._on_connect_cb self.client.on_disconnect = self._on_disconnect_cb self.state = ConnectionState.NEW # self.client.enable_logger(logger) # self.client.on_subscribe = lambda client, userdata, mid, granted_qos: logger.debug("Subscribed: %s" %(mid)) # self.client.on_message = lambda client, userdata, message: logger.debug("Message received: %s" % str(message)) # self.client.on_publish = lambda client, userdata, mid: logger.debug("Message published: %s" % str(mid)) ps.subscribe(self.on_run_state_change, "run-state") self.registrars = [] if cfg.MQTT_USERNAME is not None and cfg.MQTT_PASSWORD is not None: self.client.username_pw_set(username=cfg.MQTT_USERNAME, password=cfg.MQTT_PASSWORD) if cfg.MQTT_TLS_CERT_PATH is not None: self.client.tls_set( ca_certs=cfg.MQTT_TLS_CERT_PATH, certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None, ) self.client.tls_insecure_set(False) self.client.will_set(self.availability_topic, "offline", 0, retain=True) self.client.on_log = self.on_client_log def on_client_log(self, client, userdata, level, buf): level_std = LOGGING_LEVEL[level] exc_info = None type_, exc, trace = sys.exc_info() if exc: # Can be (socket.error, OSError, WebsocketConnectionError, ...) if hasattr(exc, "errno"): exc_msg = f"{os.strerror(exc.errno)}({exc.errno})" if exc.errno in [22, 49]: level_std = logging.ERROR buf = f"{buf}: Please check MQTT connection settings. Especially MQTT_BIND_ADDRESS and MQTT_BIND_PORT" else: exc_msg = str(exc) buf = f"{buf}: {exc_msg}" if "Connection failed" in buf: level_std = logging.WARNING if level_std > logging.DEBUG: logger.log(level_std, buf, exc_info=exc_info) def on_run_state_change(self, state: RunState): v = RUN_STATE_2_PAYLOAD.get(state, "unknown") self._report_pai_status(v) def start(self): if self.state == ConnectionState.NEW: self.client.loop_start() # TODO: Some initial connection retry mechanism required try: self.client.connect_async( host=cfg.MQTT_HOST, port=cfg.MQTT_PORT, keepalive=cfg.MQTT_KEEPALIVE, bind_address=cfg.MQTT_BIND_ADDRESS, bind_port=cfg.MQTT_BIND_PORT, ) self.state = ConnectionState.CONNECTING logger.info("MQTT loop started") except socket.gaierror: logger.exception("Failed to connect to MQTT (%s:%d)", cfg.MQTT_HOST, cfg.MQTT_PORT) def stop(self): if self.state in [ ConnectionState.CONNECTING, ConnectionState.CONNECTED ]: self.disconnect() self.client.loop_stop() logger.info("MQTT loop stopped") def publish(self, topic, payload=None, *args, **kwargs): logger.debug("MQTT: {}={}".format(topic, payload)) self.client.publish(topic, payload, *args, **kwargs) def _call_registars(self, method, *args, **kwargs): for r in self.registrars: try: if hasattr(r, method) and isinstance(getattr(r, method), typing.Callable): getattr(r, method)(*args, **kwargs) except: logger.exception('Failed to call "%s" on "%s"', method, r.__class__.__name__) def register(self, cls): self.registrars.append(cls) self.start() def unregister(self, cls): self.registrars.remove(cls) if len(self.registrars) == 0: self.stop() @property def connected(self): return self.state == ConnectionState.CONNECTED def _report_pai_status(self, status): self._last_pai_status = status self.publish(self.pai_status_topic, status, qos=cfg.MQTT_QOS, retain=True) self.publish( self.availability_topic, "online" if status in ["online", "paused"] else "offline", qos=cfg.MQTT_QOS, retain=True, ) def _on_connect_cb(self, client, userdata, flags, result, properties=None): # called on Thread-6 if result == MQTT_ERR_SUCCESS: logger.info("MQTT Broker Connected") self.state = ConnectionState.CONNECTED self._report_pai_status(self._last_pai_status) self._call_registars("on_connect", client, userdata, flags, result) else: logger.error( f"Failed to connect to MQTT: {connack_string(result)} ({result})" ) def _on_disconnect_cb(self, client, userdata, rc): # called on Thread-6 if rc == MQTT_ERR_SUCCESS: logger.info("MQTT Broker Disconnected") else: logger.error(f"MQTT Broker unexpectedly disconnected. Code: {rc}") self.state = ConnectionState.NEW self._call_registars("on_disconnect", client, userdata, rc) def disconnect(self, reasoncode=None, properties=None): self.state = ConnectionState.DISCONNECTING self._report_pai_status("offline") self.client.disconnect() def message_callback_add(self, *args, **kwargs): self.client.message_callback_add(*args, **kwargs) def subscribe(self, *args, **kwargs): self.client.subscribe(*args, **kwargs)
class Bridge(object): WEBSOCKETS = "websocket" SSE = "sse" def __init__(self, args, ioloop, dynamic_subscriptions): """ parse config values and setup Routes. """ self.mqtt_topics = [] try: self.mqtt_host = args["mqtt-to-server"]["broker"]["host"] self.mqtt_port = args["mqtt-to-server"]["broker"]["port"] self.bridge_port = args["server-to-client"]["port"] self.stream_protocol = args["server-to-client"]["protocol"] logger.info("Using protocol %s" % self.stream_protocol.lower()) if self.stream_protocol.lower( ) != "websocket" and self.stream_protocol.lower() != "sse": raise ConfigException("Invalid protocol") self.dynamic_subscriptions = dynamic_subscriptions if not self.dynamic_subscriptions: self.mqtt_topics = args["mqtt-to-server"]["topics"] except KeyError as e: raise ConfigException("Error when accessing field", e) from e logger.info("connecting to mqtt") self.topic_dict = {} self.ioloop = ioloop self.mqtt_client = Client() self.mqtt_client.on_message = self.on_mqtt_message self.mqtt_client.on_connect = self.on_mqtt_connect self.mqtt_client.connect_async(host=self.mqtt_host, port=self.mqtt_port) self.mqtt_client.loop_start() self._app = web.Application([ (r'.*', WebsocketHandler if self.stream_protocol == "websocket" else ServeSideEventsHandler, dict(parent=self)), ], debug=True) def get_app(self): return self._app def get_port(self): return self.bridge_port async def parse_req_path(self, req_path): candidate_path = req_path if len(candidate_path) is 1 and candidate_path[0] is "/": return "#" if candidate_path[len(req_path) - 1] is "/": candidate_path = candidate_path + "#" if candidate_path[0] == "/": candidate_path = candidate_path[1:] return candidate_path def on_mqtt_message(self, client, userdata, message): logger.debug("received message on topic %s" % message.topic) async def socket_write_message(self, socket, message): try: await socket.write_message(json.dumps(message)) except Exception as e: logger.error(e) try: await socket.write_message(message) except Exception as e: logger.error(e) def append_dynamic(self, topic): logger.info("adding dynamic subscription for %s " % topic) self.message_callback_add_with_sub_topic(topic, dynamic=True) def remove_dynamic(self, topic): logger.info("removing dynamic subscription for %s " % topic) self.topic_dict.pop(topic, None) self.mqtt_client.unsubscribe(topic) def message_callback_add_with_sub_topic(self, sub_topic, dynamic): logger.info("adding callback for mqtt topic: %s" % sub_topic) def message_callback(client, userdata, message): logger.debug( "Recieved Mqtt Message on %s as result of subscription on %s" % (message.topic, sub_topic)) if sub_topic is not message.topic: if message.topic not in self.topic_dict[sub_topic]["matches"]: self.topic_dict[sub_topic]["matches"].append(message.topic) for topic in self.topic_dict: if topic == message.topic: for socket in self.topic_dict[topic]["sockets"]: logger.debug("sending to socket:") logger.debug(socket) self.ioloop.add_callback( self.socket_write_message, socket=socket, message={ "topic": message.topic, "payload": message.payload.decode('utf-8') }) elif message.topic in self.topic_dict[topic]["matches"]: for socket in self.topic_dict[topic]["sockets"]: logger.debug("sending to socket:") logger.debug(socket) self.ioloop.add_callback( self.socket_write_message, socket=socket, message={ "topic": message.topic, "payload": message.payload.decode('utf-8') }) if sub_topic not in self.topic_dict: self.mqtt_client.message_callback_add(sub_topic, message_callback) self.topic_dict[sub_topic] = { "matches": [], "sockets": [], "dynamic": dynamic } self.mqtt_client.subscribe(sub_topic) def on_mqtt_connect(self, client, userdata, flags, rc): logger.info("mqtt connected to broker %s:%s" % (self.mqtt_host, str(self.mqtt_port))) for topic in self.mqtt_topics: self.message_callback_add_with_sub_topic(topic, dynamic=False) async def mqtt_disconnect(self): t = Thread(target=self.mqtt_client.disconnect, daemon=True) t.start() self.mqtt_client.loop_stop()
def addTempReference(self, mqttclient: MqttClient, tempRefTopic: str, factor: float): if tempRefTopic not in self.tempReferences: mqttclient.message_callback_add(tempRefTopic, self.onTempRefValue) mqttclient.subscribe(tempRefTopic) self.tempReferences[tempRefTopic] = factor
from paho.mqtt.client import Client client = Client() client.username_pw_set('RpiLights', '649t15Db#@4Z') client.enable_logger() def topic_callback(client, userdata, message): print('topic_callback', message) if message.payload.decode('utf-8') == 'on': client.publish('topic2', payload='off') def on_connect(client, userdata, flags, rc): print("Connected") client.subscribe('topic1') client.message_callback_add('topic1', topic_callback) client.on_connect = on_connect client.connect('cloud.iot-playground.com', port=10048) print(str(client)) client.subscribe('topic1') client.publish('topic2', payload=1) client.loop_forever()
class LampiApp(App): _updated = False _updatingUI = False _hue = NumericProperty() _saturation = NumericProperty() _brightness = NumericProperty() _preset = NumericProperty() lamp_is_on = BooleanProperty() _preset_color = NumericProperty() _preset_temp = NumericProperty() remote_connection = StringProperty("[b]Connected:[/b] No") trusted_remotes = StringProperty("[b]Trusted Remotes:[/b] None") mp = Mixpanel(MIXPANEL_TOKEN, consumer=AsyncBufferedConsumer()) def _get_hue(self): return self._hue def _set_hue(self, value): self._hue = value def _get_saturation(self): return self._saturation def _set_saturation(self, value): self._saturation = value def _get_brightness(self): return self._brightness def _set_brightness(self, value): self._brightness = value def _get_preset(self): return self._preset def _set_preset(self, value): self._preset = value def _get_preset_color(self): return self._preset_color def _set_preset_color(self, value): self._preset_color = value def _get_preset_temp(self): return self._preset_temp def _set_preset_temp(self, value): self._preset_temp = value hue = AliasProperty(_get_hue, _set_hue, bind=['_hue']) saturation = AliasProperty(_get_saturation, _set_saturation, bind=['_saturation']) brightness = AliasProperty(_get_brightness, _set_brightness, bind=['_brightness']) preset = AliasProperty(_get_preset, _set_preset, bind=['_preset']) preset_color = AliasProperty(_get_preset_color, _set_preset_color, bind=['_preset_color']) preset_temp = AliasProperty(_get_preset_temp, _set_preset_temp, bind=['_preset_temp']) gpio17_pressed = BooleanProperty(False) device_associated = BooleanProperty(True) def on_start(self): self._publish_clock = None self.mqtt_broker_bridged = False self._associated = True self.association_code = None self.mqtt = Client(client_id=MQTT_CLIENT_ID) self.mqtt.enable_logger() self.mqtt.will_set(client_state_topic(MQTT_CLIENT_ID), "0", qos=2, retain=True) self.mqtt.on_connect = self.on_connect self.mqtt.connect(MQTT_BROKER_HOST, port=MQTT_BROKER_PORT, keepalive=MQTT_BROKER_KEEP_ALIVE_SECS) self.mqtt.loop_start() self.set_up_GPIO_and_device_status_popup() self.associated_status_popup = self._build_associated_status_popup() self.associated_status_popup.bind(on_open=self.update_popup_associated) self._remote = None self._popup_remote = None self.pairing_popup = self._build_pairing_popup() self._update_remotes_ui() self.discoverswitch = self.root.ids.discoverswitch self.discoverswitch.bind(active=self.toggle_discovery) Clock.schedule_interval(self._poll_associated, 0.1) def _build_associated_status_popup(self): return Popup(title='Associate your Lamp', content=Label(text='Msg here', font_size='30sp'), size_hint=(1, 1), auto_dismiss=False) def _build_pairing_popup(self): layout = StackLayout() label = Label( text= 'A new remote is attempting\nto connect to your lamp.\n\nWould you like to\nallow it?', size_hint=(1, None), padding=(4, 4)) label.bind(size=self._update_textsize) deny = Button(text='Deny', size_hint=(0.49, None), height=40) allow = Button(text='Trust', size_hint=(0.49, None), height=40) allow.on_release = self._allow_remote deny.on_release = self._decline_remote layout.add_widget(label) layout.add_widget(Label(size_hint=(1, None), height=15)) layout.add_widget(deny) layout.add_widget(Label(size_hint=(0.02, None), height=1)) layout.add_widget(allow) return Popup(title='Remote Pairing Request', content=layout, size_hint=(1, 0.68), auto_dismiss=False) def _update_textsize(self, instance, value): instance.text_size = (value[0], value[1]) def on_new_remote(self, client, userdata, message): isEmpty = message.payload == b'' if isEmpty: self._remote = None else: remote = json.loads(message.payload.decode('utf-8')) self._remote = remote self._popup_remote = remote if (not remote['allowed']): self.pairing_popup.open() self._update_remotes_ui() def _allow_remote(self): print("Pairing allowed for {}".format(self._popup_remote['address'])) remotes.saveAddress(self._popup_remote['address']) self._remote = None self._popup_remote = None self.pairing_popup.dismiss() self._update_remotes_ui() # Display confirmation conf = Popup( title='Remote Trusted', content=Label( text= 'You have successfully trusted\nyour remote. Pair it again to\nuse it' ), size_hint=(1, 0.5), auto_dismiss=False) conf.open() Clock.schedule_once(lambda dt: conf.dismiss(), 3) def _decline_remote(self): print("Pairing denied for {}".format(self._popup_remote['address'])) self._popup_remote = None self._remote = None self.pairing_popup.dismiss() self._update_remotes_ui() def clear_remotes(self): remotes.clear() self.mqtt.publish(DISCONNECT_TOPIC, b'') self._update_remotes_ui() def toggle_discovery(self, instance, value): # Send message accordingly self.mqtt.publish(DISCOVERY_TOPIC, ("true" if value else "false").encode('utf8'), retain=True) def _update_remotes_ui(self): savedremotes = remotes._read() statustext = "[b]Connected:[/b] False\n\n" if (self._remote is not None): self.remote_connection = "[b]Connected:[/b] [color=32ff32]{}[/color]".format( self._remote['address']) else: self.remote_connection = "[b]Connected:[/b] [color=ff3232]Not connected[/color]" if (len(savedremotes) == 0): self.trusted_remotes = "[b]Trusted Remotes:[/b] None" else: self.trusted_remotes = "[b]Trusted Remotes:[/b]\n" + "\n".join( [" • {}".format(addr) for addr in savedremotes]) def on_hue(self, instance, value): if self._updatingUI: return self._track_ui_event('Slider Change', { 'slider': 'hue-slider', 'value': value }) if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_saturation(self, instance, value): if self._updatingUI: return self._track_ui_event('Slider Change', { 'slider': 'saturation-slider', 'value': value }) if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_brightness(self, instance, value): if self._updatingUI: return self._track_ui_event('Slider Change', { 'slider': 'brightness-slider', 'value': value }) if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_lamp_is_on(self, instance, value): if self._updatingUI: return self._track_ui_event('Toggle Power', {'isOn': value}) if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_preset_temp(self, instance, value): if self._updatingUI: return self._track_ui_event('Slider Change', { 'slider': 'preset_hue_slider', 'value': value }) def _track_ui_event(self, event_name, additional_props={}): device_id = lampi.lampi_util.get_device_id() event_props = { 'event_type': 'ui', 'interface': 'lampi', 'device_id': device_id } event_props.update(additional_props) self.mp.track(device_id, event_name, event_props) def on_connect(self, client, userdata, flags, rc): self.mqtt.publish(client_state_topic(MQTT_CLIENT_ID), b"1", qos=2, retain=True) self.mqtt.message_callback_add(TOPIC_LAMP_CHANGE_NOTIFICATION, self.receive_new_lamp_state) self.mqtt.message_callback_add(broker_bridge_connection_topic(), self.receive_bridge_connection_status) self.mqtt.message_callback_add(TOPIC_LAMP_ASSOCIATED, self.receive_associated) self.mqtt.message_callback_add(NEW_REMOTE_TOPIC, self.on_new_remote) self.mqtt.subscribe(broker_bridge_connection_topic(), qos=1) self.mqtt.subscribe(TOPIC_LAMP_CHANGE_NOTIFICATION, qos=1) self.mqtt.subscribe(TOPIC_LAMP_ASSOCIATED, qos=2) self.mqtt.subscribe(NEW_REMOTE_TOPIC, qos=2) def _poll_associated(self, dt): # this polling loop allows us to synchronize changes from the # MQTT callbacks (which happen in a different thread) to the # Kivy UI self.device_associated = self._associated def receive_associated(self, client, userdata, message): # this is called in MQTT event loop thread new_associated = json.loads(message.payload.decode('utf-8')) if self._associated != new_associated['associated']: if not new_associated['associated']: self.association_code = new_associated['code'] else: self.association_code = None self._associated = new_associated['associated'] def on_device_associated(self, instance, value): if value: self.associated_status_popup.dismiss() else: self.associated_status_popup.open() def update_popup_associated(self, instance): code = self.association_code[0:6] instance.content.text = ("Please use the\n" "following code\n" "to associate\n" "your device\n" "on the Web\n{}".format(code)) def receive_bridge_connection_status(self, client, userdata, message): # monitor if the MQTT bridge to our cloud broker is up if message.payload == b"1": self.mqtt_broker_bridged = True else: self.mqtt_broker_bridged = False def receive_new_lamp_state(self, client, userdata, message): new_state = json.loads(message.payload.decode('utf-8')) Clock.schedule_once(lambda dt: self._update_ui(new_state), 0.01) def _update_ui(self, new_state): if self._updated and new_state['client'] == MQTT_CLIENT_ID: # ignore updates generated by this client, except the first to # make sure the UI is syncrhonized with the lamp_service return self._updatingUI = True try: if 'color' in new_state: self.hue = new_state['color']['h'] self.saturation = new_state['color']['s'] if 'brightness' in new_state: self.brightness = new_state['brightness'] if 'on' in new_state: self.lamp_is_on = new_state['on'] finally: self._updatingUI = False self._updated = True def _update_leds(self): msg = { 'color': { 'h': self._hue, 's': self._saturation }, 'brightness': self._brightness, 'on': self.lamp_is_on, 'client': MQTT_CLIENT_ID } self.mqtt.publish(TOPIC_SET_LAMP_CONFIG, json.dumps(msg).encode('utf-8'), qos=1) self._publish_clock = None def set_up_GPIO_and_device_status_popup(self): self.pi = pigpio.pi() self.pi.set_mode(17, pigpio.INPUT) self.pi.set_pull_up_down(17, pigpio.PUD_UP) Clock.schedule_interval(self._poll_GPIO, 0.05) self.network_status_popup = self._build_network_status_popup() self.network_status_popup.bind(on_open=self.update_device_status_popup) def _build_network_status_popup(self): return Popup(title='Device Status', content=Label(text='IP ADDRESS WILL GO HERE'), size_hint=(1, 1), auto_dismiss=False) def update_device_status_popup(self, instance): interface = "wlan0" ipaddr = lampi.lampi_util.get_ip_address(interface) deviceid = lampi.lampi_util.get_device_id() msg = ("Version: {}\n" "{}: {}\n" "DeviceID: {}\n" "Broker Bridged: {}\n" "Async Analytics").format(LAMPI_APP_VERSION, interface, ipaddr, deviceid, self.mqtt_broker_bridged) instance.content.text = msg def on_gpio17_pressed(self, instance, value): if value: self.network_status_popup.open() else: self.network_status_popup.dismiss() def _poll_GPIO(self, dt): # GPIO17 is the rightmost button when looking front of LAMPI self.gpio17_pressed = not self.pi.read(17) def write_preset(self, num): filewrite = { "stateList": [ { "state": { "h": self._preset_color, "s": 1.0, "b": 1.0 }, "smooth": False, "waitTime": 0, "transitionTime": 0 }, ], 'loop': False } with open(PRESETS[num - 1] + ".json", "w") as f: json.dump(filewrite, f)
def on_Soc(client, userdata, message): try: val = json.loads(message.payload) print("SOC = %s" % val["value"]) except: print("SOC Exception") reconnect() def on_connect(client, userdata, rc, *args): client.subscribe(bat_topic) client.subscribe(soc_topic) #client.subscribe("N/%s/+/+/ProductId" % portal_id) #client.subscribe("N/%s/#" % portal_id) client = Client("P1") client.tls_set(cert_reqs=ssl.CERT_NONE) client.tls_insecure_set(True) client.username_pw_set(username, password=password) client.connect(mqtt_broker, port=8883) client.on_connect = on_connect client.on_message = on_message client.message_callback_add(bat_topic, on_BatVoltage) client.message_callback_add(soc_topic, on_Soc) client.loop_start() while True: sleep(1)
class MqttBus(IBus): """ Args transport from pusher to subscribers via MQTT """ _subscribers = dict() def __init__(self, host=None, *args, publish_time_out=0.05, publish_waiting=0.1, mid_max_len=64, qos=2, retain=True, **kwargs): super().__init__() if host is None: host = 'localhost' self.client = Client(*args, **kwargs) self.client.connect(host) self.client.loop_start() self.client.on_publish = self._on_publish self._received_messages = [] self.mid_max_len = mid_max_len self.publish_time_out = publish_time_out self.publish_waiting = publish_waiting self.qos = qos self.retain = retain @staticmethod def _loads(payload): try: return pickle.loads(payload, fix_imports=True, encoding="utf-8", errors="strict") except Exception as e: log.error('mqtt loads error:' + str(e)) return None @staticmethod def _dumps(data): try: return pickle.dumps(data, protocol=3, fix_imports=True) except Exception as e: log.error('mqtt dumps error:' + str(e)) return None def _on_publish(self, client, userdata, mid): if len(self._received_messages) >= self.mid_max_len: self._received_messages.pop(0) self._received_messages.append(mid) else: self._received_messages.append(mid) def _on_message(self, client, userdata, msg): args, kwargs = self._loads(msg.payload) if self._subscribers.get(msg.topic): for fn in self._subscribers[msg.topic]: fn(*args, **kwargs) else: log.warning( 'mqtt Callback _subscribers is not set for topic={}'.format( msg.topic)) def subscribe(self, topic, fn=None): """ Add function fn to subscribers list """ if fn: assert hasattr(fn, '__call__') self.client.subscribe(topic, qos=self.qos) self.client.message_callback_add( topic, lambda client, userdata, msg: self._on_message( client, userdata, msg)) if self._subscribers.get(topic) is None: self._subscribers[topic] = [fn] log.info('mqtt Function {} subscribed to topic "{}"'.format( fn.__name__, topic)) else: self._subscribers[topic].append(fn) log.info('mqtt Function {} subscribed to topic "{}"'.format( fn.__name__, topic)) else: self.client.message_callback_remove(topic) self.client.unsubscribe(topic) def push(self, topic, *args, **kwargs): """ Call all the subscribers """ _args = [arg for arg in args if isinstance(arg, (float, int, str))] _kwargs = { key: arg for key, arg in kwargs.items() if isinstance(arg, (float, int, str)) } if _args or _kwargs: log.debug( 'pushed topic "{}" with args {} and kwargs {} and {}'.format( topic, str(_args), str(_kwargs), str(set(kwargs.keys()) - set(_kwargs.keys())))) send_data = self._dumps((args, kwargs)) log.info('mqtt Pushing data with size={} to topic={}...'.format( len(send_data), topic)) rc, mid = self.client.publish(topic, send_data, qos=self.qos, retain=self.retain) publish_time = time.time() while mid not in self._received_messages: time.sleep(self.publish_time_out) if (time.time() - publish_time) >= self.publish_waiting: break if rc == 0: log.info('mqtt Pushed data to topic={} with rc {}'.format( topic, rc)) else: log.warning('mqtt Not pushed data to topic={} with rc {}'.format( topic, rc))
class SphinxApp: _updated = False _hue = 0.0 _saturation = 0.0 _brightness = 0.0 lamp_is_on = 0 #def _get_hue(self): return self._hue #def _set_hue(self, value): self._hue = value #def _get_saturation(self): return self._saturation #def _set_saturation(self, value): self._saturation = value #def _get_brightness(self): return self._brightness #def _set_brightness(self, value): self._brightness = value hue = 0 # AliasProperty(_get_hue, _set_hue, bind=['_hue']) saturation = 0 # AliasProperty(_get_saturation, _set_saturation, bind=['_saturation']) brightness = 0 # AliasProperty(_get_brightness, _set_brightness, bind=['_brightness']) gpio17_pressed = 0 # BooleanProperty(False) device_associated = 0 # BooleanProperty(True) def on_start(self): self._publish_clock = None self.mqtt_broker_bridged = False self._associated = True self.association_code = None self.mqtt = Client(client_id=MQTT_CLIENT_ID) self.mqtt.enable_logger() self.mqtt.will_set(client_state_topic(MQTT_CLIENT_ID), "0", qos=2, retain=True) self.mqtt.on_connect = self.on_connect self.mqtt.connect(MQTT_BROKER_HOST, port=MQTT_BROKER_PORT, keepalive=MQTT_BROKER_KEEP_ALIVE_SECS) print("broker host", MQTT_BROKER_HOST, " mqtt broker port", MQTT_BROKER_PORT) #self.mqtt.loop_forever() # self.mqtt.loop_start() #self.set_up_GPIO_and_network_status_popup() #self.associated_status_popup = self._build_associated_status_popup() #self.associated_status_popup.bind(on_open=self.update_popup_associated) Clock.schedule_interval(self._poll_associated, 0.1) print("hue", self._hue, self.hue) print("saturation", self._saturation, self.saturation) print("brightness", self._brightness, self.brightness) print("onoff", self.lamp_is_on, Clock) def _build_associated_status_popup(self): return Popup(title='Associate your Lamp', content=Label(text='Msg here', font_size='30sp'), size_hint=(1, 1), auto_dismiss=False) def on_hue(self, instance, value): if self._updatingUI: return if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_saturation(self, instance, value): if self._updatingUI: return if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_brightness(self, instance, value): if self._updatingUI: return if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_lamp_is_on(self, instance, value): if self._updatingUI: return if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_connect(self, client, userdata, flags, rc): print("lampi_sphinx_app on_connect") self.mqtt.publish(client_state_topic(MQTT_CLIENT_ID), b"1", qos=2, retain=True) self.mqtt.message_callback_add(TOPIC_LAMP_CHANGE_NOTIFICATION, self.receive_new_lamp_state) self.mqtt.message_callback_add(broker_bridge_connection_topic(), self.receive_bridge_connection_status) self.mqtt.message_callback_add(TOPIC_LAMP_ASSOCIATED, self.receive_associated) self.mqtt.subscribe(broker_bridge_connection_topic(), qos=1) self.mqtt.subscribe(TOPIC_LAMP_CHANGE_NOTIFICATION, qos=1) self.mqtt.subscribe(TOPIC_LAMP_ASSOCIATED, qos=2) def _poll_associated(self, dt): # this polling loop allows us to synchronize changes from the # MQTT callbacks (which happen in a different thread) to the # Kivy UI print("lampi_sphinx_app _poll_associated") self.device_associated = self._associated def receive_associated(self, client, userdata, message): print("lampi_sphinx_app receive_assocaited") # this is called in MQTT event loop thread new_associated = json.loads(message.payload.decode('utf-8')) if self._associated != new_associated['associated']: if not new_associated['associated']: self.association_code = new_associated['code'] else: self.association_code = None self._associated = new_associated['associated'] def on_device_associated(self, instance, value): if value: self.associated_status_popup.dismiss() else: self.associated_status_popup.open() def update_popup_associated(self, instance): code = self.association_code[0:6] instance.content.text = ("Please use the\n" "following code\n" "to associate\n" "your device\n" "on the Web\n{}".format(code)) def receive_bridge_connection_status(self, client, userdata, message): print("lampi_sphinx_app receive_bridge_connection_status", userdata, message) # monitor if the MQTT bridge to our cloud broker is up if message.payload == b"1": self.mqtt_broker_bridged = True else: self.mqtt_broker_bridged = False def update_new_config(self, config): msg = { 'color': { 'h': self.hue, 's': self.saturation }, 'brightness': self.brightness, 'on': self.lamp_is_on, 'client': MQTT_CLIENT_ID } print("DEBUG update_new_config", config) print("current config:", msg) for piece in config: if type(piece) == type({}): print("piece", piece) for key in piece.keys(): if 'h' == key: print("Setting hue to:", piece[key]) self.hue = piece[key] if 's' == key: print("Setting sat to:", piece[key]) self.saturation = piece[key] if 'b' == key: print("Setting brightness to:", piece[key]) self.brightness = piece[key] elif type(piece) == type(""): if piece.find("LAMPI SET POWER") != -1: if piece.find("0") != -1: self.lamp_is_on = False elif piece.find("1") != -1: self.lamp_is_on = True else: print("Can't detect piece1!", piece) elif piece.find("TOGGLE") != -1: self.lamp_is_on = not self.lamp_is_on else: print("Can't detect piece2!", piece) msg = { 'color': { 'h': self.hue, 's': self.saturation }, 'brightness': self.brightness, 'on': self.lamp_is_on, 'client': MQTT_CLIENT_ID } self._update_leds() print("config now:", msg) # if 'color' in new_state: # self.hue = new_state['color']['h'] # self.saturation = new_state['color']['s'] # if 'brightness' in new_state: # self.brightness = new_state['brightness'] # if 'on' in new_state: # self.lamp_is_on = new_state['on'] def receive_new_lamp_state(self, client, userdata, message): print("lampi_sphinx_app receive_new_lamp_state", userdata, message) new_state = json.loads(message.payload.decode('utf-8')) self._update_ui(new_state) #Clock.schedule_once(lambda dt: self._update_ui(new_state), 0.01) print("updateUI Scheduled!") def _update_ui(self, new_state): print("lamp_sphinx_app _update_ui", new_state) if self._updated and new_state['client'] == MQTT_CLIENT_ID: # ignore updates generated by this client, except the first to # make sure the UI is syncrhonized with the lamp_service return try: if 'color' in new_state: self.hue = new_state['color']['h'] self.saturation = new_state['color']['s'] if 'brightness' in new_state: self.brightness = new_state['brightness'] if 'on' in new_state: self.lamp_is_on = new_state['on'] finally: pass self._updated = True def _update_leds(self): msg = { 'color': { 'h': self.hue, 's': self.saturation }, 'brightness': self.brightness, 'on': self.lamp_is_on, 'client': MQTT_CLIENT_ID } self.mqtt.publish(TOPIC_SET_LAMP_CONFIG, json.dumps(msg).encode('utf-8'), qos=1) self._publish_clock = None
# if __name__ == "__main__": broker = "wild.mat.ucm.es" choques = "clients/stop" #topic=choques+"/servidor... ###choques: para evitar colisiones en el broker en las pruebas nombre_usuario = input("¿nombre usuario? ") #identificador del usuario mqttc = Client(userdata=[nombre_usuario, 0, 0]) #userdata=[nombre_usuario,puntos_usuario,numero_partida_usuario] #funciones callback: #redirigimos los mensajes según el topic del que vengan para mayor claridad mqttc.message_callback_add(choques + "/servidor/#", callback_servidor) mqttc.message_callback_add(choques + "/partidas/#", callback_partidas) mqttc.message_callback_add(choques + "/jugadores/" + nombre_usuario, callback_jugadores) #will_set: #ultimo mensaje que se envía si el Client se desconecta sin usar disconnect() mqttc.will_set(choques + "/jugadores/" + nombre_usuario, payload="DISCONNECT") mqttc.connect(broker) #suscripciones iniciales del cliente mqttc.subscribe(choques + "/jugadores/" + nombre_usuario) mqttc.subscribe(choques + "/servidor") mqttc.subscribe(choques + "/servidor/" + nombre_usuario)
iii = input("¿Puntuación máxima?\n->") max_puntuacion = max(50, int(iii)) except: max_puntuacion = 500 if min_jugadores_partida > max_jugadores_partida: min_jugadores_partida = 2 mqttc = Client( userdata={}) #diccionario como userdata para la info del juego #'info' indica el estado de la partida: #estado: 0 es sin empezar,1 en espera,2 jugando,3 en recuento #alfabeto: las letras que quedan por jugar, de inicio ya están desordenadas #confirmados para tener una forma de ver si todos envian la info #funciones callback: mqttc.message_callback_add(choques + "/jugadores/#", callback_jugadores) mqttc.message_callback_add(choques + "/partidas/#", callback_partidas) mqttc.message_callback_add(choques + "/servidor/#", callback_servidor) mqttc.message_callback_add(choques + "/solicitudes/#", callback_solicitudes) #will_set: #ultimo mensaje que se envía si el Client se desconecta sin usar disconnect() mqttc.will_set(choques + "/servidor", payload="SERVER_FAIL") mqttc.connect(broker) mqttc.publish(choques + "/servidor", payload="SERVER_READY") print("\nSERVIDOR ACTIVO...") print("\nDATOS del juego") print("-nº mínimo de jugadores por partida:", min_jugadores_partida)
class MqttClient: def __init__(self) -> None: self.client = Client() self.client.on_connect = self.on_connect self.client.on_message = self.on_message self.client.on_publish = self.on_publish self.client.on_disconnect = self.on_disconnect self.client.on_unsubscribe = self.on_unsubscribe self.client.on_subscribe = self.on_subscribe def connect(self, host: str, port: int, user: str, passwd: str, keepalive: int = 600): """connect server""" # set user passwd self.client.username_pw_set(user, passwd) # connect self.client.connect(host=host, port=port, keepalive=keepalive) def loop_start(self): """loop start""" self.client.loop_start() def loop_forever(self): """loop forever""" self.client.loop_forever() def subscribe(self, topic: str): """subscribe topic""" self.client.subscribe(topic) def publish(self, topic, msg, qos=0, retain=False, properties=None): """publish msg""" payload = json.dumps(msg, ensure_ascii=False) self.client.publish(topic=topic, payload=payload, qos=qos, retain=retain, properties=properties) def add_callback(self, topic, callback): """message_callback_add""" self.client.message_callback_add(topic, callback) def on_connect(self, client, userdata, flags, rc): """连接事件""" logger.info('on_connect'.center(40, '*')) logger.info(f'Connected with result code: {rc}') def on_disconnect(self, client, userdata, rc): """断开连接事件""" # logger.info('on_disconnect'.center(40, '*')) # logger.info('Unexpected disconnected rc = {rc}') pass def on_subscribe(self, client, userdata, mid, granted_qos): """订阅事件""" logger.info('on_subscribe'.center(40, '*')) logger.info('on_subscribe: qos = {granted_qos}') def on_unsubscribe(self, client, userdata, mid): """取消订阅事件""" # logger.info('on_unsubscribe'.center(40, '*')) # logger.info('on_unsubscribe: qos = {granted_qos}') pass def on_publish(self, client, userdata, mid): """发布消息事件""" logger.info('on_publish'.center(40, '*')) logger.info(f'on_publish: mid = {mid}') def on_message(self, client, userdata, msg): """获得消息事件,触发动作,匹配不到 message_callback_add 时使用这个""" logger.info('on_message'.center(40, '*')) payload = msg.payload.decode('utf-8') logger.info(f'on_message topic: {msg.topic}') logger.info(payload)
class LampiApp(App): _updatingUI = False _hue = NumericProperty() _saturation = NumericProperty() _brightness = NumericProperty() lamp_is_on = BooleanProperty() just_turned_on = True def _get_hue(self): return self._hue def _set_hue(self, value): self._hue = value def _get_saturation(self): return self._saturation def _set_saturation(self, value): self._saturation = value def _get_brightness(self): return self._brightness def _set_brightness(self, value): self._brightness = value hue = AliasProperty(_get_hue, _set_hue, bind=['_hue']) saturation = AliasProperty(_get_saturation, _set_saturation, bind=['_saturation']) brightness = AliasProperty(_get_brightness, _set_brightness, bind=['_brightness']) gpio17_pressed = BooleanProperty(False) def on_start(self): self._publish_clock = None self.mqtt = Client(client_id=MQTT_CLIENT_ID, protocol=MQTT_VERSION) self.mqtt.on_connect = self.on_connect self.mqtt.connect(MQTT_BROKER_HOST, port=MQTT_BROKER_PORT, keepalive=MQTT_BROKER_KEEP_ALIVE_SECS) self.mqtt.will_set('lamp/connection/lamp_ui/state', "0", qos=2, retain=True) self.mqtt.publish('lamp/connection/lamp_ui/state', "1", qos=2, retain=True) self.mqtt.loop_start() self.set_up_GPIO_and_IP_popup() def on_hue(self, instance, value): if self._updatingUI: return if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_saturation(self, instance, value): if self._updatingUI: return if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_brightness(self, instance, value): if self._updatingUI: return if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_lamp_is_on(self, instance, value): if self._updatingUI: return if self._publish_clock is None: self._publish_clock = Clock.schedule_once( lambda dt: self._update_leds(), 0.01) def on_connect(self, client, userdata, flags, rc): self.mqtt.subscribe(TOPIC_LAMP_CHANGE_NOTIFICATION) self.mqtt.message_callback_add(TOPIC_LAMP_CHANGE_NOTIFICATION, self.receive_new_lamp_state) def receive_new_lamp_state(self, client, userdata, message): new_state = json.loads(message.payload) Clock.schedule_once(lambda dt: self._update_ui(new_state), 0.01) def _update_ui(self, new_state): self._updatingUI = True try: if 'client' in new_state: if new_state['client'] == 'lamp_ui' and not self.just_turned_on: return if 'color' in new_state: self.hue = new_state['color']['h'] self.saturation = new_state['color']['s'] if 'brightness' in new_state: self.brightness = new_state['brightness'] if 'on' in new_state: self.lamp_is_on = new_state['on'] finally: self._updatingUI = False def _update_leds(self): msg = {'color': {'h': self._hue, 's': self._saturation}, 'brightness': self._brightness, 'on': self.lamp_is_on, 'client': 'lamp_ui'} self.mqtt.publish(TOPIC_SET_LAMP_CONFIG, json.dumps(msg), qos = 1) self._publish_clock = None def set_up_GPIO_and_IP_popup(self): self.pi = pigpio.pi() self.pi.set_mode(17, pigpio.INPUT) self.pi.set_pull_up_down(17, pigpio.PUD_UP) Clock.schedule_interval(self._poll_GPIO, 0.05) self.popup = Popup(title='IP Addresses', content=Label(text='IP ADDRESS WILL GO HERE'), size_hint=(1, 1), auto_dismiss=False) self.popup.bind(on_open=self.update_popup_ip_address) def update_popup_ip_address(self, instance): interface = "wlan0" ipaddr = lampi_util.get_ip_address(interface) instance.content.text = "{}: {}".format(interface, ipaddr) def on_gpio17_pressed(self, instance, value): if value: self.popup.open() else: self.popup.dismiss() def _poll_GPIO(self, dt): # GPIO17 is the rightmost button when looking front of LAMPI self.gpio17_pressed = not self.pi.read(17)