From 64513be6b8984d2ba2a4a5794c8d2807cebb3eab Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Wed, 2 Nov 2022 16:38:17 +0100 Subject: [PATCH] Initial implementation of sensor entities. Implemented (at least in `zigbee.mqtt`, for now): - `TemperatureSensor` - `HumiditySensor` - `VoltageSensor` - `CurrentSensor` - `EnergySensor` - `PowerSensor` - `NumericSensor` (generic fallback 1) - `RawSensor` (generic fallback 2) - `Sensor` (root class) --- .../panels/Entities/CurrentSensor.vue | 1 + .../panels/Entities/EnergySensor.vue | 1 + .../panels/Entities/HumiditySensor.vue | 1 + .../panels/Entities/NumericSensor.vue | 1 + .../panels/Entities/PowerSensor.vue | 1 + .../components/panels/Entities/RawSensor.vue | 1 + .../src/components/panels/Entities/Sensor.vue | 52 ++++ .../panels/Entities/TemperatureSensor.vue | 1 + .../panels/Entities/VoltageSensor.vue | 1 + .../src/components/panels/Entities/meta.json | 58 +++- platypush/entities/electricity.py | 76 ++++++ platypush/entities/humidity.py | 22 ++ platypush/entities/sensors.py | 8 +- platypush/entities/temperature.py | 22 ++ platypush/plugins/zigbee/mqtt/__init__.py | 256 ++++++++---------- 15 files changed, 359 insertions(+), 143 deletions(-) create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/CurrentSensor.vue create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/EnergySensor.vue create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/HumiditySensor.vue create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/NumericSensor.vue create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/PowerSensor.vue create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/RawSensor.vue create mode 100644 platypush/backend/http/webapp/src/components/panels/Entities/Sensor.vue create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/TemperatureSensor.vue create mode 120000 platypush/backend/http/webapp/src/components/panels/Entities/VoltageSensor.vue create mode 100644 platypush/entities/electricity.py create mode 100644 platypush/entities/humidity.py create mode 100644 platypush/entities/temperature.py diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/CurrentSensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/CurrentSensor.vue new file mode 120000 index 000000000..70b944608 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/CurrentSensor.vue @@ -0,0 +1 @@ +Sensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/EnergySensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/EnergySensor.vue new file mode 120000 index 000000000..70b944608 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/EnergySensor.vue @@ -0,0 +1 @@ +Sensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/HumiditySensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/HumiditySensor.vue new file mode 120000 index 000000000..70b944608 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/HumiditySensor.vue @@ -0,0 +1 @@ +Sensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/NumericSensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/NumericSensor.vue new file mode 120000 index 000000000..70b944608 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/NumericSensor.vue @@ -0,0 +1 @@ +Sensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/PowerSensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/PowerSensor.vue new file mode 120000 index 000000000..70b944608 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/PowerSensor.vue @@ -0,0 +1 @@ +Sensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/RawSensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/RawSensor.vue new file mode 120000 index 000000000..70b944608 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/RawSensor.vue @@ -0,0 +1 @@ +Sensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/Sensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/Sensor.vue new file mode 100644 index 000000000..9dda493ef --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/Sensor.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/TemperatureSensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/TemperatureSensor.vue new file mode 120000 index 000000000..70b944608 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/TemperatureSensor.vue @@ -0,0 +1 @@ +Sensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/VoltageSensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/VoltageSensor.vue new file mode 120000 index 000000000..70b944608 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/VoltageSensor.vue @@ -0,0 +1 @@ +Sensor.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json index 7e90c5670..abdfd3667 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json @@ -7,6 +7,14 @@ } }, + "current_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-bolt" + } + }, + "device": { "name": "Device", "name_plural": "Devices", @@ -23,6 +31,14 @@ } }, + "energy_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-plug" + } + }, + "entity": { "name": "Entity", "name_plural": "Entities", @@ -31,6 +47,14 @@ } }, + "humidity_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-droplet" + } + }, + "light": { "name": "Light", "name_plural": "Lights", @@ -43,7 +67,31 @@ "name": "Link Quality", "name_plural": "Link Qualities", "icon": { - "class": "fas fa-signal" + "class": "fas fa-tower-broadcast" + } + }, + + "numeric_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-thermometer" + } + }, + + "power_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-plug" + } + }, + + "temperature_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-temperature-half" } }, @@ -53,5 +101,13 @@ "icon": { "class": "fas fa-toggle-on" } + }, + + "voltage_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-car-battery" + } } } diff --git a/platypush/entities/electricity.py b/platypush/entities/electricity.py new file mode 100644 index 000000000..7c6a466cc --- /dev/null +++ b/platypush/entities/electricity.py @@ -0,0 +1,76 @@ +from sqlalchemy import Column, Integer, ForeignKey + +from .devices import entity_types_registry +from .sensors import NumericSensor + + +if not entity_types_registry.get('PowerSensor'): + + class PowerSensor(NumericSensor): + __tablename__ = 'power_sensor' + + id = Column( + Integer, ForeignKey(NumericSensor.id, ondelete='CASCADE'), primary_key=True + ) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + entity_types_registry['PowerSensor'] = PowerSensor +else: + PowerSensor = entity_types_registry['PowerSensor'] + + +if not entity_types_registry.get('CurrentSensor'): + + class CurrentSensor(NumericSensor): + __tablename__ = 'current_sensor' + + id = Column( + Integer, ForeignKey(NumericSensor.id, ondelete='CASCADE'), primary_key=True + ) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + entity_types_registry['CurrentSensor'] = CurrentSensor +else: + CurrentSensor = entity_types_registry['CurrentSensor'] + + +if not entity_types_registry.get('VoltageSensor'): + + class VoltageSensor(NumericSensor): + __tablename__ = 'voltage_sensor' + + id = Column( + Integer, ForeignKey(NumericSensor.id, ondelete='CASCADE'), primary_key=True + ) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + entity_types_registry['VoltageSensor'] = VoltageSensor +else: + VoltageSensor = entity_types_registry['VoltageSensor'] + + +if not entity_types_registry.get('EnergySensor'): + + class EnergySensor(NumericSensor): + __tablename__ = 'energy_sensor' + + id = Column( + Integer, ForeignKey(NumericSensor.id, ondelete='CASCADE'), primary_key=True + ) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + entity_types_registry['EnergySensor'] = EnergySensor +else: + EnergySensor = entity_types_registry['EnergySensor'] diff --git a/platypush/entities/humidity.py b/platypush/entities/humidity.py new file mode 100644 index 000000000..a78218ea1 --- /dev/null +++ b/platypush/entities/humidity.py @@ -0,0 +1,22 @@ +from sqlalchemy import Column, Integer, ForeignKey + +from .devices import entity_types_registry +from .sensors import NumericSensor + + +if not entity_types_registry.get('HumiditySensor'): + + class HumiditySensor(NumericSensor): + __tablename__ = 'humidity_sensor' + + id = Column( + Integer, ForeignKey(NumericSensor.id, ondelete='CASCADE'), primary_key=True + ) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + entity_types_registry['HumiditySensor'] = HumiditySensor +else: + HumiditySensor = entity_types_registry['HumiditySensor'] diff --git a/platypush/entities/sensors.py b/platypush/entities/sensors.py index abc998d9c..afd2d4637 100644 --- a/platypush/entities/sensors.py +++ b/platypush/entities/sensors.py @@ -3,9 +3,13 @@ from sqlalchemy import Column, Integer, ForeignKey, Numeric, String from .devices import Device, entity_types_registry +class Sensor(Device): + __abstract__ = True + + if not entity_types_registry.get('RawSensor'): - class RawSensor(Device): + class RawSensor(Sensor): __tablename__ = 'raw_sensor' id = Column( @@ -24,7 +28,7 @@ else: if not entity_types_registry.get('NumericSensor'): - class NumericSensor(Device): + class NumericSensor(Sensor): __tablename__ = 'numeric_sensor' id = Column( diff --git a/platypush/entities/temperature.py b/platypush/entities/temperature.py new file mode 100644 index 000000000..e9b2d12ab --- /dev/null +++ b/platypush/entities/temperature.py @@ -0,0 +1,22 @@ +from sqlalchemy import Column, Integer, ForeignKey + +from .devices import entity_types_registry +from .sensors import NumericSensor + + +if not entity_types_registry.get('TemperatureSensor'): + + class TemperatureSensor(NumericSensor): + __tablename__ = 'temperature_sensor' + + id = Column( + Integer, ForeignKey(NumericSensor.id, ondelete='CASCADE'), primary_key=True + ) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + entity_types_registry['TemperatureSensor'] = TemperatureSensor +else: + TemperatureSensor = entity_types_registry['TemperatureSensor'] diff --git a/platypush/plugins/zigbee/mqtt/__init__.py b/platypush/plugins/zigbee/mqtt/__init__.py index 04b3af183..3988ec23f 100644 --- a/platypush/plugins/zigbee/mqtt/__init__.py +++ b/platypush/plugins/zigbee/mqtt/__init__.py @@ -6,15 +6,24 @@ from typing import Optional, List, Any, Dict, Union from platypush.entities import manages from platypush.entities.batteries import Battery +from platypush.entities.electricity import ( + CurrentSensor, + EnergySensor, + PowerSensor, + VoltageSensor, +) +from platypush.entities.humidity import HumiditySensor from platypush.entities.lights import Light from platypush.entities.linkquality import LinkQuality +from platypush.entities.sensors import Sensor, NumericSensor from platypush.entities.switches import Switch +from platypush.entities.temperature import TemperatureSensor from platypush.message import Mapping from platypush.message.response import Response from platypush.plugins.mqtt import MqttPlugin, action -@manages(Light, Switch, LinkQuality, Battery) +@manages(Light, Switch, LinkQuality, Battery, Sensor) class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] """ This plugin allows you to interact with Zigbee devices over MQTT through any Zigbee sniffer and @@ -170,7 +179,6 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] if not dev: continue - converted_entities = [] dev_def = dev.get("definition") or {} dev_info = { "type": dev.get("type"), @@ -187,13 +195,12 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] light_info = self._get_light_meta(dev) switch_info = self._get_switch_meta(dev) - battery_info = self._get_battery_meta(dev) - link_quality_info = self._get_link_quality_meta(dev) + sensors = self._get_sensors(dev) if light_info: - converted_entities.append( + compatible_entities.append( Light( - id=dev['ieee_address'], + id=f'{dev["ieee_address"]}|light', name=dev.get('friendly_name'), on=dev.get('state', {}).get('state') == switch_info.get('value_on'), @@ -240,52 +247,21 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] ) ) elif switch_info and dev.get('state', {}).get('state') is not None: - converted_entities.append( + compatible_entities.append( Switch( - id=dev['ieee_address'], + id=f'{dev["ieee_address"]}:switch', name=dev.get('friendly_name'), state=dev.get('state', {}).get('state') == switch_info['value_on'], description=dev_def.get("description"), data=dev_info, - is_read_only=link_quality_info['is_read_only'], - is_write_only=link_quality_info['is_write_only'], + is_read_only=switch_info['is_read_only'], + is_write_only=switch_info['is_write_only'], ) ) - if battery_info: - converted_entities.append( - Battery( - id=dev['ieee_address'], - name=battery_info.get('friendly_name'), - value=dev.get('state', {}).get('battery'), - description=battery_info.get('description'), - min=battery_info['min'], - max=battery_info['max'], - is_read_only=battery_info['is_read_only'], - is_write_only=battery_info['is_write_only'], - data=dev_info, - ) - ) - - if link_quality_info: - converted_entities.append( - LinkQuality( - id=dev['ieee_address'], - name=link_quality_info.get('friendly_name'), - value=dev.get('state', {}).get('linkquality'), - description=link_quality_info.get('description'), - min=link_quality_info['min'], - max=link_quality_info['max'], - unit=link_quality_info['unit'], - is_read_only=link_quality_info['is_read_only'], - is_write_only=link_quality_info['is_write_only'], - data=dev_info, - ) - ) - - if converted_entities: - compatible_entities += converted_entities + if sensors: + compatible_entities += sensors return super().transform_entities(compatible_entities) # type: ignore @@ -619,7 +595,7 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] """ Change the options of a device. Options can only be changed, not added or deleted. - :param device: Display name of the device. + :param device: Display name or IEEE address of the device. :param option: Option name. :param value: New value. :param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish`` @@ -767,7 +743,6 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] device, self._info['devices_by_addr'].get(device, {}) ) - # noinspection PyShadowingBuiltins @action def device_get( self, device: str, property: Optional[str] = None, **kwargs @@ -786,7 +761,7 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] kwargs = self._mqtt_args(**kwargs) device_info = self._get_device_info(device) if device_info: - device = device_info.get('friendly_name') or device_info['ieee_address'] + device = device_info.get('friendly_name') or self._ieee_address(device_info) if property: properties = self.publish( @@ -904,7 +879,6 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] """ return self.devices_get([device] if device else None, *args, **kwargs) - # noinspection PyShadowingBuiltins,DuplicatedCode @action def device_set( self, @@ -1381,7 +1355,7 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] """ switch_info = self._get_switch_info(device) assert switch_info, '{} is not a valid switch'.format(device) - device = switch_info.get('friendly_name') or switch_info['ieee_address'] + device = switch_info.get('friendly_name') or self._ieee_address(switch_info) props = self.device_set( device, switch_info['property'], switch_info['value_on'] ).output # type: ignore[reportGeneralTypeIssues] @@ -1397,7 +1371,7 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] """ switch_info = self._get_switch_info(device) assert switch_info, '{} is not a valid switch'.format(device) - device = switch_info.get('friendly_name') or switch_info['ieee_address'] + device = switch_info.get('friendly_name') or self._ieee_address(switch_info) props = self.device_set( device, switch_info['property'], switch_info['value_off'] ).output # type: ignore[reportGeneralTypeIssues] @@ -1408,12 +1382,12 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] @action def toggle(self, device, *_, **__) -> dict: """ - Implements :meth:`platypush.plugins.switch.plugin.SwitchPlugin.toggle` and toggles a Zigbee device with a - writable binary property. + Implements :meth:`platypush.plugins.switch.plugin.SwitchPlugin.toggle` + and toggles a Zigbee device with a writable binary property. """ switch_info = self._get_switch_info(device) assert switch_info, '{} is not a valid switch'.format(device) - device = switch_info.get('friendly_name') or switch_info['ieee_address'] + device = switch_info.get('friendly_name') or self._ieee_address(switch_info) props = self.device_set( device, switch_info['property'], switch_info['value_toggle'] ).output # type: ignore[reportGeneralTypeIssues] @@ -1442,7 +1416,28 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] } @staticmethod - def _get_switch_meta(device_info: dict) -> dict: + def _is_read_only(feature: dict) -> bool: + return bool(feature.get('access', 0) & 2) == 0 and ( + bool(feature.get('access', 0) & 1) == 1 + or bool(feature.get('access', 0) & 4) == 1 + ) + + @staticmethod + def _is_write_only(feature: dict) -> bool: + return bool(feature.get('access', 0) & 2) == 1 and ( + bool(feature.get('access', 0) & 1) == 0 + or bool(feature.get('access', 0) & 4) == 0 + ) + + @staticmethod + def _ieee_address(device: dict) -> str: + # Entity value IDs are stored in the `
:` + # format. Therefore, we need to split by `:` if we want to + # retrieve the original address. + return device['ieee_address'].split(':')[0] + + @classmethod + def _get_switch_meta(cls, device_info: dict) -> dict: exposes = (device_info.get('definition', {}) or {}).get('exposes', []) for exposed in exposes: for feature in exposed.get('features', []): @@ -1459,69 +1454,67 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] 'value_on': feature['value_on'], 'value_off': feature['value_off'], 'value_toggle': feature.get('value_toggle', None), - 'is_read_only': not bool(feature.get('access', 0) & 2), - 'is_write_only': not bool(feature.get('access', 0) & 1), + 'is_read_only': cls._is_read_only(feature), + 'is_write_only': cls._is_write_only(feature), } return {} - @staticmethod - def _get_battery_meta(device_info: dict) -> dict: - exposes = (device_info.get('definition', {}) or {}).get('exposes', []) + @classmethod + def _get_sensors(cls, device_info: dict) -> List[Sensor]: + sensors = [] + exposes = [ + exposed + for exposed in (device_info.get('definition', {}) or {}).get('exposes', []) + if (exposed.get('property') and cls._is_read_only(exposed)) + ] + for exposed in exposes: - for feature in exposed.get('features', []): - if ( - feature.get('property') == 'battery' - and feature.get('type') == 'numeric' - ): - return { - 'friendly_name': ( - device_info.get('friendly_name', '[Unnamed device]') - + ' [' - + feature.get('description', 'Battery') - + ']' - ), - 'ieee_address': device_info.get('friendly_name'), - 'property': feature['property'], - 'description': feature.get('description'), - 'min': feature.get('value_min', 0), - 'max': feature.get('value_max', 100), - 'unit': feature.get('unit', '%'), - 'is_read_only': not bool(feature.get('access', 0) & 2), - 'is_write_only': not bool(feature.get('access', 0) & 1), - } + entity_type = None + sensor_args = { + 'id': f'{device_info["ieee_address"]}:{exposed["property"]}', + 'name': ( + device_info.get('friendly_name', '[Unnamed device]') + + ' [' + + exposed.get('description', '') + + ']' + ), + 'value': device_info.get('state', {}).get(exposed['property']), + 'description': exposed.get('description'), + 'min': exposed.get('value_min'), + 'max': exposed.get('value_max'), + 'unit': exposed.get('unit'), + 'is_read_only': cls._is_read_only(exposed), + 'is_write_only': cls._is_read_only(exposed), + 'data': device_info, + } - return {} + if exposed.get('property') == 'battery': + entity_type = Battery + elif exposed.get('property') == 'linkquality': + entity_type = LinkQuality + elif exposed.get('property') == 'current': + entity_type = CurrentSensor + elif exposed.get('property') == 'energy': + entity_type = EnergySensor + elif exposed.get('property') == 'power': + entity_type = PowerSensor + elif exposed.get('property') == 'voltage': + entity_type = VoltageSensor + elif exposed.get('property', '').endswith('temperature'): + entity_type = TemperatureSensor + elif exposed.get('property', '').endswith('humidity'): + entity_type = HumiditySensor + elif exposed.get('type') == 'numeric': + entity_type = NumericSensor - @staticmethod - def _get_link_quality_meta(device_info: dict) -> dict: - exposes = (device_info.get('definition', {}) or {}).get('exposes', []) - for exposed in exposes: - if ( - exposed.get('property') == 'linkquality' - and exposed.get('type') == 'numeric' - ): - return { - 'friendly_name': ( - device_info.get('friendly_name', '[Unnamed device]') - + ' [' - + exposed.get('description', 'Link Quality') - + ']' - ), - 'ieee_address': device_info.get('friendly_name'), - 'property': exposed['property'], - 'description': exposed.get('description'), - 'min': exposed.get('value_min', 0), - 'max': exposed.get('value_max', 100), - 'unit': exposed.get('unit', '%'), - 'is_read_only': not bool(exposed.get('access', 0) & 2), - 'is_write_only': not bool(exposed.get('access', 0) & 1), - } + if entity_type: + sensors.append(entity_type(**sensor_args)) - return {} + return sensors - @staticmethod - def _get_light_meta(device_info: dict) -> dict: + @classmethod + def _get_light_meta(cls, device_info: dict) -> dict: exposes = (device_info.get('definition', {}) or {}).get('exposes', []) for exposed in exposes: if exposed.get('type') == 'light': @@ -1543,8 +1536,8 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] 'value_off': feature['value_off'], 'state_name': feature['name'], 'value_toggle': feature.get('value_toggle', None), - 'is_read_only': not bool(feature.get('access', 0) & 2), - 'is_write_only': not bool(feature.get('access', 0) & 1), + 'is_read_only': cls._is_read_only(feature), + 'is_write_only': cls._is_write_only(feature), } elif ( feature.get('property') == 'brightness' @@ -1556,8 +1549,8 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] 'brightness_name': feature['name'], 'brightness_min': feature['value_min'], 'brightness_max': feature['value_max'], - 'is_read_only': not bool(feature.get('access', 0) & 2), - 'is_write_only': not bool(feature.get('access', 0) & 1), + 'is_read_only': cls._is_read_only(feature), + 'is_write_only': cls._is_write_only(feature), } elif ( feature.get('property') == 'color_temp' @@ -1569,8 +1562,8 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] 'temperature_name': feature['name'], 'temperature_min': feature['value_min'], 'temperature_max': feature['value_max'], - 'is_read_only': not bool(feature.get('access', 0) & 2), - 'is_write_only': not bool(feature.get('access', 0) & 1), + 'is_read_only': cls._is_read_only(feature), + 'is_write_only': cls._is_write_only(feature), } elif ( feature.get('property') == 'color' @@ -1586,12 +1579,8 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] 'hue_max': color_feature.get( 'value_max', 65535 ), - 'is_read_only': not bool( - feature.get('access', 0) & 2 - ), - 'is_write_only': not bool( - feature.get('access', 0) & 1 - ), + 'is_read_only': cls._is_read_only(feature), + 'is_write_only': cls._is_write_only(feature), } ) elif color_feature.get('property') == 'saturation': @@ -1604,12 +1593,8 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] 'saturation_max': color_feature.get( 'value_max', 255 ), - 'is_read_only': not bool( - feature.get('access', 0) & 2 - ), - 'is_write_only': not bool( - feature.get('access', 0) & 1 - ), + 'is_read_only': cls._is_read_only(feature), + 'is_write_only': cls._is_write_only(feature), } ) elif color_feature.get('property') == 'x': @@ -1618,12 +1603,8 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] 'x_name': color_feature['name'], 'x_min': color_feature.get('value_min', 0.0), 'x_max': color_feature.get('value_max', 1.0), - 'is_read_only': not bool( - feature.get('access', 0) & 2 - ), - 'is_write_only': not bool( - feature.get('access', 0) & 1 - ), + 'is_read_only': cls._is_read_only(feature), + 'is_write_only': cls._is_write_only(feature), } ) elif color_feature.get('property') == 'y': @@ -1632,12 +1613,8 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] 'y_name': color_feature['name'], 'y_min': color_feature.get('value_min', 0), 'y_max': color_feature.get('value_max', 255), - 'is_read_only': not bool( - feature.get('access', 0) & 2 - ), - 'is_write_only': not bool( - feature.get('access', 0) & 1 - ), + 'is_read_only': cls._is_read_only(feature), + 'is_write_only': cls._is_write_only(feature), } ) @@ -1662,7 +1639,7 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] continue switches_info[ - device.get('friendly_name', device.get('ieee_address')) + device.get('friendly_name', device['ieee_address'] + ':switch') ] = info return switches_info @@ -1675,7 +1652,6 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] ``state`` property that can be set to ``ON`` or ``OFF``). """ switches_info = self._get_switches_info() - # noinspection PyUnresolvedReferences return [ self._properties_to_switch( device=name, props=switch, switch_info=switches_info[name] @@ -1694,7 +1670,7 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] devices = [ dev for dev in self._get_network_info().get('devices', []) - if dev.get('ieee_address') in lights or dev.get('friendly_name') in lights + if self._ieee_address(dev) in lights or dev.get('friendly_name') in lights ] for dev in devices: