Implemented remaining supported entities for the `smartthings` integration

This commit is contained in:
Fabio Manganiello 2023-01-26 22:10:02 +01:00
parent 334ccc35a2
commit afdeb91f66
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
8 changed files with 650 additions and 73 deletions

View File

@ -60,6 +60,8 @@ export default {
if (this.isBoolean)
return this.parseBoolean(this.value)
if (Array.isArray(this.value) || typeof(this.value) === 'object')
return JSON.stringify(this.value)
let value = parseFloat(this.value)
if (this.decimals != null)

View File

@ -0,0 +1 @@
EnumSensor.vue

View File

@ -7,6 +7,14 @@
}
},
"button": {
"name": "Button",
"name_plural": "Buttons",
"icon": {
"class": "fas fa-circle-dot"
}
},
"current_sensor": {
"name": "Sensor",
"name_plural": "Sensors",
@ -143,6 +151,14 @@
}
},
"multi_value_sensor": {
"name": "Sensor",
"name_plural": "Sensors",
"icon": {
"class": "fas fa-thermometer"
}
},
"binary_sensor": {
"name": "Sensor",
"name_plural": "Sensors",

View 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__,
}

View File

@ -66,9 +66,9 @@ if 'binary_sensor' not in Base.metadata:
if isinstance(value, str):
value = value.lower()
if value in {True, 1, '1', 't', 'true', 'on'}:
if value in {True, 1, '1', 't', 'true', 'on', 'ON'}:
value = True
elif value in {False, 0, '0', 'f', 'false', 'off'}:
elif value in {False, 0, '0', 'f', 'false', 'off', 'OFF'}:
value = False
elif value is not None:
logger.warning(f'Unsupported value for BinarySensor type: {value}')
@ -100,3 +100,18 @@ if 'enum_sensor' not in Base.metadata:
__mapper_args__ = {
'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__,
}

View File

@ -1,32 +1,30 @@
import asyncio
import aiohttp
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 (
Attribute,
Capability,
Command,
Device,
DeviceEntity,
DeviceStatus,
SmartThings,
)
from platypush.entities import Entity, manages
from platypush.entities.devices import Device as PDevice
from platypush.entities.devices import Device
from platypush.entities.dimmers import Dimmer
from platypush.entities.lights import Light
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.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):
"""
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)
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]
@staticmethod
@ -328,7 +326,7 @@ class SmartthingsPlugin(RunnablePlugin):
def _get_existing_and_missing_devices(
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
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}
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)
if missing_devs:
self.refresh_info()
@ -376,8 +374,8 @@ class SmartthingsPlugin(RunnablePlugin):
}
"""
device = self._get_device(device)
return self._device_to_dict(device)
dev = self._get_device(device)
return self._device_to_dict(dev)
async def _execute(
self,
@ -457,58 +455,92 @@ class SmartthingsPlugin(RunnablePlugin):
loop.stop()
@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(
device: Device, property: str, entity_type: Type[Entity], **kwargs
cls, device: DeviceEntity, property: str, entity_type: Type[Entity], **kwargs
) -> Entity:
return entity_type(
id=f'{device.device_id}:{property}',
name=entity_type.__name__,
name=cls._property_to_entity_name(property),
**kwargs,
)
@staticmethod
def _get_status_attr_info(device: Device, attr: str) -> dict:
status = device.status.attributes.get(attr)
@classmethod
def _get_status_attr_info(cls, device: DeviceEntity, mapper: DeviceMapper) -> dict:
status = device.status.attributes.get(mapper.attribute)
info = {}
if status:
if getattr(status, 'unit', None) is not None:
info['unit'] = status.unit
if getattr(status, 'min', None) is not None:
info['min'] = status.min
if getattr(status, 'max', None) is not None:
info['max'] = status.max
info.update(
{
attr: getattr(status, attr, None)
for attr in ('unit', 'min', 'max')
if getattr(status, attr, None) is not None
}
)
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
@staticmethod
def _merge_dicts(*dicts: dict) -> dict:
ret = {}
for d in dicts:
ret.update(d)
return ret
@classmethod
def _get_supported_entities(
cls,
device: Device,
device: DeviceEntity,
entity_type: Optional[Type[Entity]] = None,
entity_value_attr: str = 'value',
**default_entity_args,
) -> List[Entity]:
mappers = [
m
for m in device_mappers
if (entity_type is None or issubclass(m.entity_type, entity_type))
and m.capability in device.capabilities
mapper
for mapper in device_mappers
if (entity_type is None or issubclass(mapper.entity_type, entity_type))
and mapper.capability in device.capabilities
]
return [
cls._to_entity(
device,
property=m.attribute,
entity_type=m.entity_type,
**{entity_value_attr: m.get_value(device)},
**default_entity_args,
**cls._get_status_attr_info(device, m.attribute),
property=mapper.attribute,
entity_type=mapper.entity_type,
**cls._merge_dicts(
{entity_value_attr: mapper.get_value(device)},
default_entity_args,
mapper.entity_args,
cls._get_status_attr_info(device, mapper),
),
)
for m in mappers
for mapper in mappers
]
@classmethod
def _get_lights(cls, device: Device) -> Iterable[Light]:
def _get_lights(cls, device: DeviceEntity) -> Iterable[Light]:
if not (
{Capability.color_control, Capability.color_temperature}.intersection(
device.capabilities
@ -517,33 +549,39 @@ class SmartthingsPlugin(RunnablePlugin):
return []
light_attrs = {}
status = device.status
if Capability.switch in device.capabilities:
light_attrs['on'] = device.status.switch
light_attrs['on'] = status.switch
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_max'] = 100
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_max'] = 30000
if getattr(device.status, 'hue', None) is not None:
light_attrs['hue'] = device.status.hue
if getattr(status, 'hue', None) is not None:
light_attrs['hue'] = status.hue
light_attrs['hue_min'] = 0
light_attrs['hue_max'] = 100
if getattr(device.status, 'saturation', None) is not None:
light_attrs['saturation'] = device.status.saturation
if getattr(status, 'saturation', None) is not None:
light_attrs['saturation'] = status.saturation
light_attrs['saturation_min'] = 0
light_attrs['saturation_max'] = 100
return [cls._to_entity(device, 'light', Light, **light_attrs)]
@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')
@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)
@classmethod
@ -703,12 +741,21 @@ class SmartthingsPlugin(RunnablePlugin):
if value is None:
# Toggle case
dev = self._get_device(device)
assert hasattr(
dev.status, property
), f'No such property on device "{dev.label}": "{property}"'
if property == 'light':
property = 'switch'
else:
assert hasattr(
dev.status, 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
value = getattr(dev.status, property) is not True
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.set_command
), f'The property "{property}" on the device "{device}" cannot be set'
command = (
mapper.set_command(data)
if callable(mapper.set_command)

View File

@ -1,14 +1,26 @@
from enum import Enum
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.audio import Muted, Volume
from platypush.entities.batteries import Battery
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.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:
@ -22,10 +34,11 @@ class DeviceMapper:
entity_type: Type[Entity],
capability: str,
attribute: str,
value_type: Type,
set_command: Optional[Union[str, Callable[[Any], List[Any]]]] = None,
get_value: Optional[Callable[[Device], Any]] = None,
value_type: Union[Type, str],
set_command: Optional[Union[str, Callable[[Any], str]]] = None,
get_value: Optional[Callable[[DeviceEntity], Any]] = None,
set_value_args: Optional[Callable[..., Any]] = None,
**kwargs
):
self.entity_type = entity_type
self.capability = capability
@ -33,31 +46,79 @@ class DeviceMapper:
self.attribute = attribute
self.value_type = value_type
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 = (
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):
value = getattr(device.status, self.attribute)
else:
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]:
return [self.value_type(v) for v in values]
return [self._cast_value(v) for v in values]
device_mappers: List[DeviceMapper] = [
# acceleration_sensor
DeviceMapper(
entity_type=Volume,
capability=Capability.audio_volume,
attribute=Attribute.volume,
value_type=int,
set_command=Command.set_volume,
entity_type=BinarySensor,
capability=Capability.acceleration_sensor,
attribute=Attribute.acceleration,
value_type=bool,
),
# 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(
entity_type=Muted,
capability=Capability.audio_mute,
@ -66,25 +127,285 @@ device_mappers: List[DeviceMapper] = [
set_command=lambda value: Command.mute if value else Command.unmute,
set_value_args=lambda *_: [],
),
# audio_volume
DeviceMapper(
entity_type=MotionSensor,
capability=Capability.motion_sensor,
attribute=Attribute.motion,
value_type=bool,
entity_type=Volume,
capability=Capability.audio_volume,
attribute=Attribute.volume,
value_type=int,
set_command=Command.set_volume,
),
# battery
DeviceMapper(
entity_type=Battery,
capability=Capability.battery,
attribute=Attribute.battery,
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(
entity_type=Dimmer,
capability=Capability.switch_level,
attribute=Attribute.level,
capability=Capability.fan_speed,
attribute=Attribute.fan_speed,
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(
entity_type=Switch,
capability=Capability.switch,
@ -93,6 +414,153 @@ device_mappers: List[DeviceMapper] = [
set_command=lambda value: Command.on if value else Command.off,
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,
),
]