fix: merge conflicts

This commit is contained in:
LiShuzhen 2025-03-07 09:19:25 +08:00
commit 6d63975eba
9 changed files with 316 additions and 104 deletions

View File

@ -189,7 +189,7 @@ class FeaturePresetMode(MIoTServiceEntity, ClimateEntity):
for prop in self.entity_data.props:
if prop.name == prop_name and prop.service.name == service_name:
if not prop.value_list:
_LOGGER.error('invalid %s %s value_list, %s',service_name,
_LOGGER.error('invalid %s %s value_list, %s', service_name,
prop_name, self.entity_id)
continue
self._mode_map = prop.value_list.to_map()
@ -229,7 +229,9 @@ class FeatureFanMode(MIoTServiceEntity, ClimateEntity):
super().__init__(miot_device=miot_device, entity_data=entity_data)
# properties
for prop in entity_data.props:
if prop.name == 'fan-level' and prop.service.name == 'fan-control':
if (prop.name == 'fan-level' and
(prop.service.name == 'fan-control' or
prop.service.name == 'thermostat')):
if not prop.value_list:
_LOGGER.error('invalid fan-level value_list, %s',
self.entity_id)
@ -665,78 +667,31 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
FeatureHumidity, FeatureFanMode):
FeatureHumidity, FeatureFanMode, FeaturePresetMode):
"""Thermostat"""
_prop_mode: Optional[MIoTSpecProperty]
_hvac_mode_map: Optional[dict[int, HVACMode]]
def __init__(self, miot_device: MIoTDevice,
entity_data: MIoTEntityData) -> None:
"""Initialize the thermostat."""
self._prop_mode = None
self._hvac_mode_map = None
super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_icon = 'mdi:thermostat'
# hvac modes
self._attr_hvac_modes = None
for prop in entity_data.props:
if prop.name == 'mode':
if not prop.value_list:
_LOGGER.error('invalid mode value_list, %s', self.entity_id)
continue
self._hvac_mode_map = {}
for item in prop.value_list.items:
if item.name in {'off', 'idle'}:
self._hvac_mode_map[item.value] = HVACMode.OFF
elif item.name in {'auto'}:
self._hvac_mode_map[item.value] = HVACMode.AUTO
elif item.name in {'cool'}:
self._hvac_mode_map[item.value] = HVACMode.COOL
elif item.name in {'heat'}:
self._hvac_mode_map[item.value] = HVACMode.HEAT
elif item.name in {'dry'}:
self._hvac_mode_map[item.value] = HVACMode.DRY
elif item.name in {'fan'}:
self._hvac_mode_map[item.value] = HVACMode.FAN_ONLY
self._attr_hvac_modes = list(self._hvac_mode_map.values())
self._prop_mode = prop
if self._attr_hvac_modes is None:
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO]
elif HVACMode.OFF not in self._attr_hvac_modes:
self._attr_hvac_modes.insert(0, HVACMode.OFF)
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
# preset modes
self._init_preset_modes('thermostat', 'mode')
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the target hvac mode."""
# set the device off
if hvac_mode == HVACMode.OFF:
if not await self.set_property_async(prop=self._prop_on,
value=False):
raise RuntimeError(f'set climate prop.on failed, {hvac_mode}, '
f'{self.entity_id}')
return
# set the device on
elif self.get_prop_value(prop=self._prop_on) is False:
await self.set_property_async(prop=self._prop_on, value=True)
# set mode
if self._prop_mode is None:
return
mode_value = self.get_map_key(map_=self._hvac_mode_map, value=hvac_mode)
if mode_value is None or not await self.set_property_async(
prop=self._prop_mode, value=mode_value):
raise RuntimeError(
f'set climate prop.mode failed, {hvac_mode}, {self.entity_id}')
await self.set_property_async(
prop=self._prop_on,
value=False if hvac_mode == HVACMode.OFF else True)
@property
def hvac_mode(self) -> Optional[HVACMode]:
"""The current hvac mode."""
if self.get_prop_value(prop=self._prop_on) is False:
return HVACMode.OFF
return (self.get_map_value(map_=self._hvac_mode_map,
key=self.get_prop_value(
prop=self._prop_mode))
if self._prop_mode else None)
return (HVACMode.AUTO if self.get_prop_value(
prop=self._prop_on) else HVACMode.OFF)
class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,

View File

@ -47,7 +47,7 @@ Cover entities for Xiaomi Home.
"""
from __future__ import annotations
import logging
from typing import Optional
from typing import Any, Optional
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@ -107,7 +107,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
_prop_status_closed: Optional[list[int]]
_prop_current_position: Optional[MIoTSpecProperty]
_prop_target_position: Optional[MIoTSpecProperty]
_prop_position_value_min: Optional[int]
_prop_position_value_max: Optional[int]
_prop_position_value_range: Optional[int]
_prop_pos_closing: bool
_prop_pos_opening: bool
def __init__(self, miot_device: MIoTDevice,
entity_data: MIoTEntityData,
@ -130,7 +134,11 @@ class Cover(MIoTServiceEntity, CoverEntity):
self._prop_status_closed = []
self._prop_current_position = None
self._prop_target_position = None
self._prop_position_value_min = None
self._prop_position_value_max = None
self._prop_position_value_range = None
self._prop_pos_closing = False
self._prop_pos_opening = False
self._close_threshold = close_threshold
self._open_threshold = open_threshold
@ -177,6 +185,8 @@ class Cover(MIoTServiceEntity, CoverEntity):
'invalid current-position value_range format, %s',
self.entity_id)
continue
self._prop_position_value_min = prop.value_range.min_
self._prop_position_value_max = prop.value_range.max_
self._prop_position_value_range = (prop.value_range.max_ -
prop.value_range.min_)
self._prop_current_position = prop
@ -186,23 +196,52 @@ class Cover(MIoTServiceEntity, CoverEntity):
'invalid target-position value_range format, %s',
self.entity_id)
continue
self._prop_position_value_min = prop.value_range.min_
self._prop_position_value_max = prop.value_range.max_
self._prop_position_value_range = (prop.value_range.max_ -
prop.value_range.min_)
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
self._prop_target_position = prop
# For the device that has the current position property but no status
# property, the current position property will be used to determine the
# opening and the closing status.
if (self._prop_status is None) and (self._prop_current_position
is not None):
self.sub_prop_changed(self._prop_current_position,
self._position_changed_handler)
def _position_changed_handler(self, prop: MIoTSpecProperty,
ctx: Any) -> None:
self._prop_pos_closing = False
self._prop_pos_opening = False
self.async_write_ha_state()
async def async_open_cover(self, **kwargs) -> None:
"""Open the cover."""
current = None if (self._prop_current_position
is None) else self.get_prop_value(
prop=self._prop_current_position)
if (current is not None) and (current < self._prop_position_value_max):
self._prop_pos_opening = True
self._prop_pos_closing = False
await self.set_property_async(self._prop_motor_control,
self._prop_motor_value_open)
async def async_close_cover(self, **kwargs) -> None:
"""Close the cover."""
current = None if (self._prop_current_position
is None) else self.get_prop_value(
prop=self._prop_current_position)
if (current is not None) and (current > self._prop_position_value_min):
self._prop_pos_opening = False
self._prop_pos_closing = True
await self.set_property_async(self._prop_motor_control,
self._prop_motor_value_close)
async def async_stop_cover(self, **kwargs) -> None:
"""Stop the cover."""
self._prop_pos_opening = False
self._prop_pos_closing = False
await self.set_property_async(self._prop_motor_control,
self._prop_motor_value_pause)
@ -211,6 +250,10 @@ class Cover(MIoTServiceEntity, CoverEntity):
pos = kwargs.get(ATTR_POSITION, None)
if pos is None:
return None
current = self.current_cover_position
if current is not None:
self._prop_pos_opening = pos > current
self._prop_pos_closing = pos < current
pos = round(pos * self._prop_position_value_range / 100)
await self.set_property_async(prop=self._prop_target_position,
value=pos)
@ -225,24 +268,14 @@ class Cover(MIoTServiceEntity, CoverEntity):
# Assume that the current position is the same as the target
# position when the current position is not defined in the device's
# MIoT-Spec-V2.
pos = None if (self._prop_target_position is None) else self.get_prop_value(
prop=self._prop_target_position)
else:
pos = self.get_prop_value(prop=self._prop_current_position)
if pos is None:
return None
# Convert the position to a percentage
percentage = round(pos * 100 / self._prop_position_value_range)
# Adjust the position to 0 if it is below the close threshold
if percentage <= self._close_threshold:
return 0
# Adjust the position to 0 if it is below the close threshold
if percentage >= self._open_threshold:
return 100
return percentage
if self._prop_target_position is None:
return None
self._prop_pos_opening = False
self._prop_pos_closing = False
return self.get_prop_value(prop=self._prop_target_position)
pos = self.get_prop_value(prop=self._prop_current_position)
return None if pos is None else round(pos * 100 /
self._prop_position_value_range)
@property
def is_opening(self) -> Optional[bool]:
@ -250,14 +283,9 @@ class Cover(MIoTServiceEntity, CoverEntity):
if self._prop_status and self._prop_status_opening:
return (self.get_prop_value(prop=self._prop_status)
in self._prop_status_opening)
# The status is prior to the numerical relationship of the current
# position and the target position when determining whether the cover
# The status has higher priority when determining whether the cover
# is opening.
if (self._prop_target_position and
self.current_cover_position is not None):
return (self.current_cover_position
< self.get_prop_value(prop=self._prop_target_position))
return None
return self._prop_pos_opening
@property
def is_closing(self) -> Optional[bool]:
@ -265,14 +293,9 @@ class Cover(MIoTServiceEntity, CoverEntity):
if self._prop_status and self._prop_status_closing:
return (self.get_prop_value(prop=self._prop_status)
in self._prop_status_closing)
# The status is prior to the numerical relationship of the current
# position and the target position when determining whether the cover
# The status has higher priority when determining whether the cover
# is closing.
if (self._prop_target_position and
self.current_cover_position is not None):
return (self.current_cover_position
> self.get_prop_value(prop=self._prop_target_position))
return None
return self._prop_pos_closing
@property
def is_closed(self) -> Optional[bool]:

View File

@ -172,7 +172,7 @@ class Fan(MIoTServiceEntity, FanEntity):
self._attr_supported_features |= FanEntityFeature.OSCILLATE
self._prop_horizontal_swing = prop
elif prop.name == 'wind-reverse':
if prop.format_ == 'bool':
if prop.format_ == bool:
self._prop_wind_reverse_forward = False
self._prop_wind_reverse_reverse = True
elif prop.value_list:
@ -186,7 +186,7 @@ class Fan(MIoTServiceEntity, FanEntity):
or self._prop_wind_reverse_reverse is None
):
# NOTICE: Value may be 0 or False
_LOGGER.info(
_LOGGER.error(
'invalid wind-reverse, %s', self.entity_id)
continue
self._attr_supported_features |= FanEntityFeature.DIRECTION

View File

@ -20,7 +20,7 @@
],
"requirements": [
"construct>=2.10.56",
"paho-mqtt<2.0.0",
"paho-mqtt",
"numpy",
"cryptography",
"psutil"
@ -29,4 +29,4 @@
"zeroconf": [
"_miot-central._tcp.local."
]
}
}

View File

@ -549,6 +549,10 @@ class MIoTDevice:
# Optional actions
# Optional events
miot_service.platform = platform
# entity_category
if entity_category := SPEC_SERVICE_TRANS_MAP[service_name].get(
'entity_category', None):
miot_service.entity_category = entity_category
return entity_data
def parse_miot_property_entity(self, miot_prop: MIoTSpecProperty) -> bool:
@ -899,6 +903,7 @@ class MIoTServiceEntity(Entity):
self._attr_name = (
f'{"* "if self.entity_data.spec.proprietary else " "}'
f'{self.entity_data.spec.description_trans}')
self._attr_entity_category = entity_data.spec.entity_category
# Set entity attr
self._attr_unique_id = self.entity_id
self._attr_should_poll = False

View File

@ -56,7 +56,7 @@ from slugify import slugify
# pylint: disable=relative-beyond-top-level
from .const import DEFAULT_INTEGRATION_LANGUAGE, SPEC_STD_LIB_EFFECTIVE_TIME
from .common import MIoTHttp, load_yaml_file
from .common import MIoTHttp, load_yaml_file, load_json_file
from .miot_error import MIoTSpecError
from .miot_storage import MIoTStorage
@ -465,7 +465,7 @@ class _MIoTSpecBase:
iid: int
type_: str
description: str
description_trans: str
description_trans: Optional[str]
proprietary: bool
need_filter: bool
name: str
@ -476,6 +476,7 @@ class _MIoTSpecBase:
device_class: Any
state_class: Any
external_unit: Any
entity_category: Optional[str]
spec_id: int
@ -494,6 +495,7 @@ class _MIoTSpecBase:
self.device_class = None
self.state_class = None
self.external_unit = None
self.entity_category = None
self.spec_id = hash(f'{self.type_}.{self.iid}')
@ -837,6 +839,7 @@ class _MIoTSpecMultiLang:
"""MIoT SPEC multi lang class."""
# pylint: disable=broad-exception-caught
_DOMAIN: str = 'miot_specs_multi_lang'
_MULTI_LANG_FILE = 'specs/multi_lang.json'
_lang: str
_storage: MIoTStorage
_main_loop: asyncio.AbstractEventLoop
@ -892,6 +895,25 @@ class _MIoTSpecMultiLang:
except Exception as err:
trans_local = {}
_LOGGER.info('get multi lang from local failed, %s, %s', urn, err)
# Revert: load multi_lang.json
try:
trans_local_json = await self._main_loop.run_in_executor(
None, load_json_file,
os.path.join(
os.path.dirname(os.path.abspath(__file__)),
self._MULTI_LANG_FILE))
urn_strs: list[str] = urn.split(':')
urn_key: str = ':'.join(urn_strs[:6])
if (
isinstance(trans_local_json, dict)
and urn_key in trans_local_json
and self._lang in trans_local_json[urn_key]
):
trans_cache.update(trans_local_json[urn_key][self._lang])
trans_local = trans_local_json[urn_key]
except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.error('multi lang, load json file error, %s', err)
# Revert end
# Default language
if not trans_cache:
if trans_cloud and DEFAULT_INTEGRATION_LANGUAGE in trans_cloud:
@ -1205,6 +1227,13 @@ class _SpecModify:
return None
return value_range
def get_prop_value_list(self, siid: int, piid: int) -> Optional[list]:
value_list = self.__get_prop_item(siid=siid, piid=piid,
key='value-list')
if not isinstance(value_list, list):
return None
return value_list
def __get_prop_item(self, siid: int, piid: int, key: str) -> Optional[str]:
if not self._selected:
return None
@ -1444,10 +1473,12 @@ class MIoTSpecParser:
key=':'.join(p_type_strs[:5]))
or property_['description']
or spec_prop.name)
if 'value-range' in property_:
spec_prop.value_range = property_['value-range']
elif 'value-list' in property_:
v_list: list[dict] = property_['value-list']
# Modify value-list before translation
v_list: list[dict] = self._spec_modify.get_prop_value_list(
siid=service['iid'], piid=property_['iid'])
if (v_list is None) and ('value-list' in property_):
v_list = property_['value-list']
if v_list is not None:
for index, v in enumerate(v_list):
if v['description'].strip() == '':
v['description'] = f'v_{v["value"]}'
@ -1461,6 +1492,8 @@ class MIoTSpecParser:
f'{v["description"]}')
or v['name'])
spec_prop.value_list = MIoTSpecValueList.from_spec(v_list)
if 'value-range' in property_:
spec_prop.value_range = property_['value-range']
elif property_['format'] == 'bool':
v_tag = ':'.join(p_type_strs[:5])
v_descriptions = (

View File

@ -0,0 +1,172 @@
{
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
"de": {
"service:001": "Geräteinformationen",
"service:001:property:003": "Geräte-ID",
"service:001:property:005": "Seriennummer (SN)",
"service:002": "Gateway",
"service:002:event:001": "Netzwerk geändert",
"service:002:event:002": "Netzwerk geändert",
"service:002:property:001": "Zugriffsmethode",
"service:002:property:001:valuelist:000": "Kabelgebunden",
"service:002:property:001:valuelist:001": "5G Drahtlos",
"service:002:property:001:valuelist:002": "2.4G Drahtlos",
"service:002:property:002": "IP-Adresse",
"service:002:property:003": "WiFi-Netzwerkname",
"service:002:property:004": "Aktuelle Zeit",
"service:002:property:005": "DHCP-Server-MAC-Adresse",
"service:003": "Anzeigelampe",
"service:003:property:001": "Schalter",
"service:004": "Virtueller Dienst",
"service:004:action:001": "Virtuelles Ereignis erzeugen",
"service:004:event:001": "Virtuelles Ereignis aufgetreten",
"service:004:property:001": "Ereignisname"
},
"en": {
"service:001": "Device Information",
"service:001:property:003": "Device ID",
"service:001:property:005": "Serial Number (SN)",
"service:002": "Gateway",
"service:002:event:001": "Network Changed",
"service:002:event:002": "Network Changed",
"service:002:property:001": "Access Method",
"service:002:property:001:valuelist:000": "Wired",
"service:002:property:001:valuelist:001": "5G Wireless",
"service:002:property:001:valuelist:002": "2.4G Wireless",
"service:002:property:002": "IP Address",
"service:002:property:003": "WiFi Network Name",
"service:002:property:004": "Current Time",
"service:002:property:005": "DHCP Server MAC Address",
"service:003": "Indicator Light",
"service:003:property:001": "Switch",
"service:004": "Virtual Service",
"service:004:action:001": "Generate Virtual Event",
"service:004:event:001": "Virtual Event Occurred",
"service:004:property:001": "Event Name"
},
"es": {
"service:001": "Información del dispositivo",
"service:001:property:003": "ID del dispositivo",
"service:001:property:005": "Número de serie (SN)",
"service:002": "Puerta de enlace",
"service:002:event:001": "Cambio de red",
"service:002:event:002": "Cambio de red",
"service:002:property:001": "Método de acceso",
"service:002:property:001:valuelist:000": "Cableado",
"service:002:property:001:valuelist:001": "5G inalámbrico",
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
"service:002:property:002": "Dirección IP",
"service:002:property:003": "Nombre de red WiFi",
"service:002:property:004": "Hora actual",
"service:002:property:005": "Dirección MAC del servidor DHCP",
"service:003": "Luz indicadora",
"service:003:property:001": "Interruptor",
"service:004": "Servicio virtual",
"service:004:action:001": "Generar evento virtual",
"service:004:event:001": "Ocurrió un evento virtual",
"service:004:property:001": "Nombre del evento"
},
"fr": {
"service:001": "Informations sur l'appareil",
"service:001:property:003": "ID de l'appareil",
"service:001:property:005": "Numéro de série (SN)",
"service:002": "Passerelle",
"service:002:event:001": "Changement de réseau",
"service:002:event:002": "Changement de réseau",
"service:002:property:001": "Méthode d'accès",
"service:002:property:001:valuelist:000": "Câblé",
"service:002:property:001:valuelist:001": "Sans fil 5G",
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
"service:002:property:002": "Adresse IP",
"service:002:property:003": "Nom du réseau WiFi",
"service:002:property:004": "Heure actuelle",
"service:002:property:005": "Adresse MAC du serveur DHCP",
"service:003": "Voyant lumineux",
"service:003:property:001": "Interrupteur",
"service:004": "Service virtuel",
"service:004:action:001": "Générer un événement virtuel",
"service:004:event:001": "Événement virtuel survenu",
"service:004:property:001": "Nom de l'événement"
},
"ja": {
"service:001": "デバイス情報",
"service:001:property:003": "デバイスID",
"service:001:property:005": "シリアル番号 (SN)",
"service:002": "ゲートウェイ",
"service:002:event:001": "ネットワークが変更されました",
"service:002:event:002": "ネットワークが変更されました",
"service:002:property:001": "アクセス方法",
"service:002:property:001:valuelist:000": "有線",
"service:002:property:001:valuelist:001": "5G ワイヤレス",
"service:002:property:001:valuelist:002": "2.4G ワイヤレス",
"service:002:property:002": "IPアドレス",
"service:002:property:003": "WiFiネットワーク名",
"service:002:property:004": "現在の時間",
"service:002:property:005": "DHCPサーバーMACアドレス",
"service:003": "インジケータライト",
"service:003:property:001": "スイッチ",
"service:004": "バーチャルサービス",
"service:004:action:001": "バーチャルイベントを生成",
"service:004:event:001": "バーチャルイベントが発生しました",
"service:004:property:001": "イベント名"
},
"ru": {
"service:001": "Информация об устройстве",
"service:001:property:003": "ID устройства",
"service:001:property:005": "Серийный номер (SN)",
"service:002": "Шлюз",
"service:002:event:001": "Сеть изменена",
"service:002:event:002": "Сеть изменена",
"service:002:property:001": "Метод доступа",
"service:002:property:001:valuelist:000": "Проводной",
"service:002:property:001:valuelist:001": "5G Беспроводной",
"service:002:property:001:valuelist:002": "2.4G Беспроводной",
"service:002:property:002": "IP Адрес",
"service:002:property:003": "Название WiFi сети",
"service:002:property:004": "Текущее время",
"service:002:property:005": "MAC адрес DHCP сервера",
"service:003": "Световой индикатор",
"service:003:property:001": "Переключатель",
"service:004": "Виртуальная служба",
"service:004:action:001": "Создать виртуальное событие",
"service:004:event:001": "Произошло виртуальное событие",
"service:004:property:001": "Название события"
},
"zh-Hant": {
"service:001": "設備信息",
"service:001:property:003": "設備ID",
"service:001:property:005": "序號 (SN)",
"service:002": "網關",
"service:002:event:001": "網路發生變化",
"service:002:event:002": "網路發生變化",
"service:002:property:001": "接入方式",
"service:002:property:001:valuelist:000": "有線",
"service:002:property:001:valuelist:001": "5G 無線",
"service:002:property:001:valuelist:002": "2.4G 無線",
"service:002:property:002": "IP地址",
"service:002:property:003": "WiFi網路名稱",
"service:002:property:004": "當前時間",
"service:002:property:005": "DHCP伺服器MAC地址",
"service:003": "指示燈",
"service:003:property:001": "開關",
"service:004": "虛擬服務",
"service:004:action:001": "產生虛擬事件",
"service:004:event:001": "虛擬事件發生",
"service:004:property:001": "事件名稱"
}
},
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
"en": {
"service:011": "Right Button On and Off",
"service:011:property:001": "Right Button On and Off",
"service:015:action:001": "Left Button Identify",
"service:016:action:001": "Middle Button Identify",
"service:017:action:001": "Right Button Identify"
},
"zh-Hans": {
"service:015:action:001": "左键确认",
"service:016:action:001": "中键确认",
"service:017:action:001": "右键确认"
}
}
}

View File

@ -49,3 +49,12 @@ urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1:
- 1
- 1
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:2: urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1
urn:miot-spec-v2:device:bath-heater:0000A028:opple-acmoto:1:
prop.5.2:
value-list:
- value: 1
description: low
- value: 128
description: medium
- value: 255
description: high

View File

@ -51,6 +51,7 @@ from homeassistant.components.event import EventDeviceClass
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
EntityCategory,
LIGHT_LUX,
UnitOfEnergy,
UnitOfPower,
@ -330,7 +331,8 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'events': set<event instance name: str>,
'actions': set<action instance name: str>
},
'entity': str
'entity': str,
'entity_category'?: str
}
}
"""
@ -348,10 +350,23 @@ SPEC_SERVICE_TRANS_MAP: dict = {
},
'entity': 'light'
},
'indicator-light': 'light',
'ambient-light': 'light',
'night-light': 'light',
'white-light': 'light',
'indicator-light': {
'required': {
'properties': {
'on': {'read', 'write'}
}
},
'optional': {
'properties': {
'mode', 'brightness',
}
},
'entity': 'light',
'entity_category': EntityCategory.CONFIG
},
'fan': {
'required': {
'properties': {