forked from platypush/platypush
Implemented remaining supported entities for the smartthings
integration
This commit is contained in:
parent
334ccc35a2
commit
afdeb91f66
8 changed files with 650 additions and 73 deletions
|
@ -60,6 +60,8 @@ export default {
|
||||||
|
|
||||||
if (this.isBoolean)
|
if (this.isBoolean)
|
||||||
return this.parseBoolean(this.value)
|
return this.parseBoolean(this.value)
|
||||||
|
if (Array.isArray(this.value) || typeof(this.value) === 'object')
|
||||||
|
return JSON.stringify(this.value)
|
||||||
|
|
||||||
let value = parseFloat(this.value)
|
let value = parseFloat(this.value)
|
||||||
if (this.decimals != null)
|
if (this.decimals != null)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
EnumSensor.vue
|
|
@ -0,0 +1 @@
|
||||||
|
Sensor.vue
|
|
@ -7,6 +7,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"button": {
|
||||||
|
"name": "Button",
|
||||||
|
"name_plural": "Buttons",
|
||||||
|
"icon": {
|
||||||
|
"class": "fas fa-circle-dot"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"current_sensor": {
|
"current_sensor": {
|
||||||
"name": "Sensor",
|
"name": "Sensor",
|
||||||
"name_plural": "Sensors",
|
"name_plural": "Sensors",
|
||||||
|
@ -143,6 +151,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"multi_value_sensor": {
|
||||||
|
"name": "Sensor",
|
||||||
|
"name_plural": "Sensors",
|
||||||
|
"icon": {
|
||||||
|
"class": "fas fa-thermometer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
"name": "Sensor",
|
"name": "Sensor",
|
||||||
"name_plural": "Sensors",
|
"name_plural": "Sensors",
|
||||||
|
|
27
platypush/entities/buttons.py
Normal file
27
platypush/entities/buttons.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import (
|
||||||
|
Column,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
)
|
||||||
|
|
||||||
|
from platypush.common.db import Base
|
||||||
|
|
||||||
|
from .sensors import EnumSensor
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
if 'button' not in Base.metadata:
|
||||||
|
|
||||||
|
class Button(EnumSensor):
|
||||||
|
__tablename__ = 'button'
|
||||||
|
|
||||||
|
id = Column(
|
||||||
|
Integer, ForeignKey(EnumSensor.id, ondelete='CASCADE'), primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
'polymorphic_identity': __tablename__,
|
||||||
|
}
|
|
@ -66,9 +66,9 @@ if 'binary_sensor' not in Base.metadata:
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
|
|
||||||
if value in {True, 1, '1', 't', 'true', 'on'}:
|
if value in {True, 1, '1', 't', 'true', 'on', 'ON'}:
|
||||||
value = True
|
value = True
|
||||||
elif value in {False, 0, '0', 'f', 'false', 'off'}:
|
elif value in {False, 0, '0', 'f', 'false', 'off', 'OFF'}:
|
||||||
value = False
|
value = False
|
||||||
elif value is not None:
|
elif value is not None:
|
||||||
logger.warning(f'Unsupported value for BinarySensor type: {value}')
|
logger.warning(f'Unsupported value for BinarySensor type: {value}')
|
||||||
|
@ -100,3 +100,18 @@ if 'enum_sensor' not in Base.metadata:
|
||||||
__mapper_args__ = {
|
__mapper_args__ = {
|
||||||
'polymorphic_identity': __tablename__,
|
'polymorphic_identity': __tablename__,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if 'multi_value_sensor' not in Base.metadata:
|
||||||
|
|
||||||
|
class MultiValueSensor(Sensor):
|
||||||
|
__tablename__ = 'multi_value_sensor'
|
||||||
|
|
||||||
|
id = Column(
|
||||||
|
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
|
||||||
|
)
|
||||||
|
value = Column(JSON)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
'polymorphic_identity': __tablename__,
|
||||||
|
}
|
||||||
|
|
|
@ -1,32 +1,30 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from typing import Optional, Dict, List, Tuple, Type, Union, Iterable
|
from typing import Dict, Iterable, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
from pysmartthings import (
|
from pysmartthings import (
|
||||||
Attribute,
|
Attribute,
|
||||||
Capability,
|
Capability,
|
||||||
Command,
|
Command,
|
||||||
Device,
|
DeviceEntity,
|
||||||
DeviceStatus,
|
DeviceStatus,
|
||||||
SmartThings,
|
SmartThings,
|
||||||
)
|
)
|
||||||
|
|
||||||
from platypush.entities import Entity, manages
|
from platypush.entities import Entity, manages
|
||||||
|
from platypush.entities.devices import Device
|
||||||
from platypush.entities.devices import Device as PDevice
|
|
||||||
from platypush.entities.dimmers import Dimmer
|
from platypush.entities.dimmers import Dimmer
|
||||||
from platypush.entities.lights import Light
|
from platypush.entities.lights import Light
|
||||||
from platypush.entities.sensors import Sensor
|
from platypush.entities.sensors import Sensor
|
||||||
from platypush.entities.switches import Switch
|
from platypush.entities.switches import EnumSwitch, Switch
|
||||||
from platypush.plugins import RunnablePlugin, action
|
from platypush.plugins import RunnablePlugin, action
|
||||||
from platypush.utils import camel_case_to_snake_case
|
from platypush.utils import camel_case_to_snake_case
|
||||||
|
|
||||||
from ._mappers import device_mappers
|
from ._mappers import DeviceMapper, device_mappers
|
||||||
|
|
||||||
|
|
||||||
@manages(PDevice, Dimmer, Sensor, Switch, Light)
|
@manages(Device, Dimmer, EnumSwitch, Light, Sensor, Switch)
|
||||||
class SmartthingsPlugin(RunnablePlugin):
|
class SmartthingsPlugin(RunnablePlugin):
|
||||||
"""
|
"""
|
||||||
Plugin to interact with devices and locations registered to a Samsung SmartThings account.
|
Plugin to interact with devices and locations registered to a Samsung SmartThings account.
|
||||||
|
@ -316,7 +314,7 @@ class SmartthingsPlugin(RunnablePlugin):
|
||||||
assert location, 'Location {} not found'.format(location_id or name)
|
assert location, 'Location {} not found'.format(location_id or name)
|
||||||
return self._location_to_dict(location)
|
return self._location_to_dict(location)
|
||||||
|
|
||||||
def _get_device(self, device: str) -> Device:
|
def _get_device(self, device: str) -> DeviceEntity:
|
||||||
return self._get_devices(device)[0]
|
return self._get_devices(device)[0]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -328,7 +326,7 @@ class SmartthingsPlugin(RunnablePlugin):
|
||||||
|
|
||||||
def _get_existing_and_missing_devices(
|
def _get_existing_and_missing_devices(
|
||||||
self, *devices: str
|
self, *devices: str
|
||||||
) -> Tuple[List[Device], List[str]]:
|
) -> Tuple[List[DeviceEntity], List[str]]:
|
||||||
# Split the external_id:type indicators and always return the parent device
|
# Split the external_id:type indicators and always return the parent device
|
||||||
devices = tuple(self._to_device_and_property(dev)[0] for dev in devices)
|
devices = tuple(self._to_device_and_property(dev)[0] for dev in devices)
|
||||||
|
|
||||||
|
@ -341,7 +339,7 @@ class SmartthingsPlugin(RunnablePlugin):
|
||||||
missing_devs = {dev for dev in devices if dev not in found_devs}
|
missing_devs = {dev for dev in devices if dev not in found_devs}
|
||||||
return list(found_devs.values()), list(missing_devs) # type: ignore
|
return list(found_devs.values()), list(missing_devs) # type: ignore
|
||||||
|
|
||||||
def _get_devices(self, *devices: str) -> List[Device]:
|
def _get_devices(self, *devices: str) -> List[DeviceEntity]:
|
||||||
devs, missing_devs = self._get_existing_and_missing_devices(*devices)
|
devs, missing_devs = self._get_existing_and_missing_devices(*devices)
|
||||||
if missing_devs:
|
if missing_devs:
|
||||||
self.refresh_info()
|
self.refresh_info()
|
||||||
|
@ -376,8 +374,8 @@ class SmartthingsPlugin(RunnablePlugin):
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
device = self._get_device(device)
|
dev = self._get_device(device)
|
||||||
return self._device_to_dict(device)
|
return self._device_to_dict(dev)
|
||||||
|
|
||||||
async def _execute(
|
async def _execute(
|
||||||
self,
|
self,
|
||||||
|
@ -457,58 +455,92 @@ class SmartthingsPlugin(RunnablePlugin):
|
||||||
loop.stop()
|
loop.stop()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
def _property_to_entity_name(property: str) -> str:
|
||||||
|
return ' '.join(
|
||||||
|
[
|
||||||
|
t[:1].upper() + t[1:]
|
||||||
|
for t in camel_case_to_snake_case(property).split('_')
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def _to_entity(
|
def _to_entity(
|
||||||
device: Device, property: str, entity_type: Type[Entity], **kwargs
|
cls, device: DeviceEntity, property: str, entity_type: Type[Entity], **kwargs
|
||||||
) -> Entity:
|
) -> Entity:
|
||||||
return entity_type(
|
return entity_type(
|
||||||
id=f'{device.device_id}:{property}',
|
id=f'{device.device_id}:{property}',
|
||||||
name=entity_type.__name__,
|
name=cls._property_to_entity_name(property),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def _get_status_attr_info(device: Device, attr: str) -> dict:
|
def _get_status_attr_info(cls, device: DeviceEntity, mapper: DeviceMapper) -> dict:
|
||||||
status = device.status.attributes.get(attr)
|
status = device.status.attributes.get(mapper.attribute)
|
||||||
info = {}
|
info = {}
|
||||||
if status:
|
if status:
|
||||||
if getattr(status, 'unit', None) is not None:
|
info.update(
|
||||||
info['unit'] = status.unit
|
{
|
||||||
if getattr(status, 'min', None) is not None:
|
attr: getattr(status, attr, None)
|
||||||
info['min'] = status.min
|
for attr in ('unit', 'min', 'max')
|
||||||
if getattr(status, 'max', None) is not None:
|
if getattr(status, attr, None) is not None
|
||||||
info['max'] = status.max
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_values = mapper.values
|
||||||
|
if isinstance(mapper.value_type, str):
|
||||||
|
# The list of supported values is actually contained on a
|
||||||
|
# device attribute
|
||||||
|
try:
|
||||||
|
supported_values = getattr(
|
||||||
|
device.status, mapper.value_type, mapper.values
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if supported_values:
|
||||||
|
info['values'] = mapper.values
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _merge_dicts(*dicts: dict) -> dict:
|
||||||
|
ret = {}
|
||||||
|
for d in dicts:
|
||||||
|
ret.update(d)
|
||||||
|
return ret
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_supported_entities(
|
def _get_supported_entities(
|
||||||
cls,
|
cls,
|
||||||
device: Device,
|
device: DeviceEntity,
|
||||||
entity_type: Optional[Type[Entity]] = None,
|
entity_type: Optional[Type[Entity]] = None,
|
||||||
entity_value_attr: str = 'value',
|
entity_value_attr: str = 'value',
|
||||||
**default_entity_args,
|
**default_entity_args,
|
||||||
) -> List[Entity]:
|
) -> List[Entity]:
|
||||||
mappers = [
|
mappers = [
|
||||||
m
|
mapper
|
||||||
for m in device_mappers
|
for mapper in device_mappers
|
||||||
if (entity_type is None or issubclass(m.entity_type, entity_type))
|
if (entity_type is None or issubclass(mapper.entity_type, entity_type))
|
||||||
and m.capability in device.capabilities
|
and mapper.capability in device.capabilities
|
||||||
]
|
]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
cls._to_entity(
|
cls._to_entity(
|
||||||
device,
|
device,
|
||||||
property=m.attribute,
|
property=mapper.attribute,
|
||||||
entity_type=m.entity_type,
|
entity_type=mapper.entity_type,
|
||||||
**{entity_value_attr: m.get_value(device)},
|
**cls._merge_dicts(
|
||||||
**default_entity_args,
|
{entity_value_attr: mapper.get_value(device)},
|
||||||
**cls._get_status_attr_info(device, m.attribute),
|
default_entity_args,
|
||||||
|
mapper.entity_args,
|
||||||
|
cls._get_status_attr_info(device, mapper),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for m in mappers
|
for mapper in mappers
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_lights(cls, device: Device) -> Iterable[Light]:
|
def _get_lights(cls, device: DeviceEntity) -> Iterable[Light]:
|
||||||
if not (
|
if not (
|
||||||
{Capability.color_control, Capability.color_temperature}.intersection(
|
{Capability.color_control, Capability.color_temperature}.intersection(
|
||||||
device.capabilities
|
device.capabilities
|
||||||
|
@ -517,33 +549,39 @@ class SmartthingsPlugin(RunnablePlugin):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
light_attrs = {}
|
light_attrs = {}
|
||||||
|
status = device.status
|
||||||
|
|
||||||
if Capability.switch in device.capabilities:
|
if Capability.switch in device.capabilities:
|
||||||
light_attrs['on'] = device.status.switch
|
light_attrs['on'] = status.switch
|
||||||
if Capability.switch_level in device.capabilities:
|
if Capability.switch_level in device.capabilities:
|
||||||
light_attrs['brightness'] = device.status.level
|
light_attrs['brightness'] = status.level
|
||||||
light_attrs['brightness_min'] = 0
|
light_attrs['brightness_min'] = 0
|
||||||
light_attrs['brightness_max'] = 100
|
light_attrs['brightness_max'] = 100
|
||||||
if Capability.color_temperature in device.capabilities:
|
if Capability.color_temperature in device.capabilities:
|
||||||
light_attrs['temperature'] = device.status.color_temperature
|
light_attrs['temperature'] = status.color_temperature
|
||||||
light_attrs['temperature_min'] = 1
|
light_attrs['temperature_min'] = 1
|
||||||
light_attrs['temperature_max'] = 30000
|
light_attrs['temperature_max'] = 30000
|
||||||
if getattr(device.status, 'hue', None) is not None:
|
if getattr(status, 'hue', None) is not None:
|
||||||
light_attrs['hue'] = device.status.hue
|
light_attrs['hue'] = status.hue
|
||||||
light_attrs['hue_min'] = 0
|
light_attrs['hue_min'] = 0
|
||||||
light_attrs['hue_max'] = 100
|
light_attrs['hue_max'] = 100
|
||||||
if getattr(device.status, 'saturation', None) is not None:
|
if getattr(status, 'saturation', None) is not None:
|
||||||
light_attrs['saturation'] = device.status.saturation
|
light_attrs['saturation'] = status.saturation
|
||||||
light_attrs['saturation_min'] = 0
|
light_attrs['saturation_min'] = 0
|
||||||
light_attrs['saturation_max'] = 100
|
light_attrs['saturation_max'] = 100
|
||||||
|
|
||||||
return [cls._to_entity(device, 'light', Light, **light_attrs)]
|
return [cls._to_entity(device, 'light', Light, **light_attrs)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_switches(cls, device: Device) -> Iterable[Switch]:
|
def _get_switches(cls, device: DeviceEntity) -> Iterable[Switch]:
|
||||||
return cls._get_supported_entities(device, Switch, entity_value_attr='state')
|
return cls._get_supported_entities(device, Switch, entity_value_attr='state')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_dimmers(cls, device: Device) -> Iterable[Dimmer]:
|
def _get_enum_switches(cls, device: DeviceEntity) -> Iterable[Switch]:
|
||||||
|
return cls._get_supported_entities(device, EnumSwitch)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_dimmers(cls, device: DeviceEntity) -> Iterable[Dimmer]:
|
||||||
return cls._get_supported_entities(device, Dimmer, min=0, max=100)
|
return cls._get_supported_entities(device, Dimmer, min=0, max=100)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -703,12 +741,21 @@ class SmartthingsPlugin(RunnablePlugin):
|
||||||
if value is None:
|
if value is None:
|
||||||
# Toggle case
|
# Toggle case
|
||||||
dev = self._get_device(device)
|
dev = self._get_device(device)
|
||||||
|
if property == 'light':
|
||||||
|
property = 'switch'
|
||||||
|
else:
|
||||||
assert hasattr(
|
assert hasattr(
|
||||||
dev.status, property
|
dev.status, property
|
||||||
), f'No such property on device "{dev.label}": "{property}"'
|
), f'No such property on device "{dev.label}": "{property}"'
|
||||||
|
|
||||||
|
value = getattr(dev.status, property, None)
|
||||||
|
assert value is not None, (
|
||||||
|
f'Could not get the current value of "{property}" for the '
|
||||||
|
f'device "{dev.device_id}"'
|
||||||
|
)
|
||||||
|
|
||||||
|
value = not value # Toggle
|
||||||
device = dev.device_id
|
device = dev.device_id
|
||||||
value = getattr(dev.status, property) is not True
|
|
||||||
|
|
||||||
return self.set_value(device, property, value)
|
return self.set_value(device, property, value)
|
||||||
|
|
||||||
|
@ -769,10 +816,10 @@ class SmartthingsPlugin(RunnablePlugin):
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mapper, f'No mappers found to set {property}={data} on device "{device}"'
|
assert mapper, f'No mappers found to set {property}={data} on device "{device}"'
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
mapper.set_command
|
mapper.set_command
|
||||||
), f'The property "{property}" on the device "{device}" cannot be set'
|
), f'The property "{property}" on the device "{device}" cannot be set'
|
||||||
|
|
||||||
command = (
|
command = (
|
||||||
mapper.set_command(data)
|
mapper.set_command(data)
|
||||||
if callable(mapper.set_command)
|
if callable(mapper.set_command)
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
|
from enum import Enum
|
||||||
from typing import Any, Callable, List, Optional, Type, Union
|
from typing import Any, Callable, List, Optional, Type, Union
|
||||||
|
|
||||||
from pysmartthings import Attribute, Capability, Command, Device
|
from pysmartthings import Attribute, Capability, Command, DeviceEntity
|
||||||
|
|
||||||
from platypush.entities import Entity
|
from platypush.entities import Entity
|
||||||
|
|
||||||
from platypush.entities.audio import Muted, Volume
|
from platypush.entities.audio import Muted, Volume
|
||||||
from platypush.entities.batteries import Battery
|
from platypush.entities.batteries import Battery
|
||||||
from platypush.entities.dimmers import Dimmer
|
from platypush.entities.dimmers import Dimmer
|
||||||
|
from platypush.entities.electricity import EnergySensor, PowerSensor, VoltageSensor
|
||||||
|
from platypush.entities.humidity import HumiditySensor
|
||||||
|
from platypush.entities.illuminance import IlluminanceSensor
|
||||||
|
from platypush.entities.linkquality import LinkQuality
|
||||||
from platypush.entities.motion import MotionSensor
|
from platypush.entities.motion import MotionSensor
|
||||||
from platypush.entities.switches import Switch
|
from platypush.entities.sensors import (
|
||||||
|
BinarySensor,
|
||||||
|
EnumSensor,
|
||||||
|
MultiValueSensor,
|
||||||
|
NumericSensor,
|
||||||
|
)
|
||||||
|
from platypush.entities.switches import EnumSwitch, Switch
|
||||||
|
from platypush.entities.temperature import TemperatureSensor
|
||||||
|
|
||||||
|
|
||||||
class DeviceMapper:
|
class DeviceMapper:
|
||||||
|
@ -22,10 +34,11 @@ class DeviceMapper:
|
||||||
entity_type: Type[Entity],
|
entity_type: Type[Entity],
|
||||||
capability: str,
|
capability: str,
|
||||||
attribute: str,
|
attribute: str,
|
||||||
value_type: Type,
|
value_type: Union[Type, str],
|
||||||
set_command: Optional[Union[str, Callable[[Any], List[Any]]]] = None,
|
set_command: Optional[Union[str, Callable[[Any], str]]] = None,
|
||||||
get_value: Optional[Callable[[Device], Any]] = None,
|
get_value: Optional[Callable[[DeviceEntity], Any]] = None,
|
||||||
set_value_args: Optional[Callable[..., Any]] = None,
|
set_value_args: Optional[Callable[..., Any]] = None,
|
||||||
|
**kwargs
|
||||||
):
|
):
|
||||||
self.entity_type = entity_type
|
self.entity_type = entity_type
|
||||||
self.capability = capability
|
self.capability = capability
|
||||||
|
@ -33,31 +46,79 @@ class DeviceMapper:
|
||||||
self.attribute = attribute
|
self.attribute = attribute
|
||||||
self.value_type = value_type
|
self.value_type = value_type
|
||||||
self.get_value = get_value if get_value else self._default_get_value
|
self.get_value = get_value if get_value else self._default_get_value
|
||||||
|
self.values = []
|
||||||
|
self.entity_args = kwargs
|
||||||
|
|
||||||
|
if isinstance(value_type, Enum):
|
||||||
|
self.values = [v.name for v in entity_type] # type: ignore
|
||||||
|
|
||||||
self.set_value_args = (
|
self.set_value_args = (
|
||||||
set_value_args if set_value_args else self._default_set_value_args
|
set_value_args if set_value_args else self._default_set_value_args
|
||||||
)
|
)
|
||||||
|
|
||||||
def _default_get_value(self, device: Device) -> Any:
|
def _cast_value(self, value: Any) -> Any:
|
||||||
|
if not isinstance(self.value_type, (str, Enum)):
|
||||||
|
try:
|
||||||
|
value = self.value_type(value)
|
||||||
|
except Exception:
|
||||||
|
# Set the value to None in case of cast errors
|
||||||
|
value = None
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _default_get_value(self, device: DeviceEntity) -> Any:
|
||||||
if hasattr(device.status, self.attribute):
|
if hasattr(device.status, self.attribute):
|
||||||
value = getattr(device.status, self.attribute)
|
value = getattr(device.status, self.attribute)
|
||||||
else:
|
else:
|
||||||
value = device.status.attributes[self.attribute].value
|
value = device.status.attributes[self.attribute].value
|
||||||
|
|
||||||
return self.value_type(value)
|
return self._cast_value(value)
|
||||||
|
|
||||||
def _default_set_value_args(self, *values: Any) -> List[Any]:
|
def _default_set_value_args(self, *values: Any) -> List[Any]:
|
||||||
return [self.value_type(v) for v in values]
|
return [self._cast_value(v) for v in values]
|
||||||
|
|
||||||
|
|
||||||
device_mappers: List[DeviceMapper] = [
|
device_mappers: List[DeviceMapper] = [
|
||||||
|
# acceleration_sensor
|
||||||
DeviceMapper(
|
DeviceMapper(
|
||||||
entity_type=Volume,
|
entity_type=BinarySensor,
|
||||||
capability=Capability.audio_volume,
|
capability=Capability.acceleration_sensor,
|
||||||
attribute=Attribute.volume,
|
attribute=Attribute.acceleration,
|
||||||
value_type=int,
|
value_type=bool,
|
||||||
set_command=Command.set_volume,
|
|
||||||
),
|
),
|
||||||
|
# air_conditioner_fan_mode
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSwitch,
|
||||||
|
capability=Capability.air_conditioner_fan_mode,
|
||||||
|
attribute=Attribute.fan_mode,
|
||||||
|
value_type=Attribute.supported_ac_fan_modes,
|
||||||
|
set_command=Command.set_fan_mode,
|
||||||
|
),
|
||||||
|
# air_conditioner_mode
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSwitch,
|
||||||
|
capability=Capability.air_conditioner_mode,
|
||||||
|
attribute=Attribute.air_conditioner_mode,
|
||||||
|
value_type=Attribute.supported_ac_modes,
|
||||||
|
set_command=Command.set_air_conditioner_mode,
|
||||||
|
),
|
||||||
|
# air_quality_sensor
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.air_quality_sensor,
|
||||||
|
attribute=Attribute.air_quality,
|
||||||
|
value_type=int,
|
||||||
|
),
|
||||||
|
# alarm
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSwitch,
|
||||||
|
capability=Capability.alarm,
|
||||||
|
attribute=Attribute.alarm,
|
||||||
|
value_type=Enum('AlarmValues', ['both', 'off', 'siren', 'strobe']),
|
||||||
|
set_command=lambda value: value,
|
||||||
|
set_value_args=lambda *_: [],
|
||||||
|
),
|
||||||
|
# audio_mute
|
||||||
DeviceMapper(
|
DeviceMapper(
|
||||||
entity_type=Muted,
|
entity_type=Muted,
|
||||||
capability=Capability.audio_mute,
|
capability=Capability.audio_mute,
|
||||||
|
@ -66,25 +127,285 @@ device_mappers: List[DeviceMapper] = [
|
||||||
set_command=lambda value: Command.mute if value else Command.unmute,
|
set_command=lambda value: Command.mute if value else Command.unmute,
|
||||||
set_value_args=lambda *_: [],
|
set_value_args=lambda *_: [],
|
||||||
),
|
),
|
||||||
|
# audio_volume
|
||||||
DeviceMapper(
|
DeviceMapper(
|
||||||
entity_type=MotionSensor,
|
entity_type=Volume,
|
||||||
capability=Capability.motion_sensor,
|
capability=Capability.audio_volume,
|
||||||
attribute=Attribute.motion,
|
attribute=Attribute.volume,
|
||||||
value_type=bool,
|
value_type=int,
|
||||||
|
set_command=Command.set_volume,
|
||||||
),
|
),
|
||||||
|
# battery
|
||||||
DeviceMapper(
|
DeviceMapper(
|
||||||
entity_type=Battery,
|
entity_type=Battery,
|
||||||
capability=Capability.battery,
|
capability=Capability.battery,
|
||||||
attribute=Attribute.battery,
|
attribute=Attribute.battery,
|
||||||
value_type=int,
|
value_type=int,
|
||||||
),
|
),
|
||||||
|
# body_mass_index_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.body_mass_index_measurement,
|
||||||
|
attribute=Attribute.body_weight_measurement,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# body_weight_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.body_weight_measurement,
|
||||||
|
attribute=Attribute.body_weight_measurement,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# button
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.button,
|
||||||
|
attribute=Attribute.button,
|
||||||
|
value_type=Enum(
|
||||||
|
'ButtonValues',
|
||||||
|
[
|
||||||
|
'pushed',
|
||||||
|
'held',
|
||||||
|
'double',
|
||||||
|
'pushed_2x',
|
||||||
|
'pushed_3x',
|
||||||
|
'pushed_4x',
|
||||||
|
'pushed_5x',
|
||||||
|
'pushed_6x',
|
||||||
|
'down',
|
||||||
|
'down_2x',
|
||||||
|
'down_3x',
|
||||||
|
'down_4x',
|
||||||
|
'down_5x',
|
||||||
|
'down_6x',
|
||||||
|
'down_hold',
|
||||||
|
'up',
|
||||||
|
'up_2x',
|
||||||
|
'up_3x',
|
||||||
|
'up_4x',
|
||||||
|
'up_5x',
|
||||||
|
'up_6x',
|
||||||
|
'up_hold',
|
||||||
|
'swipe_up',
|
||||||
|
'swipe_down',
|
||||||
|
'swipe_left',
|
||||||
|
'swipe_right',
|
||||||
|
'unknown',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# carbon_dioxide_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.carbon_dioxide_measurement,
|
||||||
|
attribute=Attribute.carbon_dioxide,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# carbon_monoxide_detector
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.carbon_monoxide_detector,
|
||||||
|
attribute=Attribute.carbon_monoxide,
|
||||||
|
value_type=Enum(
|
||||||
|
'CarbonMonoxideValues', ['clear', 'detected', 'tested', 'unknown']
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# carbon_monoxide_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.carbon_monoxide_measurement,
|
||||||
|
attribute=Attribute.carbon_monoxide_level,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# contact_sensor
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=BinarySensor,
|
||||||
|
capability=Capability.contact_sensor,
|
||||||
|
attribute=Attribute.contact,
|
||||||
|
value_type=bool,
|
||||||
|
),
|
||||||
|
# dishwasher_operating_state
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.dishwasher_operating_state,
|
||||||
|
attribute=Attribute.machine_state,
|
||||||
|
value_type=Attribute.supported_machine_states,
|
||||||
|
),
|
||||||
|
# door_control
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=Switch,
|
||||||
|
capability=Capability.door_control,
|
||||||
|
attribute=Attribute.door,
|
||||||
|
value_type=bool,
|
||||||
|
get_value=lambda device: device.status.door in {'open', 'opening'},
|
||||||
|
set_command=lambda value: Command.open if value else Command.close,
|
||||||
|
),
|
||||||
|
# dryer_operating_state
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.dryer_operating_state,
|
||||||
|
attribute=Attribute.machine_state,
|
||||||
|
value_type=Attribute.supported_machine_states,
|
||||||
|
),
|
||||||
|
# dust_sensor
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.dust_sensor,
|
||||||
|
attribute=Attribute.dust_level,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# energy_meter
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnergySensor,
|
||||||
|
capability=Capability.energy_meter,
|
||||||
|
attribute=Attribute.energy,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# equivalent_carbon_dioxide_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.equivalent_carbon_dioxide_measurement,
|
||||||
|
attribute=Attribute.equivalent_carbon_dioxide_measurement,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# fan_speed
|
||||||
DeviceMapper(
|
DeviceMapper(
|
||||||
entity_type=Dimmer,
|
entity_type=Dimmer,
|
||||||
capability=Capability.switch_level,
|
capability=Capability.fan_speed,
|
||||||
attribute=Attribute.level,
|
attribute=Attribute.fan_speed,
|
||||||
value_type=int,
|
value_type=int,
|
||||||
set_command=Command.set_level,
|
set_command=Command.set_fan_speed,
|
||||||
),
|
),
|
||||||
|
# formaldehyde_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.formaldehyde_measurement,
|
||||||
|
attribute=Attribute.formaldehyde_level,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# gas_meter
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.gas_meter,
|
||||||
|
attribute=Attribute.gas_meter,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# illuminance_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=IlluminanceSensor,
|
||||||
|
capability=Capability.illuminance_measurement,
|
||||||
|
attribute=Attribute.illuminance,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# infrared_level
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.infrared_level,
|
||||||
|
attribute=Attribute.infrared_level,
|
||||||
|
value_type=int,
|
||||||
|
min=0,
|
||||||
|
max=100,
|
||||||
|
),
|
||||||
|
# lock
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=Switch,
|
||||||
|
capability=Capability.lock,
|
||||||
|
attribute=Attribute.lock,
|
||||||
|
value_type=bool,
|
||||||
|
get_value=lambda device: device.status.lock in {True, 'locked'},
|
||||||
|
set_command=lambda value: Command.lock if value else Command.unlock,
|
||||||
|
set_value_args=lambda *_: [],
|
||||||
|
),
|
||||||
|
# media_input_source
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSwitch,
|
||||||
|
capability=Capability.media_input_source,
|
||||||
|
attribute=Attribute.input_source,
|
||||||
|
value_type=Attribute.supported_input_sources,
|
||||||
|
set_command=Command.set_input_source,
|
||||||
|
),
|
||||||
|
# motion_sensor
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=MotionSensor,
|
||||||
|
capability=Capability.motion_sensor,
|
||||||
|
attribute=Attribute.motion,
|
||||||
|
value_type=bool,
|
||||||
|
),
|
||||||
|
# odor_sensor
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.odor_sensor,
|
||||||
|
attribute=Attribute.odor_level,
|
||||||
|
value_type=int,
|
||||||
|
),
|
||||||
|
# oven_operating_state
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.oven_operating_state,
|
||||||
|
attribute=Attribute.machine_state,
|
||||||
|
value_type=Attribute.supported_machine_states,
|
||||||
|
),
|
||||||
|
# power_consumption_report
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=PowerSensor,
|
||||||
|
capability=Capability.power_consumption_report,
|
||||||
|
attribute=Attribute.power_consumption,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# power_meter
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=PowerSensor,
|
||||||
|
capability=Capability.power_meter,
|
||||||
|
attribute=Attribute.power,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# power_source
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.power_source,
|
||||||
|
attribute=Attribute.power_source,
|
||||||
|
value_type=Enum('PowerSourceValues', ['battery', 'dc', 'mains', 'unknown']),
|
||||||
|
),
|
||||||
|
# presence_sensor
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=BinarySensor,
|
||||||
|
capability=Capability.presence_sensor,
|
||||||
|
attribute=Attribute.presence,
|
||||||
|
value_type=bool,
|
||||||
|
),
|
||||||
|
# relative_humidity_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=HumiditySensor,
|
||||||
|
capability=Capability.relative_humidity_measurement,
|
||||||
|
attribute=Attribute.humidity,
|
||||||
|
value_type=int,
|
||||||
|
min=0,
|
||||||
|
max=100,
|
||||||
|
),
|
||||||
|
# signal_strength
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=LinkQuality,
|
||||||
|
capability=Capability.signal_strength,
|
||||||
|
attribute=Attribute.lqi,
|
||||||
|
value_type=int,
|
||||||
|
min=0,
|
||||||
|
max=255,
|
||||||
|
),
|
||||||
|
# smoke_detector
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.smoke_detector,
|
||||||
|
attribute=Attribute.smoke,
|
||||||
|
value_type=Enum('SmokeDetectorValues', ['clear', 'detected', 'tested']),
|
||||||
|
),
|
||||||
|
# sound_sensor
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=BinarySensor,
|
||||||
|
capability=Capability.sound_sensor,
|
||||||
|
attribute=Attribute.sound,
|
||||||
|
value_type=bool,
|
||||||
|
),
|
||||||
|
# switch
|
||||||
DeviceMapper(
|
DeviceMapper(
|
||||||
entity_type=Switch,
|
entity_type=Switch,
|
||||||
capability=Capability.switch,
|
capability=Capability.switch,
|
||||||
|
@ -93,6 +414,153 @@ device_mappers: List[DeviceMapper] = [
|
||||||
set_command=lambda value: Command.on if value else Command.off,
|
set_command=lambda value: Command.on if value else Command.off,
|
||||||
set_value_args=lambda *_: [],
|
set_value_args=lambda *_: [],
|
||||||
),
|
),
|
||||||
|
# switch_level
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=Dimmer,
|
||||||
|
capability=Capability.switch_level,
|
||||||
|
attribute=Attribute.level,
|
||||||
|
value_type=int,
|
||||||
|
set_command=Command.set_level,
|
||||||
|
),
|
||||||
|
# tamper_alert
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=BinarySensor,
|
||||||
|
capability=Capability.tamper_alert,
|
||||||
|
attribute=Attribute.tamper,
|
||||||
|
value_type=bool,
|
||||||
|
),
|
||||||
|
# temperature_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=TemperatureSensor,
|
||||||
|
capability=Capability.temperature_measurement,
|
||||||
|
attribute=Attribute.temperature,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# thermostat_cooling_setpoint
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=Dimmer,
|
||||||
|
capability=Capability.thermostat_cooling_setpoint,
|
||||||
|
attribute=Attribute.cooling_setpoint,
|
||||||
|
value_type=float,
|
||||||
|
min=-460,
|
||||||
|
max=10000,
|
||||||
|
set_command=Command.set_cooling_setpoint,
|
||||||
|
),
|
||||||
|
# thermostat_fan_mode
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSwitch,
|
||||||
|
capability=Capability.thermostat_fan_mode,
|
||||||
|
attribute=Attribute.thermostat_fan_mode,
|
||||||
|
value_type=Attribute.supported_thermostat_fan_modes,
|
||||||
|
set_command=Command.set_thermostat_fan_mode,
|
||||||
|
),
|
||||||
|
# thermostat_heating_setpoint
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=Dimmer,
|
||||||
|
capability=Capability.thermostat_heating_setpoint,
|
||||||
|
attribute=Attribute.heating_setpoint,
|
||||||
|
value_type=float,
|
||||||
|
min=-460,
|
||||||
|
max=10000,
|
||||||
|
set_command=Command.set_heating_setpoint,
|
||||||
|
),
|
||||||
|
# thermostat_mode
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSwitch,
|
||||||
|
capability=Capability.thermostat_mode,
|
||||||
|
attribute=Attribute.thermostat_mode,
|
||||||
|
value_type=Attribute.supported_thermostat_modes,
|
||||||
|
set_command=Command.set_thermostat_mode,
|
||||||
|
),
|
||||||
|
# thermostat_operating_state
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.thermostat_operating_state,
|
||||||
|
attribute=Attribute.thermostat_operating_state,
|
||||||
|
value_type=Enum(
|
||||||
|
'ThermostatOperatingState',
|
||||||
|
[
|
||||||
|
'cooling',
|
||||||
|
'fan only',
|
||||||
|
'heating',
|
||||||
|
'idle',
|
||||||
|
'pending cool',
|
||||||
|
'pending heat',
|
||||||
|
'vent economizer',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# three_axis
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=MultiValueSensor,
|
||||||
|
capability=Capability.three_axis,
|
||||||
|
attribute=Attribute.three_axis,
|
||||||
|
value_type=list,
|
||||||
|
),
|
||||||
|
# tv_channel
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=Dimmer,
|
||||||
|
capability=Capability.tv_channel,
|
||||||
|
attribute=Attribute.tv_channel,
|
||||||
|
value_type=int,
|
||||||
|
set_command=Command.set_tv_channel,
|
||||||
|
min=1,
|
||||||
|
max=1000,
|
||||||
|
),
|
||||||
|
# tvoc_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.tvoc_measurement,
|
||||||
|
attribute=Attribute.tvoc_level,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# ultraviolet_index
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=NumericSensor,
|
||||||
|
capability=Capability.ultraviolet_index,
|
||||||
|
attribute=Attribute.ultraviolet_index,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# valve
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=Switch,
|
||||||
|
capability=Capability.valve,
|
||||||
|
attribute=Attribute.valve,
|
||||||
|
value_type=bool,
|
||||||
|
set_command=lambda value: Command.open if value else Command.close,
|
||||||
|
set_value_args=lambda *_: [],
|
||||||
|
),
|
||||||
|
# voltage_measurement
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=VoltageSensor,
|
||||||
|
capability=Capability.voltage_measurement,
|
||||||
|
attribute=Attribute.voltage,
|
||||||
|
value_type=float,
|
||||||
|
),
|
||||||
|
# washer_operating_state
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=EnumSensor,
|
||||||
|
capability=Capability.washer_operating_state,
|
||||||
|
attribute=Attribute.machine_state,
|
||||||
|
value_type=Attribute.supported_machine_states,
|
||||||
|
),
|
||||||
|
# water_sensor
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=BinarySensor,
|
||||||
|
capability=Capability.water_sensor,
|
||||||
|
attribute=Attribute.water,
|
||||||
|
value_type=bool,
|
||||||
|
),
|
||||||
|
# window_shade
|
||||||
|
DeviceMapper(
|
||||||
|
entity_type=Dimmer,
|
||||||
|
capability=Capability.window_shade,
|
||||||
|
attribute=Attribute.window_shade,
|
||||||
|
value_type=int,
|
||||||
|
set_command='setWindowShade',
|
||||||
|
min=0,
|
||||||
|
max=100,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue