diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/EnumSensor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/EnumSensor.vue new file mode 100644 index 000000000..6d133d023 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/EnumSensor.vue @@ -0,0 +1,60 @@ + + + + + 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 8158aa488..b80c9df8b 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json @@ -127,6 +127,14 @@ } }, + "enum_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-thermometer" + } + }, + "sensor": { "name": "Sensor", "name_plural": "Sensors", diff --git a/platypush/backend/zwave/mqtt/__init__.py b/platypush/backend/zwave/mqtt/__init__.py index 5a5d426c3..65ca7bbe6 100644 --- a/platypush/backend/zwave/mqtt/__init__.py +++ b/platypush/backend/zwave/mqtt/__init__.py @@ -106,7 +106,7 @@ class ZwaveMqttBackend(MqttBackend): value: Optional[dict] = None, **kwargs, ): - if value and 'id' not in value: + if node and value and 'id' not in value: value_id = f"{value['commandClass']}-{value.get('endpoint', 0)}-{value['property']}" if 'propertyKey' in value: value_id += '-' + str(value['propertyKey']) diff --git a/platypush/entities/sensors.py b/platypush/entities/sensors.py index efcf3e57f..0af0553e8 100644 --- a/platypush/entities/sensors.py +++ b/platypush/entities/sensors.py @@ -1,6 +1,14 @@ import logging -from sqlalchemy import Column, Integer, ForeignKey, Boolean, Numeric, String +from sqlalchemy import ( + Boolean, + Column, + ForeignKey, + Integer, + JSON, + Numeric, + String, +) from .devices import Device, entity_types_registry @@ -83,3 +91,23 @@ if not entity_types_registry.get('BinarySensor'): entity_types_registry['BinarySensor'] = BinarySensor else: BinarySensor = entity_types_registry['BinarySensor'] + + +if not entity_types_registry.get('EnumSensor'): + + class EnumSensor(Sensor): + __tablename__ = 'enum_sensor' + + id = Column( + Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True + ) + value = Column(String) + values = Column(JSON) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + entity_types_registry['EnumSensor'] = EnumSensor +else: + EnumSensor = entity_types_registry['EnumSensor'] diff --git a/platypush/plugins/zigbee/mqtt/__init__.py b/platypush/plugins/zigbee/mqtt/__init__.py index 52c4bf8b8..6cc33fcbc 100644 --- a/platypush/plugins/zigbee/mqtt/__init__.py +++ b/platypush/plugins/zigbee/mqtt/__init__.py @@ -17,7 +17,12 @@ from platypush.entities.electricity import ( from platypush.entities.humidity import HumiditySensor from platypush.entities.lights import Light from platypush.entities.linkquality import LinkQuality -from platypush.entities.sensors import Sensor, BinarySensor, NumericSensor +from platypush.entities.sensors import ( + BinarySensor, + EnumSensor, + NumericSensor, + Sensor, +) from platypush.entities.switches import Switch, EnumSwitch from platypush.entities.temperature import TemperatureSensor from platypush.message import Mapping @@ -1586,6 +1591,9 @@ class ZigbeeMqttPlugin(MqttPlugin): # lgtm [py/missing-call-to-init] sensor_args['value'] = sensor_args['value'] == exposed.get( 'value_on', True ) + elif exposed.get('type') == 'enum': + entity_type = EnumSensor + sensor_args['values'] = exposed.get('values', []) elif exposed.get('type') == 'numeric': entity_type = NumericSensor diff --git a/platypush/plugins/zwave/mqtt/__init__.py b/platypush/plugins/zwave/mqtt/__init__.py index ebe9a7135..83ca2c6fb 100644 --- a/platypush/plugins/zwave/mqtt/__init__.py +++ b/platypush/plugins/zwave/mqtt/__init__.py @@ -26,7 +26,7 @@ from platypush.entities.electricity import ( VoltageSensor, ) from platypush.entities.humidity import HumiditySensor -from platypush.entities.sensors import BinarySensor, NumericSensor +from platypush.entities.sensors import BinarySensor, EnumSensor, NumericSensor from platypush.entities.switches import Switch from platypush.entities.temperature import TemperatureSensor from platypush.message.event.zwave import ZwaveNodeRenamedEvent, ZwaveNodeEvent @@ -507,46 +507,64 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin): def _get_sensor_args( cls, value: Mapping ) -> Tuple[Optional[Type], Optional[Mapping]]: - if not value.get('is_read_only'): - return None, None + sensor_type, args = None, None + known_sensor_classes = { + 'battery', + 'door_lock', + 'lock', + 'meter', + 'notification', + 'sensor_alarm', + 'sensor_binary', + 'sensor_multilevel', + 'thermostat_fan_mode', + 'thermostat_fan_state', + 'thermostat_heating', + } - if ( - cls._matches_classes(value, 'sensor_binary', 'sensor_alarm', 'meter') - and value.get('type') == 'Bool' + if value.get('is_read_only') and cls._matches_classes( + value, *known_sensor_classes ): - return ( - BinarySensor, - { - 'value': value.get('data', False), - }, - ) + if value.get('type') == 'Bool': + sensor_type, args = ( + BinarySensor, + { + 'value': value.get('data', False), + }, + ) + elif value.get('type') == 'List': + sensor_type, args = ( + EnumSensor, + { + 'value': value.get('data'), + 'values': { + i['value']: i['text'] for i in value.get('data_items', []) + }, + }, + ) + elif value.get('type') == 'Decimal': + sensor_type = NumericSensor + if re.search(r'\s*power$', value['property_id'], re.IGNORECASE): + sensor_type = PowerSensor + if re.search(r'\s*voltage$', value['property_id'], re.IGNORECASE): + sensor_type = VoltageSensor + elif re.search( + r'\s*consumption$', value['property_id'], re.IGNORECASE + ) or re.search(r'Wh$', (value.get('units') or '')): + sensor_type = EnergySensor + elif re.search(r'\s*temperature$', value['property_id'], re.IGNORECASE): + sensor_type = TemperatureSensor + elif re.search(r'\s*humidity$', value['property_id'], re.IGNORECASE): + sensor_type = HumiditySensor - if ( - cls._matches_classes(value, 'sensor_multilevel', 'sensor_alarm', 'meter') - and value.get('type') == 'Decimal' - ): - args = { - 'value': value.get('data'), - 'min': value.get('min'), - 'max': value.get('max'), - 'unit': value.get('units'), - } + args = { + 'value': value.get('data'), + 'min': value.get('min'), + 'max': value.get('max'), + 'unit': value.get('units'), + } - sensor_type = NumericSensor - if re.search(r'\s*power$', value['property_id'], re.IGNORECASE): - sensor_type = PowerSensor - if re.search(r'\s*voltage$', value['property_id'], re.IGNORECASE): - sensor_type = VoltageSensor - elif re.search(r'Wh$', value.get('units', '')): - sensor_type = EnergySensor - elif re.search(r'\s*temperature$', value['property_id'], re.IGNORECASE): - sensor_type = TemperatureSensor - elif re.search(r'\s*humidity$', value['property_id'], re.IGNORECASE): - sensor_type = HumiditySensor - - return sensor_type, args - - return None, None + return sensor_type, args @classmethod def _is_battery(cls, value: Mapping):