This commit is contained in:
unknown
2023-09-10 12:08:27 +08:00
parent 216bfc53db
commit 2246948ddf
15 changed files with 636 additions and 218 deletions

View File

@@ -1,11 +1,13 @@
import logging
import os import os
import base64 import base64
from importlib import import_module
from homeassistant.config_entries import ConfigEntry
from homeassistant.util.json import load_json from homeassistant.util.json import load_json
try: try:
from homeassistant.helpers.json import save_json from homeassistant.helpers.json import save_json
except ImportError: except ImportError:
from homeassistant.util.json import save_json from homeassistant.util.json import save_json
from homeassistant.helpers.typing import ConfigType
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.const import ( from homeassistant.const import (
Platform, Platform,
@@ -20,7 +22,7 @@ from homeassistant.const import (
CONF_DEVICE, CONF_DEVICE,
CONF_ENTITIES CONF_ENTITIES
) )
from .device_map.device_mapping import DEVICE_MAPPING from .core.logger import MideaLogger
from .core.device import MiedaDevice from .core.device import MiedaDevice
from .const import ( from .const import (
DOMAIN, DOMAIN,
@@ -35,35 +37,56 @@ ALL_PLATFORM = [
Platform.SENSOR, Platform.SENSOR,
Platform.SWITCH, Platform.SWITCH,
Platform.CLIMATE, Platform.CLIMATE,
Platform.SELECT,
Platform.WATER_HEATER,
Platform.FAN
] ]
_LOGGER = logging.getLogger(__name__)
def get_sn8_used(hass: HomeAssistant, sn8):
entries = hass.config_entries.async_entries(DOMAIN)
count = 0
for entry in entries:
if sn8 == entry.data.get("sn8"):
count += 1
return count
def load_device_config(hass, device_type, sn8): def remove_device_config(hass: HomeAssistant, sn8):
config_file = hass.config.path(f"{CONFIG_PATH}/{sn8}.json")
try:
os.remove(config_file)
except FileNotFoundError:
pass
def load_device_config(hass: HomeAssistant, device_type, sn8):
os.makedirs(hass.config.path(CONFIG_PATH), exist_ok=True) os.makedirs(hass.config.path(CONFIG_PATH), exist_ok=True)
config_file = hass.config.path(f"{CONFIG_PATH}/{sn8}.json") config_file = hass.config.path(f"{CONFIG_PATH}/{sn8}.json")
json_data = load_json(config_file, default={}) json_data = load_json(config_file, default={})
d_type = "0x%02X" % device_type if len(json_data) > 0:
if len(json_data) >0:
json_data = json_data.get(sn8) json_data = json_data.get(sn8)
elif d_type in DEVICE_MAPPING: else:
if sn8 in DEVICE_MAPPING[d_type]: device_path = f".device_mapping.{'T0x%02X' % device_type}"
json_data = DEVICE_MAPPING[d_type][sn8] try:
save_data = {sn8: json_data} mapping_module = import_module(device_path, __package__)
save_json(config_file, save_data) if sn8 in mapping_module.DEVICE_MAPPING.keys():
elif "default" in DEVICE_MAPPING[d_type]: json_data = mapping_module.DEVICE_MAPPING[sn8]
json_data = DEVICE_MAPPING[d_type]["default"] elif "default" in mapping_module.DEVICE_MAPPING:
save_data = {sn8: json_data} json_data = mapping_module.DEVICE_MAPPING["default"]
save_json(config_file, save_data) if len(json_data) > 0:
save_data = {sn8: json_data}
save_json(config_file, save_data)
except ModuleNotFoundError:
MideaLogger.warning(f"Can't load mapping file for type {'T0x%02X' % device_type}")
return json_data return json_data
async def update_listener(hass, config_entry): async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry):
pass pass
async def async_setup(hass: HomeAssistant, hass_config: dict): async def async_setup(hass: HomeAssistant, config: ConfigType):
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
cjson = os.getcwd() + "/cjson.lua" cjson = os.getcwd() + "/cjson.lua"
bit = os.getcwd() + "/bit.lua" bit = os.getcwd() + "/bit.lua"
@@ -80,7 +103,7 @@ async def async_setup(hass: HomeAssistant, hass_config: dict):
return True return True
async def async_setup_entry(hass: HomeAssistant, config_entry): async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
device_type = config_entry.data.get(CONF_TYPE) device_type = config_entry.data.get(CONF_TYPE)
if device_type == CONF_ACCOUNT: if device_type == CONF_ACCOUNT:
return True return True
@@ -100,7 +123,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry):
sn8 = config_entry.data.get("sn8") sn8 = config_entry.data.get("sn8")
lua_file = config_entry.data.get("lua_file") lua_file = config_entry.data.get("lua_file")
if protocol == 3 and (key is None or key is None): if protocol == 3 and (key is None or key is None):
_LOGGER.error("For V3 devices, the key and the token is required.") MideaLogger.error("For V3 devices, the key and the token is required.")
return False return False
device = MiedaDevice( device = MiedaDevice(
name=name, name=name,
@@ -144,15 +167,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry):
return False return False
async def async_unload_entry(hass: HomeAssistant, config_entry): async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
device_id = config_entry.data.get(CONF_DEVICE_ID) device_id = config_entry.data.get(CONF_DEVICE_ID)
lua_file = config_entry.data.get("lua_file")
os.remove(lua_file)
if device_id is not None: if device_id is not None:
device = hass.data[DOMAIN][DEVICES][device_id][CONF_DEVICE] device = hass.data[DOMAIN][DEVICES][device_id][CONF_DEVICE]
if device is not None: if device is not None:
config_file = hass.config.path(f"{CONFIG_PATH}/{device.sn8}.json") if get_sn8_used(hass, device.sn8) == 1:
os.remove(config_file) lua_file = config_entry.data.get("lua_file")
os.remove(lua_file)
remove_device_config(hass, device.sn8)
device.close() device.close()
hass.data[DOMAIN][DEVICES].pop(device_id) hass.data[DOMAIN][DEVICES].pop(device_id)
for platform in ALL_PLATFORM: for platform in ALL_PLATFORM:

View File

@@ -8,6 +8,7 @@ from homeassistant.const import (
CONF_DEVICE, CONF_DEVICE,
CONF_ENTITIES CONF_ENTITIES
) )
from .const import ( from .const import (
DOMAIN, DOMAIN,
DEVICES DEVICES
@@ -19,11 +20,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device_id = config_entry.data.get(CONF_DEVICE_ID) device_id = config_entry.data.get(CONF_DEVICE_ID)
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE) device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer") manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.BINARY_SENSOR) entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.BINARY_SENSOR)
devs = [MideaDeviceStatusSensorEntity(device, manufacturer,"Status", {})] devs = [MideaDeviceStatusSensorEntity(device, manufacturer, rationale,"Status", {})]
if entities is not None: if entities is not None:
for entity_key, config in entities.items(): for entity_key, config in entities.items():
devs.append(MideaBinarySensorEntity(device, manufacturer, entity_key, config)) devs.append(MideaBinarySensorEntity(device, manufacturer, rationale, entity_key, config))
async_add_entities(devs) async_add_entities(devs)

View File

@@ -1,15 +1,21 @@
from homeassistant.components.climate import * from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityFeature,
HVACMode,
ATTR_HVAC_MODE,
)
from homeassistant.const import ( from homeassistant.const import (
Platform, Platform,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_ENTITIES, CONF_ENTITIES,
CONF_DEVICE, CONF_DEVICE,
ATTR_TEMPERATURE
) )
from .const import ( from .const import (
DOMAIN, DOMAIN,
DEVICES DEVICES
) )
from .core.logger import MideaLogger
from .midea_entities import MideaEntity, Rationale from .midea_entities import MideaEntity, Rationale
@@ -17,34 +23,31 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device_id = config_entry.data.get(CONF_DEVICE_ID) device_id = config_entry.data.get(CONF_DEVICE_ID)
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE) device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer") manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.CLIMATE) entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.CLIMATE)
devs = [] devs = []
if entities is not None: if entities is not None:
for entity_key, config in entities.items(): for entity_key, config in entities.items():
devs.append(MideaClimateEntity(device, manufacturer, entity_key, config)) devs.append(MideaClimateEntity(device, manufacturer, rationale, entity_key, config))
async_add_entities(devs) async_add_entities(devs)
class MideaClimateEntity(MideaEntity, ClimateEntity): class MideaClimateEntity(MideaEntity, ClimateEntity):
def __init__(self, device, manufacturer, entity_key, config): def __init__(self, device, manufacturer, rationale, entity_key, config):
super().__init__(device, manufacturer, entity_key, config) super().__init__(device, manufacturer, rationale, entity_key, config)
self._key_power = self._config.get("power") self._key_power = self._config.get("power")
self._key_hvac_modes = self._config.get("hvac_modes") self._key_hvac_modes = self._config.get("hvac_modes")
self._key_preset_modes = self._config.get("preset_modes") self._key_preset_modes = self._config.get("preset_modes")
self._key_aux_heat = self._config.get("aux_heat") self._key_aux_heat = self._config.get("aux_heat")
self._key_swing_modes = self._config.get("swing_modes") self._key_swing_modes = self._config.get("swing_modes")
self._key_fan_modes = self._config.get("fan_modes") self._key_fan_modes = self._config.get("fan_modes")
self._key_current_temperature_low = self._config.get("current_temperature_low")
self._key_min_temp = self._config.get("min_temp") self._key_min_temp = self._config.get("min_temp")
self._key_max_temp = self._config.get("max_temp") self._key_max_temp = self._config.get("max_temp")
self._key_current_temperature = self._config.get("current_temperature")
self._key_target_temperature = self._config.get("target_temperature") self._key_target_temperature = self._config.get("target_temperature")
self._attr_temperature_unit = self._config.get("temperature_unit") self._attr_temperature_unit = self._config.get("temperature_unit")
self._attr_precision = self._config.get("precision") self._attr_precision = self._config.get("precision")
@property
def state(self):
return self.hvac_mode
@property @property
def supported_features(self): def supported_features(self):
features = 0 features = 0
@@ -62,7 +65,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
@property @property
def current_temperature(self): def current_temperature(self):
return self._device.get_attribute("indoor_temperature") return self._device.get_attribute(self._key_current_temperature)
@property @property
def target_temperature(self): def target_temperature(self):
@@ -103,7 +106,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
@property @property
def preset_mode(self): def preset_mode(self):
return self.get_mode(self._key_preset_modes) return self._dict_get_selected(self._key_preset_modes)
@property @property
def fan_modes(self): def fan_modes(self):
@@ -111,7 +114,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
@property @property
def fan_mode(self): def fan_mode(self):
return self.get_mode(self._key_fan_modes, Rationale.LESS) return self._dict_get_selected(self._key_fan_modes, Rationale.LESS)
@property @property
def swing_modes(self): def swing_modes(self):
@@ -119,7 +122,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
@property @property
def swing_mode(self): def swing_mode(self):
return self.get_mode(self._key_swing_modes) return self._dict_get_selected(self._key_swing_modes)
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
@@ -127,7 +130,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
@property @property
def hvac_mode(self): def hvac_mode(self):
return self.get_mode(self._key_hvac_modes) return self._dict_get_selected(self._key_hvac_modes)
@property @property
def hvac_modes(self): def hvac_modes(self):
@@ -135,13 +138,13 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
@property @property
def is_aux_heat(self): def is_aux_heat(self):
return self._device.get_attribute(self._key_aux_heat) == "on" return self._get_status_on_off(self._key_aux_heat)
def turn_on(self): def turn_on(self):
self._device.set_attribute(attribute=self._key_power, value="on") self._set_status_on_off(self._key_power, True)
def turn_off(self): def turn_off(self):
self._device.set_attribute(attribute=self._key_power, value="off") self._set_status_on_off(self._key_power, False)
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
if ATTR_TEMPERATURE not in kwargs: if ATTR_TEMPERATURE not in kwargs:
@@ -159,16 +162,15 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
new_status[self._key_target_temperature[1]] = temp_dec new_status[self._key_target_temperature[1]] = temp_dec
else: else:
new_status[self._key_target_temperature] = temperature new_status[self._key_target_temperature] = temperature
MideaLogger.error(new_status)
self._device.set_attributes(new_status) self._device.set_attributes(new_status)
def set_fan_mode(self, fan_mode: str): def set_fan_mode(self, fan_mode: str):
new_statis = self._key_fan_modes.get(fan_mode) new_status = self._key_fan_modes.get(fan_mode)
self._device.set_attributes(new_statis) self._device.set_attributes(new_status)
def set_preset_mode(self, preset_mode: str): def set_preset_mode(self, preset_mode: str):
new_statis = self._key_preset_modes.get(preset_mode) new_status = self._key_preset_modes.get(preset_mode)
self._device.set_attributes(new_statis) self._device.set_attributes(new_status)
def set_hvac_mode(self, hvac_mode: str): def set_hvac_mode(self, hvac_mode: str):
new_status = self._key_hvac_modes.get(hvac_mode) new_status = self._key_hvac_modes.get(hvac_mode)
@@ -179,10 +181,10 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
self._device.set_attributes(new_status) self._device.set_attributes(new_status)
def turn_aux_heat_on(self) -> None: def turn_aux_heat_on(self) -> None:
self._device.set_attribute(attr=self._key_aux_heat, value="on") self._set_status_on_off(self._key_aux_heat, True)
def turn_aux_heat_off(self) -> None: def turn_aux_heat_off(self) -> None:
self._device.set_attribute(attr=self._key_aux_heat, value="off") self._set_status_on_off(self._key_aux_heat, False)
def update_state(self, status): def update_state(self, status):
try: try:

View File

@@ -122,15 +122,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
device_list = {} device_list = {}
for device in devices: for device in devices:
if not self._device_configured(int(device.get("applianceCode"))): if not self._device_configured(int(device.get("applianceCode"))):
try:
subtype = int(device.get("modelNumber")) if device.get("modelNumber") is not None else 0
except ValueError:
subtype = 0
self._device_list[int(device.get("applianceCode"))] = { self._device_list[int(device.get("applianceCode"))] = {
"device_id": int(device.get("applianceCode")), "device_id": int(device.get("applianceCode")),
"name": device.get("name"), "name": device.get("name"),
"type": int(device.get("type"), 16), "type": int(device.get("type"), 16),
"sn8": device.get("sn8"), "sn8": device.get("sn8", "00000000"),
"sn": device.get("sn"), "sn": device.get("sn"),
"model": device.get("productModel"), "model": device.get("productModel", "0"),
"subtype": int(device.get("modelNumber")) if device.get("modelNumber") is not None else 0, "subtype": subtype,
"enterprise_code": device.get("enterpriseCode"), "enterprise_code": device.get("enterpriseCode","0000"),
"online": device.get("onlineStatus") == "1" "online": device.get("onlineStatus") == "1"
} }
device_list[int(device.get("applianceCode"))] = \ device_list[int(device.get("applianceCode"))] = \

View File

@@ -35,7 +35,7 @@ class MideaCloudBase:
self.security = security self.security = security
self.server = server self.server = server
async def api_request(self, endpoint, args=None, data=None): async def api_request(self, endpoint, args=None, data=None) -> dict | None:
args = args or {} args = args or {}
headers = {} headers = {}
if data is None: if data is None:
@@ -127,7 +127,7 @@ class MideaCloudBase:
udpid = CloudSecurity.get_udpid(device_id.to_bytes(6, "big")) udpid = CloudSecurity.get_udpid(device_id.to_bytes(6, "big"))
else: else:
udpid = CloudSecurity.get_udpid(device_id.to_bytes(6, "little")) udpid = CloudSecurity.get_udpid(device_id.to_bytes(6, "little"))
_LOGGER.error(f"The udpid of deivce [{device_id}] generated " _LOGGER.debug(f"The udpid of deivce [{device_id}] generated "
f"with byte order '{'big' if byte_order_big else 'little'}': {udpid}") f"with byte order '{'big' if byte_order_big else 'little'}': {udpid}")
response = await self.api_request( response = await self.api_request(
"/v1/iot/secure/getToken", "/v1/iot/secure/getToken",
@@ -171,11 +171,12 @@ class MeijuCloudExtend(MideaCloudBase):
response = await self.api_request("/v1/appliance/home/list/get", args={ response = await self.api_request("/v1/appliance/home/list/get", args={
'homegroupId': home 'homegroupId': home
}) })
for h in response.get("homeList") or []: if response:
for r in h.get("roomList") or []: for h in response.get("homeList") or []:
for a in r.get("applianceList"): for r in h.get("roomList") or []:
a["sn"] = CloudSecurity.decrypt(bytes.fromhex(a["sn"]), self.key).decode() for a in r.get("applianceList"):
devices.append(a) a["sn"] = CloudSecurity.decrypt(bytes.fromhex(a["sn"]), self.key).decode()
devices.append(a)
return devices return devices
async def get_lua(self, sn, device_type, path, enterprise_code=None): async def get_lua(self, sn, device_type, path, enterprise_code=None):
@@ -183,7 +184,7 @@ class MeijuCloudExtend(MideaCloudBase):
"/v1/appliance/protocol/lua/luaGet", "/v1/appliance/protocol/lua/luaGet",
data={ data={
"applianceSn": sn, "applianceSn": sn,
"applianceType": f"0x{'%02X' % device_type}", "applianceType": "0x%02X" % device_type,
"applianceMFCode": enterprise_code if enterprise_code else "0000", "applianceMFCode": enterprise_code if enterprise_code else "0000",
'version': "0", 'version': "0",
"iotAppId": "900", "iotAppId": "900",

View File

@@ -60,6 +60,7 @@ class MiedaDevice(threading.Thread):
self._sn = sn self._sn = sn
self._sn8 = sn8 self._sn8 = sn8
self._attributes = { self._attributes = {
"device_type": "T0x%02X" % device_type,
"sn": sn, "sn": sn,
"sn8": sn8, "sn8": sn8,
"subtype": subtype "subtype": subtype
@@ -135,8 +136,8 @@ class MiedaDevice(threading.Thread):
for attr in self._centralized: for attr in self._centralized:
new_status[attr] = self._attributes.get(attr) new_status[attr] = self._attributes.get(attr)
new_status[attribute] = value new_status[attribute] = value
set_cmd = self._lua_runtime.build_control(new_status) if set_cmd := self._lua_runtime.build_control(new_status):
self.build_send(set_cmd) self.build_send(set_cmd)
def set_attributes(self, attributes): def set_attributes(self, attributes):
new_status = {} new_status = {}
@@ -148,8 +149,8 @@ class MiedaDevice(threading.Thread):
has_new = True has_new = True
new_status[attribute] = value new_status[attribute] = value
if has_new: if has_new:
set_cmd = self._lua_runtime.build_control(new_status) if set_cmd := self._lua_runtime.build_control(new_status):
self.build_send(set_cmd) self.build_send(set_cmd)
@staticmethod @staticmethod
def fetch_v2_message(msg): def fetch_v2_message(msg):
@@ -231,8 +232,8 @@ class MiedaDevice(threading.Thread):
def refresh_status(self): def refresh_status(self):
for query in self._queries: for query in self._queries:
query_cmd = self._lua_runtime.build_query(query) if query_cmd := self._lua_runtime.build_query(query):
self.build_send(query_cmd) self.build_send(query_cmd)
def parse_message(self, msg): def parse_message(self, msg):
if self._protocol == 3: if self._protocol == 3:
@@ -255,16 +256,16 @@ class MiedaDevice(threading.Thread):
decrypted = self._security.aes_decrypt(cryptographic) decrypted = self._security.aes_decrypt(cryptographic)
MideaLogger.debug(f"Received: {decrypted.hex()}") MideaLogger.debug(f"Received: {decrypted.hex()}")
# 这就是最终消息 # 这就是最终消息
status = self._lua_runtime.decode_status(decrypted.hex()) if status := self._lua_runtime.decode_status(decrypted.hex()):
MideaLogger.debug(f"Decoded: {status}") MideaLogger.debug(f"Decoded: {status}")
new_status = {} new_status = {}
for single in status.keys(): for single in status.keys():
value = status.get(single) value = status.get(single)
if single not in self._attributes or self._attributes[single] != value: if single not in self._attributes or self._attributes[single] != value:
self._attributes[single] = value self._attributes[single] = value
new_status[single] = value new_status[single] = value
if len(new_status) > 0: if len(new_status) > 0:
self.update_all(new_status) self.update_all(new_status)
return ParseMessageResult.SUCCESS return ParseMessageResult.SUCCESS
def send_heartbeat(self): def send_heartbeat(self):

View File

@@ -1,7 +1,8 @@
import lupa import lupa
import threading import threading
import json import json
from .logger import MideaLogger
lupa.LuaMemoryError
class LuaRuntime: class LuaRuntime:
def __init__(self, file): def __init__(self, file):
@@ -44,22 +45,34 @@ class MideaCodec(LuaRuntime):
query_dict = self._build_base_dict() query_dict = self._build_base_dict()
query_dict["query"] = {} if append is None else append query_dict["query"] = {} if append is None else append
json_str = json.dumps(query_dict) json_str = json.dumps(query_dict)
result = self.json_to_data(json_str) try:
return result result = self.json_to_data(json_str)
return result
except lupa.LuaError as e:
MideaLogger.error(f"LuaRuntimeError in build_query {json_str}: {repr(e)}")
return None
def build_control(self, append=None): def build_control(self, append=None):
query_dict = self._build_base_dict() query_dict = self._build_base_dict()
query_dict["control"] = {} if append is None else append query_dict["control"] = {} if append is None else append
json_str = json.dumps(query_dict) json_str = json.dumps(query_dict)
result = self.json_to_data(json_str) try:
return result result = self.json_to_data(json_str)
return result
except lupa.LuaError as e:
MideaLogger.error(f"LuaRuntimeError in build_control {json_str}: {repr(e)}")
return None
def build_status(self, append=None): def build_status(self, append=None):
query_dict = self._build_base_dict() query_dict = self._build_base_dict()
query_dict["status"] = {} if append is None else append query_dict["status"] = {} if append is None else append
json_str = json.dumps(query_dict) json_str = json.dumps(query_dict)
result = self.json_to_data(json_str) try:
return result result = self.json_to_data(json_str)
return result
except lupa.LuaError as e:
MideaLogger.error(f"LuaRuntimeError in build_status {json_str}: {repr(e)}")
return None
def decode_status(self, data: str): def decode_status(self, data: str):
data_dict = self._build_base_dict() data_dict = self._build_base_dict()
@@ -67,6 +80,11 @@ class MideaCodec(LuaRuntime):
"data": data "data": data
} }
json_str = json.dumps(data_dict) json_str = json.dumps(data_dict)
result = self.data_to_json(json_str) try:
status = json.loads(result) result = self.data_to_json(json_str)
return status.get("status") status = json.loads(result)
return status.get("status")
except lupa.LuaError as e:
MideaLogger.error(f"LuaRuntimeError in decode_status {data}: {repr(e)}")
return None

View File

@@ -1,106 +0,0 @@
from homeassistant.const import *
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
from homeassistant.components.climate import (
HVACMode,
PRESET_NONE,
PRESET_ECO,
PRESET_COMFORT,
PRESET_SLEEP,
PRESET_BOOST,
SWING_OFF,
SWING_BOTH,
SWING_VERTICAL,
SWING_HORIZONTAL,
FAN_AUTO,
FAN_LOW,
FAN_MEDIUM,
FAN_HIGH,
)
DEVICE_MAPPING = {
"0xAC": {
"default": {
"manufacturer": "美的",
"queries": [{}, {"query_type": "prevent_straight_wind"}],
"centralized": ["power", "temperature", "small_temperature", "mode", "eco", "comfort_power_save",
"comfort_sleep", "strong_wind", "wind_swing_lr", "wind_swing_lr", "wind_speed",
"ptc", "dry"],
"entities": {
Platform.CLIMATE: {
"thermostat": {
"name": "Thermostat",
"power": "power",
"target_temperature": ["temperature", "small_temperature"],
"hvac_modes": {
HVACMode.OFF: {"power": "off"},
HVACMode.HEAT: {"power": "on", "mode": "heat"},
HVACMode.COOL: {"power": "on", "mode": "cool"},
HVACMode.AUTO: {"power": "on", "mode": "auto"},
HVACMode.DRY: {"power": "on", "mode": "dry"},
HVACMode.FAN_ONLY: {"power": "on", "mode": "fan"}
},
"preset_modes": {
PRESET_NONE: {
"eco": "off",
"comfort_power_save": "off",
"comfort_sleep": "off",
"strong_wind": "off"
},
PRESET_ECO: {"eco": "on"},
PRESET_COMFORT: {"comfort_power_save": "on"},
PRESET_SLEEP: {"comfort_sleep": "on"},
PRESET_BOOST: {"strong_wind": "on"}
},
"swing_modes": {
SWING_OFF: {"wind_swing_lr": "off", "wind_swing_ud": "off"},
SWING_BOTH: {"wind_swing_lr": "on", "wind_swing_ud": "on"},
SWING_HORIZONTAL: {"wind_swing_lr": "on", "wind_swing_ud": "off"},
SWING_VERTICAL: {"wind_swing_lr": "off", "wind_swing_ud": "on"},
},
"fan_modes": {
"silent": {"wind_speed": 20},
FAN_LOW: {"wind_speed": 40},
FAN_MEDIUM: {"wind_speed": 60},
FAN_HIGH: {"wind_speed": 80},
"full": {"wind_speed": 100},
FAN_AUTO: {"wind_speed": 102}
},
"current_temperature": "indoor_temperature",
"aux_heat": "ptc",
"min_temp": 17,
"max_temp": 30,
"temperature_unit": TEMP_CELSIUS,
"precision": PRECISION_HALVES,
}
},
Platform.SWITCH: {
"dry": {
"name": "Dry",
"device_class": SwitchDeviceClass.SWITCH,
},
"prevent_straight_wind": {
"binary_rationale": [1, 2]
}
},
Platform.SENSOR: {
"indoor_temperature": {
"name": "室内温度",
"device_class": SensorDeviceClass.TEMPERATURE,
"unit": TEMP_CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"outdoor_temperature": {
"name": "室外机温度",
"device_class": SensorDeviceClass.TEMPERATURE,
"unit": TEMP_CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
},
Platform.BINARY_SENSOR: {
"power": {}
}
}
}
},
}

View File

@@ -0,0 +1,152 @@
from homeassistant.const import *
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
DEVICE_MAPPING = {
"default": {
"manufacturer": "美的",
"rationale": ["off", "on"],
"queries": [{}, {"query_type": "prevent_straight_wind"}],
"centralized": ["power", "temperature", "small_temperature", "mode", "eco", "comfort_power_save",
"comfort_sleep", "strong_wind", "wind_swing_lr", "wind_swing_lr", "wind_speed",
"ptc", "dry"],
"entities": {
Platform.CLIMATE: {
"thermostat": {
"name": "Thermostat",
"power": "power",
"hvac_modes": {
"off": {"power": "off"},
"heat": {"power": "on", "mode": "heat"},
"cool": {"power": "on", "mode": "cool"},
"auto": {"power": "on", "mode": "auto"},
"dry": {"power": "on", "mode": "dry"},
"fan_only": {"power": "on", "mode": "fan"}
},
"preset_modes": {
"none": {
"eco": "off",
"comfort_power_save": "off",
"comfort_sleep": "off",
"strong_wind": "off"
},
"eco": {"eco": "on"},
"comfort": {"comfort_power_save": "on"},
"sleep": {"comfort_sleep": "on"},
"boost": {"strong_wind": "on"}
},
"swing_modes": {
"off": {"wind_swing_lr": "off", "wind_swing_ud": "off"},
"both": {"wind_swing_lr": "on", "wind_swing_ud": "on"},
"horizontal": {"wind_swing_lr": "on", "wind_swing_ud": "off"},
"vertical": {"wind_swing_lr": "off", "wind_swing_ud": "on"},
},
"fan_modes": {
"silent": {"wind_speed": 20},
"low": {"wind_speed": 40},
"medium": {"wind_speed": 60},
"high": {"wind_speed": 80},
"full": {"wind_speed": 100},
"auto": {"wind_speed": 102}
},
"target_temperature": ["temperature", "small_temperature"],
"current_temperature": "indoor_temperature",
"aux_heat": "ptc",
"min_temp": 17,
"max_temp": 30,
"temperature_unit": TEMP_CELSIUS,
"precision": PRECISION_HALVES,
}
},
Platform.SWITCH: {
"dry": {
"name": "Dry",
"device_class": SwitchDeviceClass.SWITCH,
},
"prevent_straight_wind": {
"name": "防直吹",
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [1, 2]
}
},
Platform.SENSOR: {
"indoor_temperature": {
"name": "室内温度",
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": TEMP_CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"outdoor_temperature": {
"name": "室外机温度",
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": TEMP_CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
},
Platform.BINARY_SENSOR: {
"power": {}
},
Platform.SELECT: {
"preset_modes": {
"options": {
"none": {
"eco": "off",
"comfort_power_save": "off",
"comfort_sleep": "off",
"strong_wind": "off"
},
"eco": {"eco": "on"},
"comfort": {"comfort_power_save": "on"},
"sleep": {"comfort_sleep": "on"},
"boost": {"strong_wind": "on"}
}
},
"hvac_modes": {
"options": {
"off": {"power": "off"},
"heat": {"power": "on", "mode": "heat"},
"cool": {"power": "on", "mode": "cool"},
"auto": {"power": "on", "mode": "auto"},
"dry": {"power": "on", "mode": "dry"},
"fan_only": {"power": "on", "mode": "fan"}
}
}
},
Platform.WATER_HEATER:{
"water_heater": {
"name": "热水器",
"power": "power",
"operation_list": {
"off": {"power": "off"},
"heat": {"power": "on", "mode": "heat"},
"cool": {"power": "on", "mode": "cool"},
"auto": {"power": "on", "mode": "auto"},
"dry": {"power": "on", "mode": "dry"},
"fan_only": {"power": "on", "mode": "fan"}
},
"target_temperature": ["temperature", "small_temperature"],
"current_temperature": "indoor_temperature",
"min_temp": 17,
"max_temp": 30,
"temperature_unit": TEMP_CELSIUS,
"precision": PRECISION_HALVES,
}
},
Platform.FAN: {
"fan": {
"power": "power",
"preset_modes": {
"off": {"power": "off"},
"heat": {"power": "on", "mode": "heat"},
"cool": {"power": "on", "mode": "cool"},
"auto": {"power": "on", "mode": "auto"},
"dry": {"power": "on", "mode": "dry"},
"fan_only": {"power": "on", "mode": "fan"}
},
"oscillate": "wind_swing_lr",
"speeds": list({"wind_speed": value + 1} for value in range(0, 100)),
}
}
}
}
}

View File

@@ -0,0 +1,112 @@
from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.const import (
Platform,
CONF_DEVICE_ID,
CONF_DEVICE,
CONF_ENTITIES,
)
from .const import (
DOMAIN,
DEVICES
)
from .midea_entities import MideaEntity
from .core.logger import MideaLogger
async def async_setup_entry(hass, config_entry, async_add_entities):
device_id = config_entry.data.get(CONF_DEVICE_ID)
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.FAN)
devs = []
if entities is not None:
for entity_key, config in entities.items():
devs.append(MideaFanEntity(device, manufacturer, rationale, entity_key, config))
async_add_entities(devs)
class MideaFanEntity(MideaEntity, FanEntity):
def __init__(self, device, manufacturer, rationale, entity_key, config):
super().__init__(device, manufacturer, rationale, entity_key, config)
self._key_power = self._config.get("power")
self._key_preset_modes = self._config.get("preset_modes")
self._key_speeds = self._config.get("speeds")
self._key_oscillate = self._config.get("oscillate")
self._key_directions = self._config.get("directions")
self._attr_speed_count = len(self._key_speeds) if self._key_speeds else 0
@property
def supported_features(self):
features = 0
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:
features |= FanEntityFeature.SET_SPEED
if self._key_oscillate is not None:
features |= FanEntityFeature.OSCILLATE
if self._key_directions is not None and len(self._key_directions) > 0:
features |= FanEntityFeature.DIRECTION
return features
@property
def is_on(self) -> bool:
return self._get_status_on_off(self._key_power)
@property
def preset_modes(self):
return list(self._key_preset_modes.keys())
@property
def preset_mode(self):
return self._dict_get_selected(self._key_preset_modes)
@property
def percentage(self):
index = self._list_get_selected(self._key_speeds)
if index is None:
return None
return round((index + 1) * 100 / self._attr_speed_count)
@property
def oscillating(self):
return self._get_status_on_off(self._key_oscillate)
def turn_on(
self,
percentage: int | None = None,
preset_mode: str | None = None,
**kwargs,
):
if preset_mode is not None:
new_status = self._key_preset_modes.get(preset_mode)
else:
new_status = {}
if percentage is not None:
index = round(percentage * self._attr_speed_count / 100) - 1
new_status.update(self._key_speeds[index])
new_status[self._key_power] = self._rationale[1]
self._device.set_attributes(new_status)
def turn_off(self):
self._set_status_on_off(self._key_power, False)
def set_percentage(self, percentage: int):
index = round(percentage * self._attr_speed_count / 100)
if 0 < index < len(self._key_speeds):
new_status = self._key_speeds[index - 1]
self._device.set_attributes(new_status)
def set_preset_mode(self, preset_mode: str):
new_status = self._key_preset_modes.get(preset_mode)
self._device.set_attributes(new_status)
def oscillate(self, oscillating: bool):
if self.oscillating != oscillating:
self._set_status_on_off(self._key_oscillate, oscillating)
def update_state(self, status):
try:
self.schedule_update_ha_state()
except Exception as e:
pass

View File

@@ -13,20 +13,25 @@ class Rationale(IntEnum):
GREATER = 1 GREATER = 1
LESS = 2 LESS = 2
class MideaEntity(Entity): class MideaEntity(Entity):
def __init__(self, device, manufacturer: str | None, entity_key: str, config: dict): def __init__(self, device, manufacturer: str | None, rationale: list | None, entity_key: str, config: dict):
self._device = device self._device = device
self._device.register_update(self.update_state) self._device.register_update(self.update_state)
self._entity_key = entity_key self._entity_key = entity_key
self._config = config self._config = config
self._device_name = self._device.device_name self._device_name = self._device.device_name
self._attr_native_unit_of_measurement = self._config.get("unit") self._rationale = rationale
rationale = config.get("rationale")
if rationale:
self._rationale = rationale
if self._rationale is None:
self._rationale = ["off", "on"]
self._attr_native_unit_of_measurement = self._config.get("unit_of_measurement")
self._attr_device_class = self._config.get("device_class") self._attr_device_class = self._config.get("device_class")
self._attr_state_class = self._config.get("state_class") self._attr_state_class = self._config.get("state_class")
self._attr_unit_of_measurement = self._config.get("unit")
self._attr_icon = self._config.get("icon") self._attr_icon = self._config.get("icon")
self._attr_unique_id = f"{DOMAIN}.{self._device.device_id}_{self._entity_key}" self._attr_unique_id = f"{DOMAIN}.{self._device.device_id}_{self._entity_key}"
MideaLogger.debug(self._attr_unique_id)
self._attr_device_info = { self._attr_device_info = {
"manufacturer": "Midea" if manufacturer is None else manufacturer, "manufacturer": "Midea" if manufacturer is None else manufacturer,
"model": f"{self._device.model}", "model": f"{self._device.model}",
@@ -43,16 +48,47 @@ class MideaEntity(Entity):
def should_poll(self): def should_poll(self):
return False return False
@property
def state(self):
raise NotImplementedError
@property @property
def available(self): def available(self):
return self._device.connected return self._device.connected
def get_mode(self, key_of_modes, rationale: Rationale = Rationale.EQUALLY): def _get_status_on_off(self, status_key: str):
for mode, status in key_of_modes.items(): result = False
status = self._device.get_attribute(status_key)
if status is not None:
try:
result = bool(self._rationale.index(status))
except ValueError:
MideaLogger.error(f"The value of attribute {status_key} ('{status}') "
f"is not in rationale {self._rationale}")
return result
def _set_status_on_off(self, status_key: str, turn_on: bool):
self._device.set_attribute(status_key, self._rationale[int(turn_on)])
def _list_get_selected(self, key_of_list: list, rationale: Rationale = Rationale.EQUALLY):
for index in range(0, len(key_of_list)):
match = True
for attr, value in key_of_list[index].items():
state_value = self._device.get_attribute(attr)
if state_value is None:
match = False
break
if rationale is Rationale.EQUALLY and state_value != value:
match = False
break
if rationale is Rationale.GREATER and state_value < value:
match = False
break
if rationale is Rationale.LESS and state_value > value:
match = False
break
if match:
return index
return None
def _dict_get_selected(self, key_of_dict: dict, rationale: Rationale = Rationale.EQUALLY):
for mode, status in key_of_dict.items():
match = True match = True
for attr, value in status.items(): for attr, value in status.items():
state_value = self._device.get_attribute(attr) state_value = self._device.get_attribute(attr)
@@ -74,7 +110,6 @@ class MideaEntity(Entity):
def update_state(self, status): def update_state(self, status):
if self._entity_key in status or "connected" in status: if self._entity_key in status or "connected" in status:
try: try:
self.schedule_update_ha_state() self.schedule_update_ha_state()
except Exception as e: except Exception as e:
@@ -82,10 +117,6 @@ class MideaEntity(Entity):
class MideaBinaryBaseEntity(MideaEntity): class MideaBinaryBaseEntity(MideaEntity):
def __init__(self, device, manufacturer: str | None, entity_key: str, config: dict):
super().__init__(device, manufacturer, entity_key, config)
binary_rationale = config.get("binary_rationale")
self._binary_rationale = binary_rationale if binary_rationale is not None else ["off", "on"]
@property @property
def state(self): def state(self):
@@ -93,4 +124,4 @@ class MideaBinaryBaseEntity(MideaEntity):
@property @property
def is_on(self): def is_on(self):
return self._device.get_attribute(self._entity_key) == self._binary_rationale[1] return self._get_status_on_off(self._entity_key)

View File

@@ -0,0 +1,50 @@
from homeassistant.components.select import SelectEntity
from homeassistant.const import (
Platform,
CONF_DEVICE_ID,
CONF_DEVICE,
CONF_ENTITIES,
)
from .const import (
DOMAIN,
DEVICES
)
from .midea_entities import MideaEntity
async def async_setup_entry(hass, config_entry, async_add_entities):
device_id = config_entry.data.get(CONF_DEVICE_ID)
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.SELECT)
devs = []
if entities is not None:
for entity_key, config in entities.items():
devs.append(MideaSelectEntity(device, manufacturer, rationale, entity_key, config))
async_add_entities(devs)
class MideaSelectEntity(MideaEntity, SelectEntity):
def __init__(self, device, manufacturer, rationale, entity_key, config):
super().__init__(device, manufacturer, rationale, entity_key, config)
self._key_options = self._config.get("options")
@property
def options(self):
return list(self._key_options.keys())
@property
def current_option(self):
return self._dict_get_selected(self._key_options)
def select_option(self, option: str):
new_status = self._key_options.get(option)
self._device.set_attributes(new_status)
def update_state(self, status):
try:
self.schedule_update_ha_state()
except Exception as e:
pass

View File

@@ -16,16 +16,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device_id = config_entry.data.get(CONF_DEVICE_ID) device_id = config_entry.data.get(CONF_DEVICE_ID)
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE) device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer") manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.SENSOR) entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.SENSOR)
devs = [] devs = []
if entities is not None: if entities is not None:
for entity_key, config in entities.items(): for entity_key, config in entities.items():
devs.append(MideaSensorEntity(device, manufacturer, entity_key, config)) devs.append(MideaSensorEntity(device, manufacturer, rationale, entity_key, config))
async_add_entities(devs) async_add_entities(devs)
class MideaSensorEntity(MideaEntity, SensorEntity): class MideaSensorEntity(MideaEntity, SensorEntity):
@property @property
def state(self): def native_value(self):
return self._device.get_attribute(self._entity_key) return self._device.get_attribute(self._entity_key)

View File

@@ -4,8 +4,6 @@ from homeassistant.const import (
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_DEVICE, CONF_DEVICE,
CONF_ENTITIES, CONF_ENTITIES,
STATE_ON,
STATE_OFF
) )
from .const import ( from .const import (
DOMAIN, DOMAIN,
@@ -18,18 +16,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
device_id = config_entry.data.get(CONF_DEVICE_ID) device_id = config_entry.data.get(CONF_DEVICE_ID)
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE) device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer") manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.SWITCH) entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.SWITCH)
devs = [] devs = []
if entities is not None: if entities is not None:
for entity_key, config in entities.items(): for entity_key, config in entities.items():
devs.append(MideaSwitchEntity(device, manufacturer, entity_key, config)) devs.append(MideaSwitchEntity(device, manufacturer, rationale, entity_key, config))
async_add_entities(devs) async_add_entities(devs)
class MideaSwitchEntity(MideaBinaryBaseEntity, SwitchEntity): class MideaSwitchEntity(MideaBinaryBaseEntity, SwitchEntity):
def turn_on(self): def turn_on(self):
self._device.set_attribute(attribute=self._entity_key, value=self._binary_rationale[1]) self._set_status_on_off(self._entity_key, True)
def turn_off(self): def turn_off(self):
self._device.set_attribute(attribute=self._entity_key, value=self._binary_rationale[0]) self._set_status_on_off(self._entity_key, False)

View File

@@ -0,0 +1,128 @@
from homeassistant.components.water_heater import WaterHeaterEntity, WaterHeaterEntityFeature
from homeassistant.const import (
Platform,
CONF_DEVICE_ID,
CONF_DEVICE,
CONF_ENTITIES,
ATTR_TEMPERATURE
)
from .const import (
DOMAIN,
DEVICES
)
from .midea_entities import MideaEntity
async def async_setup_entry(hass, config_entry, async_add_entities):
device_id = config_entry.data.get(CONF_DEVICE_ID)
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.WATER_HEATER)
devs = []
if entities is not None:
for entity_key, config in entities.items():
devs.append(MideaWaterHeaterEntityEntity(device, manufacturer, rationale, entity_key, config))
async_add_entities(devs)
class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
def __init__(self, device, manufacturer, rationale, entity_key, config):
super().__init__(device, manufacturer, rationale, entity_key, config)
self._key_power = self._config.get("power")
self._key_operation_list = self._config.get("operation_list")
self._key_min_temp = self._config.get("min_temp")
self._key_max_temp = self._config.get("max_temp")
self._key_current_temperature = self._config.get("current_temperature")
self._key_target_temperature = self._config.get("target_temperature")
self._attr_temperature_unit = self._config.get("temperature_unit")
self._attr_precision = self._config.get("precision")
@property
def supported_features(self):
features = 0
if self._key_target_temperature is not None:
features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE
if self._key_operation_list is not None:
features |= WaterHeaterEntityFeature.OPERATION_MODE
return features
@property
def operation_list(self):
return list(self._key_operation_list.keys())
@property
def current_operation(self):
return self._dict_get_selected(self._key_operation_list)
@property
def current_temperature(self):
return self._device.get_attribute(self._key_current_temperature)
@property
def target_temperature(self):
if isinstance(self._key_target_temperature, list):
temp_int = self._device.get_attribute(self._key_target_temperature[0])
tem_dec = self._device.get_attribute(self._key_target_temperature[1])
if temp_int is not None and tem_dec is not None:
return temp_int + tem_dec
return None
else:
return self._device.get_attribute(self._key_target_temperature)
@property
def min_temp(self):
if isinstance(self._key_min_temp, str):
return float(self._device.get_attribute(self._key_min_temp))
else:
return float(self._key_min_temp)
@property
def max_temp(self):
if isinstance(self._key_max_temp, str):
return float(self._device.get_attribute(self._key_max_temp))
else:
return float(self._key_max_temp)
@property
def target_temperature_low(self):
return self.min_temp
@property
def target_temperature_high(self):
return self.max_temp
@property
def is_on(self) -> bool:
return self._get_status_on_off(self._key_power)
def turn_on(self):
self._set_status_on_off(self._key_power, True)
def turn_off(self):
self._set_status_on_off(self._key_power, False)
def set_temperature(self, **kwargs):
if ATTR_TEMPERATURE not in kwargs:
return
temperature = kwargs.get(ATTR_TEMPERATURE)
temp_int, temp_dec = divmod(temperature, 1)
temp_int = int(temp_int)
new_status = {}
if isinstance(self._key_target_temperature, list):
new_status[self._key_target_temperature[0]] = temp_int
new_status[self._key_target_temperature[1]] = temp_dec
else:
new_status[self._key_target_temperature] = temperature
self._device.set_attributes(new_status)
def set_operation_mode(self, operation_mode: str) -> None:
new_status = self._key_operation_list.get(operation_mode)
self._device.set_attributes(new_status)
def update_state(self, status):
try:
self.schedule_update_ha_state()
except Exception as e:
pass