Repository: mill1000/midea-msmart
Pull request #171 (2024-08-30 21:44:36)

Refactor preset properties to avoid "_mode" prefix which may have been conflatable with operational modes
Keep original device properties around but wrapped with a deprecated decorator that warns on first usage.


Patch:
diff --git a/example.py b/example.py
index 3859ec8..bf1fe94 100644
--- a/example.py
+++ b/example.py
@@ -47,8 +47,8 @@ async def main():
         'operational_mode': device.operational_mode,
         'fan_speed': device.fan_speed,
         'swing_mode': device.swing_mode,
-        'eco_mode': device.eco_mode,
-        'turbo_mode': device.turbo_mode,
+        'eco': device.eco,
+        'turbo': device.turbo,
         'fahrenheit': device.fahrenheit,
         'indoor_temperature': device.indoor_temperature,
         'outdoor_temperature': device.outdoor_temperature
@@ -74,8 +74,8 @@ async def main():
         'operational_mode': device.operational_mode,
         'fan_speed': device.fan_speed,
         'swing_mode': device.swing_mode,
-        'eco_mode': device.eco_mode,
-        'turbo_mode': device.turbo_mode,
+        'eco': device.eco,
+        'turbo': device.turbo,
         'fahrenheit': device.fahrenheit,
         'indoor_temperature': device.indoor_temperature,
         'outdoor_temperature': device.outdoor_temperature
diff --git a/msmart/cli.py b/msmart/cli.py
index d977790..06d3efe 100644
--- a/msmart/cli.py
+++ b/msmart/cli.py
@@ -94,9 +94,9 @@ async def _query(args) -> None:
             "supported_swing_modes": device.supported_swing_modes,
             "supported_fan_speeds": device.supported_fan_speeds,
             "supports_custom_fan_speed": device.supports_custom_fan_speed,
-            "supports_eco_mode": device.supports_eco_mode,
-            "supports_turbo_mode": device.supports_turbo_mode,
-            "supports_freeze_protection_mode": device.supports_freeze_protection_mode,
+            "supports_eco": device.supports_eco,
+            "supports_turbo": device.supports_turbo,
+            "supports_freeze_protection": device.supports_freeze_protection,
             "supports_display_control": device.supports_display_control,
             "supports_filter_reminder": device.supports_filter_reminder,
             "max_target_temperature": device.max_target_temperature,
diff --git a/msmart/device/AC/command.py b/msmart/device/AC/command.py
index 1e6c1fc..0a7396e 100644
--- a/msmart/device/AC/command.py
+++ b/msmart/device/AC/command.py
@@ -248,12 +248,12 @@ class SetStateCommand(Command):
         self.target_temperature = 25.0
         self.operational_mode = 0
         self.fan_speed = 0
-        self.eco_mode = True
+        self.eco = True
         self.swing_mode = 0
-        self.turbo_mode = False
+        self.turbo = False
         self.fahrenheit = True
-        self.sleep_mode = False
-        self.freeze_protection_mode = False
+        self.sleep = False
+        self.freeze_protection = False
         self.follow_me = False
         self.purifier = False
         self.target_humidity = 40
@@ -286,23 +286,23 @@ class SetStateCommand(Command):
         swing_mode = 0x30 | (self.swing_mode & 0x3F)
 
         # Build eco mode and purifier byte
-        eco_mode = 0x80 if self.eco_mode else 0
+        eco = 0x80 if self.eco else 0
         purifier = 0x20 if self.purifier else 0
 
         # Build sleep, turbo and fahrenheit byte
-        sleep = 0x01 if self.sleep_mode else 0
-        turbo = 0x02 if self.turbo_mode else 0
+        sleep = 0x01 if self.sleep else 0
+        turbo = 0x02 if self.turbo else 0
         fahrenheit = 0x04 if self.fahrenheit else 0
 
         # Build alternate turbo byte
-        turbo_alt = 0x20 if self.turbo_mode else 0
+        turbo_alt = 0x20 if self.turbo else 0
         follow_me = 0x80 if self.follow_me else 0
 
         # Build target humidity byte
         humidity = self.target_humidity & 0x7F
 
         # Build freeze protection byte
-        freeze_protect = 0x80 if self.freeze_protection_mode else 0
+        freeze_protect = 0x80 if self.freeze_protection else 0
 
         return super().tobytes(bytes([
             # Set state
@@ -320,7 +320,7 @@ class SetStateCommand(Command):
             # Follow me and alternate turbo mode
             follow_me | turbo_alt,
             # ECO mode and purifier/anion
-            eco_mode | purifier,
+            eco | purifier,
             # Sleep mode, turbo mode and fahrenheit
             sleep | turbo | fahrenheit,
             # Unknown
@@ -512,6 +512,11 @@ class CapabilitiesResponse(Response):
             CapabilityId.BREEZELESS: reader("breezeless", get_value(1)),
             CapabilityId.BUZZER:  reader("buzzer", get_value(1)),
             CapabilityId.DISPLAY_CONTROL: reader("display_control", lambda v: v in [1, 2, 100]),
+            CapabilityId.ENERGY: [
+                reader("energy_stats", lambda v: v in [2, 3, 4, 5]),
+                reader("energy_setting", lambda v: v in [3, 5]),
+                reader("energy_bcd", lambda v: v in [2, 3]),
+            ],
             CapabilityId.FAHRENHEIT: reader("fahrenheit", get_value(0)),
             CapabilityId.FAN_SPEED_CONTROL: [
                 reader("fan_silent", get_value(6)),
@@ -530,20 +535,15 @@ class CapabilitiesResponse(Response):
                 reader("humidity_auto_set", lambda v: v in [1, 2]),
                 reader("humidity_manual_set", lambda v: v in [2, 3]),
             ],
-            CapabilityId.PRESET_IECO: reader("ieco", get_value(1)),
             CapabilityId.MODES: [
                 reader("heat_mode", lambda v: v in [1, 2, 4, 6, 7, 9]),
                 reader("cool_mode", lambda v: v != 2),
                 reader("dry_mode", lambda v: v in [0, 1, 5, 6, 9]),
                 reader("auto_mode", lambda v: v in [0, 1, 2, 7, 8, 9]),
             ],
-            CapabilityId.ENERGY: [
-                reader("energy_stats", lambda v: v in [2, 3, 4, 5]),
-                reader("energy_setting", lambda v: v in [3, 5]),
-                reader("energy_bcd", lambda v: v in [2, 3]),
-            ],
-            CapabilityId.PRESET_ECO: reader("eco_mode", lambda v: v in [1, 2]),
+            CapabilityId.PRESET_ECO: reader("eco", lambda v: v in [1, 2]),
             CapabilityId.PRESET_FREEZE_PROTECTION: reader("freeze_protection", get_value(1)),
+            CapabilityId.PRESET_IECO: reader("ieco", get_value(1)),
             CapabilityId.PRESET_TURBO:  [
                 reader("turbo_heat", lambda v: v in [1, 3]),
                 reader("turbo_cool", lambda v: v < 2),
@@ -742,20 +742,20 @@ class CapabilitiesResponse(Response):
         return self._capabilities.get("auto_mode", False)
 
     @property
-    def eco_mode(self) -> bool:
-        return self._capabilities.get("eco_mode", False)
+    def eco(self) -> bool:
+        return self._capabilities.get("eco", False)
 
     @property
-    def ieco_mode(self) -> bool:
+    def ieco(self) -> bool:
         return self._capabilities.get("ieco", False)
 
     @property
-    def turbo_mode(self) -> bool:
+    def turbo(self) -> bool:
         return (self._capabilities.get("turbo_heat", False)
                 or self._capabilities.get("turbo_cool", False))
 
     @property
-    def freeze_protection_mode(self) -> bool:
+    def freeze_protection(self) -> bool:
         return self._capabilities.get("freeze_protection", False)
 
     @property
@@ -816,15 +816,15 @@ class StateResponse(Response):
         self.operational_mode = None
         self.fan_speed = None
         self.swing_mode = None
-        self.turbo_mode = None
-        self.eco_mode = None
-        self.sleep_mode = None
+        self.turbo = None
+        self.eco = None
+        self.sleep = None
         self.fahrenheit = None
         self.indoor_temperature = None
         self.outdoor_temperature = None
         self.filter_alert = None
         self.display_on = None
-        self.freeze_protection_mode = None
+        self.freeze_protection = None
         self.follow_me = None
         self.purifier = None
         self.target_humidity = None
@@ -872,19 +872,19 @@ class StateResponse(Response):
         # self.cozy_sleep = payload[8] & 0x03
         # self.save = (payload[8] & 0x08) > 0
         # self.low_frequency_fan = (payload[8] & 0x10) > 0
-        self.turbo_mode = bool(payload[8] & 0x20)
+        self.turbo = bool(payload[8] & 0x20)
         self.follow_me = bool(payload[8] & 0x80)
 
-        self.eco_mode = bool(payload[9] & 0x10)
+        self.eco = bool(payload[9] & 0x10)
         self.purifier = bool(payload[9] & 0x20)
-        # self.child_sleep_mode = (payload[9] & 0x01) > 0
+        # self.child_sleep = (payload[9] & 0x01) > 0
         # self.exchange_air = (payload[9] & 0x02) > 0
         # self.dry_clean = (payload[9] & 0x04) > 0
         # self.aux_heat = (payload[9] & 0x08) > 0
         # self.temp_unit = (payload[9] & 0x80) > 0
 
-        self.sleep_mode = bool(payload[10] & 0x1)
-        self.turbo_mode |= bool(payload[10] & 0x2)
+        self.sleep = bool(payload[10] & 0x1)
+        self.turbo |= bool(payload[10] & 0x2)
         self.fahrenheit = bool(payload[10] & 0x4)
         # self.catch_cold = (payload[10] & 0x08) > 0
         # self.night_light = (payload[10] & 0x10) > 0
@@ -926,7 +926,7 @@ class StateResponse(Response):
         if len(payload) < 22:
             return
 
-        self.freeze_protection_mode = bool(payload[21] & 0x80)
+        self.freeze_protection = bool(payload[21] & 0x80)
 
 
 class PropertiesResponse(Response):
diff --git a/msmart/device/AC/device.py b/msmart/device/AC/device.py
index 7327bbc..24e7a33 100644
--- a/msmart/device/AC/device.py
+++ b/msmart/device/AC/device.py
@@ -6,7 +6,7 @@ from typing import Any, List, Optional, Union, cast
 from msmart.base_device import Device
 from msmart.const import DeviceType
 from msmart.frame import InvalidFrameException
-from msmart.utils import MideaIntEnum
+from msmart.utils import MideaIntEnum, deprecated
 
 from .command import (CapabilitiesResponse, EnergyUsageResponse,
                       GetCapabilitiesCommand, GetEnergyUsageCommand,
@@ -88,7 +88,7 @@ class AirConditioner(Device):
         PropertyId.BREEZE_AWAY: lambda s: s._breeze_mode == AirConditioner.BreezeMode.BREEZE_AWAY,
         PropertyId.BREEZE_CONTROL: lambda s: s._breeze_mode,
         PropertyId.BREEZELESS: lambda s: s._breeze_mode == AirConditioner.BreezeMode.BREEZELESS,
-        PropertyId.IECO: lambda s: s._ieco_mode,
+        PropertyId.IECO: lambda s: s._ieco,
         PropertyId.RATE_SELECT: lambda s: s._rate_select,
         PropertyId.SWING_LR_ANGLE: lambda s: s._horizontal_swing_angle,
         PropertyId.SWING_UD_ANGLE: lambda s: s._vertical_swing_angle
@@ -107,10 +107,10 @@ class AirConditioner(Device):
         self._operational_mode = AirConditioner.OperationalMode.AUTO
         self._fan_speed = AirConditioner.FanSpeed.AUTO
         self._swing_mode = AirConditioner.SwingMode.OFF
-        self._eco_mode = False
-        self._turbo_mode = False
-        self._freeze_protection_mode = False
-        self._sleep_mode = False
+        self._eco = False
+        self._turbo = False
+        self._freeze_protection = False
+        self._sleep = False
         self._fahrenheit_unit = False  # Display temperature in Fahrenheit
         self._display_on = False
         self._filter_alert = False
@@ -126,9 +126,9 @@ class AirConditioner(Device):
         self._supported_fan_speeds = cast(
             List[AirConditioner.FanSpeed], AirConditioner.FanSpeed.list())
         self._supports_custom_fan_speed = True
-        self._supports_eco_mode = True
-        self._supports_turbo_mode = True
-        self._supports_freeze_protection_mode = True
+        self._supports_eco = True
+        self._supports_turbo = True
+        self._supports_freeze_protection = True
         self._supports_display_control = True
         self._supports_filter_reminder = True
         self._supports_purifier = True
@@ -160,7 +160,7 @@ class AirConditioner(Device):
 
         self._breeze_mode = AirConditioner.BreezeMode.OFF
 
-        self._ieco_mode = False
+        self._ieco = False
 
     def _update_state(self, res: Response) -> None:
         """Update the local state from a device state response."""
@@ -188,10 +188,10 @@ class AirConditioner(Device):
                 AirConditioner.SwingMode,
                 AirConditioner.SwingMode.get_from_value(res.swing_mode))
 
-            self._eco_mode = res.eco_mode
-            self._turbo_mode = res.turbo_mode
-            self._freeze_protection_mode = res.freeze_protection_mode
-            self._sleep_mode = res.sleep_mode
+            self._eco = res.eco
+            self._turbo = res.turbo
+            self._freeze_protection = res.freeze_protection
+            self._sleep = res.sleep
 
             self._indoor_temperature = res.indoor_temperature
             self._outdoor_temperature = res.outdoor_temperature
@@ -239,7 +239,7 @@ class AirConditioner(Device):
                                          else AirConditioner.BreezeMode.OFF)
 
             if (value := res.get_property(PropertyId.IECO)) is not None:
-                self._ieco_mode = value
+                self._ieco = value
 
         elif isinstance(res, EnergyUsageResponse):
             self._total_energy_usage = res.total_energy
@@ -300,9 +300,9 @@ class AirConditioner(Device):
         self._supported_fan_speeds = fan_speeds
         self._supports_custom_fan_speed = res.fan_custom
 
-        self._supports_eco_mode = res.eco_mode
-        self._supports_turbo_mode = res.turbo_mode
-        self._supports_freeze_protection_mode = res.freeze_protection_mode
+        self._supports_eco = res.eco
+        self._supports_turbo = res.turbo
+        self._supports_freeze_protection = res.freeze_protection
 
         self._supports_display_control = res.display_control
         self._supports_filter_reminder = res.filter_reminder
@@ -360,7 +360,7 @@ class AirConditioner(Device):
             if res.breezeless:
                 self._supported_properties.add(PropertyId.BREEZELESS)
 
-        if res.ieco_mode:
+        if res.ieco:
             self._supported_properties.add(PropertyId.IECO)
 
     async def _send_command_get_responses(self, command) -> List[Response]:
@@ -512,13 +512,13 @@ class AirConditioner(Device):
             _LOGGER.warning(
                 "Device is not capable of swing mode %r.", self._swing_mode)
 
-        if self._turbo_mode and not self._supports_turbo_mode:
+        if self._turbo and not self._supports_turbo:
             _LOGGER.warning("Device is not capable of turbo mode.")
 
-        if self._eco_mode and not self._supports_eco_mode:
+        if self._eco and not self._supports_eco:
             _LOGGER.warning("Device is not capable of eco mode.")
 
-        if self._freeze_protection_mode and not self._supports_freeze_protection_mode:
+        if self._freeze_protection and not self._supports_freeze_protection:
             _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:
@@ -535,11 +535,11 @@ class AirConditioner(Device):
         cmd.operational_mode = self._operational_mode
         cmd.fan_speed = self._fan_speed
         cmd.swing_mode = self._swing_mode
-        cmd.eco_mode = or_default(self._eco_mode, False)
-        cmd.turbo_mode = or_default(self._turbo_mode, False)
-        cmd.freeze_protection_mode = or_default(
-            self._freeze_protection_mode, False)
-        cmd.sleep_mode = or_default(self._sleep_mode, False)
+        cmd.eco = or_default(self._eco, False)
+        cmd.turbo = or_default(self._turbo, False)
+        cmd.freeze_protection = or_default(
+            self._freeze_protection, False)
+        cmd.sleep = or_default(self._sleep, False)
         cmd.fahrenheit = or_default(self._fahrenheit_unit, False)
         cmd.follow_me = or_default(self._follow_me, False)
         cmd.purifier = or_default(self._purifier, False)
@@ -735,61 +735,61 @@ class AirConditioner(Device):
         self._updated_properties.add(PropertyId.SWING_UD_ANGLE)
 
     @property
-    def supports_eco_mode(self) -> bool:
-        return self._supports_eco_mode
+    def supports_eco(self) -> bool:
+        return self._supports_eco
 
     @property
-    def eco_mode(self) -> Optional[bool]:
-        return self._eco_mode
+    def eco(self) -> Optional[bool]:
+        return self._eco
 
-    @eco_mode.setter
-    def eco_mode(self, enabled: bool) -> None:
-        self._eco_mode = enabled
+    @eco.setter
+    def eco(self, enabled: bool) -> None:
+        self._eco = enabled
 
     @property
-    def supports_ieco_mode(self) -> bool:
+    def supports_ieco(self) -> bool:
         return PropertyId.IECO in self._supported_properties
 
     @property
-    def ieco_mode(self) -> Optional[bool]:
-        return self._ieco_mode
+    def ieco(self) -> Optional[bool]:
+        return self._ieco
 
-    @ieco_mode.setter
-    def ieco_mode(self, enabled: bool) -> None:
-        self._ieco_mode = enabled
+    @ieco.setter
+    def ieco(self, enabled: bool) -> None:
+        self._ieco = enabled
         self._updated_properties.add(PropertyId.IECO)
 
     @property
-    def supports_turbo_mode(self) -> bool:
-        return self._supports_turbo_mode
+    def supports_turbo(self) -> bool:
+        return self._supports_turbo
 
     @property
-    def turbo_mode(self) -> Optional[bool]:
-        return self._turbo_mode
+    def turbo(self) -> Optional[bool]:
+        return self._turbo
 
-    @turbo_mode.setter
-    def turbo_mode(self, enabled: bool) -> None:
-        self._turbo_mode = enabled
+    @turbo.setter
+    def turbo(self, enabled: bool) -> None:
+        self._turbo = enabled
 
     @property
-    def supports_freeze_protection_mode(self) -> bool:
-        return self._supports_freeze_protection_mode
+    def supports_freeze_protection(self) -> bool:
+        return self._supports_freeze_protection
 
     @property
-    def freeze_protection_mode(self) -> Optional[bool]:
-        return self._freeze_protection_mode
+    def freeze_protection(self) -> Optional[bool]:
+        return self._freeze_protection
 
-    @freeze_protection_mode.setter
-    def freeze_protection_mode(self, enabled: bool) -> None:
-        self._freeze_protection_mode = enabled
+    @freeze_protection.setter
+    def freeze_protection(self, enabled: bool) -> None:
+        self._freeze_protection = enabled
 
     @property
-    def sleep_mode(self) -> Optional[bool]:
-        return self._sleep_mode
+    def sleep(self) -> Optional[bool]:
+        return self._sleep
 
-    @sleep_mode.setter
-    def sleep_mode(self, enabled: bool) -> None:
-        self._sleep_mode = enabled
+    @sleep.setter
+    def sleep(self, enabled: bool) -> None:
+        self._sleep = enabled
 
     @property
     def follow_me(self) -> Optional[bool]:
@@ -901,10 +901,10 @@ class AirConditioner(Device):
             "outdoor_temperature": self.outdoor_temperature,
             "target_humidity": self.target_humidity,
             "indoor_humidity": self.indoor_humidity,
-            "eco": self.eco_mode,
-            "turbo": self.turbo_mode,
-            "freeze_protection": self.freeze_protection_mode,
-            "sleep": self.sleep_mode,
+            "eco": self.eco,
+            "turbo": self.turbo,
+            "freeze_protection": self.freeze_protection,
+            "sleep": self.sleep,
             "display_on": self.display_on,
             "beep": self.beep,
             "fahrenheit": self.fahrenheit,
@@ -917,3 +917,59 @@ class AirConditioner(Device):
             "real_time_power_usage": self.real_time_power_usage,
             "rate_select": self.rate_select,
         }}
+
+    # Deprecated methods and properties
+    @property
+    @deprecated("supports_eco")
+    def supports_eco_mode(self) -> bool:
+        return self.supports_eco
+
+    @property
+    @deprecated("eco")
+    def eco_mode(self) -> Optional[bool]:
+        return self.eco
+
+    @eco_mode.setter
+    @deprecated("eco")
+    def eco_mode(self, enabled: bool) -> None:
+        self.eco = enabled
+
+    @property
+    @deprecated("supports_freeze_protection")
+    def supports_freeze_protection_mode(self) -> bool:
+        return self.supports_freeze_protection
+
+    @property
+    @deprecated("freeze_protection")
+    def freeze_protection_mode(self) -> Optional[bool]:
+        return self.freeze_protection
+
+    @freeze_protection_mode.setter
+    @deprecated("freeze_protection")
+    def freeze_protection_mode(self, enabled: bool) -> None:
+        self.freeze_protection = enabled
+
+    @property
+    @deprecated("sleep")
+    def sleep_mode(self) -> Optional[bool]:
+        return self.sleep
+
+    @sleep_mode.setter
+    @deprecated("sleep")
+    def sleep_mode(self, enabled: bool) -> None:
+        self.sleep = enabled
+
+    @property
+    @deprecated("supports_turbo")
+    def supports_turbo_mode(self) -> bool:
+        return self.supports_turbo
+
+    @property
+    @deprecated("turbo")
+    def turbo_mode(self) -> Optional[bool]:
+        return self.turbo
+
+    @turbo_mode.setter
+    @deprecated("turbo")
+    def turbo_mode(self, enabled: bool) -> None:
+        self.turbo = enabled
diff --git a/msmart/utils.py b/msmart/utils.py
index 7b2f82d..5a3b835 100644
--- a/msmart/utils.py
+++ b/msmart/utils.py
@@ -1,9 +1,10 @@
 """Utility classes and methods for Midea AC."""
 from __future__ import annotations
 
+import functools
 import logging
 from enum import IntEnum
-from typing import List, Optional, cast
+from typing import Any, Callable, List, Optional, cast
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -34,3 +35,29 @@ class MideaIntEnum(IntEnum):
             if default is None:
                 default = cls.DEFAULT  # pyright: ignore[reportAttributeAccessIssue] # nopep8
             return cls(default)
+
+
+def deprecated(replacement: str) -> Callable[[Callable], Callable]:
+    """Mark function as deprecated and recommend a replacement."""
+
+    def deprecated_decorator(func: Callable) -> Callable:
+        """Decorate function as deprecated."""
+
+        @functools.wraps(func)
+        def deprecated_func(*args, **kwargs) -> Any:
+            """Wrap for the original function."""
+
+            # Check if already warned
+            if not getattr(func, "_warn_deprecate", False):
+                logger = logging.getLogger(func.__module__)
+                logger.warning("'%s' is deprecated. Please use '%s' instead.",
+                               func.__name__,
+                               replacement,
+                               )
+                setattr(func, "_warn_deprecate", True)
+
+            return func(*args, **kwargs)
+
+        return deprecated_func
+
+    return deprecated_decorator
diff --git a/pyproject.toml b/pyproject.toml
index 640b6f2..7a0ee6c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,8 +23,8 @@ classifiers = [
   "Topic :: Home Automation"
 ]
 dependencies = [
-  "pycryptodome",
-  "httpx"
+  "httpx",
+  "pycryptodome"
 ]
 dynamic = ["version"]
 


Test patch:
diff --git a/msmart/device/AC/test_command.py b/msmart/device/AC/test_command.py
index 4acfca5..4786141 100644
--- a/msmart/device/AC/test_command.py
+++ b/msmart/device/AC/test_command.py
@@ -67,10 +67,10 @@ class TestStateResponse(_TestResponseBase):
 
     # Attributes expected in state response objects
     EXPECTED_ATTRS = ["power_on", "target_temperature", "operational_mode",
-                      "fan_speed", "swing_mode", "turbo_mode", "eco_mode",
-                      "sleep_mode", "fahrenheit", "indoor_temperature",
+                      "fan_speed", "swing_mode", "turbo", "eco",
+                      "sleep", "fahrenheit", "indoor_temperature",
                       "outdoor_temperature", "filter_alert", "display_on",
-                      "freeze_protection_mode"]
+                      "freeze_protection"]
 
     def _test_response(self, msg) -> StateResponse:
         resp = self._test_build_response(msg)
@@ -234,7 +234,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
     EXPECTED_PROPERTIES = ["swing_horizontal", "swing_vertical", "swing_both",
                            "fan_silent", "fan_low", "fan_medium", "fan_high", "fan_auto", "fan_custom",
                            "dry_mode", "cool_mode", "heat_mode", "auto_mode",
-                           "eco_mode", "turbo_mode", "freeze_protection_mode",
+                           "eco", "turbo", "freeze_protection",
                            "min_temperature", "max_temperature",
                            "display_control", "filter_reminder", "rate_select_levels"]
 
@@ -268,16 +268,15 @@ class TestCapabilitiesResponse(_TestResponseBase):
         self.assertEqual(_build_capability_response(
             CapabilityId.BREEZELESS, 100)._capabilities["breezeless"], False)
 
-        # Test PRESET_ECO capability which uses 2 get_value parsers.
-        # e.g. eco_mode -> X == 1, eco_mode2 -> X == 2
+        # Test PRESET_ECO capability which uses an array parser
         resp = _build_capability_response(CapabilityId.PRESET_ECO, 0)
-        self.assertEqual(resp._capabilities["eco_mode"], False)
+        self.assertEqual(resp._capabilities["eco"], False)
 
         resp = _build_capability_response(CapabilityId.PRESET_ECO, 1)
-        self.assertEqual(resp._capabilities["eco_mode"], True)
+        self.assertEqual(resp._capabilities["eco"], True)
 
         resp = _build_capability_response(CapabilityId.PRESET_ECO, 2)
-        self.assertEqual(resp._capabilities["eco_mode"], True)
+        self.assertEqual(resp._capabilities["eco"], True)
 
         # Test PRESET_TURBO capability which uses 2 custom parsers.
         # e.g. turbo_heat -> X == 1 or X == 3, turbo_cool -> X < 2
@@ -308,7 +307,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         resp = cast(CapabilitiesResponse, resp)
 
         EXPECTED_RAW_CAPABILITIES = {
-            "eco_mode": True,
+            "eco": True,
             "freeze_protection": True, "heat_mode": True,
             "cool_mode": True, "dry_mode": True,
             "auto_mode": True,
@@ -323,7 +322,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         EXPECTED_CAPABILITIES = {
             "swing_horizontal": True, "swing_vertical": True, "swing_both": True,
             "dry_mode": True, "heat_mode": True, "cool_mode": True, "auto_mode": True,
-            "eco_mode": True, "turbo_mode": True, "freeze_protection_mode": True,
+            "eco": True, "turbo": True, "freeze_protection": True,
             "fan_custom": False, "fan_silent": False, "fan_low": True,
             "fan_medium": True,  "fan_high": True, "fan_auto": True,
             "min_temperature": 16, "max_temperature": 30,
@@ -354,7 +353,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
                              "Ignored unknown capability ID: 0x0040")
 
         EXPECTED_RAW_CAPABILITIES = {
-            "eco_mode": True, "breezeless": False,
+            "eco": True, "breezeless": False,
             "heat_mode": True, "cool_mode": True, "dry_mode": True,
             "auto_mode": True, "swing_horizontal": True, "swing_vertical": True,
             "energy_stats": False, "energy_setting": False, "energy_bcd": False,
@@ -373,7 +372,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         EXPECTED_CAPABILITIES = {
             "swing_horizontal": True, "swing_vertical": True, "swing_both": True,
             "dry_mode": True, "heat_mode": True, "cool_mode": True, "auto_mode": True,
-            "eco_mode": True, "turbo_mode": True, "freeze_protection_mode": False,
+            "eco": True, "turbo": True, "freeze_protection": False,
             "fan_custom": True, "fan_silent": True, "fan_low": True,
             "fan_medium": True,  "fan_high": True, "fan_auto": True,
             "min_temperature": 16, "max_temperature": 30,
@@ -397,7 +396,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         resp = cast(CapabilitiesResponse, resp)
 
         EXPECTED_RAW_CAPABILITIES = {
-            "eco_mode": True, "heat_mode": False,
+            "eco": True, "heat_mode": False,
             "cool_mode": True, "dry_mode": True, "auto_mode": True,
             "swing_horizontal": False, "swing_vertical": False,
             "filter_notice": True, "filter_clean": False, "turbo_heat": False,
@@ -412,7 +411,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         EXPECTED_CAPABILITIES = {
             "swing_horizontal": False, "swing_vertical": False, "swing_both": False,
             "dry_mode": True, "heat_mode": False, "cool_mode": True, "auto_mode": True,
-            "eco_mode": True, "turbo_mode": False, "freeze_protection_mode": False,
+            "eco": True, "turbo": False, "freeze_protection": False,
             "fan_custom": False, "fan_silent": False, "fan_low": True,
             "fan_medium": True,  "fan_high": True, "fan_auto": True,
             "min_temperature": 16, "max_temperature": 30,
@@ -436,7 +435,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         resp = cast(CapabilitiesResponse, resp)
 
         EXPECTED_RAW_CAPABILITIES = {
-            "eco_mode": True, "freeze_protection": False,
+            "eco": True, "freeze_protection": False,
             "heat_mode": False, "cool_mode": True, "dry_mode": True, "auto_mode": True,
             "swing_horizontal": False, "swing_vertical": True, "filter_notice": True,
             "filter_clean": False, "turbo_heat": False, "turbo_cool": True,
@@ -453,7 +452,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         EXPECTED_CAPABILITIES = {
             "swing_horizontal": False, "swing_vertical": True, "swing_both": False,
             "dry_mode": True, "heat_mode": False, "cool_mode": True, "auto_mode": True,
-            "eco_mode": True, "turbo_mode": True, "freeze_protection_mode": False,
+            "eco": True, "turbo": True, "freeze_protection": False,
             "fan_custom": True, "fan_silent": True, "fan_low": True,
             "fan_medium": True,  "fan_high": True, "fan_auto": True,
             "min_temperature": 16, "max_temperature": 30,
@@ -485,7 +484,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
                              "Ignored unknown capability ID: 0x0040")
 
         EXPECTED_RAW_CAPABILITIES = {
-            "eco_mode": True,
+            "eco": True,
             "breeze_control": True,
             "heat_mode": True, "cool_mode": True, "dry_mode": True, "auto_mode": True,
             "swing_horizontal": True, "swing_vertical": True,
@@ -529,7 +528,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         resp.merge(additional_resp)
 
         EXPECTED_MERGED_RAW_CAPABILITIES = {
-            "eco_mode": True,
+            "eco": True,
             "breeze_control": True,
             "heat_mode": True, "cool_mode": True, "dry_mode": True, "auto_mode": True,
             "swing_horizontal": True, "swing_vertical": True,
@@ -553,7 +552,7 @@ class TestCapabilitiesResponse(_TestResponseBase):
         EXPECTED_CAPABILITIES = {
             "swing_horizontal": True, "swing_vertical": True, "swing_both": True,
             "dry_mode": True, "heat_mode": True, "cool_mode": True, "auto_mode": True,
-            "eco_mode": True, "turbo_mode": True, "freeze_protection_mode": True,
+            "eco": True, "turbo": True, "freeze_protection": True,
             "fan_custom": True, "fan_silent": True, "fan_low": True,
             "fan_medium": True,  "fan_high": True, "fan_auto": True,
             "min_temperature": 16, "max_temperature": 30,
diff --git a/msmart/device/AC/test_device.py b/msmart/device/AC/test_device.py
index 2580a6b..7bf576e 100644
--- a/msmart/device/AC/test_device.py
+++ b/msmart/device/AC/test_device.py
@@ -129,10 +129,10 @@ class TestUpdateStateFromResponse(unittest.TestCase):
         self.assertEqual(device.indoor_temperature, 21.0)
         self.assertEqual(device.outdoor_temperature, 28.5)
 
-        self.assertEqual(device.eco_mode, True)
-        self.assertEqual(device.turbo_mode, False)
-        self.assertEqual(device.freeze_protection_mode, False)
-        self.assertEqual(device.sleep_mode, False)
+        self.assertEqual(device.eco, True)
+        self.assertEqual(device.turbo, False)
+        self.assertEqual(device.freeze_protection, False)
+        self.assertEqual(device.sleep, False)
 
         self.assertEqual(device.operational_mode, AC.OperationalMode.COOL)
         self.assertEqual(device.fan_speed, AC.FanSpeed.AUTO)
@@ -370,9 +370,9 @@ class TestCapabilities(unittest.TestCase):
         self.assertEqual(device.supports_purifier, True)
         self.assertEqual(device.supports_self_clean, True)
 
-        self.assertEqual(device.supports_eco_mode, True)
-        self.assertEqual(device.supports_freeze_protection_mode, True)
-        self.assertEqual(device.supports_turbo_mode, True)
+        self.assertEqual(device.supports_eco, True)
+        self.assertEqual(device.supports_freeze_protection, True)
+        self.assertEqual(device.supports_turbo, True)
 
     def test_rate_select(self) -> None:
         """Test rate select device capability."""