diff --git a/midealocal/devices/cd/__init__.py b/midealocal/devices/cd/__init__.py index 2729d794..a4415b00 100644 --- a/midealocal/devices/cd/__init__.py +++ b/midealocal/devices/cd/__init__.py @@ -2,7 +2,7 @@ import json import logging -from enum import StrEnum +from enum import IntEnum, StrEnum from typing import Any, ClassVar from midealocal.const import DeviceType, ProtocolVersion @@ -13,6 +13,20 @@ _LOGGER = logging.getLogger(__name__) +class CDSubType(IntEnum): + """CD Device sub type.""" + + T186 = 186 + + +class LuaProtocol(StrEnum): + """Lua protocol.""" + + auto = "auto" # default is auto + old = "old" # true + new = "new" # false + + class DeviceAttributes(StrEnum): """Midea CD device attributes.""" @@ -26,12 +40,21 @@ class DeviceAttributes(StrEnum): condenser_temperature = "condenser_temperature" compressor_temperature = "compressor_temperature" compressor_status = "compressor_status" + water_level = "water_level" + fahrenheit = "fahrenheit" class MideaCDDevice(MideaDevice): """Midea CD device.""" - _modes: ClassVar[list[str]] = ["Energy-save", "Standard", "Dual", "Smart"] + _modes: ClassVar[dict[int, str]] = { + 0x00: "None", + 0x01: "Energy-save", + 0x02: "Standard", + 0x03: "Dual", + 0x04: "Smart", + 0x05: "Vacation", + } def __init__( self, @@ -60,7 +83,7 @@ def __init__( subtype=subtype, attributes={ DeviceAttributes.power: False, - DeviceAttributes.mode: None, + DeviceAttributes.mode: 0, DeviceAttributes.max_temperature: 65, DeviceAttributes.min_temperature: 35, DeviceAttributes.target_temperature: 40, @@ -69,13 +92,58 @@ def __init__( DeviceAttributes.condenser_temperature: None, DeviceAttributes.compressor_temperature: None, DeviceAttributes.compressor_status: None, + DeviceAttributes.water_level: None, + DeviceAttributes.fahrenheit: False, }, ) self._fields: dict[Any, Any] = {} self._temperature_step: float | None = None self._default_temperature_step = 1 + # customize lua_protocol + self._default_lua_protocol = LuaProtocol.auto + self._lua_protocol = self._default_lua_protocol + # fahrenheit or celsius switch, default is celsius, update with message + self._fahrenheit: bool = False self.set_customize(customize) + def _value_to_temperature(self, value: float) -> float: + # fahrenheit to celsius + if self._fahrenheit: + return (value - 32) * 5.0 / 9.0 + # celsius + # old protocol + if self._lua_protocol == LuaProtocol.old: + return round((value - 30) / 2) + # new protocol + return value + + def _temperature_to_value(self, value: float) -> float: + # fahrenheit to celsius + if self._fahrenheit: + return value * 9.0 / 5.0 + 32 + # celsius + # old protocol + if self._lua_protocol == LuaProtocol.old: + return round(value * 2 + 30) + # new protocol + return value + + def _normalize_lua_protocol(self, value: str | bool | int) -> LuaProtocol: + # current only have str + if isinstance(value, str): + return_value = LuaProtocol(value) + # auto mode, use subtype to set value as old or new + if return_value == LuaProtocol.auto: + # new protocol, [subtype0, model RSJRAC01] [subtype186, model RSJ000CB] + # old protocol. current subtype is unknown, to be done. + check_device = ( + self.subtype == CDSubType.T186 or self.model == "RSJRAC01", + ) + return_value = LuaProtocol.new if check_device else LuaProtocol.old + if isinstance(value, bool | int): + return_value = LuaProtocol.new if value else LuaProtocol.old + return return_value + @property def temperature_step(self) -> float | None: """Midea CD device temperature step.""" @@ -84,7 +152,7 @@ def temperature_step(self) -> float | None: @property def preset_modes(self) -> list[str]: """Midea CD device preset modes.""" - return MideaCDDevice._modes + return list(MideaCDDevice._modes.values()) def build_query(self) -> list[MessageQuery]: """Midea CD device build query.""" @@ -97,17 +165,32 @@ def process_message(self, msg: bytes) -> dict[str, Any]: new_status = {} if hasattr(message, "fields"): self._fields = message.fields - for status in self._attributes: - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - self._attributes[status] = MideaCDDevice._modes[value] + # parse fahrenheit switch for temperature value + if hasattr(message, DeviceAttributes.fahrenheit): + self._fahrenheit = getattr(message, DeviceAttributes.fahrenheit) + for attr in self._attributes: + if hasattr(message, str(attr)): + value = getattr(message, str(attr)) + # parse modes + if attr == DeviceAttributes.mode: + self._attributes[attr] = MideaCDDevice._modes.get(value, value) + # process temperature + elif attr in [ + DeviceAttributes.max_temperature, + DeviceAttributes.min_temperature, + DeviceAttributes.target_temperature, + DeviceAttributes.current_temperature, + DeviceAttributes.outdoor_temperature, + DeviceAttributes.condenser_temperature, + DeviceAttributes.compressor_temperature, + ]: + self._attributes[attr] = self._value_to_temperature(value) else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] + self._attributes[attr] = value + new_status[str(attr)] = self._attributes[attr] return new_status - def set_attribute(self, attr: str, value: str | int | bool) -> None: + def set_attribute(self, attr: str, value: str | float | bool) -> None: """Midea CD device set attribute.""" if attr in [ DeviceAttributes.mode, @@ -116,16 +199,18 @@ def set_attribute(self, attr: str, value: str | int | bool) -> None: ]: message = MessageSet(self._message_protocol_version) message.fields = self._fields - message.mode = MideaCDDevice._modes.index( - self._attributes[DeviceAttributes.mode], - ) + message.mode = self._attributes[DeviceAttributes.mode] message.power = self._attributes[DeviceAttributes.power] message.target_temperature = self._attributes[ DeviceAttributes.target_temperature ] + # process modes value to str if attr == DeviceAttributes.mode: - if value in MideaCDDevice._modes: - setattr(message, str(attr), MideaCDDevice._modes.index(str(value))) + value = int(value) + setattr(message, str(attr), MideaCDDevice._modes.get(value, value)) + # process target temperature to data value + elif attr == DeviceAttributes.target_temperature: + setattr(message, str(attr), self._temperature_to_value(float(value))) else: setattr(message, str(attr), value) self.build_send(message) @@ -133,14 +218,24 @@ def set_attribute(self, attr: str, value: str | int | bool) -> None: def set_customize(self, customize: str) -> None: """Midea CD device set customize.""" self._temperature_step = self._default_temperature_step + self._lua_protocol = self._default_lua_protocol if customize and len(customize) > 0: try: params = json.loads(customize) if params and "temperature_step" in params: self._temperature_step = params.get("temperature_step") + if params and "lua_protocol" in params: + self._lua_protocol = self._normalize_lua_protocol( + params["lua_protocol"], + ) except Exception: _LOGGER.exception("[%s] Set customize error", self.device_id) - self.update_all({"temperature_step": self._temperature_step}) + self.update_all( + { + "temperature_step": self._temperature_step, + "lua_protocol": self._lua_protocol, + }, + ) class MideaAppliance(MideaCDDevice): diff --git a/midealocal/devices/cd/message.py b/midealocal/devices/cd/message.py index e3d2f1fc..ef05a377 100644 --- a/midealocal/devices/cd/message.py +++ b/midealocal/devices/cd/message.py @@ -11,6 +11,8 @@ MessageType, ) +OLD_BODY_LENGTH = 29 + class MessageCDBase(MessageRequest): """CD message base.""" @@ -97,34 +99,108 @@ def __init__(self, body: bytearray) -> None: """Initialize CD message general body.""" super().__init__(body) self.power = (body[2] & 0x01) > 0 - self.target_temperature = round((body[3] - 30) / 2) + # energyMode if (body[2] & 0x02) > 0: - self.mode = 0 + self.mode = 0x01 + # standardMode elif (body[2] & 0x04) > 0: - self.mode = 1 + self.mode = 0x02 + # compatibilizingMode elif (body[2] & 0x08) > 0: - self.mode = 2 - self.current_temperature = round((body[4] - 30) / 2) - self.condenser_temperature = (body[7] - 30) / 2 - self.outdoor_temperature = (body[8] - 30) / 2 - self.compressor_temperature = (body[9] - 30) / 2 - self.max_temperature = round((body[10] - 30) / 2) - self.min_temperature = round((body[11] - 30) / 2) + self.mode = 0x03 + # heatValue + self.heat = body[2] & 0x20 + # dicaryonHeat + self.heat = body[2] & 0x30 + # eco + self.eco = body[2] & 0x40 + # tsValue + self.target_temperature = body[3] + # washBoxTemp + self.current_temperature = body[4] + # boxTopTemp + self.top_temperature = body[5] + # boxBottomTemp + self.bottom_temperature = body[6] + # t3Value + self.condenser_temperature = body[7] + # t4Value + self.outdoor_temperature = body[8] + # compressorTopTemp + self.compressor_temperature = body[9] + # tsMaxValue + self.max_temperature = body[10] + # tsMinValue + self.min_temperature = body[11] + # errorCode + self.error_code = body[20] + # bottomElecHeat + self.bottom_elec_heat = (body[27] & 0x01) > 0 + # topElecHeat + self.top_elec_heat = (body[27] & 0x02) > 0 + # waterPump + self.water_pump = (body[27] & 0x04) > 0 + # compressor self.compressor_status = (body[27] & 0x08) > 0 + # middleWind + if (body[27] & 0x10) > 0: + self.wind = "middle" + # lowWind + if (body[27] & 0x40) > 0: + self.wind = "low" + # highWind + if (body[27] & 0x80) > 0: + self.wind = "high" + # fourWayValve + self.four_way = (body[27] & 0x20) > 0 + # elecHeatSupport + self.elec_heat = (body[28] & 0x01) > 0 + # smartMode if (body[28] & 0x20) > 0: - self.mode = 3 + self.mode = 0x04 + # backwaterEffect + self.back_water = (body[28] & 0x40) > 0 + # sterilizeEffect + self.sterilize = (body[28] & 0x80) > 0 + # typeInfo + self.typeinfo = body[29] + # hotWater + self.water_level = body[34] if len(body) > OLD_BODY_LENGTH else None + # vacationMode + if len(body) > OLD_BODY_LENGTH and (body[35] & 0x01) > 0: + self.mode = 0x05 + # smartGrid + self.smart_grid = ( + ((body[35] & 0x01) > 0) if len(body) > OLD_BODY_LENGTH else False + ) + # multiTerminal + self.multi_terminal = ( + ((body[35] & 0x40) > 0) if len(body) > OLD_BODY_LENGTH else False + ) + # fahrenheitEffect + self.fahrenheit = ( + ((body[35] & 0x80) > 0) if len(body) > OLD_BODY_LENGTH else False + ) + # mute_effect + self.mute_effect = ( + ((body[39] & 0x40) > 0) if len(body) > OLD_BODY_LENGTH else False + ) + # mute_status + self.mute_status = ( + ((body[39] & 0x880) > 0) if len(body) > OLD_BODY_LENGTH else False + ) -class CD02MessageBody(MessageBody): - """CD message 02 body.""" +class CD01MessageBody(MessageBody): + """CD message set 01 body.""" def __init__(self, body: bytearray) -> None: - """Initialize CD message 02 body.""" + """Initialize CD message set 01 body.""" super().__init__(body) self.fields = {} self.power = (body[2] & 0x01) > 0 self.mode = body[3] - self.target_temperature = round((body[4] - 30) / 2) + self.target_temperature = body[4] self.fields["trValue"] = body[5] self.fields["openPTC"] = body[5] self.fields["ptcTemp"] = body[7] @@ -137,8 +213,10 @@ class MessageCDResponse(MessageResponse): def __init__(self, message: bytes) -> None: """Initialize CD message response.""" super().__init__(bytearray(message)) + # parse query/notify response message if self.message_type in [MessageType.query, MessageType.notify2]: self.set_body(CDGeneralMessageBody(super().body)) + # parse set message with body_type 0x01 elif self.message_type == MessageType.set and self.body_type == 0x01: - self.set_body(CD02MessageBody(super().body)) + self.set_body(CD01MessageBody(super().body)) self.set_attr()