mirror of
https://github.com/sususweet/midea-meiju-codec.git
synced 2025-10-15 10:48:26 +00:00
feat: update device control for T0xCC and T0xAC
This commit is contained in:
@@ -238,7 +238,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
mapping = {}
|
||||
|
||||
try:
|
||||
device.set_queries(mapping.get("queries", []))
|
||||
device.set_queries(mapping.get("queries", [{}]))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
|
@@ -80,7 +80,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
features = ClimateEntityFeature(0)
|
||||
if self._key_target_temperature is not None:
|
||||
features |= ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
if self._key_preset_modes is not None:
|
||||
@@ -95,7 +95,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
temp = self.device_attributes.get(self._key_current_temperature)
|
||||
temp = self._get_nested_value(self._key_current_temperature)
|
||||
if temp is not None:
|
||||
try:
|
||||
return float(temp)
|
||||
@@ -106,8 +106,8 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
@property
|
||||
def target_temperature(self):
|
||||
if isinstance(self._key_target_temperature, list):
|
||||
temp_int = self.device_attributes.get(self._key_target_temperature[0])
|
||||
tem_dec = self.device_attributes.get(self._key_target_temperature[1])
|
||||
temp_int = self._get_nested_value(self._key_target_temperature[0])
|
||||
tem_dec = self._get_nested_value(self._key_target_temperature[1])
|
||||
if temp_int is not None and tem_dec is not None:
|
||||
try:
|
||||
return float(temp_int) + float(tem_dec)
|
||||
@@ -115,7 +115,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
return None
|
||||
return None
|
||||
else:
|
||||
temp = self.device_attributes.get(self._key_target_temperature)
|
||||
temp = self._get_nested_value(self._key_target_temperature)
|
||||
if temp is not None:
|
||||
try:
|
||||
return float(temp)
|
||||
@@ -159,7 +159,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
return self._dict_get_selected(self._key_fan_modes, "EQUALLY")
|
||||
return self._dict_get_selected(self._key_fan_modes)
|
||||
|
||||
@property
|
||||
def swing_modes(self):
|
||||
@@ -167,7 +167,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def swing_mode(self):
|
||||
return self._dict_get_selected(self._key_swing_modes, "EQUALLY")
|
||||
return self._dict_get_selected(self._key_swing_modes)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
@@ -175,7 +175,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
return self._dict_get_selected(self._key_hvac_modes, "EQUALLY")
|
||||
return self._dict_get_selected(self._key_hvac_modes)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
@@ -235,7 +235,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
"""Get on/off status from device attributes."""
|
||||
if key is None:
|
||||
return False
|
||||
value = self.device_attributes.get(key)
|
||||
value = self._get_nested_value(key)
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return value == 1 or value == "on" or value == "true"
|
||||
@@ -245,33 +245,3 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
if key is None:
|
||||
return
|
||||
await self.async_set_attribute(key, value)
|
||||
|
||||
def _dict_get_selected(self, dict_config, rationale="EQUALLY"):
|
||||
"""Get selected value from dictionary configuration."""
|
||||
if dict_config is None:
|
||||
return None
|
||||
|
||||
for key, config in dict_config.items():
|
||||
if isinstance(config, dict):
|
||||
# Check if all conditions match
|
||||
match = True
|
||||
for attr_key, attr_value in config.items():
|
||||
device_value = self.device_attributes.get(attr_key)
|
||||
if device_value is None:
|
||||
match = False
|
||||
break
|
||||
if rationale == "EQUALLY":
|
||||
if device_value != attr_value:
|
||||
match = False
|
||||
break
|
||||
elif rationale == "LESS":
|
||||
if device_value >= attr_value:
|
||||
match = False
|
||||
break
|
||||
elif rationale == "GREATER":
|
||||
if device_value <= attr_value:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
return key
|
||||
return None
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import threading
|
||||
import socket
|
||||
import traceback
|
||||
from enum import IntEnum
|
||||
|
||||
from .cloud import MideaCloud
|
||||
@@ -134,19 +135,49 @@ class MiedaDevice(threading.Thread):
|
||||
def get_attribute(self, attribute):
|
||||
return self._attributes.get(attribute)
|
||||
|
||||
def _convert_to_nested_structure(self, attributes):
|
||||
"""Convert dot-notation attributes to nested structure."""
|
||||
nested = {}
|
||||
for key, value in attributes.items():
|
||||
if '.' in key:
|
||||
# Handle nested attributes with dot notation
|
||||
keys = key.split('.')
|
||||
current_dict = nested
|
||||
|
||||
# Navigate to the parent dictionary
|
||||
for k in keys[:-1]:
|
||||
if k not in current_dict:
|
||||
current_dict[k] = {}
|
||||
current_dict = current_dict[k]
|
||||
|
||||
# Set the final value
|
||||
current_dict[keys[-1]] = value
|
||||
else:
|
||||
# Handle flat attributes
|
||||
nested[key] = value
|
||||
return nested
|
||||
|
||||
async def set_attribute(self, attribute, value):
|
||||
if attribute in self._attributes.keys():
|
||||
new_status = {}
|
||||
for attr in self._centralized:
|
||||
new_status[attr] = self._attributes.get(attr)
|
||||
new_status[attribute] = value
|
||||
|
||||
# Convert dot-notation attributes to nested structure for transmission
|
||||
nested_status = self._convert_to_nested_structure(new_status)
|
||||
|
||||
try:
|
||||
if set_cmd := self._lua_runtime.build_control(new_status):
|
||||
if set_cmd := self._lua_runtime.build_control(nested_status):
|
||||
await self._build_send(set_cmd)
|
||||
return
|
||||
except Exception as e:
|
||||
cloud = self._cloud
|
||||
if cloud and hasattr(cloud, "send_device_control"):
|
||||
await cloud.send_device_control(self._device_id, control=new_status, status=self._attributes)
|
||||
MideaLogger.debug(f"LuaRuntimeError in set_attribute {nested_status}: {repr(e)}")
|
||||
traceback.print_exc()
|
||||
|
||||
cloud = self._cloud
|
||||
if cloud and hasattr(cloud, "send_device_control"):
|
||||
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
|
||||
|
||||
async def set_attributes(self, attributes):
|
||||
new_status = {}
|
||||
@@ -157,14 +188,22 @@ class MiedaDevice(threading.Thread):
|
||||
if attribute in self._attributes.keys():
|
||||
has_new = True
|
||||
new_status[attribute] = value
|
||||
|
||||
# Convert dot-notation attributes to nested structure for transmission
|
||||
nested_status = self._convert_to_nested_structure(new_status)
|
||||
|
||||
if has_new:
|
||||
try:
|
||||
if set_cmd := self._lua_runtime.build_control(new_status):
|
||||
if set_cmd := self._lua_runtime.build_control(nested_status):
|
||||
await self._build_send(set_cmd)
|
||||
return
|
||||
except Exception as e:
|
||||
cloud = self._cloud
|
||||
if cloud and hasattr(cloud, "send_device_control"):
|
||||
await cloud.send_device_control(self._device_id, control=new_status, status=self._attributes)
|
||||
MideaLogger.debug(f"LuaRuntimeError in set_attributes {nested_status}: {repr(e)}")
|
||||
traceback.print_exc()
|
||||
|
||||
cloud = self._cloud
|
||||
if cloud and hasattr(cloud, "send_device_control"):
|
||||
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
|
||||
|
||||
def set_ip_address(self, ip_address):
|
||||
MideaLogger.debug(f"Update IP address to {ip_address}")
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import traceback
|
||||
|
||||
import lupa
|
||||
import threading
|
||||
import json
|
||||
@@ -74,6 +76,7 @@ class MideaCodec(LuaRuntime):
|
||||
MideaLogger.debug(f"LuaRuntime Result {result}")
|
||||
return result
|
||||
except lupa.LuaError as e:
|
||||
traceback.print_exc()
|
||||
MideaLogger.error(f"LuaRuntimeError in build_control {json_str}: {repr(e)}")
|
||||
return None
|
||||
|
||||
|
60
custom_components/midea_auto_cloud/decrypt_lua.py
Normal file
60
custom_components/midea_auto_cloud/decrypt_lua.py
Normal file
File diff suppressed because one or more lines are too long
@@ -7,12 +7,46 @@ DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}, {"query_type":"run_status"}],
|
||||
"centralized": [
|
||||
"power", "temperature", "small_temperature", "mode", "eco",
|
||||
"comfort_power_save", "strong_wind",
|
||||
"wind_swing_lr", "wind_swing_lr", "wind_speed","ptc", "dry"
|
||||
],
|
||||
"centralized": [],
|
||||
"entities": {
|
||||
Platform.FAN: {
|
||||
"fan": {
|
||||
"power": "power",
|
||||
"speeds": [
|
||||
{"wind_speed_real": 20},
|
||||
{"wind_speed_real": 40},
|
||||
{"wind_speed_real": 60},
|
||||
{"wind_speed_real": 80},
|
||||
{"wind_speed_real": 100},
|
||||
],
|
||||
"preset_modes": {
|
||||
"heat": {
|
||||
"mode": "heat"
|
||||
},
|
||||
"cool": {
|
||||
"mode": "cool"
|
||||
},
|
||||
"auto": {
|
||||
"mode": "auto"
|
||||
},
|
||||
"dry": {
|
||||
"mode": "dry"
|
||||
},
|
||||
"fan": {
|
||||
"mode": "fan"
|
||||
},
|
||||
"standby": {
|
||||
"mode": "standby"
|
||||
},
|
||||
"dryconstant": {
|
||||
"mode": "dryconstant"
|
||||
},
|
||||
"dryauto": {
|
||||
"mode": "dryauto"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.CLIMATE: {
|
||||
"thermostat": {
|
||||
"power": "power",
|
||||
|
214
custom_components/midea_auto_cloud/device_mapping/T0xCC.py
Normal file
214
custom_components/midea_auto_cloud/device_mapping/T0xCC.py
Normal file
@@ -0,0 +1,214 @@
|
||||
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}, {"query_type":"run_status"}],
|
||||
"centralized": [],
|
||||
"entities": {
|
||||
Platform.CLIMATE: {
|
||||
"thermostat": {
|
||||
"power": "power",
|
||||
"hvac_modes": {
|
||||
"off": {"power": "off"},
|
||||
"heat": {"power": "on", "mode.current": "heat"},
|
||||
"cool": {"power": "on", "mode.current": "cool"},
|
||||
"dry": {"power": "on", "mode.current": "dry"},
|
||||
"fan_only": {"power": "on", "mode.current": "fan"}
|
||||
},
|
||||
"preset_modes": {
|
||||
"none": {
|
||||
"eco.status": "off",
|
||||
"strong.status": "off",
|
||||
"sterilize.status": "off",
|
||||
"selfclean.status": "off",
|
||||
"humidification.value": "0"
|
||||
},
|
||||
"eco": {"eco.status": "on"},
|
||||
"boost": {"strong.status": "on"},
|
||||
"sterilize": {"sterilize.status": "on"},
|
||||
"selfclean": {"selfclean.status": "on"},
|
||||
"humidify": {"humidification.value": "1"}
|
||||
},
|
||||
"swing_modes": {
|
||||
"off": {"swing.multiple": "false"},
|
||||
"both": {"swing.multiple": "true"},
|
||||
"horizontal": {"swing.louver_horizontal.enable": "true"},
|
||||
"vertical": {"swing.louver_vertical.enable": "true"}
|
||||
},
|
||||
"fan_modes": {
|
||||
"silent": {"wind_speed.level": 1},
|
||||
"low": {"wind_speed.level": 2},
|
||||
"medium": {"wind_speed.level": 3},
|
||||
"high": {"wind_speed.level": 4},
|
||||
"full": {"wind_speed.level": 5},
|
||||
"auto": {"wind_speed.level": 6}
|
||||
},
|
||||
"target_temperature": "temperature.current",
|
||||
"current_temperature": "temperature.room",
|
||||
"min_temp": 17,
|
||||
"max_temp": 30,
|
||||
"temperature_unit": UnitOfTemperature.CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"eco": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "eco.status"
|
||||
},
|
||||
"strong": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "strong.status"
|
||||
},
|
||||
"selfclean": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "selfclean.status"
|
||||
},
|
||||
"diagnose": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "diagnose.status"
|
||||
},
|
||||
"idu_silent": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "idu_silent.status"
|
||||
},
|
||||
"idu_light": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "idu_light"
|
||||
},
|
||||
"idu_sleep": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "idu_sleep.status"
|
||||
},
|
||||
"filter_notification": {
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"attribute": "filter_notification.status"
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"room_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "temperature.room"
|
||||
},
|
||||
"outside_temperature": {
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": UnitOfTemperature.CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "temperature.outside"
|
||||
},
|
||||
"co2_value": {
|
||||
"device_class": SensorDeviceClass.CO2,
|
||||
"unit_of_measurement": "ppm",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "co2.value"
|
||||
},
|
||||
"hcho_value": {
|
||||
"device_class": SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
"unit_of_measurement": "μg/m³",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "hcho.value"
|
||||
},
|
||||
"pm25_value": {
|
||||
"device_class": SensorDeviceClass.PM25,
|
||||
"unit_of_measurement": "μg/m³",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "pm2_5.value"
|
||||
},
|
||||
"wind_speed_level": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"attribute": "wind_speed.level"
|
||||
},
|
||||
"timer_on_timeout": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": "min",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "timer.on.timeout"
|
||||
},
|
||||
"timer_off_timeout": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": "min",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "timer.off.timeout"
|
||||
},
|
||||
"selfclean_time_left": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": "min",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "selfclean.time_left"
|
||||
},
|
||||
"backup_time": {
|
||||
"device_class": SensorDeviceClass.DURATION,
|
||||
"unit_of_measurement": "min",
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
"attribute": "backup.time"
|
||||
},
|
||||
"cur_fault_code": {
|
||||
"device_class": SensorDeviceClass.ENUM,
|
||||
"attribute": "cur_fault.code"
|
||||
}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"mode": {
|
||||
"options": {
|
||||
"cool": {"mode.current": "cool"},
|
||||
"dry": {"mode.current": "dry"},
|
||||
"fan": {"mode.current": "fan"},
|
||||
"heat": {"mode.current": "heat"}
|
||||
},
|
||||
"attribute": "mode.current"
|
||||
},
|
||||
"ptc": {
|
||||
"options": {
|
||||
"auto": {"ptc.status": "auto"},
|
||||
"on": {"ptc.status": "on"},
|
||||
"off": {"ptc.status": "off"},
|
||||
"separate": {"ptc.status": "separate"}
|
||||
},
|
||||
"attribute": "ptc.status"
|
||||
},
|
||||
"wind_feeling_mode": {
|
||||
"options": {
|
||||
"close": {"wind_feeling.current": "close"},
|
||||
"soft": {"wind_feeling.current": "soft"}
|
||||
},
|
||||
"attribute": "wind_feeling.current"
|
||||
},
|
||||
"swing_louver": {
|
||||
"options": {
|
||||
"1": {"swing.louver1": "1"},
|
||||
"2": {"swing.louver1": "2"},
|
||||
"3": {"swing.louver1": "3"},
|
||||
"4": {"swing.louver1": "4"},
|
||||
"5": {"swing.louver1": "5"}
|
||||
},
|
||||
"attribute": "swing.louver1"
|
||||
},
|
||||
"swing_horizontal": {
|
||||
"options": {
|
||||
"1": {"swing.louver_horizontal.level": "1"},
|
||||
"2": {"swing.louver_horizontal.level": "2"},
|
||||
"3": {"swing.louver_horizontal.level": "3"},
|
||||
"4": {"swing.louver_horizontal.level": "4"},
|
||||
"5": {"swing.louver_horizontal.level": "5"}
|
||||
},
|
||||
"attribute": "swing.louver_horizontal.level"
|
||||
},
|
||||
"swing_vertical": {
|
||||
"options": {
|
||||
"1": {"swing.louver_vertical.level": "1"},
|
||||
"2": {"swing.louver_vertical.level": "2"},
|
||||
"3": {"swing.louver_vertical.level": "3"},
|
||||
"4": {"swing.louver_vertical.level": "4"},
|
||||
"5": {"swing.louver_vertical.level": "5"}
|
||||
},
|
||||
"attribute": "swing.louver_vertical.level"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +1,19 @@
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .midea_entity import MideaEntity
|
||||
from . import load_device_config
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
account_bucket = hass.data.get(DOMAIN, {}).get("accounts", {}).get(config_entry.entry_id)
|
||||
if not account_bucket:
|
||||
async_add_entities([])
|
||||
@@ -53,7 +61,9 @@ class MideaFanEntity(MideaEntity, FanEntity):
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
features = FanEntityFeature(0)
|
||||
features |= FanEntityFeature.TURN_ON
|
||||
features |= FanEntityFeature.TURN_OFF
|
||||
if self._key_preset_modes is not None and len(self._key_preset_modes) > 0:
|
||||
features |= FanEntityFeature.PRESET_MODE
|
||||
if self._key_speeds is not None and len(self._key_speeds) > 0:
|
||||
@@ -100,8 +110,7 @@ class MideaFanEntity(MideaEntity, FanEntity):
|
||||
index = round(percentage * self._attr_speed_count / 100) - 1
|
||||
index = max(0, min(index, len(self._key_speeds) - 1))
|
||||
new_status.update(self._key_speeds[index])
|
||||
if self._key_power is not None:
|
||||
new_status[self._key_power] = True
|
||||
await self._async_set_status_on_off(self._key_power, True)
|
||||
if new_status:
|
||||
await self.async_set_attributes(new_status)
|
||||
|
||||
@@ -127,8 +136,3 @@ class MideaFanEntity(MideaEntity, FanEntity):
|
||||
if self.oscillating != oscillating:
|
||||
await self._async_set_status_on_off(self._key_oscillate, oscillating)
|
||||
|
||||
def update_state(self, status):
|
||||
try:
|
||||
self.schedule_update_ha_state()
|
||||
except Exception:
|
||||
pass
|
||||
|
@@ -141,15 +141,42 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
||||
pass
|
||||
|
||||
# ===== Unified helpers migrated from legacy entity base =====
|
||||
def _get_nested_value(self, attribute_key: str | None) -> Any:
|
||||
"""Get nested value from device attributes using dot notation.
|
||||
|
||||
Supports both flat and nested attribute access.
|
||||
Examples: 'power', 'eco.status', 'temperature.room'
|
||||
"""
|
||||
if attribute_key is None:
|
||||
return None
|
||||
|
||||
# Handle nested attributes with dot notation
|
||||
if '.' in attribute_key:
|
||||
keys = attribute_key.split('.')
|
||||
value = self.device_attributes
|
||||
try:
|
||||
for key in keys:
|
||||
if isinstance(value, dict):
|
||||
value = value.get(key)
|
||||
else:
|
||||
return None
|
||||
return value
|
||||
except (KeyError, TypeError):
|
||||
return None
|
||||
else:
|
||||
# Handle flat attributes
|
||||
return self.device_attributes.get(attribute_key)
|
||||
|
||||
def _get_status_on_off(self, attribute_key: str | None) -> bool:
|
||||
"""Return boolean value from device attributes for given key.
|
||||
|
||||
Accepts common truthy representations: True/1/"on"/"true".
|
||||
Supports nested attributes with dot notation.
|
||||
"""
|
||||
result = False
|
||||
if attribute_key is None:
|
||||
return result
|
||||
status = self.device_attributes.get(attribute_key)
|
||||
status = self._get_nested_value(attribute_key)
|
||||
if status is not None:
|
||||
try:
|
||||
result = bool(self._rationale.index(status))
|
||||
@@ -158,6 +185,32 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
||||
f"is not in rationale {self._rationale}")
|
||||
return result
|
||||
|
||||
def _set_nested_value(self, attribute_key: str, value: Any) -> None:
|
||||
"""Set nested value in device attributes using dot notation.
|
||||
|
||||
Supports both flat and nested attribute setting.
|
||||
Examples: 'power', 'eco.status', 'temperature.room'
|
||||
"""
|
||||
if attribute_key is None:
|
||||
return
|
||||
|
||||
# Handle nested attributes with dot notation
|
||||
if '.' in attribute_key:
|
||||
keys = attribute_key.split('.')
|
||||
current_dict = self.device_attributes
|
||||
|
||||
# Navigate to the parent dictionary
|
||||
for key in keys[:-1]:
|
||||
if key not in current_dict:
|
||||
current_dict[key] = {}
|
||||
current_dict = current_dict[key]
|
||||
|
||||
# Set the final value
|
||||
current_dict[keys[-1]] = value
|
||||
else:
|
||||
# Handle flat attributes
|
||||
self.device_attributes[attribute_key] = value
|
||||
|
||||
async def _async_set_status_on_off(self, attribute_key: str | None, turn_on: bool) -> None:
|
||||
"""Set boolean attribute via coordinator, no-op if key is None."""
|
||||
if attribute_key is None:
|
||||
@@ -168,7 +221,7 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
||||
for index in range(0, len(key_of_list)):
|
||||
match = True
|
||||
for attr, value in key_of_list[index].items():
|
||||
state_value = self.device_attributes.get(attr)
|
||||
state_value = self._get_nested_value(attr)
|
||||
if state_value is None:
|
||||
match = False
|
||||
break
|
||||
@@ -189,7 +242,7 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
|
||||
for mode, status in key_of_dict.items():
|
||||
match = True
|
||||
for attr, value in status.items():
|
||||
state_value = self.device_attributes.get(attr)
|
||||
state_value = self._get_nested_value(attr)
|
||||
if state_value is None:
|
||||
match = False
|
||||
break
|
||||
|
@@ -60,7 +60,14 @@ class MideaSelectEntity(MideaEntity, SelectEntity):
|
||||
|
||||
@property
|
||||
def current_option(self):
|
||||
return self._dict_get_selected(self._key_options)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
if attribute and attribute != self._entity_key:
|
||||
# For simple attribute access, get the value directly
|
||||
return self._get_nested_value(attribute)
|
||||
else:
|
||||
# For complex mapping, use the existing logic
|
||||
return self._dict_get_selected(self._key_options)
|
||||
|
||||
async def async_select_option(self, option: str):
|
||||
new_status = self._key_options.get(option)
|
||||
|
@@ -61,7 +61,9 @@ class MideaSensorEntity(MideaEntity, SensorEntity):
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the native value of the sensor."""
|
||||
value = self.device_attributes.get(self._entity_key)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
value = self._get_nested_value(attribute)
|
||||
|
||||
# Handle invalid string values
|
||||
if isinstance(value, str) and value.lower() in ['invalid', 'none', 'null', '']:
|
||||
|
@@ -61,12 +61,18 @@ class MideaSwitchEntity(MideaEntity, SwitchEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return if the switch is on."""
|
||||
return self._get_status_on_off(self._entity_key)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
return self._get_status_on_off(attribute)
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn the switch on."""
|
||||
await self._async_set_status_on_off(self._entity_key, True)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
await self._async_set_status_on_off(attribute, True)
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn the switch off."""
|
||||
await self._async_set_status_on_off(self._entity_key, False)
|
||||
# Use attribute from config if available, otherwise fall back to entity_key
|
||||
attribute = self._config.get("attribute", self._entity_key)
|
||||
await self._async_set_status_on_off(attribute, False)
|
||||
|
@@ -344,6 +344,21 @@
|
||||
},
|
||||
"temperature": {
|
||||
"name": "Temperature"
|
||||
},
|
||||
"ptc": {
|
||||
"name": "PTC"
|
||||
},
|
||||
"wind_feeling_mode": {
|
||||
"name": "Wind Feeling Mode"
|
||||
},
|
||||
"swing_louver": {
|
||||
"name": "Swing Louver"
|
||||
},
|
||||
"swing_horizontal": {
|
||||
"name": "Swing Horizontal"
|
||||
},
|
||||
"swing_vertical": {
|
||||
"name": "Swing Vertical"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -1105,6 +1120,33 @@
|
||||
},
|
||||
"warm_left_time": {
|
||||
"name": "Warm Left Time"
|
||||
},
|
||||
"room_temperature": {
|
||||
"name": "Room Temperature"
|
||||
},
|
||||
"outside_temperature": {
|
||||
"name": "Outside Temperature"
|
||||
},
|
||||
"lua_version": {
|
||||
"name": "Lua Version"
|
||||
},
|
||||
"wind_speed_level": {
|
||||
"name": "Wind Speed Level"
|
||||
},
|
||||
"timer_on_timeout": {
|
||||
"name": "Timer On Timeout"
|
||||
},
|
||||
"timer_off_timeout": {
|
||||
"name": "Timer Off Timeout"
|
||||
},
|
||||
"selfclean_time_left": {
|
||||
"name": "Self Clean Time Left"
|
||||
},
|
||||
"backup_time": {
|
||||
"name": "Backup Time"
|
||||
},
|
||||
"cur_fault_code": {
|
||||
"name": "Current Fault Code"
|
||||
}
|
||||
},
|
||||
"binary_sensor": {
|
||||
@@ -2032,6 +2074,60 @@
|
||||
},
|
||||
"wash_enable": {
|
||||
"name": "Wash Enable"
|
||||
},
|
||||
"eco": {
|
||||
"name": "Eco Mode"
|
||||
},
|
||||
"strong": {
|
||||
"name": "Strong Mode"
|
||||
},
|
||||
"sterilize": {
|
||||
"name": "Sterilize"
|
||||
},
|
||||
"selfclean": {
|
||||
"name": "Self Clean"
|
||||
},
|
||||
"humidification": {
|
||||
"name": "Humidification"
|
||||
},
|
||||
"diagnose": {
|
||||
"name": "Diagnose"
|
||||
},
|
||||
"co2_check": {
|
||||
"name": "CO2 Check"
|
||||
},
|
||||
"hcho_check": {
|
||||
"name": "HCHO Check"
|
||||
},
|
||||
"pm25_check": {
|
||||
"name": "PM2.5 Check"
|
||||
},
|
||||
"idu_silent": {
|
||||
"name": "IDU Silent"
|
||||
},
|
||||
"idu_light": {
|
||||
"name": "IDU Light"
|
||||
},
|
||||
"idu_sleep": {
|
||||
"name": "IDU Sleep"
|
||||
},
|
||||
"wind_feeling": {
|
||||
"name": "Wind Feeling"
|
||||
},
|
||||
"timer_on": {
|
||||
"name": "Timer On"
|
||||
},
|
||||
"timer_off": {
|
||||
"name": "Timer Off"
|
||||
},
|
||||
"backup": {
|
||||
"name": "Backup"
|
||||
},
|
||||
"cur_fault": {
|
||||
"name": "Current Fault"
|
||||
},
|
||||
"filter_notification": {
|
||||
"name": "Filter Notification"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -533,6 +533,21 @@
|
||||
},
|
||||
"temperature": {
|
||||
"name": "温度"
|
||||
},
|
||||
"ptc": {
|
||||
"name": "电辅热"
|
||||
},
|
||||
"wind_feeling_mode": {
|
||||
"name": "风感模式"
|
||||
},
|
||||
"swing_louver": {
|
||||
"name": "摆风叶片"
|
||||
},
|
||||
"swing_horizontal": {
|
||||
"name": "水平摆风"
|
||||
},
|
||||
"swing_vertical": {
|
||||
"name": "垂直摆风"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@@ -1297,6 +1312,33 @@
|
||||
},
|
||||
"warm_left_time": {
|
||||
"name": "剩余加热时间"
|
||||
},
|
||||
"room_temperature": {
|
||||
"name": "室内温度"
|
||||
},
|
||||
"outside_temperature": {
|
||||
"name": "室外温度"
|
||||
},
|
||||
"lua_version": {
|
||||
"name": "Lua版本"
|
||||
},
|
||||
"wind_speed_level": {
|
||||
"name": "风速档位"
|
||||
},
|
||||
"timer_on_timeout": {
|
||||
"name": "定时开机时间"
|
||||
},
|
||||
"timer_off_timeout": {
|
||||
"name": "定时关机时间"
|
||||
},
|
||||
"selfclean_time_left": {
|
||||
"name": "自清洁剩余时间"
|
||||
},
|
||||
"backup_time": {
|
||||
"name": "备用时间"
|
||||
},
|
||||
"cur_fault_code": {
|
||||
"name": "当前故障代码"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -2037,6 +2079,60 @@
|
||||
},
|
||||
"wash_enable": {
|
||||
"name": "洗涤启用"
|
||||
},
|
||||
"eco": {
|
||||
"name": "节能模式"
|
||||
},
|
||||
"strong": {
|
||||
"name": "强劲模式"
|
||||
},
|
||||
"sterilize": {
|
||||
"name": "杀菌功能"
|
||||
},
|
||||
"selfclean": {
|
||||
"name": "自清洁"
|
||||
},
|
||||
"humidification": {
|
||||
"name": "加湿功能"
|
||||
},
|
||||
"diagnose": {
|
||||
"name": "诊断功能"
|
||||
},
|
||||
"co2_check": {
|
||||
"name": "CO2检测"
|
||||
},
|
||||
"hcho_check": {
|
||||
"name": "甲醛检测"
|
||||
},
|
||||
"pm25_check": {
|
||||
"name": "PM2.5检测"
|
||||
},
|
||||
"idu_silent": {
|
||||
"name": "室内机静音"
|
||||
},
|
||||
"idu_light": {
|
||||
"name": "室内机灯光"
|
||||
},
|
||||
"idu_sleep": {
|
||||
"name": "室内机睡眠"
|
||||
},
|
||||
"wind_feeling": {
|
||||
"name": "风感功能"
|
||||
},
|
||||
"timer_on": {
|
||||
"name": "定时开机"
|
||||
},
|
||||
"timer_off": {
|
||||
"name": "定时关机"
|
||||
},
|
||||
"backup": {
|
||||
"name": "备用功能"
|
||||
},
|
||||
"cur_fault": {
|
||||
"name": "当前故障"
|
||||
},
|
||||
"filter_notification": {
|
||||
"name": "滤网提醒"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
features = WaterHeaterEntityFeature(0)
|
||||
if self._key_target_temperature is not None:
|
||||
features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
if self._key_operation_list is not None:
|
||||
|
Reference in New Issue
Block a user