Skip to content

Commit

Permalink
Add "rate select" functionality (#157)
Browse files Browse the repository at this point in the history
* Add rate select (gear) capability parser and property

* Add rate select parsing to property response

* Add rate select to device properties

* Add methods for supported rates
  • Loading branch information
mill1000 authored Aug 7, 2024
1 parent 460497b commit e538f30
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 6 deletions.
14 changes: 14 additions & 0 deletions msmart/device/AC/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,11 @@ def get_value(w) -> Callable[[int], bool]: return lambda v: v == w
reader("turbo_heat", lambda v: v == 1 or v == 3),
reader("turbo_cool", lambda v: v < 2),
],
CapabilityId.RATE_SELECT: [
reader("rate_select_2_level", get_value(1)), # Gear
reader("rate_select_5_level", lambda v: v in [
2, 3]), # Genmode and Gear5
],
CapabilityId.SELF_CLEAN: reader("self_clean", get_value(1)),
CapabilityId.SILKY_COOL: reader("silky_cool", get_value(1)),
CapabilityId.SMART_EYE: reader("smart_eye", get_value(1)),
Expand Down Expand Up @@ -728,6 +733,15 @@ def target_humidity(self) -> bool:
def self_clean(self) -> bool:
return self._capabilities.get("self_clean", False)

@property
def rate_select_levels(self) -> Optional[int]:
if self._capabilities.get("rate_select_5_level", False):
return 5
elif self._capabilities.get("rate_select_2_level", False):
return 2

return None


class StateResponse(Response):
"""Response to state query."""
Expand Down
62 changes: 62 additions & 0 deletions msmart/device/AC/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,25 @@ class SwingAngle(MideaIntEnum):

DEFAULT = OFF

class RateSelect(MideaIntEnum):
OFF = 100

# 2 levels
GEAR_50 = 50
GEAR_75 = 75

# 5 levels
LEVEL_1 = 1
LEVEL_2 = 20
LEVEL_3 = 40
LEVEL_4 = 60
LEVEL_5 = 80

DEFAULT = OFF

# Create a dict to map properties to attribute names
_PROPERTY_MAP = {
PropertyId.RATE_SELECT: "_rate_select",
PropertyId.SWING_LR_ANGLE: "_horizontal_swing_angle",
PropertyId.SWING_UD_ANGLE: "_vertical_swing_angle"
}
Expand Down Expand Up @@ -124,6 +141,8 @@ def __init__(self, ip: str, device_id: int, port: int, **kwargs) -> None:
self._horizontal_swing_angle = AirConditioner.SwingAngle.OFF
self._vertical_swing_angle = AirConditioner.SwingAngle.OFF
self._self_clean_active = False
self._rate_select = AirConditioner.RateSelect.OFF
self._supported_rate_selects = [AirConditioner.RateSelect.OFF]

def _update_state(self, res: Response) -> None:
"""Update the local state from a device state response."""
Expand Down Expand Up @@ -183,6 +202,11 @@ def _update_state(self, res: Response) -> None:
if (value := res.get_property(PropertyId.SELF_CLEAN)) is not None:
self._self_clean_active = bool(value)

if (rate := res.get_property(PropertyId.RATE_SELECT)) is not None:
self._rate_select = cast(
AirConditioner.RateSelect,
AirConditioner.RateSelect.get_from_value(rate))

elif isinstance(res, EnergyUsageResponse):
self._total_energy_usage = res.total_energy
self._current_energy_usage = res.current_energy
Expand Down Expand Up @@ -272,6 +296,26 @@ def _update_capabilities(self, res: CapabilitiesResponse) -> None:
if res.self_clean:
self._supported_properties.add(PropertyId.SELF_CLEAN)

# Add supported rate select levels
if (rates := res.rate_select_levels) is not None:
self._supported_properties.add(PropertyId.RATE_SELECT)

if rates > 2:
self._supported_rate_selects = [
AirConditioner.RateSelect.OFF,
AirConditioner.RateSelect.LEVEL_5,
AirConditioner.RateSelect.LEVEL_4,
AirConditioner.RateSelect.LEVEL_3,
AirConditioner.RateSelect.LEVEL_2,
AirConditioner.RateSelect.LEVEL_1,
]
else:
self._supported_rate_selects = [
AirConditioner.RateSelect.OFF,
AirConditioner.RateSelect.GEAR_75,
AirConditioner.RateSelect.GEAR_50,
]

async def _send_command_get_responses(self, command) -> List[Response]:
"""Send a command and return all valid responses."""

Expand Down Expand Up @@ -430,6 +474,10 @@ async def apply(self) -> None:
if self._freeze_protection_mode and not self._supports_freeze_protection_mode:
_LOGGER.warning("Device is not capable of freeze protection.")

if self._rate_select != AirConditioner.RateSelect.OFF and self._rate_select not in self._supported_rate_selects:
_LOGGER.warning(
"Device is not capable of rate select %r.", self._rate_select)

# Define function to return value or a default if value is None
def or_default(v, d) -> Any: return v if v is not None else d

Expand Down Expand Up @@ -716,6 +764,19 @@ def supports_self_clean(self) -> bool:
def self_clean_active(self) -> bool:
return self._self_clean_active

@property
def supported_rate_selects(self) -> List[RateSelect]:
return self._supported_rate_selects

@property
def rate_select(self) -> RateSelect:
return self._rate_select

@rate_select.setter
def rate_select(self, rate: RateSelect) -> None:
self._rate_select = rate
self._updated_properties.add(PropertyId.RATE_SELECT)

def to_dict(self) -> dict:
return {**super().to_dict(), **{
"power": self.power_state,
Expand Down Expand Up @@ -743,4 +804,5 @@ def to_dict(self) -> dict:
"total_energy_usage": self.total_energy_usage,
"current_energy_usage": self.current_energy_usage,
"real_time_power_usage": self.real_time_power_usage,
"rate_select": self.rate_select,
}}
16 changes: 10 additions & 6 deletions msmart/device/AC/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
"dry_mode", "cool_mode", "heat_mode", "auto_mode",
"eco_mode", "turbo_mode", "freeze_protection_mode",
"min_temperature", "max_temperature",
"display_control", "filter_reminder"]
"display_control", "filter_reminder", "rate_select_levels"]

def test_properties(self) -> None:
"""Test that the capabilities response has the expected properties."""
Expand Down Expand Up @@ -330,7 +330,8 @@ def test_capabilities(self) -> None:
"fan_custom": False, "fan_silent": False, "fan_low": True,
"fan_medium": True, "fan_high": True, "fan_auto": True,
"min_temperature": 16, "max_temperature": 30,
"display_control": False, "filter_reminder": False
"display_control": False, "filter_reminder": False,
"rate_select_levels": None
}
# Check capabilities properties match
for prop in self.EXPECTED_PROPERTIES:
Expand Down Expand Up @@ -379,7 +380,8 @@ def test_capabilities_2(self) -> None:
"fan_custom": True, "fan_silent": True, "fan_low": True,
"fan_medium": True, "fan_high": True, "fan_auto": True,
"min_temperature": 16, "max_temperature": 30,
"display_control": False, "filter_reminder": False
"display_control": False, "filter_reminder": False,
"rate_select_levels": None
}
# Check capabilities properties match
for prop in self.EXPECTED_PROPERTIES:
Expand Down Expand Up @@ -417,7 +419,8 @@ def test_capabilities_3(self) -> None:
"fan_custom": False, "fan_silent": False, "fan_low": True,
"fan_medium": True, "fan_high": True, "fan_auto": True,
"min_temperature": 16, "max_temperature": 30,
"display_control": True, "filter_reminder": True
"display_control": True, "filter_reminder": True,
"rate_select_levels": None
}
# Check capabilities properties match
for prop in self.EXPECTED_PROPERTIES:
Expand Down Expand Up @@ -457,7 +460,8 @@ def test_capabilities_4(self) -> None:
"fan_custom": True, "fan_silent": True, "fan_low": True,
"fan_medium": True, "fan_high": True, "fan_auto": True,
"min_temperature": 16, "max_temperature": 30,
"display_control": True, "filter_reminder": True
"display_control": True, "filter_reminder": True,
"rate_select_levels": None
}
# Check capabilities properties match
for prop in self.EXPECTED_PROPERTIES:
Expand Down Expand Up @@ -557,7 +561,7 @@ def test_additional_capabilities(self) -> None:
"fan_medium": True, "fan_high": True, "fan_auto": True,
"min_temperature": 16, "max_temperature": 30,
"display_control": False, "filter_reminder": False,
"anion": True
"anion": True, "rate_select_levels": None
}
# Check capabilities properties match
for prop in self.EXPECTED_PROPERTIES:
Expand Down

0 comments on commit e538f30

Please sign in to comment.