Example #1
0
 async def _connect_forever(self) -> None:
     dev_id = hex(getnode())
     while True:
         try:
             mqtt_connection = await self._mqtt_client.connect(
                 host=self._mqtt_host,
                 port=self._mqtt_port,
                 username=self._mqtt_user,
                 password=self._mqtt_password,
                 client_id=f'ble2mqtt_{dev_id}',
                 will_message=aio_mqtt.PublishableMessage(
                     topic_name=self.availability_topic,
                     payload='offline',
                     qos=aio_mqtt.QOSLevel.QOS_1,
                     retain=True,
                 ),
             )
             _LOGGER.info(f'Connected to {self._mqtt_host}')
             await self._mqtt_client.publish(
                 aio_mqtt.PublishableMessage(
                     topic_name=self.availability_topic,
                     payload='online',
                     qos=aio_mqtt.QOSLevel.QOS_1,
                     retain=True,
                 ), )
             await self._run_device_tasks(mqtt_connection.disconnect_reason)
         except (aio.CancelledError, KeyboardInterrupt):
             await self._mqtt_client.publish(
                 aio_mqtt.PublishableMessage(
                     topic_name=self.availability_topic,
                     payload='offline',
                     qos=aio_mqtt.QOSLevel.QOS_0,
                     retain=True,
                 ), )
             raise
         except Exception:
             _LOGGER.exception(
                 "Connection lost. Will retry in %d seconds.",
                 self._reconnection_interval,
             )
             try:
                 await self.stop_device_manage_tasks()
             except aio.CancelledError:
                 raise
             except Exception:
                 _LOGGER.exception('Exception in _connect_forever()')
             try:
                 await self._mqtt_client.disconnect()
             except aio.CancelledError:
                 raise
             except Exception:
                 _LOGGER.error('Disconnect from MQTT broker error')
             await aio.sleep(self._reconnection_interval)
Example #2
0
 async def _publish_light(self, light: Light):
     await self._client.publish(
         aio_mqtt.PublishableMessage(
             topic_name=self._get_topic(light.topic),
             payload=json.dumps(light.state),
             qos=aio_mqtt.QOSLevel.QOS_1,
         ), )
Example #3
0
    async def _connect_forever(self) -> None:
        while True:
            try:
                connect_result = await self._client.connect(
                    host=self._mqtt_host,
                    port=self._mqtt_port,
                    username=self._mqtt_user,
                    password=self._mqtt_password,
                    client_id=f'lumimqtt_{self.dev_id}',
                    will_message=self._will_message,
                )
                logger.info(f"Connected to {self._mqtt_host}")

                await self._client.publish(
                    aio_mqtt.PublishableMessage(
                        topic_name=self._topic_lwt,
                        payload='online',
                        qos=aio_mqtt.QOSLevel.QOS_1,
                        retain=True,
                    ), )

                await self._client.subscribe(*[(t, aio_mqtt.QOSLevel.QOS_1)
                                               for t in self.subscribed_topics]
                                             )
                await self.send_config()

                logger.info("Wait for network interruptions...")
                await connect_result.disconnect_reason
            except aio.CancelledError:
                raise

            except aio_mqtt.AccessRefusedError as e:
                logger.error("Access refused", exc_info=e)
                raise
            except (
                    aio_mqtt.ConnectionLostError,
                    aio_mqtt.ConnectFailedError,
                    aio_mqtt.ServerDiedError,
            ):
                logger.error(
                    "Connection lost. Will retry in %d seconds",
                    self._reconnection_interval,
                )
                await aio.sleep(self._reconnection_interval)

            except aio_mqtt.ConnectionCloseForcedError as e:
                logger.error("Connection close forced", exc_info=e)
                return

            except Exception as e:
                logger.error(
                    "Unhandled exception during connecting",
                    exc_info=e,
                )
                return

            else:
                logger.info("Disconnected")
                return
Example #4
0
 async def _publish_sensor(self, sensor: Sensor, value=None):
     if value is None:
         value = sensor.get_value()
     await self._client.publish(
         aio_mqtt.PublishableMessage(
             topic_name=self._get_topic(sensor.topic),
             payload=value,
             qos=aio_mqtt.QOSLevel.QOS_1,
             retain=self._sensor_retain,
         ), )
Example #5
0
    def __init__(
        self,
        device_id: str,
        topic_root: str,
        host: str,
        port: int = None,
        user: ty.Optional[str] = None,
        password: ty.Optional[str] = None,
        reconnection_interval: int = 10,
        *,
        auto_discovery: bool,
        sensor_retain: bool,
        sensor_threshold: int,
        sensor_debounce_period: int,
        light_transition_period: float,
        light_notification_period: float,
        loop: ty.Optional[aio.AbstractEventLoop] = None,
    ) -> None:
        self.dev_id = device_id
        self._topic_root = topic_root
        self._topic_lwt = f'{topic_root}/status'
        self._mqtt_host = host
        self._mqtt_port = port
        self._mqtt_user = user
        self._mqtt_password = password

        self._will_message = aio_mqtt.PublishableMessage(
            topic_name=self._topic_lwt,
            payload='offline',
            qos=aio_mqtt.QOSLevel.QOS_1,
            retain=True,
        )

        self._auto_discovery = auto_discovery
        self._sensor_retain = sensor_retain
        self._sensor_threshold = sensor_threshold
        self._sensor_debounce_period = sensor_debounce_period
        self._light_transition_period = light_transition_period
        self._light_notification_period = light_notification_period
        self._light_last_sent = None

        self._reconnection_interval = reconnection_interval
        self._loop = loop or aio.get_event_loop()
        self._client = aio_mqtt.Client(
            loop=self._loop,
            client_id_prefix='lumimqtt_',
        )
        self._tasks: ty.List[aio.Future] = []

        self.sensors: ty.List[Sensor] = []
        self.lights: ty.List[Light] = []
        self.buttons: ty.List[Button] = []
        self.custom_commands: ty.List[Command] = []

        self._debounce_sensors: ty.Dict[Sensor, DebounceSensor] = {}
Example #6
0
 async def _read_and_publish(self):
     dt, co2_ppm, temp = self._read()
     await self._client.publish(
         aio_mqtt.PublishableMessage(
             topic_name=f"{self.mqtt_prefix}/sensor/state",
             payload=json.dumps({
                 'co2': co2_ppm,
                 'temperature': round(temp, 2),
             }),
             qos=aio_mqtt.QOSLevel.QOS_1,
         ), )
Example #7
0
 async def _handle_click(self, button: Button):
     await aio.gather(
         self._client.publish(
             aio_mqtt.PublishableMessage(
                 topic_name=self._get_topic(button.topic),
                 payload=json.dumps({'action': 'single'}),
                 qos=aio_mqtt.QOSLevel.QOS_1,
             ), ),
         self._client.publish(
             aio_mqtt.PublishableMessage(
                 topic_name=self._get_topic(f'{button.topic}/action'),
                 payload='single',
                 qos=aio_mqtt.QOSLevel.QOS_1,
             ), ),
     )
     await self._client.publish(
         aio_mqtt.PublishableMessage(
             topic_name=self._get_topic(button.topic),
             payload=json.dumps({'action': ''}),
             qos=aio_mqtt.QOSLevel.QOS_1,
         ), )
Example #8
0
 async def publish_topic_callback(self, topic, value, nowait=False):
     _LOGGER.debug(f'call publish callback topic={topic} value={value}')
     if not self._mqtt_client.is_connected():
         _LOGGER.warning(f'{self.device} mqtt is disconnected')
         return
     await self._mqtt_client.publish(
         aio_mqtt.PublishableMessage(
             topic_name='/'.join((self._base_topic, topic)),
             payload=value,
             qos=aio_mqtt.QOSLevel.QOS_1,
         ),
         nowait=nowait,
     )
Example #9
0
 async def _handle_click(self, button: Button, action: str):
     logger.debug(f'{button} sent "{action}" event')
     await aio.gather(
         self._client.publish(
             aio_mqtt.PublishableMessage(
                 topic_name=self._get_topic(button.topic),
                 payload=json.dumps({'action': action}),
                 qos=aio_mqtt.QOSLevel.QOS_1,
             ), ),
         self._client.publish(
             aio_mqtt.PublishableMessage(
                 topic_name=self._get_topic(f'{button.topic}/action'),
                 payload=action,
                 qos=aio_mqtt.QOSLevel.QOS_1,
             ), ),
     )
     await self._client.publish(
         aio_mqtt.PublishableMessage(
             topic_name=self._get_topic(button.topic),
             payload=json.dumps({'action': ''}),
             qos=aio_mqtt.QOSLevel.QOS_1,
         ), )
Example #10
0
    async def _periodic_publish(self, period=1):
        while True:
            if not self._client.is_connected():
                await aio.sleep(1)
                continue
            for sensor in self.sensors:
                try:
                    value = sensor.get_value()
                    debounce_val = self._debounce_sensors.get(sensor)
                    if self._is_binary(sensor):
                        should_send = (debounce_val is None
                                       or value != debounce_val.value)
                    else:
                        should_send = (
                            debounce_val is None
                            or abs(value - debounce_val.value) >=
                            self._sensor_threshold or
                            (datetime.now() - debounce_val.last_sent).seconds
                            >= self._sensor_debounce_period)

                    if should_send:
                        self._debounce_sensors[sensor] = DebounceSensor(
                            value=value,
                            last_sent=datetime.now(),
                        )
                        await self._client.publish(
                            aio_mqtt.PublishableMessage(
                                topic_name=self._get_topic(sensor.topic),
                                payload=value,
                                qos=aio_mqtt.QOSLevel.QOS_1,
                                retain=self._sensor_retain,
                            ), )
                except (
                        aio_mqtt.ConnectionClosedError,
                        aio_mqtt.ServerDiedError,
                ) as e:
                    logger.error("Connection closed", exc_info=e)
                    await self._client.wait_for_connect()
                    continue
            await aio.sleep(period)
Example #11
0
    async def _handle_messages(self) -> None:
        async for message in self._client.delivered_messages(
                f'{self._topic_root}/#', ):
            while True:
                if message.topic_name not in self.subscribed_topics:
                    continue
                light: ty.Optional[Light] = None
                for _light in self.lights:
                    if message.topic_name == self._get_topic(_light.topic_set):
                        light = _light
                if not light:
                    logger.error("Invalid topic for light")
                    break

                try:
                    value = json.loads(message.payload)
                except ValueError as e:
                    logger.exception(str(e))
                    break

                try:
                    await light.set(value)
                    await self._client.publish(
                        aio_mqtt.PublishableMessage(
                            topic_name=self._get_topic(light.topic),
                            payload=json.dumps(light.state),
                            qos=aio_mqtt.QOSLevel.QOS_1,
                        ), )
                except aio_mqtt.ConnectionClosedError as e:
                    logger.error("Connection closed", exc_info=e)
                    await self._client.wait_for_connect()
                    continue

                except Exception as e:
                    logger.error(
                        "Unhandled exception during echo message publishing",
                        exc_info=e)
                break
Example #12
0
    async def send_config(self):
        device = {
            'identifiers': [
                f'xiaomi_gateway_{self.dev_id}',
            ],
            'name': f'xiaomi_gateway_{self.dev_id}',
            'sw_version': VERSION,
            'model': 'Xiaomi Gateway',
            'manufacturer': 'Xiaomi',
        }

        def get_generic_vals(name):
            return {
                'name': f'{name}_{self.dev_id}',
                'unique_id': f'{name}_{self.dev_id}',
                'device': device,
                'availability_topic': self._topic_lwt,
            }

        # set sensors config
        for sensor in self.sensors:
            await self._client.publish(
                aio_mqtt.PublishableMessage(
                    topic_name=(
                        f'homeassistant/'
                        f"{'binary_' if self._is_binary(sensor) else ''}sensor"
                        f'/{self.dev_id}/{sensor.topic}/config'),
                    payload=json.dumps({
                        **get_generic_vals(sensor.name),
                        **(sensor.MQTT_VALUES or {}),
                        'state_topic':
                        self._get_topic(sensor.topic),
                    }),
                    qos=aio_mqtt.QOSLevel.QOS_1,
                    retain=True,
                ), )

        # set buttons config
        for button in self.buttons:
            base_topic = self._get_topic(button.topic)
            await aio.gather(
                self._client.publish(
                    aio_mqtt.PublishableMessage(
                        topic_name=(f'homeassistant/sensor/{self.dev_id}/'
                                    f'{button.topic}/config'),
                        payload=json.dumps({
                            **get_generic_vals(button.name),
                            **(button.MQTT_VALUES or {}),
                            'json_attributes_topic':
                            base_topic,
                            'state_topic':
                            base_topic,
                            'value_template':
                            '{{ value_json.action }}',
                        }),
                        qos=aio_mqtt.QOSLevel.QOS_1,
                        retain=True,
                    ), ),
                self._client.publish(
                    aio_mqtt.PublishableMessage(
                        topic_name=(
                            f'homeassistant/device_automation/'
                            f'{button.name}_{self.dev_id}/action_single/config'
                        ),
                        payload=json.dumps({
                            # device_automation should not have
                            # name and unique_id
                            'device': device,
                            'automation_type': 'trigger',
                            'topic': f'{base_topic}/action',
                            'subtype': 'single',
                            'payload': 'single',
                            'type': 'action',
                        }),
                        qos=aio_mqtt.QOSLevel.QOS_1,
                        retain=True,
                    ), ),
            )

        # set LED lights config
        for light in self.lights:
            await self._client.publish(
                aio_mqtt.PublishableMessage(
                    topic_name=f'homeassistant/light/{self.dev_id}/'
                    f'{light.topic}/config',
                    payload=json.dumps({
                        **get_generic_vals(light.name),
                        'schema':
                        'json',
                        'rgb':
                        light.RGB,
                        'brightness':
                        light.BRIGHTNESS,
                        'state_topic':
                        self._get_topic(light.topic),
                        'command_topic':
                        self._get_topic(light.topic_set),
                    }),
                    qos=aio_mqtt.QOSLevel.QOS_1,
                    retain=True,
                ), )
Example #13
0
    async def _connect_forever(self) -> None:
        while True:
            try:
                connect_result = await self._client.connect(
                    host=config.MQTT_SERVER,
                    username=config.MQTT_USER,
                    password=config.MQTT_PASSWORD,
                )
                logger.info(f"Connected to {config.MQTT_SERVER}.")

                device = {
                    "identifiers": [
                        self.device_name,
                    ],
                    "name": self.device_name,
                    "sw_version": self._co2.info["serial_no"],
                    "model": self.device_name,
                    "manufacturer": self._co2.info['manufacturer'],
                }

                await self._client.publish(
                    aio_mqtt.PublishableMessage(
                        topic_name=f'{self.mqtt_prefix}/co2/config',
                        payload=json.dumps({
                            'name': f'co2_{self.device_name}',
                            'unique_id': f'co2_{self.device_name}',
                            "state_topic": f"{self.mqtt_prefix}/sensor/state",
                            "unit_of_measurement": "ppm",
                            "value_template": "{{ value_json.co2 }}",
                            "device": device,
                        }),
                        qos=aio_mqtt.QOSLevel.QOS_1,
                        retain=True,
                    ), )
                await self._client.publish(
                    aio_mqtt.PublishableMessage(
                        topic_name=f'{self.mqtt_prefix}/temperature/config',
                        payload=json.dumps({
                            "device_class": "temperature",
                            'name': f'temperature_{self.device_name}',
                            'unique_id': f'temperature_{self.device_name}',
                            "state_topic": f"{self.mqtt_prefix}/sensor/state",
                            "unit_of_measurement": "\u00b0C",
                            "value_template": "{{ value_json.temperature }}",
                            "device": device,
                        }),
                        qos=aio_mqtt.QOSLevel.QOS_1,
                        retain=True,
                    ), )

                logger.info("Wait for network interruptions...")
                await connect_result.disconnect_reason
            except asyncio.CancelledError:
                raise

            except aio_mqtt.AccessRefusedError:
                logger.exception("Access refused")

            except (
                    aio_mqtt.ConnectionLostError,
                    aio_mqtt.ConnectionClosedError,
                    aio_mqtt.ServerDiedError,
                    OSError,
            ):
                logger.exception(
                    "Connection lost. Will retry in %d seconds",
                    self._reconnection_interval,
                )
                await asyncio.sleep(self._reconnection_interval)

            except aio_mqtt.ConnectionCloseForcedError:
                logger.error("Connection close forced")
                for t in self._tasks:
                    t.cancel()
                return

            except Exception:
                logger.exception("Unhandled exception during connecting")

            else:
                logger.info("Disconnected")
                for t in self._tasks:
                    t.cancel()
                return
Example #14
0
    async def send_device_config(self):
        device = self.device
        device_info = {
            'identifiers': [
                device.unique_id,
            ],
            'name': device.unique_name,
            'model': device.model,
        }
        if device.manufacturer:
            device_info['manufacturer'] = device.manufacturer
        if device.version:
            device_info['sw_version'] = device.version

        def get_generic_vals(entity: dict):
            name = entity.pop('name')
            result = {
                'name':
                f'{name}_{device.friendly_id}',
                'unique_id':
                f'{name}_{device.dev_id}',
                'device':
                device_info,
                'availability_mode':
                'all',
                'availability': [
                    {
                        'topic': self._global_availability_topic
                    },
                    {
                        'topic':
                        '/'.join((self._base_topic,
                                  self.device.availability_topic), )
                    },
                ],
            }
            icon = entity.pop('icon', None)
            if icon:
                result['icon'] = f'mdi:{icon}'
            entity.pop('topic', None)
            entity.pop('json', None)
            entity.pop('main_value', None)
            result.update(entity)
            return result

        messages_to_send = []
        for cls, entities in device.entities_with_lqi.items():
            if cls in (
                    BINARY_SENSOR_DOMAIN,
                    SENSOR_DOMAIN,
                    DEVICE_TRACKER_DOMAIN,
            ):
                for entity in entities:
                    entity_name = entity['name']
                    state_topic = self._get_topic(
                        device.unique_id,
                        entity.get('topic', device.STATE_TOPIC),
                    )
                    config_topic = '/'.join((
                        CONFIG_MQTT_NAMESPACE,
                        cls,
                        self._config_device_topic,
                        entity_name,
                        'config',
                    ))
                    if entity.get('json') and entity.get('main_value'):
                        state_topic_part = {
                            'json_attributes_topic':
                            state_topic,
                            'state_topic':
                            state_topic,
                            'value_template':
                            f'{{{{ value_json.{entity["main_value"]} }}}}',
                        }
                    else:
                        state_topic_part = {
                            'state_topic':
                            state_topic,
                            'value_template':
                            f'{{{{ value_json.{entity_name} }}}}',
                        }

                    if cls == DEVICE_TRACKER_DOMAIN:
                        state_topic_part['source_type'] = 'bluetooth_le'

                    payload = json.dumps({
                        **get_generic_vals(entity),
                        **state_topic_part,
                    })
                    _LOGGER.debug(
                        f'Publish config topic={config_topic}: {payload}', )
                    messages_to_send.append(
                        aio_mqtt.PublishableMessage(
                            topic_name=config_topic,
                            payload=payload,
                            qos=aio_mqtt.QOSLevel.QOS_1,
                            retain=True,
                        ), )
            if cls == SWITCH_DOMAIN:
                for entity in entities:
                    entity_name = entity['name']
                    state_topic = self._get_topic(
                        device.unique_id,
                        entity.get('topic', device.STATE_TOPIC),
                    )
                    command_topic = '/'.join((state_topic, device.SET_POSTFIX))
                    config_topic = '/'.join((
                        CONFIG_MQTT_NAMESPACE,
                        cls,
                        self._config_device_topic,
                        entity_name,
                        'config',
                    ))
                    payload = json.dumps({
                        **get_generic_vals(entity),
                        'state_topic':
                        state_topic,
                        'command_topic':
                        command_topic,
                    })
                    _LOGGER.debug(
                        f'Publish config topic={config_topic}: {payload}', )
                    messages_to_send.append(
                        aio_mqtt.PublishableMessage(
                            topic_name=config_topic,
                            payload=payload,
                            qos=aio_mqtt.QOSLevel.QOS_1,
                            retain=True,
                        ), )
                    # TODO: send real state on receiving status from a device
                    _LOGGER.debug(f'Publish initial state topic={state_topic}')
                    await self._mqtt_client.publish(
                        aio_mqtt.PublishableMessage(
                            topic_name=state_topic,
                            payload='OFF',
                            qos=aio_mqtt.QOSLevel.QOS_1,
                        ), )
            if cls == LIGHT_DOMAIN:
                for entity in entities:
                    entity_name = entity['name']
                    state_topic = self._get_topic(
                        device.unique_id,
                        entity.get('topic', device.STATE_TOPIC),
                    )
                    set_topic = '/'.join((state_topic, device.SET_POSTFIX))
                    config_topic = '/'.join((
                        CONFIG_MQTT_NAMESPACE,
                        cls,
                        self._config_device_topic,
                        entity_name,
                        'config',
                    ))
                    payload = json.dumps({
                        **get_generic_vals(entity),
                        'schema':
                        'json',
                        'color_mode':
                        bool(entity.get('color_mode', True)),
                        'supported_color_modes':
                        entity.get(
                            'color_mode',
                            ['rgb'],
                        ),
                        'brightness':
                        entity.get('brightness', True),
                        'state_topic':
                        state_topic,
                        'command_topic':
                        set_topic,
                    })
                    _LOGGER.debug(
                        f'Publish config topic={config_topic}: {payload}', )
                    messages_to_send.append(
                        aio_mqtt.PublishableMessage(
                            topic_name=config_topic,
                            payload=payload,
                            qos=aio_mqtt.QOSLevel.QOS_1,
                            retain=True,
                        ), )
            if cls == COVER_DOMAIN:
                for entity in entities:
                    entity_name = entity['name']
                    state_topic = self._get_topic(
                        device.unique_id,
                        entity.get('topic', device.STATE_TOPIC),
                    )
                    set_topic = '/'.join((state_topic, device.SET_POSTFIX))
                    set_position_topic = '/'.join(
                        (state_topic, device.SET_POSITION_POSTFIX), )
                    config_topic = '/'.join((
                        CONFIG_MQTT_NAMESPACE,
                        cls,
                        self._config_device_topic,
                        entity_name,
                        'config',
                    ))
                    config_params = {
                        **get_generic_vals(entity),
                        'state_topic': state_topic,
                        'position_topic': state_topic,
                        'json_attributes_topic': state_topic,
                        'value_template': '{{ value_json.state }}',
                        'position_template': '{{ value_json.position }}',
                        'command_topic': set_topic,
                        'set_position_topic': set_position_topic,
                    }
                    payload = json.dumps(config_params)
                    _LOGGER.debug(
                        f'Publish config topic={config_topic}: {payload}', )
                    messages_to_send.append(
                        aio_mqtt.PublishableMessage(
                            topic_name=config_topic,
                            payload=payload,
                            qos=aio_mqtt.QOSLevel.QOS_1,
                            retain=True,
                        ), )
            if cls == SELECT_DOMAIN:
                for entity in entities:
                    entity_name = entity['name']
                    state_topic = self._get_topic(
                        device.unique_id,
                        entity.get('topic', device.STATE_TOPIC),
                    )
                    set_topic = '/'.join((state_topic, device.SET_POSTFIX))
                    config_topic = '/'.join((
                        CONFIG_MQTT_NAMESPACE,
                        cls,
                        self._config_device_topic,
                        entity_name,
                        'config',
                    ))
                    config_params = {
                        **get_generic_vals(entity),
                        'state_topic': state_topic,
                        'command_topic': set_topic,
                    }
                    payload = json.dumps(config_params)
                    _LOGGER.debug(
                        f'Publish config topic={config_topic}: {payload}', )
                    messages_to_send.append(
                        aio_mqtt.PublishableMessage(
                            topic_name=config_topic,
                            payload=payload,
                            qos=aio_mqtt.QOSLevel.QOS_1,
                            retain=True,
                        ), )
        await aio.gather(*[
            self._mqtt_client.publish(message) for message in messages_to_send
        ])
        device.config_sent = True
Example #15
0
    async def send_config(self):
        device = {
            'identifiers': [
                f'xiaomi_gateway_{self.dev_id}',
            ],
            'name': f'xiaomi_gateway_{self.dev_id}',
            'sw_version': version,
            'model': 'Xiaomi Gateway',
            'manufacturer': 'Xiaomi',
        }

        def get_generic_vals(name):
            return {
                'name': f'{name}_{self.dev_id}',
                'unique_id': f'{name}_{self.dev_id}',
                'device': device,
                'availability_topic': self._topic_lwt,
            }

        # set sensors config
        for sensor in self.sensors:
            await self._client.publish(
                aio_mqtt.PublishableMessage(
                    topic_name=(
                        f'homeassistant/'
                        f"{'binary_' if self._is_binary(sensor) else ''}sensor"
                        f'/{self.dev_id}/{sensor.topic}/config'),
                    payload=json.dumps({
                        **get_generic_vals(sensor.name),
                        **(sensor.MQTT_VALUES or {}),
                        'state_topic':
                        self._get_topic(sensor.topic),
                    }),
                    qos=aio_mqtt.QOSLevel.QOS_1,
                    retain=True,
                ), )

        # set buttons config
        for button in self.buttons:
            base_topic = self._get_topic(button.topic)
            messages = [
                aio_mqtt.PublishableMessage(
                    topic_name=(f'homeassistant/sensor/{self.dev_id}/'
                                f'{button.topic}/config'),
                    payload=json.dumps({
                        **get_generic_vals(button.name),
                        **(button.MQTT_VALUES or {}),
                        'json_attributes_topic':
                        base_topic,
                        'state_topic':
                        base_topic,
                        'value_template':
                        '{{ value_json.action }}',
                    }),
                    qos=aio_mqtt.QOSLevel.QOS_1,
                    retain=True,
                ),
            ]
            for event in button.PROVIDE_EVENTS:
                messages.append(
                    aio_mqtt.PublishableMessage(
                        topic_name=(
                            f'homeassistant/device_automation/'
                            f'{button.name}_{self.dev_id}/action_{event}/config'
                        ),
                        payload=json.dumps({
                            # device_automation should not have
                            # name and unique_id
                            'device': device,
                            'automation_type': 'trigger',
                            'topic': f'{base_topic}/action',
                            'subtype': event,
                            'payload': event,
                            'type': 'action',
                        }),
                        qos=aio_mqtt.QOSLevel.QOS_1,
                        retain=True,
                    ), )
            await aio.gather(*[self._client.publish(m) for m in messages])

        # set LED lights config
        for light in self.lights:
            await self._client.publish(
                aio_mqtt.PublishableMessage(
                    topic_name=f'homeassistant/light/{self.dev_id}/'
                    f'{light.topic}/config',
                    payload=json.dumps({
                        **get_generic_vals(light.name),
                        'schema':
                        'json',
                        'color_mode':
                        True,
                        'supported_color_modes': [light.COLOR_MODE],
                        'brightness':
                        light.BRIGHTNESS,
                        'state_topic':
                        self._get_topic(light.topic),
                        'command_topic':
                        self._get_topic(light.topic_set),
                    }),
                    qos=aio_mqtt.QOSLevel.QOS_1,
                    retain=True,
                ), )
        for command in self.custom_commands:
            await self._client.publish(
                aio_mqtt.PublishableMessage(
                    topic_name=f'homeassistant/switch/'
                    f'{self.dev_id}_{command.name}/config',
                    payload=json.dumps({
                        **get_generic_vals(command.name),
                        'state_topic':
                        self._get_topic(command.topic),
                        'command_topic':
                        self._get_topic(command.topic_set),
                    }),
                    qos=aio_mqtt.QOSLevel.QOS_1,
                    retain=True,
                ), )
Example #16
0
    async def _handle_messages(self) -> None:
        async for message in self._client.delivered_messages(
                f'{self._topic_root}/#', ):
            while True:
                if message.topic_name not in self.subscribed_topics:
                    continue
                light: ty.Optional[Light] = None
                for _light in self.lights:
                    if message.topic_name == self._get_topic(_light.topic_set):
                        light = _light
                if light:
                    try:
                        value = json.loads(message.payload)
                    except ValueError as e:
                        logger.exception(str(e))
                        break

                    try:
                        await light.set(value, self._light_transition_period)
                        await self._publish_light(light)
                    except aio_mqtt.ConnectionClosedError as e:
                        logger.error("Connection closed", exc_info=e)
                        await self._client.wait_for_connect()
                        continue

                    except Exception as e:
                        logger.error(
                            "Unhandled exception during echo "
                            "message publishing",
                            exc_info=e)
                    break

                command: ty.Optional[Command] = None
                for _command in self.custom_commands:
                    if message.topic_name == self._get_topic(
                            _command.topic_set, ):
                        command = _command
                if command:
                    try:
                        value = json.loads(message.payload)
                    except ValueError:
                        value = message.payload.decode()
                    try:
                        await command.set(value)
                        await self._client.publish(
                            aio_mqtt.PublishableMessage(
                                topic_name=self._get_topic(command.topic),
                                payload='OFF',
                                qos=aio_mqtt.QOSLevel.QOS_1,
                            ), )
                    except aio_mqtt.ConnectionClosedError as e:
                        logger.error("Connection closed", exc_info=e)
                        await self._client.wait_for_connect()
                        continue

                    except Exception as e:
                        logger.error(
                            "Unhandled exception during echo "
                            "message publishing",
                            exc_info=e,
                        )
                    break

                logger.error("Invalid topic for light")
                break
Example #17
0
 async def send_message(self, topic, message, retain=False):
     await self._client.publish(aio_mqtt.PublishableMessage(topic, message, retain=retain))