Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cd): add water_level and temperature option with customize lua_protocol #345

Merged
merged 12 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Loading