zwave.mqtt additions

- Infer entity types on the basis of their semantic type (bool, decimal,
  list) and read-only attribute (read-only bool is `BinarySensor`,
  read-write bool is `Switch`, read-only decimal is `NumericSensor`,
  read-write decimal is `Dimmer`, etc.) instead of trying to infer it
  from the command class. Only a small set of command classes (like
  configurations, vendor-specific or internal values) will be excluded.
  This should greatly increase the number of supported values.

- Added support for `EnumSwitch` entities.

- Added inference for illuminance and humidity sensors.
This commit is contained in:
Fabio Manganiello 2022-11-27 22:53:53 +01:00
parent 78c59f437a
commit 0e0c90f0f2
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
1 changed files with 51 additions and 27 deletions

View File

@ -26,8 +26,9 @@ from platypush.entities.electricity import (
VoltageSensor, VoltageSensor,
) )
from platypush.entities.humidity import HumiditySensor from platypush.entities.humidity import HumiditySensor
from platypush.entities.illuminance import IlluminanceSensor
from platypush.entities.sensors import BinarySensor, EnumSensor, NumericSensor from platypush.entities.sensors import BinarySensor, EnumSensor, NumericSensor
from platypush.entities.switches import Switch from platypush.entities.switches import EnumSwitch, Switch
from platypush.entities.temperature import TemperatureSensor from platypush.entities.temperature import TemperatureSensor
from platypush.message.event.zwave import ZwaveNodeRenamedEvent, ZwaveNodeEvent from platypush.message.event.zwave import ZwaveNodeRenamedEvent, ZwaveNodeEvent
@ -73,6 +74,33 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin):
""" """
# These classes are ignored by the entity parsing logic
_ignored_entity_classes = {
'application_status',
'association_command_configuration',
'configuration',
'controller_replication',
'crc16_encap',
'firmware_update_md',
'grouping_name',
'ip_configuration',
'manufacturer_proprietary',
'manufacturer_specific',
'multi_cmd',
'multi_instance',
'multi_instance_association',
'no_operation',
'node_naming',
'non_interoperable',
'proprietary',
'scene_actuator_conf',
'scene_controller_conf',
'sensor_configuration',
'version',
'wake_up',
'zwave_plus_info',
}
def __init__( def __init__(
self, self,
name: str, name: str,
@ -488,43 +516,27 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin):
@staticmethod @staticmethod
def _matches_classes(value: Mapping, *names: str): def _matches_classes(value: Mapping, *names: str):
classes = {command_class_by_name[name] for name in names} classes = {command_class_by_name[name] for name in names}
return value.get('command_class', '') in classes if value else False return value.get('command_class', '') in classes if value else False
@classmethod @classmethod
def _is_switch(cls, value: Mapping): def _is_switch(cls, value: Mapping):
return cls._matches_classes( return value.get('type') == 'Bool' and not value.get('is_read_only')
value, 'switch_binary', 'switch_toggle_binary', 'switch_all'
) and not value.get('is_read_only')
@classmethod @classmethod
def _is_dimmer(cls, value: Mapping): def _is_dimmer(cls, value: Mapping):
return cls._matches_classes( return value.get('type') == 'Decimal' and not value.get('is_read_only')
value, 'switch_multilevel', 'switch_toggle_multilevel'
) and not value.get('is_read_only') @classmethod
def _is_enum_switch(cls, value: Mapping):
return value.get('type') == 'List' and not value.get('is_read_only')
@classmethod @classmethod
def _get_sensor_args( def _get_sensor_args(
cls, value: Mapping cls, value: Mapping
) -> Tuple[Optional[Type], Optional[Mapping]]: ) -> Tuple[Optional[Type], Optional[Mapping]]:
sensor_type, args = 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 value.get('is_read_only') and cls._matches_classes( if value.get('is_read_only'):
value, *known_sensor_classes
):
if value.get('type') == 'Bool': if value.get('type') == 'Bool':
sensor_type, args = ( sensor_type, args = (
BinarySensor, BinarySensor,
@ -554,8 +566,14 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin):
sensor_type = EnergySensor sensor_type = EnergySensor
elif re.search(r'\s*temperature$', value['property_id'], re.IGNORECASE): elif re.search(r'\s*temperature$', value['property_id'], re.IGNORECASE):
sensor_type = TemperatureSensor sensor_type = TemperatureSensor
elif re.search(r'\s*humidity$', value['property_id'], re.IGNORECASE): elif re.search(
r'\s*(humidity|moisture)$', value['property_id'], re.IGNORECASE
):
sensor_type = HumiditySensor sensor_type = HumiditySensor
elif re.search(
r'\s*(illuminance|luminosity)$', value['property_id'], re.IGNORECASE
):
sensor_type = IlluminanceSensor
args = { args = {
'value': value.get('data'), 'value': value.get('data'),
@ -571,7 +589,7 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin):
return ( return (
cls._matches_classes(value, 'battery') cls._matches_classes(value, 'battery')
and value.get('is_read_only') and value.get('is_read_only')
and not value['id'].endswith('-isLow') and value.get('type') == 'Decimal'
) )
def _to_entity_args(self, value: Mapping) -> dict: def _to_entity_args(self, value: Mapping) -> dict:
@ -611,7 +629,7 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin):
entities = [] entities = []
for value in values: for value in values:
if not value: if not value or self._matches_classes(value, *self._ignored_entity_classes):
continue continue
entity_type = None entity_type = None
@ -623,6 +641,12 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin):
entity_args['value'] = value['data'] entity_args['value'] = value['data']
entity_args['min'] = value['min'] entity_args['min'] = value['min']
entity_args['max'] = value['max'] entity_args['max'] = value['max']
elif self._is_enum_switch(value):
entity_type = EnumSwitch
entity_args['value'] = value['data']
entity_args['values'] = {
i['value']: i['text'] for i in value.get('data_items', [])
}
elif self._is_battery(value): elif self._is_battery(value):
entity_type = Battery entity_type = Battery
entity_args['value'] = value['data'] entity_args['value'] = value['data']