Skip to content

Commit

Permalink
feat(cd): add water_level and temperature option with customize lua_p…
Browse files Browse the repository at this point in the history
…rotocol (#345)

1. add set customize option: `lua_protocol`, default is auto, user can
manual set it to `old` or `new`, I'm not sure whether we will have more
than two options, just use a str option for future.
> for `auto` mode, use subtype and model to process current github issue
known device.

3. add `water_level` attribute, current only can display the raw value
in debug log, I'm not sure the 4 level range now.
4. add more attributes in debug log output, we can add it in future.


fix: 
wuwentao/midea_ac_lan#347
wuwentao/midea_ac_lan#420
wuwentao/midea_ac_lan#425
  • Loading branch information
wuwentao authored Jan 15, 2025
1 parent da38007 commit db2fdf0
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 34 deletions.
131 changes: 113 additions & 18 deletions midealocal/devices/cd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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."""

Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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."""
Expand All @@ -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."""
Expand All @@ -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,
Expand All @@ -116,31 +199,43 @@ 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)

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):
Expand Down
110 changes: 94 additions & 16 deletions midealocal/devices/cd/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
MessageType,
)

OLD_BODY_LENGTH = 29


class MessageCDBase(MessageRequest):
"""CD message base."""
Expand Down Expand Up @@ -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]
Expand All @@ -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()

0 comments on commit db2fdf0

Please sign in to comment.