diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py index 014d7650..68ebb37d 100644 --- a/custom_components/midea_ac_lan/__init__.py +++ b/custom_components/midea_ac_lan/__init__.py @@ -13,6 +13,7 @@ CONF_TYPE, ) from homeassistant.core import HomeAssistant +from midealocal.devices import device_selector from .const import ( ALL_PLATFORM, @@ -25,7 +26,6 @@ DOMAIN, EXTRA_SWITCH, ) -from .midea.devices import async_device_selector from .midea_devices import MIDEA_DEVICES _LOGGER = logging.getLogger(__name__) @@ -153,19 +153,19 @@ async def async_setup_entry(hass: HomeAssistant, config_entry): if protocol == 3 and (key is None or key is None): _LOGGER.error("For V3 devices, the key and the token is required.") return False - device = await async_device_selector( - hass=hass, - name=name, - device_id=device_id, - device_type=device_type, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - customize=customize, + device = await hass.async_add_import_executor_job( + device_selector, + name, + device_id, + device_type, + ip_address, + port, + token, + key, + protocol, + model, + subtype, + customize, ) if refresh_interval is not None: device.set_refresh_interval(refresh_interval) diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py index 1e0df6bc..1a89c666 100644 --- a/custom_components/midea_ac_lan/climate.py +++ b/custom_components/midea_ac_lan/climate.py @@ -32,13 +32,13 @@ Platform, UnitOfTemperature, ) +from midealocal.devices.ac import DeviceAttributes as ACAttributes +from midealocal.devices.c3 import DeviceAttributes as C3Attributes +from midealocal.devices.cc import DeviceAttributes as CCAttributes +from midealocal.devices.cf import DeviceAttributes as CFAttributes +from midealocal.devices.fb import DeviceAttributes as FBAttributes from .const import DEVICES, DOMAIN -from .midea.devices.ac.device import DeviceAttributes as ACAttributes -from .midea.devices.c3.device import DeviceAttributes as C3Attributes -from .midea.devices.cc.device import DeviceAttributes as CCAttributes -from .midea.devices.cf.device import DeviceAttributes as CFAttributes -from .midea.devices.fb.device import DeviceAttributes as FBAttributes from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py index 6c5b1ecc..7c10c84b 100644 --- a/custom_components/midea_ac_lan/config_flow.py +++ b/custom_components/midea_ac_lan/config_flow.py @@ -28,6 +28,9 @@ from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.util.json import load_json +from midealocal.cloud import get_midea_cloud +from midealocal.device import MideaDevice +from midealocal.discover import discover from .const import ( CONF_ACCOUNT, @@ -40,9 +43,6 @@ EXTRA_CONTROL, EXTRA_SENSOR, ) -from .midea.core.cloud import get_midea_cloud -from .midea.core.device import MiedaDevice -from .midea.core.discover import discover from .midea_devices import MIDEA_DEVICES _LOGGER = logging.getLogger(__name__) @@ -308,7 +308,7 @@ async def async_step_auto(self, user_input=None, error=None): return await self.async_step_auto(error="preset_account") keys = await self.cloud.get_keys(user_input[CONF_DEVICE]) for method, key in keys.items(): - dm = MiedaDevice( + dm = MideaDevice( name="", device_id=device_id, device_type=device.get(CONF_TYPE), @@ -363,7 +363,7 @@ async def async_step_manually(self, user_input=None, error=None): len(user_input[CONF_TOKEN]) == 0 or len(user_input[CONF_KEY]) == 0 ): return await self.async_step_manually(error="invalid_token") - dm = MiedaDevice( + dm = MideaDevice( name="", device_id=user_input[CONF_DEVICE_ID], device_type=user_input[CONF_TYPE], diff --git a/custom_components/midea_ac_lan/fan.py b/custom_components/midea_ac_lan/fan.py index 7ddd8614..a6f9657f 100644 --- a/custom_components/midea_ac_lan/fan.py +++ b/custom_components/midea_ac_lan/fan.py @@ -9,11 +9,11 @@ STATE_ON, Platform, ) +from midealocal.devices.ac import DeviceAttributes as ACAttributes +from midealocal.devices.ce import DeviceAttributes as CEAttributes +from midealocal.devices.x40 import DeviceAttributes as X40Attributes from .const import DEVICES, DOMAIN -from .midea.devices.ac.device import DeviceAttributes as ACAttributes -from .midea.devices.ce.device import DeviceAttributes as CEAttributes -from .midea.devices.x40.device import DeviceAttributes as X40Attributes from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity diff --git a/custom_components/midea_ac_lan/light.py b/custom_components/midea_ac_lan/light.py index 91747b87..b8ab5020 100644 --- a/custom_components/midea_ac_lan/light.py +++ b/custom_components/midea_ac_lan/light.py @@ -13,9 +13,9 @@ LightEntityFeature, ) from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform +from midealocal.devices.x13 import DeviceAttributes as X13Attributes from .const import DEVICES, DOMAIN -from .midea.devices.x13.device import DeviceAttributes as X13Attributes from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity diff --git a/custom_components/midea_ac_lan/manifest.json b/custom_components/midea_ac_lan/manifest.json index 88ab0214..87060d8f 100644 --- a/custom_components/midea_ac_lan/manifest.json +++ b/custom_components/midea_ac_lan/manifest.json @@ -2,7 +2,8 @@ "domain": "midea_ac_lan", "name": "Midea AC LAN", "codeowners": [ - "@wuwentao" + "@wuwentao", + "@rokam" ], "config_flow": true, "dependencies": [], @@ -11,7 +12,8 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/wuwentao/midea_ac_lan/issues", "requirements": [ - "pycryptodome" + "pycryptodome", + "midea-local==1.0.3" ], - "version": "v0.3.23" -} + "version": "v0.4.0-alpha" +} \ No newline at end of file diff --git a/custom_components/midea_ac_lan/midea/backports/myenum.py b/custom_components/midea_ac_lan/midea/backports/myenum.py deleted file mode 100644 index c4de8762..00000000 --- a/custom_components/midea_ac_lan/midea/backports/myenum.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Enum backports from standard lib.""" - -from __future__ import annotations - -from enum import Enum -from typing import Any, TypeVar - -_StrEnumSelfT = TypeVar("_StrEnumSelfT", bound="StrEnum") - - -class StrEnum(str, Enum): - """Partial backport of Python 3.11's StrEnum for our basic use cases.""" - - def __new__( - cls: type[_StrEnumSelfT], value: str, *args: Any, **kwargs: Any - ) -> _StrEnumSelfT: - """Create a new StrEnum instance.""" - if not isinstance(value, str): - raise TypeError(f"{value!r} is not a string") - return super().__new__(cls, value, *args, **kwargs) - - def __str__(self) -> str: - """Return self.value.""" - return str(self.value) - - @staticmethod - def _generate_next_value_( - name: str, start: int, count: int, last_values: list[Any] - ) -> Any: - """ - Make `auto()` explicitly unsupported. - We may revisit this when it's very clear that Python 3.11's - `StrEnum.auto()` behavior will no longer change. - """ - raise TypeError("auto() is not supported by this implementation") diff --git a/custom_components/midea_ac_lan/midea/core/cloud.py b/custom_components/midea_ac_lan/midea/core/cloud.py deleted file mode 100644 index 2ced6fc3..00000000 --- a/custom_components/midea_ac_lan/midea/core/cloud.py +++ /dev/null @@ -1,667 +0,0 @@ -import base64 -import datetime -import json -import logging -import time -from secrets import token_hex -from threading import Lock - -from aiohttp import ClientSession - -from .security import ( - CloudSecurity, - MeijuCloudSecurity, - MideaAirSecurity, - MSmartCloudSecurity, -) - -_LOGGER = logging.getLogger(__name__) - -clouds = { - "美的美居": { - "class_name": "MeijuCloud", - "app_id": "900", - "app_key": "46579c15", - "login_key": "ad0ee21d48a64bf49f4fb583ab76e799", - "iot_key": bytes.fromhex( - format(9795516279659324117647275084689641883661667, "x") - ).decode(), - "hmac_key": bytes.fromhex( - format(117390035944627627450677220413733956185864939010425, "x") - ).decode(), - "api_url": "https://mp-prod.smartmidea.net/mas/v5/app/proxy?alias=", - }, - "MSmartHome": { - "class_name": "MSmartHomeCloud", - "app_id": "1010", - "app_key": "ac21b9f9cbfe4ca5a88562ef25e2b768", - "iot_key": bytes.fromhex(format(7882822598523843940, "x")).decode(), - "hmac_key": bytes.fromhex( - format(117390035944627627450677220413733956185864939010425, "x") - ).decode(), - "api_url": "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=", - }, - "Midea Air": { - "class_name": "MideaAirCloud", - "app_id": "1117", - "app_key": "ff0cf6f5f0c3471de36341cab3f7a9af", - "api_url": "https://mapp.appsmb.com", - }, - "NetHome Plus": { - "class_name": "MideaAirCloud", - "app_id": "1017", - "app_key": "3742e9e5842d4ad59c2db887e12449f9", - "api_url": "https://mapp.appsmb.com", - }, - "Ariston Clima": { - "class_name": "MideaAirCloud", - "app_id": "1005", - "app_key": "434a209a5ce141c3b726de067835d7f0", - "api_url": "https://mapp.appsmb.com", - }, -} - -default_keys = { - 99: { - "token": "ee755a84a115703768bcc7c6c13d3d629aa416f1e2fd798beb9f78cbb1381d09" - "1cc245d7b063aad2a900e5b498fbd936c811f5d504b2e656d4f33b3bbc6d1da3", - "key": "ed37bd31558a4b039aaf4e7a7a59aa7a75fd9101682045f69baf45d28380ae5c", - } -} - - -class MideaCloud: - def __init__( - self, - session: ClientSession, - security: CloudSecurity, - app_id: str, - app_key: str, - account: str, - password: str, - api_url: str, - ): - self._device_id = CloudSecurity.get_deviceid(account) - self._session = session - self._security = security - self._api_lock = Lock() - self._app_id = app_id - self._app_key = app_key - self._account = account - self._password = password - self._api_url = api_url - self._access_token = None - self._uid = None - self._login_id = None - - def _make_general_data(self): - return {} - - async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: - header = header or {} - if not data.get("reqId"): - data.update({"reqId": token_hex(16)}) - if not data.get("stamp"): - data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) - random = str(int(time.time())) - url = self._api_url + endpoint - dump_data = json.dumps(data) - sign = self._security.sign("", dump_data, random) - header.update( - { - "content-type": "application/json; charset=utf-8", - "secretVersion": "1", - "sign": sign, - "random": random, - } - ) - if self._uid is not None: - header.update({"uid": self._uid}) - if self._access_token is not None: - header.update({"accessToken": self._access_token}) - response: dict = {"code": -1} - for i in range(0, 3): - try: - with self._api_lock: - r = await self._session.request( - "POST", url, headers=header, data=dump_data, timeout=10 - ) - raw = await r.read() - _LOGGER.debug( - f"Midea cloud API url: {url}, data: {data}, response: {raw}" - ) - response = json.loads(raw) - break - except Exception as e: - _LOGGER.warning(f"Midea cloud API error, url: {url}, error: {repr(e)}") - if int(response["code"]) == 0 and "data" in response: - return response["data"] - return None - - async def _get_login_id(self) -> str | None: - data = self._make_general_data() - data.update({"loginAccount": f"{self._account}"}) - if response := await self._api_request( - endpoint="/v1/user/login/id/get", data=data - ): - return response.get("loginId") - return None - - async def login(self) -> bool: - raise NotImplementedError() - - async def get_keys(self, appliance_id: int): - result = {} - for method in [1, 2]: - udp_id = self._security.get_udp_id(appliance_id, method) - data = self._make_general_data() - data.update({"udpid": udp_id}) - response = await self._api_request( - endpoint="/v1/iot/secure/getToken", data=data - ) - if response and "tokenlist" in response: - for token in response["tokenlist"]: - if token["udpId"] == udp_id: - result[method] = { - "token": token["token"].lower(), - "key": token["key"].lower(), - } - result.update(default_keys) - return result - - async def list_home(self) -> dict | None: - return {1: "My home"} - - async def list_appliances(self, home_id) -> dict | None: - raise NotImplementedError() - - async def get_device_info(self, device_id: int): - if response := await self.list_appliances(home_id=None): - if device_id in response.keys(): - return response[device_id] - return None - - async def download_lua( - self, - path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", - ): - raise NotImplementedError() - - -class MeijuCloud(MideaCloud): - def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, - ): - super().__init__( - session=session, - security=MeijuCloudSecurity( - login_key=clouds[cloud_name]["login_key"], - iot_key=clouds[cloud_name]["iot_key"], - hmac_key=clouds[cloud_name]["hmac_key"], - ), - app_id=clouds[cloud_name]["app_id"], - app_key=clouds[cloud_name]["app_key"], - account=account, - password=password, - api_url=clouds[cloud_name]["api_url"], - ) - - async def login(self) -> bool: - if login_id := await self._get_login_id(): - self._login_id = login_id - stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - data = { - "iotData": { - "clientType": 1, - "deviceId": self._device_id, - "iampwd": self._security.encrypt_iam_password( - self._login_id, self._password - ), - "iotAppId": self._app_id, - "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), - "reqId": token_hex(16), - "stamp": stamp, - }, - "data": { - "appKey": self._app_key, - "deviceId": self._device_id, - "platform": 2, - }, - "timestamp": stamp, - "stamp": stamp, - } - if response := await self._api_request( - endpoint="/mj/user/login", data=data - ): - self._access_token = response["mdata"]["accessToken"] - self._security.set_aes_keys( - self._security.aes_decrypt_with_fixed_key(response["key"]), None - ) - - return True - return False - - async def list_home(self): - if response := await self._api_request( - endpoint="/v1/homegroup/list/get", data={} - ): - homes = {} - for home in response["homeList"]: - homes.update({int(home["homegroupId"]): home["name"]}) - return homes - return None - - async def list_appliances(self, home_id) -> dict | None: - data = {"homegroupId": home_id} - if response := await self._api_request( - endpoint="/v1/appliance/home/list/get", data=data - ): - appliances = {} - for home in response.get("homeList") or []: - for room in home.get("roomList") or []: - for appliance in room.get("applianceList"): - try: - model_number = int(appliance.get("modelNumber", 0)) - except (ValueError, TypeError): - model_number = 0 - device_info = { - "name": appliance.get("name"), - "type": int(appliance.get("type"), 16), - "sn": ( - self._security.aes_decrypt(appliance.get("sn")) - if appliance.get("sn") - else "" - ), - "sn8": appliance.get("sn8", "00000000"), - "model_number": model_number, - "manufacturer_code": appliance.get( - "enterpriseCode", "0000" - ), - "model": appliance.get("productModel"), - "online": appliance.get("onlineStatus") == "1", - } - if ( - device_info.get("sn8") is None - or len(device_info.get("sn8")) == 0 - ): - device_info["sn8"] = "00000000" - if ( - device_info.get("model") is None - or len(device_info.get("model")) == 0 - ): - device_info["model"] = device_info["sn8"] - appliances[int(appliance["applianceCode"])] = device_info - return appliances - return None - - async def get_device_info(self, device_id: int): - data = {"applianceCode": device_id} - if response := await self._api_request( - endpoint="/v1/appliance/info/get", data=data - ): - try: - model_number = int(response.get("modelNumber", 0)) - except ValueError: - model_number = 0 - device_info = { - "name": response.get("name"), - "type": int(response.get("type"), 16), - "sn": ( - self._security.aes_decrypt(response.get("sn")) - if response.get("sn") - else "" - ), - "sn8": response.get("sn8", "00000000"), - "model_number": model_number, - "manufacturer_code": response.get("enterpriseCode", "0000"), - "model": response.get("productModel"), - "online": response.get("onlineStatus") == "1", - } - if device_info.get("sn8") is None or len(device_info.get("sn8")) == 0: - device_info["sn8"] = "00000000" - if device_info.get("model") is None or len(device_info.get("model")) == 0: - device_info["model"] = device_info["sn8"] - return device_info - return None - - async def download_lua( - self, - path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", - ): - data = { - "applianceSn": sn, - "applianceType": "0x%02X" % device_type, - "applianceMFCode": manufacturer_code, - "version": "0", - "iotAppId": self._app_id, - } - fnm = None - if response := await self._api_request( - endpoint="/v1/appliance/protocol/lua/luaGet", data=data - ): - res = await self._session.get(response["url"]) - if res.status == 200: - lua = await res.text() - if lua: - stream = ( - 'local bit = require "bit"\n' - + self._security.aes_decrypt_with_fixed_key(lua) - ) - stream = stream.replace("\r\n", "\n") - fnm = f"{path}/{response['fileName']}" - with open(fnm, "w") as fp: - fp.write(stream) - return fnm - - -class MSmartHomeCloud(MideaCloud): - def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, - ): - super().__init__( - session=session, - security=MSmartCloudSecurity( - login_key=clouds[cloud_name]["app_key"], - iot_key=clouds[cloud_name]["iot_key"], - hmac_key=clouds[cloud_name]["hmac_key"], - ), - app_id=clouds[cloud_name]["app_id"], - app_key=clouds[cloud_name]["app_key"], - account=account, - password=password, - api_url=clouds[cloud_name]["api_url"], - ) - self._auth_base = base64.b64encode( - f"{self._app_key}:{clouds['MSmartHome']['iot_key']}".encode("ascii") - ).decode("ascii") - - def _make_general_data(self): - return { - # "appVersion": self.APP_VERSION, - "src": self._app_id, - "format": "2", - "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"), - "platformId": "1", - "deviceId": self._device_id, - "reqId": token_hex(16), - "uid": self._uid, - "clientType": "1", - "appId": self._app_id, - } - - async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: - header = header or {} - header.update( - {"x-recipe-app": self._app_id, "authorization": f"Basic {self._auth_base}"} - ) - - return await super()._api_request(endpoint, data, header) - - async def _re_route(self): - data = self._make_general_data() - data.update({"userType": "0", "userName": f"{self._account}"}) - if response := await self._api_request( - endpoint="/v1/multicloud/platform/user/route", data=data - ): - if api_url := response.get("masUrl"): - self._api_url = api_url - - async def login(self) -> bool: - await self._re_route() - if login_id := await self._get_login_id(): - self._login_id = login_id - iot_data = self._make_general_data() - iot_data.pop("uid") - stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - iot_data.update( - { - "iampwd": self._security.encrypt_iam_password( - self._login_id, self._password - ), - "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), - "stamp": stamp, - } - ) - data = { - "iotData": iot_data, - "data": { - "appKey": self._app_key, - "deviceId": self._device_id, - "platform": "2", - }, - "stamp": stamp, - } - if response := await self._api_request( - endpoint="/mj/user/login", data=data - ): - self._uid = response["uid"] - self._access_token = response["mdata"]["accessToken"] - self._security.set_aes_keys( - response["accessToken"], response["randomData"] - ) - return True - return False - - async def list_appliances(self, home_id) -> dict | None: - data = self._make_general_data() - if response := await self._api_request( - endpoint="/v1/appliance/user/list/get", data=data - ): - appliances = {} - for appliance in response["list"]: - try: - model_number = int(appliance.get("modelNumber", 0)) - except ValueError: - model_number = 0 - device_info = { - "name": appliance.get("name"), - "type": int(appliance.get("type"), 16), - "sn": ( - self._security.aes_decrypt(appliance.get("sn")) - if appliance.get("sn") - else "" - ), - "sn8": "", - "model_number": model_number, - "manufacturer_code": appliance.get("enterpriseCode", "0000"), - "model": "", - "online": appliance.get("onlineStatus") == "1", - } - device_info["sn8"] = ( - device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" - ) - device_info["model"] = device_info.get("sn8") - appliances[int(appliance["id"])] = device_info - return appliances - return None - - async def download_lua( - self, - path: str, - device_type: int, - sn: str, - model_number: str | None, - manufacturer_code: str = "0000", - ): - data = { - "clientType": "1", - "appId": self._app_id, - "format": "2", - "deviceId": self._device_id, - "iotAppId": self._app_id, - "applianceMFCode": manufacturer_code, - "applianceType": "0x%02X" % device_type, - "modelNumber": model_number, - "applianceSn": self._security.aes_encrypt_with_fixed_key( - sn.encode("ascii") - ).hex(), - "version": "0", - "encryptedType ": "2", - } - fnm = None - if response := await self._api_request( - endpoint="/v2/luaEncryption/luaGet", data=data - ): - res = await self._session.get(response["url"]) - if res.status == 200: - lua = await res.text() - if lua: - stream = ( - 'local bit = require "bit"\n' - + self._security.aes_decrypt_with_fixed_key(lua) - ) - stream = stream.replace("\r\n", "\n") - fnm = f"{path}/{response['fileName']}" - with open(fnm, "w") as fp: - fp.write(stream) - return fnm - - -class MideaAirCloud(MideaCloud): - def __init__( - self, - cloud_name: str, - session: ClientSession, - account: str, - password: str, - ): - super().__init__( - session=session, - security=MideaAirSecurity(login_key=clouds[cloud_name]["app_key"]), - app_id=clouds[cloud_name]["app_id"], - app_key=clouds[cloud_name]["app_key"], - account=account, - password=password, - api_url=clouds[cloud_name]["api_url"], - ) - self._session_id = None - - def _make_general_data(self): - data = { - "src": self._app_id, - "format": "2", - "stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"), - "deviceId": self._device_id, - "reqId": token_hex(16), - "clientType": "1", - "appId": self._app_id, - } - if self._session_id is not None: - data.update({"sessionId": self._session_id}) - return data - - async def _api_request(self, endpoint: str, data: dict, header=None) -> dict | None: - header = header or {} - if not data.get("reqId"): - data.update({"reqId": token_hex(16)}) - if not data.get("stamp"): - data.update({"stamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S")}) - url = self._api_url + endpoint - - sign = self._security.sign(url, data, "") - data.update({"sign": sign}) - if self._uid is not None: - header.update({"uid": self._uid}) - if self._access_token is not None: - header.update({"accessToken": self._access_token}) - response: dict = {"code": -1} - for i in range(0, 3): - try: - with self._api_lock: - r = await self._session.request( - "POST", url, headers=header, data=data, timeout=10 - ) - raw = await r.read() - _LOGGER.debug( - f"Midea cloud API url: {url}, data: {data}, response: {raw}" - ) - response = json.loads(raw) - break - except Exception as e: - _LOGGER.warning(f"Midea cloud API error, url: {url}, error: {repr(e)}") - if int(response["errorCode"]) == 0 and "result" in response: - return response["result"] - return None - - async def login(self) -> bool: - if login_id := await self._get_login_id(): - self._login_id = login_id - data = self._make_general_data() - data.update( - { - "loginAccount": self._account, - "password": self._security.encrypt_password( - self._login_id, self._password - ), - } - ) - if response := await self._api_request( - endpoint="/v1/user/login", data=data - ): - self._access_token = response["accessToken"] - self._uid = response["userId"] - self._session_id = response["sessionId"] - return True - return False - - async def list_appliances(self, home_id) -> dict | None: - data = self._make_general_data() - if response := await self._api_request( - endpoint="/v1/appliance/user/list/get", data=data - ): - appliances = {} - for appliance in response["list"]: - try: - model_number = int(appliance.get("modelNumber", 0)) - except ValueError: - model_number = 0 - device_info = { - "name": appliance.get("name"), - "type": int(appliance.get("type"), 16), - "sn": appliance.get("sn"), - "sn8": "", - "model_number": model_number, - "manufacturer_code": appliance.get("enterpriseCode", "0000"), - "model": "", - "online": appliance.get("onlineStatus") == "1", - } - device_info["sn8"] = ( - device_info.get("sn")[9:17] if len(device_info["sn"]) > 17 else "" - ) - device_info["model"] = device_info.get("sn8") - appliances[int(appliance["id"])] = device_info - return appliances - return None - - -def get_midea_cloud( - cloud_name: str, session: ClientSession, account: str, password: str -) -> MideaCloud | None: - cloud = None - if cloud_name in clouds.keys(): - cloud = globals()[clouds[cloud_name]["class_name"]]( - cloud_name=cloud_name, session=session, account=account, password=password - ) - return cloud diff --git a/custom_components/midea_ac_lan/midea/core/crc8.py b/custom_components/midea_ac_lan/midea/core/crc8.py deleted file mode 100644 index 8ce11bcf..00000000 --- a/custom_components/midea_ac_lan/midea/core/crc8.py +++ /dev/null @@ -1,270 +0,0 @@ -crc8_854_table = [ - 0x00, - 0x5E, - 0xBC, - 0xE2, - 0x61, - 0x3F, - 0xDD, - 0x83, - 0xC2, - 0x9C, - 0x7E, - 0x20, - 0xA3, - 0xFD, - 0x1F, - 0x41, - 0x9D, - 0xC3, - 0x21, - 0x7F, - 0xFC, - 0xA2, - 0x40, - 0x1E, - 0x5F, - 0x01, - 0xE3, - 0xBD, - 0x3E, - 0x60, - 0x82, - 0xDC, - 0x23, - 0x7D, - 0x9F, - 0xC1, - 0x42, - 0x1C, - 0xFE, - 0xA0, - 0xE1, - 0xBF, - 0x5D, - 0x03, - 0x80, - 0xDE, - 0x3C, - 0x62, - 0xBE, - 0xE0, - 0x02, - 0x5C, - 0xDF, - 0x81, - 0x63, - 0x3D, - 0x7C, - 0x22, - 0xC0, - 0x9E, - 0x1D, - 0x43, - 0xA1, - 0xFF, - 0x46, - 0x18, - 0xFA, - 0xA4, - 0x27, - 0x79, - 0x9B, - 0xC5, - 0x84, - 0xDA, - 0x38, - 0x66, - 0xE5, - 0xBB, - 0x59, - 0x07, - 0xDB, - 0x85, - 0x67, - 0x39, - 0xBA, - 0xE4, - 0x06, - 0x58, - 0x19, - 0x47, - 0xA5, - 0xFB, - 0x78, - 0x26, - 0xC4, - 0x9A, - 0x65, - 0x3B, - 0xD9, - 0x87, - 0x04, - 0x5A, - 0xB8, - 0xE6, - 0xA7, - 0xF9, - 0x1B, - 0x45, - 0xC6, - 0x98, - 0x7A, - 0x24, - 0xF8, - 0xA6, - 0x44, - 0x1A, - 0x99, - 0xC7, - 0x25, - 0x7B, - 0x3A, - 0x64, - 0x86, - 0xD8, - 0x5B, - 0x05, - 0xE7, - 0xB9, - 0x8C, - 0xD2, - 0x30, - 0x6E, - 0xED, - 0xB3, - 0x51, - 0x0F, - 0x4E, - 0x10, - 0xF2, - 0xAC, - 0x2F, - 0x71, - 0x93, - 0xCD, - 0x11, - 0x4F, - 0xAD, - 0xF3, - 0x70, - 0x2E, - 0xCC, - 0x92, - 0xD3, - 0x8D, - 0x6F, - 0x31, - 0xB2, - 0xEC, - 0x0E, - 0x50, - 0xAF, - 0xF1, - 0x13, - 0x4D, - 0xCE, - 0x90, - 0x72, - 0x2C, - 0x6D, - 0x33, - 0xD1, - 0x8F, - 0x0C, - 0x52, - 0xB0, - 0xEE, - 0x32, - 0x6C, - 0x8E, - 0xD0, - 0x53, - 0x0D, - 0xEF, - 0xB1, - 0xF0, - 0xAE, - 0x4C, - 0x12, - 0x91, - 0xCF, - 0x2D, - 0x73, - 0xCA, - 0x94, - 0x76, - 0x28, - 0xAB, - 0xF5, - 0x17, - 0x49, - 0x08, - 0x56, - 0xB4, - 0xEA, - 0x69, - 0x37, - 0xD5, - 0x8B, - 0x57, - 0x09, - 0xEB, - 0xB5, - 0x36, - 0x68, - 0x8A, - 0xD4, - 0x95, - 0xCB, - 0x29, - 0x77, - 0xF4, - 0xAA, - 0x48, - 0x16, - 0xE9, - 0xB7, - 0x55, - 0x0B, - 0x88, - 0xD6, - 0x34, - 0x6A, - 0x2B, - 0x75, - 0x97, - 0xC9, - 0x4A, - 0x14, - 0xF6, - 0xA8, - 0x74, - 0x2A, - 0xC8, - 0x96, - 0x15, - 0x4B, - 0xA9, - 0xF7, - 0xB6, - 0xE8, - 0x0A, - 0x54, - 0xD7, - 0x89, - 0x6B, - 0x35, -] - - -def calculate(data): - crc_value = 0 - for m in data: - k = crc_value ^ m - if k > 256: - k -= 256 - if k < 0: - k += 256 - crc_value = crc8_854_table[k] - return crc_value diff --git a/custom_components/midea_ac_lan/midea/core/device.py b/custom_components/midea_ac_lan/midea/core/device.py deleted file mode 100644 index f47c2955..00000000 --- a/custom_components/midea_ac_lan/midea/core/device.py +++ /dev/null @@ -1,423 +0,0 @@ -import threading - -try: - from enum import StrEnum -except ImportError: - from ..backports.myenum import StrEnum - -import logging -import socket -import time -from enum import IntEnum - -from .message import ( - MessageApplianceResponse, - MessageQueryAppliance, - MessageQuestCustom, - MessageType, -) -from .packet_builder import PacketBuilder -from .security import ( - MSGTYPE_ENCRYPTED_REQUEST, - MSGTYPE_HANDSHAKE_REQUEST, - LocalSecurity, -) - -_LOGGER = logging.getLogger(__name__) - - -class AuthException(Exception): - pass - - -class ResponseException(Exception): - pass - - -class RefreshFailed(Exception): - pass - - -class DeviceAttributes(StrEnum): - pass - - -class ParseMessageResult(IntEnum): - SUCCESS = 0 - PADDING = 1 - ERROR = 99 - - -class MiedaDevice(threading.Thread): - def __init__( - self, - name: str, - device_id: int, - device_type: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - attributes: dict, - ): - threading.Thread.__init__(self) - self._attributes = attributes if attributes else {} - self._socket = None - self._ip_address = ip_address - self._port = port - self._security = LocalSecurity() - self._token = bytes.fromhex(token) if token else None - self._key = bytes.fromhex(key) if key else None - self._buffer = b"" - self._device_name = name - self._device_id = device_id - self._device_type = device_type - self._protocol = protocol - self._model = model - self._subtype = subtype - self._protocol_version = 0 - self._updates = [] - self._unsupported_protocol = [] - self._is_run = False - self._available = True - self._appliance_query = True - self._refresh_interval = 30 - self._heartbeat_interval = 10 - self._default_refresh_interval = 30 - - @property - def name(self): - return self._device_name - - @property - def available(self): - return self._available - - @property - def device_id(self): - return self._device_id - - @property - def device_type(self): - return self._device_type - - @property - def model(self): - return self._model - - @property - def subtype(self): - return self._subtype - - @staticmethod - def fetch_v2_message(msg): - result = [] - while len(msg) > 0: - factual_msg_len = len(msg) - if factual_msg_len < 6: - break - alleged_msg_len = msg[4] + (msg[5] << 8) - if factual_msg_len >= alleged_msg_len: - result.append(msg[:alleged_msg_len]) - msg = msg[alleged_msg_len:] - else: - break - return result, msg - - def connect(self, refresh_status=True): - try: - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._socket.settimeout(10) - _LOGGER.debug( - f"[{self._device_id}] Connecting to {self._ip_address}:{self._port}" - ) - self._socket.connect((self._ip_address, self._port)) - _LOGGER.debug(f"[{self._device_id}] Connected") - if self._protocol == 3: - self.authenticate() - _LOGGER.debug(f"[{self._device_id}] Authentication success") - if refresh_status: - self.refresh_status(wait_response=True) - self.enable_device(True) - return True - except socket.timeout: - _LOGGER.debug(f"[{self._device_id}] Connection timed out") - except socket.error: - _LOGGER.debug(f"[{self._device_id}] Connection error") - except AuthException: - _LOGGER.debug(f"[{self._device_id}] Authentication failed") - except ResponseException: - _LOGGER.debug(f"[{self._device_id}] Unexpected response received") - except RefreshFailed: - _LOGGER.debug(f"[{self._device_id}] Refresh status is timed out") - except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Unknown error: {e.__traceback__.tb_frame.f_globals['__file__']}, " - f"{e.__traceback__.tb_lineno}, {repr(e)}" - ) - self.enable_device(False) - return False - - def authenticate(self): - request = self._security.encode_8370(self._token, MSGTYPE_HANDSHAKE_REQUEST) - _LOGGER.debug(f"[{self._device_id}] Handshaking") - self._socket.send(request) - response = self._socket.recv(512) - if len(response) < 20: - raise AuthException() - response = response[8:72] - self._security.tcp_key(response, self._key) - - def send_message(self, data): - if self._protocol == 3: - self.send_message_v3(data, msg_type=MSGTYPE_ENCRYPTED_REQUEST) - else: - self.send_message_v2(data) - - def send_message_v2(self, data): - if self._socket is not None: - self._socket.send(data) - else: - _LOGGER.debug( - f"[{self._device_id}] Send failure, device disconnected, data: {data.hex()}" - ) - - def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST): - data = self._security.encode_8370(data, msg_type) - self.send_message_v2(data) - - def build_send(self, cmd): - data = cmd.serialize() - _LOGGER.debug(f"[{self._device_id}] Sending: {cmd}") - msg = PacketBuilder(self._device_id, data).finalize() - self.send_message(msg) - - def refresh_status(self, wait_response=False): - cmds: list = self.build_query() - if self._appliance_query: - cmds = [MessageQueryAppliance(self.device_type)] + cmds - error_count = 0 - for cmd in cmds: - if cmd.__class__.__name__ not in self._unsupported_protocol: - self.build_send(cmd) - if wait_response: - try: - while True: - msg = self._socket.recv(512) - if len(msg) == 0: - raise socket.error - result = self.parse_message(msg) - if result == ParseMessageResult.SUCCESS: - break - elif result == ParseMessageResult.PADDING: - continue - else: - raise ResponseException - except socket.timeout: - error_count += 1 - self._unsupported_protocol.append(cmd.__class__.__name__) - _LOGGER.debug( - f"[{self._device_id}] Does not supports " - f"the protocol {cmd.__class__.__name__}, ignored" - ) - except ResponseException: - error_count += 1 - else: - error_count += 1 - if error_count == len(cmds): - raise RefreshFailed - - def pre_process_message(self, msg): - if msg[9] == MessageType.query_appliance: - message = MessageApplianceResponse(msg) - self._appliance_query = False - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - self._protocol_version = message.protocol_version - _LOGGER.debug( - f"[{self._device_id}] Device protocol version: {self._protocol_version}" - ) - return False - return True - - def parse_message(self, msg): - if self._protocol == 3: - messages, self._buffer = self._security.decode_8370(self._buffer + msg) - else: - messages, self._buffer = self.fetch_v2_message(self._buffer + msg) - if len(messages) == 0: - return ParseMessageResult.PADDING - for message in messages: - if message == b"ERROR": - return ParseMessageResult.ERROR - payload_len = message[4] + (message[5] << 8) - 56 - payload_type = message[2] + (message[3] << 8) - if payload_type in [0x1001, 0x0001]: - # Heartbeat detected - pass - elif len(message) > 56: - cryptographic = message[40:-16] - if payload_len % 16 == 0: - decrypted = self._security.aes_decrypt(cryptographic) - try: - cont = True - if self._appliance_query: - cont = self.pre_process_message(decrypted) - if cont: - status = self.process_message(decrypted) - if len(status) > 0: - self.update_all(status) - else: - _LOGGER.debug( - f"[{self._device_id}] Unidentified protocol" - ) - except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Error in process message: {e}, msg = {decrypted.hex()}" - ) - else: - _LOGGER.warning( - f"[{self._device_id}] Illegal payload, " - f"original message = {msg.hex()}, buffer = {self._buffer.hex()}, " - f"8370 decoded = {message.hex()}, payload type = {payload_type}, " - f"alleged payload length = {payload_len}, factual payload length = {len(cryptographic)}" - ) - else: - _LOGGER.warning( - f"[{self._device_id}] Illegal message, " - f"original message = {msg.hex()}, buffer = {self._buffer.hex()}, " - f"8370 decoded = {message.hex()}, payload type = {payload_type}, " - f"alleged payload length = {payload_len}, message length = {len(message)}, " - ) - return ParseMessageResult.SUCCESS - - def build_query(self): - raise NotImplementedError - - def process_message(self, msg): - raise NotImplementedError - - def send_command(self, cmd_type, cmd_body: bytearray): - cmd = MessageQuestCustom( - self._device_type, self._protocol_version, cmd_type, cmd_body - ) - try: - self.build_send(cmd) - except socket.error as e: - _LOGGER.debug( - f"[{self._device_id}] Interface send_command failure, {repr(e)}, " - f"cmd_type: {cmd_type}, cmd_body: {cmd_body.hex()}" - ) - - def send_heartbeat(self): - msg = PacketBuilder(self._device_id, bytearray([0x00])).finalize(msg_type=0) - self.send_message(msg) - - def register_update(self, update): - self._updates.append(update) - - def update_all(self, status): - _LOGGER.debug(f"[{self._device_id}] Status update: {status}") - for update in self._updates: - update(status) - - def enable_device(self, available=True): - self._available = available - status = {"available": available} - self.update_all(status) - - def open(self): - if not self._is_run: - self._is_run = True - threading.Thread.start(self) - - def close(self): - if self._is_run: - self._is_run = False - self.close_socket() - - def close_socket(self): - self._unsupported_protocol = [] - self._buffer = b"" - if self._socket: - self._socket.close() - self._socket = None - - def set_ip_address(self, ip_address): - if self._ip_address != ip_address: - _LOGGER.debug(f"[{self._device_id}] Update IP address to {ip_address}") - self._ip_address = ip_address - self.close_socket() - - def set_refresh_interval(self, refresh_interval): - self._refresh_interval = refresh_interval - - def run(self): - while self._is_run: - while self._socket is None: - if self.connect(refresh_status=True) is False: - if not self._is_run: - return - self.close_socket() - time.sleep(5) - timeout_counter = 0 - start = time.time() - previous_refresh = start - previous_heartbeat = start - self._socket.settimeout(1) - while True: - try: - now = time.time() - if 0 < self._refresh_interval <= now - previous_refresh: - self.refresh_status() - previous_refresh = now - if now - previous_heartbeat >= self._heartbeat_interval: - self.send_heartbeat() - previous_heartbeat = now - msg = self._socket.recv(512) - msg_len = len(msg) - if msg_len == 0: - raise socket.error("Connection closed by peer") - result = self.parse_message(msg) - if result == ParseMessageResult.ERROR: - _LOGGER.debug(f"[{self._device_id}] Message 'ERROR' received") - self.close_socket() - break - elif result == ParseMessageResult.SUCCESS: - timeout_counter = 0 - except socket.timeout: - timeout_counter = timeout_counter + 1 - if timeout_counter >= 120: - _LOGGER.debug(f"[{self._device_id}] Heartbeat timed out") - self.close_socket() - break - except socket.error as e: - if self._is_run: - _LOGGER.debug(f"[{self._device_id}] Socket error {repr(e)}") - self.close_socket() - break - except Exception as e: - _LOGGER.error( - f"[{self._device_id}] Unknown error :{e.__traceback__.tb_frame.f_globals['__file__']}, " - f"{e.__traceback__.tb_lineno}, {repr(e)}" - ) - self.close_socket() - break - - def set_attribute(self, attr, value): - raise NotImplementedError - - def get_attribute(self, attr): - return self._attributes.get(attr) - - def set_customize(self, customize): - pass - - @property - def attributes(self): - ret = {} - for status in self._attributes.keys(): - ret[str(status)] = self._attributes[status] - return ret diff --git a/custom_components/midea_ac_lan/midea/core/discover.py b/custom_components/midea_ac_lan/midea/core/discover.py deleted file mode 100644 index 0deb1790..00000000 --- a/custom_components/midea_ac_lan/midea/core/discover.py +++ /dev/null @@ -1,308 +0,0 @@ -import logging -import socket -from ipaddress import IPv4Network - -import ifaddr - -from .security import LocalSecurity - -try: - import xml.etree.cElementTree as ET -except ImportError: - import xml.etree.ElementTree as ET - -_LOGGER = logging.getLogger(__name__) - -BROADCAST_MSG = bytearray( - [ - 0x5A, - 0x5A, - 0x01, - 0x11, - 0x48, - 0x00, - 0x92, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x7F, - 0x75, - 0xBD, - 0x6B, - 0x3E, - 0x4F, - 0x8B, - 0x76, - 0x2E, - 0x84, - 0x9C, - 0x6E, - 0x57, - 0x8D, - 0x65, - 0x90, - 0x03, - 0x6E, - 0x9D, - 0x43, - 0x42, - 0xA5, - 0x0F, - 0x1F, - 0x56, - 0x9E, - 0xB8, - 0xEC, - 0x91, - 0x8E, - 0x92, - 0xE5, - ] -) - -DEVICE_INFO_MSG = bytearray( - [ - 0x5A, - 0x5A, - 0x15, - 0x00, - 0x00, - 0x38, - 0x00, - 0x04, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x27, - 0x33, - 0x05, - 0x13, - 0x06, - 0x14, - 0x14, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x03, - 0xE8, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0xCA, - 0x8D, - 0x9B, - 0xF9, - 0xA0, - 0x30, - 0x1A, - 0xE3, - 0xB7, - 0xE4, - 0x2D, - 0x53, - 0x49, - 0x47, - 0x62, - 0xBE, - ] -) - - -def discover(discover_type=None, ip_address=None): - if discover_type is None: - discover_type = [] - security = LocalSecurity() - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - sock.settimeout(5) - found_devices = {} - if ip_address is None: - addrs = enum_all_broadcast() - else: - addrs = [ip_address] - _LOGGER.debug(f"All addresses for broadcast: {addrs}") - for addr in addrs: - try: - sock.sendto(BROADCAST_MSG, (addr, 6445)) - sock.sendto(BROADCAST_MSG, (addr, 20086)) - except Exception: - _LOGGER.warning(f"Can't access network {addr}") - while True: - try: - data, addr = sock.recvfrom(512) - ip = addr[0] - _LOGGER.debug(f"Received response from {addr}: {data.hex()}") - if len(data) >= 104 and ( - data[:2].hex() == "5a5a" or data[8:10].hex() == "5a5a" - ): - if data[:2].hex() == "5a5a": - protocol = 2 - elif data[:2].hex() == "8370": - protocol = 3 - if data[8:10].hex() == "5a5a": - data = data[8:-16] - else: - continue - device_id = int.from_bytes( - bytearray.fromhex(data[20:26].hex()), "little" - ) - if device_id in found_devices: - continue - encrypt_data = data[40:-16] - reply = security.aes_decrypt(encrypt_data) - _LOGGER.debug(f"Declassified reply: {reply.hex()}") - ssid = reply[41 : 41 + reply[40]].decode("utf-8") - device_type = ssid.split("_")[1] - port = bytes2port(reply[4:8]) - model = reply[17:25].decode("utf-8") - sn = reply[8:40].decode("utf-8") - elif data[:6].hex() == "3c3f786d6c20": - protocol = 1 - root = ET.fromstring(data.decode(encoding="utf-8", errors="replace")) - child = root.find("body/device") - m = child.attrib - port, sn, device_type = ( - int(m["port"]), - m["apc_sn"], - str(hex(int(m["apc_type"])))[2:], - ) - response = get_device_info(ip, int(port)) - device_id = get_id_from_response(response) - if len(sn) == 32: - model = sn[9:17] - elif len(sn) == 22: - model = sn[3:11] - else: - model = "" - else: - continue - device = { - "device_id": device_id, - "type": int(device_type, 16), - "ip_address": ip, - "port": port, - "model": model, - "sn": sn, - "protocol": protocol, - } - if len(discover_type) == 0 or device.get("type") in discover_type: - found_devices[device_id] = device - _LOGGER.debug(f"Found a supported device: {device}") - else: - _LOGGER.debug(f"Found a unsupported device: {device}") - except socket.timeout: - break - except socket.error as e: - _LOGGER.error(f"Socket error: {repr(e)}") - return found_devices - - -def get_id_from_response(response): - if response[64:-16][:6].hex() == "3c3f786d6c20": - xml = response[64:-16] - root = ET.fromstring(xml.decode(encoding="utf-8", errors="replace")) - child = root.find("smartDevice") - m = child.attrib - return int.from_bytes(bytearray.fromhex(m["devId"]), "little") - else: - return 0 - - -def bytes2port(paramArrayOfbyte): - if paramArrayOfbyte is None: - return 0 - b, i = 0, 0 - while b < 4: - if b < len(paramArrayOfbyte): - b1 = paramArrayOfbyte[b] & 0xFF - else: - b1 = 0 - i |= b1 << b * 8 - b += 1 - return i - - -def get_device_info(device_ip, device_port: int): - response = bytearray(0) - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.settimeout(8) - device_address = (device_ip, device_port) - sock.connect(device_address) - _LOGGER.debug( - f"Sending to {device_ip}:{device_port} {DEVICE_INFO_MSG.hex()}" - ) - sock.sendall(DEVICE_INFO_MSG) - response = sock.recv(512) - except socket.timeout: - _LOGGER.warning( - f"Connect the device {device_ip}:{device_port} timed out for 8s. " - f"Don't care about a small amount of this. if many maybe not support." - ) - except socket.error: - _LOGGER.warning(f"Can't connect to Device {device_ip}:{device_port}") - return response - - -def enum_all_broadcast(): - nets = [] - adapters = ifaddr.get_adapters() - for adapter in adapters: - for ip in adapter.ips: - if ip.is_IPv4 and ip.network_prefix < 32: - local_network = IPv4Network( - f"{ip.ip}/{ip.network_prefix}", strict=False - ) - if ( - local_network.is_private - and not local_network.is_loopback - and not local_network.is_link_local - ): - addr = str(local_network.broadcast_address) - if addr not in nets: - nets.append(addr) - return nets diff --git a/custom_components/midea_ac_lan/midea/core/message.py b/custom_components/midea_ac_lan/midea/core/message.py deleted file mode 100644 index 91e485c6..00000000 --- a/custom_components/midea_ac_lan/midea/core/message.py +++ /dev/null @@ -1,272 +0,0 @@ -import logging -from abc import ABC -from enum import IntEnum - -_LOGGER = logging.getLogger(__name__) - - -class MessageLenError(Exception): - pass - - -class MessageBodyError(Exception): - pass - - -class MessageCheckSumError(Exception): - pass - - -class MessageType(IntEnum): - set = (0x02,) - query = (0x03,) - notify1 = (0x04,) - notify2 = (0x05,) - exception = (0x06,) - exception2 = (0x0A,) - query_appliance = 0xA0 - - -class MessageBase(ABC): - HEADER_LENGTH = 10 - - def __init__(self): - self._device_type = 0x00 - self._message_type = 0x00 - self._body_type = 0x00 - self._protocol_version = 0x00 - - @staticmethod - def checksum(data): - return (~sum(data) + 1) & 0xFF - - @property - def header(self): - raise NotImplementedError - - @property - def body(self): - raise NotImplementedError - - @property - def message_type(self): - return self._message_type - - @message_type.setter - def message_type(self, value): - self._message_type = value - - @property - def device_type(self): - return self._device_type - - @device_type.setter - def device_type(self, value): - self._device_type = value - - @property - def body_type(self): - return self._body_type - - @body_type.setter - def body_type(self, value): - self._body_type = value - - @property - def protocol_version(self): - return self._protocol_version - - @protocol_version.setter - def protocol_version(self, protocol_version): - self._protocol_version = protocol_version - - def __str__(self) -> str: - output = { - "header": self.header.hex(), - "body": self.body.hex(), - "message type": "%02x" % self._message_type, - "body type": ( - ("%02x" % self._body_type) if self._body_type is not None else "None" - ), - } - return str(output) - - -class MessageRequest(MessageBase): - def __init__(self, device_type, protocol_version, message_type, body_type): - super().__init__() - self.device_type = device_type - self.protocol_version = protocol_version - self.message_type = message_type - self.body_type = body_type - - @property - def header(self): - length = self.HEADER_LENGTH + len(self.body) - return bytearray( - [ - # flag - 0xAA, - # length - length, - # device type - self.device_type, - # frame checksum - 0x00, # self._device_type ^ length, - # unused - 0x00, - 0x00, - # frame ID - 0x00, - # frame protocol version - 0x00, - # device protocol version - self.protocol_version, - # frame type - self.message_type, - ] - ) - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([]) - if self.body_type is not None: - body.append(self.body_type) - if self._body is not None: - body.extend(self._body) - return body - - def serialize(self): - stream = self.header + self.body - stream.append(MessageBase.checksum(stream[1:])) - return stream - - -class MessageQuestCustom(MessageRequest): - def __init__(self, device_type, protocol_version, cmd_type, cmd_body): - super().__init__( - device_type=device_type, - protocol_version=protocol_version, - message_type=cmd_type, - body_type=None, - ) - self._cmd_body = cmd_body - - @property - def _body(self): - return bytearray([]) - - @property - def body(self): - return self._cmd_body - - -class MessageQueryAppliance(MessageRequest): - def __init__(self, device_type): - super().__init__( - device_type=device_type, - protocol_version=0, - message_type=MessageType.query_appliance, - body_type=None, - ) - - @property - def _body(self): - return bytearray([]) - - @property - def body(self): - return bytearray([0x00] * 19) - - -class MessageBody: - def __init__(self, body): - self._data = body - - @property - def data(self): - return self._data - - @property - def body_type(self): - return self._data[0] - - @staticmethod - def read_byte(body, byte, default_value=0): - return body[byte] if len(body) > byte else default_value - - -class NewProtocolMessageBody(MessageBody): - def __init__(self, body, bt): - super().__init__(body) - if bt == 0xB5: - self._pack_len = 4 - else: - self._pack_len = 5 - - @staticmethod - def pack(param, value: bytearray, pack_len=4): - length = len(value) - if pack_len == 4: - stream = bytearray([param & 0xFF, param >> 8, length]) + value - else: - stream = bytearray([param & 0xFF, param >> 8, 0x00, length]) + value - return stream - - def parse(self): - result = {} - try: - pos = 2 - for pack in range(0, self.data[1]): - param = self.data[pos] + (self.data[pos + 1] << 8) - if self._pack_len == 5: - pos += 1 - length = self.data[pos + 2] - if length > 0: - value = self.data[pos + 3 : pos + 3 + length] - result[param] = value - pos += 3 + length - except IndexError: - # Some device used non-standard new-protocol(美的乐享三代中央空调?) - _LOGGER.debug(f"Non-standard new-protocol {self.data.hex()}") - return result - - -class MessageResponse(MessageBase): - def __init__(self, message): - super().__init__() - if message is None or len(message) < self.HEADER_LENGTH + 1: - raise MessageLenError - self._header = message[: self.HEADER_LENGTH] - self.protocol_version = self._header[-2] - self.message_type = self._header[-1] - self.device_type = self._header[2] - body = message[self.HEADER_LENGTH : -1] - self._body = MessageBody(body) - self.body_type = self._body.body_type - - @property - def header(self): - return self._header - - @property - def body(self): - return self._body.data - - def set_body(self, body: MessageBody): - self._body = body - - def set_attr(self): - for key in vars(self._body).keys(): - if key != "data": - value = getattr(self._body, key, None) - setattr(self, key, value) - - -class MessageApplianceResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) diff --git a/custom_components/midea_ac_lan/midea/core/packet_builder.py b/custom_components/midea_ac_lan/midea/core/packet_builder.py deleted file mode 100644 index 04780fee..00000000 --- a/custom_components/midea_ac_lan/midea/core/packet_builder.py +++ /dev/null @@ -1,94 +0,0 @@ -import datetime - -from .security import LocalSecurity - - -class PacketBuilder: - def __init__(self, device_id: int, command): - self.command = None - self.security = LocalSecurity() - # aa20ac00000000000003418100ff03ff000200000000000000000000000006f274 - # Init the packet with the header data. - self.packet = bytearray( - [ - # 2 bytes - StaicHeader - 0x5A, - 0x5A, - # 2 bytes - mMessageType - 0x01, - 0x11, - # 2 bytes - PacketLenght - 0x00, - 0x00, - # 2 bytes - 0x20, - 0x00, - # 4 bytes - MessageId - 0x00, - 0x00, - 0x00, - 0x00, - # 8 bytes - Date&Time - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - # 6 bytes - mDeviceID - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - # 12 bytes - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - self.packet[12:20] = self.packet_time() - self.packet[20:28] = device_id.to_bytes(8, "little") - self.command = command - - def finalize(self, msg_type=1): - if msg_type != 1: - self.packet[3] = 0x10 - self.packet[6] = 0x7B - else: - self.packet.extend(self.security.aes_encrypt(self.command)) - # PacketLenght - self.packet[4:6] = (len(self.packet) + 16).to_bytes(2, "little") - # Append a basic checksum data(16 bytes) to the packet - self.packet.extend(self.encode32(self.packet)) - return self.packet - - def encode32(self, data: bytearray): - return self.security.encode32_data(data) - - @staticmethod - def checksum(data): - return (~sum(data) + 1) & 0xFF - - @staticmethod - def packet_time(): - t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:16] - b = bytearray() - for i in range(0, len(t), 2): - d = int(t[i : i + 2]) - b.insert(0, d) - return b diff --git a/custom_components/midea_ac_lan/midea/core/security.py b/custom_components/midea_ac_lan/midea/core/security.py deleted file mode 100644 index 90166ea1..00000000 --- a/custom_components/midea_ac_lan/midea/core/security.py +++ /dev/null @@ -1,264 +0,0 @@ -import hmac -from hashlib import md5, sha256 -from typing import Any -from urllib.parse import unquote_plus, urlencode, urlparse - -from Crypto.Cipher import AES -from Crypto.Random import get_random_bytes -from Crypto.Util.Padding import pad, unpad -from Crypto.Util.strxor import strxor - -MSGTYPE_HANDSHAKE_REQUEST = 0x0 -MSGTYPE_HANDSHAKE_RESPONSE = 0x1 -MSGTYPE_ENCRYPTED_RESPONSE = 0x3 -MSGTYPE_ENCRYPTED_REQUEST = 0x6 - - -class CloudSecurity: - def __init__(self, login_key, iot_key, hmac_key, fixed_key=None, fixed_iv=None): - self._login_key = login_key - self._iot_key = iot_key - self._hmac_key = hmac_key - self._aes_key = None - self._aes_iv = None - self._fixed_key = format(fixed_key, "x").encode("ascii") if fixed_key else None - self._fixed_iv = format(fixed_iv, "x").encode("ascii") if fixed_iv else None - - def sign(self, url: str, data: Any, random: str) -> str: - msg = self._iot_key - msg += str(data) - msg += random - sign = hmac.new(self._hmac_key.encode("ascii"), msg.encode("ascii"), sha256) - return sign.hexdigest() - - def encrypt_password(self, login_id, data): - m = sha256() - m.update(data.encode("ascii")) - login_hash = login_id + m.hexdigest() + self._login_key - m = sha256() - m.update(login_hash.encode("ascii")) - return m.hexdigest() - - def encrypt_iam_password(self, login_id, data) -> str: - raise NotImplementedError - - @staticmethod - def get_deviceid(username): - return sha256(f"Hello, {username}!".encode("ascii")).digest().hex()[:16] - - @staticmethod - def get_udp_id(appliance_id, method=0): - if method == 0: - bytes_id = bytes(reversed(appliance_id.to_bytes(8, "big"))) - elif method == 1: - bytes_id = appliance_id.to_bytes(6, "big") - elif method == 2: - bytes_id = appliance_id.to_bytes(6, "little") - else: - return None - data = bytearray(sha256(bytes_id).digest()) - for i in range(0, 16): - data[i] ^= data[i + 16] - return data[0:16].hex() - - def set_aes_keys(self, key, iv): - if isinstance(key, str): - key = key.encode("ascii") - if isinstance(iv, str): - iv = iv.encode("ascii") - self._aes_key = key - self._aes_iv = iv - - def aes_encrypt_with_fixed_key(self, data): - return self.aes_encrypt(data, self._fixed_key, self._fixed_iv) - - def aes_decrypt_with_fixed_key(self, data): - return self.aes_decrypt(data, self._fixed_key, self._fixed_iv) - - def aes_encrypt(self, data, key=None, iv=None): - if key is not None: - aes_key = key - aes_iv = iv - else: - aes_key = self._aes_key - aes_iv = self._aes_iv - if aes_key is None: - raise ValueError("Encrypt need a key") - if isinstance(data, str): - data = bytes.fromhex(data) - if aes_iv is None: # ECB - return AES.new(aes_key, AES.MODE_ECB).encrypt(pad(data, 16)) - else: # CBC - return AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).encrypt(pad(data, 16)) - - def aes_decrypt(self, data, key=None, iv=None): - if key is not None: - aes_key = key - aes_iv = iv - else: - aes_key = self._aes_key - aes_iv = self._aes_iv - if aes_key is None: - raise ValueError("Encrypt need a key") - if isinstance(data, str): - data = bytes.fromhex(data) - if aes_iv is None: # ECB - return unpad( - AES.new(aes_key, AES.MODE_ECB).decrypt(data), len(aes_key) - ).decode() - else: # CBC - return unpad( - AES.new(aes_key, AES.MODE_CBC, iv=aes_iv).decrypt(data), len(aes_key) - ).decode() - - -class MeijuCloudSecurity(CloudSecurity): - def __init__(self, login_key, iot_key, hmac_key): - super().__init__(login_key, iot_key, hmac_key, 10864842703515613082) - - def encrypt_iam_password(self, login_id, data) -> str: - md = md5() - md.update(data.encode("ascii")) - md_second = md5() - md_second.update(md.hexdigest().encode("ascii")) - return md_second.hexdigest() - - -class MSmartCloudSecurity(CloudSecurity): - def __init__(self, login_key, iot_key, hmac_key): - super().__init__( - login_key, iot_key, hmac_key, 13101328926877700970, 16429062708050928556 - ) - - def encrypt_iam_password(self, login_id, data) -> str: - md = md5() - md.update(data.encode("ascii")) - md_second = md5() - md_second.update(md.hexdigest().encode("ascii")) - login_hash = login_id + md_second.hexdigest() + self._login_key - sha = sha256() - sha.update(login_hash.encode("ascii")) - return sha.hexdigest() - - def set_aes_keys(self, encrypted_key, encrypted_iv): - key_digest = sha256(self._login_key.encode("ascii")).hexdigest() - tmp_key = key_digest[:16].encode("ascii") - tmp_iv = key_digest[16:32].encode("ascii") - self._aes_key = self.aes_decrypt(encrypted_key, tmp_key, tmp_iv).encode("ascii") - self._aes_iv = self.aes_decrypt(encrypted_iv, tmp_key, tmp_iv).encode("ascii") - - -class MideaAirSecurity(CloudSecurity): - def __init__(self, login_key): - super().__init__(login_key, None, None) - - def sign(self, url: str, data: Any, random: str) -> str: - payload = unquote_plus(urlencode(sorted(data.items(), key=lambda x: x[0]))) - sha = sha256() - sha.update((urlparse(url).path + payload + self._login_key).encode("ascii")) - return sha.hexdigest() - - -class LocalSecurity: - def __init__(self): - self.blockSize = 16 - self.iv = b"\0" * 16 - self.aes_key = bytes.fromhex( - format(141661095494369103254425781617665632877, "x") - ) - self.salt = bytes.fromhex( - format( - 233912452794221312800602098970898185176935770387238278451789080441632479840061417076563, - "x", - ) - ) - self._tcp_key = None - self._request_count = 0 - self._response_count = 0 - - def aes_decrypt(self, raw): - try: - return unpad( - AES.new(self.aes_key, AES.MODE_ECB).decrypt(bytearray(raw)), 16 - ) - except ValueError: - return bytearray(0) - - def aes_encrypt(self, raw): - return AES.new(self.aes_key, AES.MODE_ECB).encrypt(bytearray(pad(raw, 16))) - - def aes_cbc_decrypt(self, raw, key): - return AES.new(key=key, mode=AES.MODE_CBC, iv=self.iv).decrypt(raw) - - def aes_cbc_encrypt(self, raw, key): - return AES.new(key=key, mode=AES.MODE_CBC, iv=self.iv).encrypt(raw) - - def encode32_data(self, raw): - return md5(raw + self.salt).digest() - - def tcp_key(self, response, key): - if response == b"ERROR": - raise Exception("authentication failed") - if len(response) != 64: - raise Exception("unexpected data length") - payload = response[:32] - sign = response[32:] - plain = self.aes_cbc_decrypt(payload, key) - if sha256(plain).digest() != sign: - raise Exception("sign does not match") - self._tcp_key = strxor(plain, key) - self._request_count = 0 - self._response_count = 0 - return self._tcp_key - - def encode_8370(self, data, msgtype): - header = bytearray([0x83, 0x70]) - size, padding = len(data), 0 - if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): - if (size + 2) % 16 != 0: - padding = 16 - (size + 2 & 0xF) - size += padding + 32 - data += get_random_bytes(padding) - header += size.to_bytes(2, "big") - header += bytearray([0x20, padding << 4 | msgtype]) - data = self._request_count.to_bytes(2, "big") + data - self._request_count += 1 - if self._request_count >= 0xFFFF: - self._request_count = 0 - if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): - sign = sha256(header + data).digest() - data = self.aes_cbc_encrypt(raw=data, key=self._tcp_key) + sign - return header + data - - def decode_8370(self, data): - if len(data) < 6: - return [], data - header = data[:6] - if header[0] != 0x83 or header[1] != 0x70: - raise Exception("not an 8370 message") - size = int.from_bytes(header[2:4], "big") + 8 - leftover = None - if len(data) < size: - return [], data - elif len(data) > size: - leftover = data[size:] - data = data[:size] - if header[4] != 0x20: - raise Exception("missing byte 4") - padding = header[5] >> 4 - msgtype = header[5] & 0xF - data = data[6:] - if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): - sign = data[-32:] - data = data[:-32] - data = self.aes_cbc_decrypt(raw=data, key=self._tcp_key) - if sha256(header + data).digest() != sign: - raise Exception("sign does not match") - if padding: - data = data[:-padding] - self._response_count = int.from_bytes(data[:2], "big") - data = data[2:] - if leftover: - packets, incomplete = self.decode_8370(leftover) - return [data] + packets, incomplete - return [data], b"" diff --git a/custom_components/midea_ac_lan/midea/devices/__init__.py b/custom_components/midea_ac_lan/midea/devices/__init__.py deleted file mode 100644 index 6a908f26..00000000 --- a/custom_components/midea_ac_lan/midea/devices/__init__.py +++ /dev/null @@ -1,50 +0,0 @@ -from importlib import import_module -from types import ModuleType - -from homeassistant.core import HomeAssistant - - -async def async_device_selector( - hass: HomeAssistant, - name: str, - device_id: int, - device_type: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, -): - try: - - if device_type < 0xA0: - device_path = f".{'x%02x' % device_type}.device" - else: - device_path = f".{'%02x' % device_type}.device" - - modules: list[ModuleType] = [] - - def _load_device_module() -> None: - """Load all service modules.""" - modules.append(import_module(device_path, __package__)) - - await hass.async_add_import_executor_job(_load_device_module) - - device = modules[0].MideaAppliance( - name=name, - device_id=device_id, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - customize=customize, - ) - except ModuleNotFoundError: - device = None - return device diff --git a/custom_components/midea_ac_lan/midea/devices/a1/device.py b/custom_components/midea_ac_lan/midea/devices/a1/device.py deleted file mode 100644 index cbff7675..00000000 --- a/custom_components/midea_ac_lan/midea/devices/a1/device.py +++ /dev/null @@ -1,184 +0,0 @@ -import logging - -from .message import MessageA1Response, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - prompt_tone = "prompt_tone" - child_lock = "child_lock" - mode = "mode" - fan_speed = "fan_speed" - swing = "swing" - target_humidity = "target_humidity" - anion = "anion" - tank = "tank" - water_level_set = "water_level_set" - tank_full = "tank_full" - current_humidity = "current_humidity" - current_temperature = "current_temperature" - - -class MideaA1Device(MiedaDevice): - _modes = ["Manual", "Continuous", "Auto", "Clothes-Dry", "Shoes-Dry"] - _speeds = { - 1: "Lowest", - 40: "Low", - 60: "Medium", - 80: "High", - 102: "Auto", - 127: "Off", - } - _water_level_sets = ["25", "50", "75", "100"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xA1, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.prompt_tone: True, - DeviceAttributes.child_lock: False, - DeviceAttributes.mode: None, - DeviceAttributes.fan_speed: 60, - DeviceAttributes.swing: False, - DeviceAttributes.target_humidity: 35, - DeviceAttributes.anion: False, - DeviceAttributes.tank: 0, - DeviceAttributes.water_level_set: 50, - DeviceAttributes.tank_full: None, - DeviceAttributes.current_humidity: None, - DeviceAttributes.current_temperature: None, - }, - ) - - @property - def modes(self): - return MideaA1Device._modes - - @property - def fan_speeds(self): - return list(MideaA1Device._speeds.values()) - - @property - def water_level_sets(self): - return MideaA1Device._water_level_sets - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageA1Response(msg) - self._protocol_version = message.protocol_version - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - if value <= len(MideaA1Device._modes): - self._attributes[status] = MideaA1Device._modes[value - 1] - else: - self._attributes[status] = None - elif status == DeviceAttributes.fan_speed: - if value in MideaA1Device._speeds.keys(): - self._attributes[status] = MideaA1Device._speeds.get(value) - else: - self._attributes[status] = None - elif status == DeviceAttributes.water_level_set: - self._attributes[status] = str(value) - else: - self._attributes[status] = value - tank_full = self._attributes[DeviceAttributes.tank] >= int( - self._attributes[DeviceAttributes.water_level_set] - ) - if ( - self._attributes[DeviceAttributes.tank_full] is None - or self._attributes[DeviceAttributes.tank_full] != tank_full - ): - self._attributes[DeviceAttributes.tank_full] = tank_full - new_status[str(DeviceAttributes.tank_full)] = tank_full - new_status[str(status)] = self._attributes[status] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.child_lock = self._attributes[DeviceAttributes.child_lock] - if self._attributes[DeviceAttributes.mode] in MideaA1Device._modes: - message.mode = ( - MideaA1Device._modes.index(self._attributes[DeviceAttributes.mode]) + 1 - ) - else: - message.mode = 1 - message.fan_speed = ( - 40 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(MideaA1Device._speeds.keys())[ - list(MideaA1Device._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) - message.target_humidity = self._attributes[DeviceAttributes.target_humidity] - message.swing = self._attributes[DeviceAttributes.swing] - message.anion = self._attributes[DeviceAttributes.anion] - message.water_level_set = int( - self._attributes[DeviceAttributes.water_level_set] - ) - return message - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.prompt_tone: - self._attributes[DeviceAttributes.prompt_tone] = value - self.update_all({DeviceAttributes.prompt_tone.value: value}) - else: - message = self.make_message_set() - if attr == DeviceAttributes.mode: - if value in MideaA1Device._modes: - message.mode = MideaA1Device._modes.index(value) + 1 - elif attr == DeviceAttributes.fan_speed: - if value in MideaA1Device._speeds.values(): - message.fan_speed = list(MideaA1Device._speeds.keys())[ - list(MideaA1Device._speeds.values()).index(value) - ] - elif attr == DeviceAttributes.water_level_set: - if value in MideaA1Device._water_level_sets: - message.water_level_set = int(value) - else: - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaA1Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/a1/message.py b/custom_components/midea_ac_lan/midea/devices/a1/message.py deleted file mode 100644 index f6a43b7a..00000000 --- a/custom_components/midea_ac_lan/midea/devices/a1/message.py +++ /dev/null @@ -1,222 +0,0 @@ -from enum import IntEnum - -from ...core.crc8 import calculate -from ...core.message import ( - MessageBody, - MessageRequest, - MessageResponse, - MessageType, - NewProtocolMessageBody, -) - - -class NewProtocolTags(IntEnum): - light = 0x005B - - -class MessageA1Base(MessageRequest): - _message_serial = 0 - - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xA1, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - MessageA1Base._message_serial += 1 - if MessageA1Base._message_serial >= 100: - MessageA1Base._message_serial = 1 - self._message_id = MessageA1Base._message_serial - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) - body.append(calculate(body)) - return body - - -class MessageQuery(MessageA1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageNewProtocolQuery(MessageA1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0xB1, - ) - - @property - def _body(self): - query_params = [NewProtocolTags.light] - _body = bytearray([len(query_params)]) - for param in query_params: - _body.extend([param & 0xFF, param >> 8]) - return _body - - -class MessageSet(MessageA1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x48, - ) - self.power = False - self.prompt_tone = False - self.mode = 1 - self.fan_speed = 40 - self.child_lock = False - self.target_humidity = 40 - self.swing = False - self.anion = False - self.water_level_set = 50 - - @property - def _body(self): - # byte1, power, prompt_tone - power = 0x01 if self.power else 0x00 - prompt_tone = 0x40 if self.prompt_tone else 0x00 - # byte2 mode - mode = self.mode - # byte3 fan_speed - fan_speed = self.fan_speed - # byte7 target_humidity - target_humidity = self.target_humidity - # byte8 child_lock - child_lock = 0x80 if self.child_lock else 0x00 - # byte9 anion - anion = 0x40 if self.anion else 0x00 - # byte10 swing - swing = 0x08 if self.swing else 0x00 - # byte 13 water_level_set - water_level_set = self.water_level_set - return bytearray( - [ - power | prompt_tone | 0x02, - mode, - fan_speed, - 0x00, - 0x00, - 0x00, - target_humidity, - child_lock, - anion, - swing, - 0x00, - 0x00, - water_level_set, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageNewProtocolSet(MessageA1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0xB0, - ) - self.light = None - - @property - def _body(self): - pack_count = 0 - payload = bytearray([0x00]) - if self.light is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.light, - value=bytearray([0x01 if self.light else 0x00]), - ) - ) - payload[0] = pack_count - return payload - - -class A1GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.mode = body[2] & 0x0F - self.fan_speed = body[3] & 0x7F - self.target_humidity = 35 if (body[7] < 35) else body[7] - self.child_lock = (body[8] & 0x80) > 0 - self.anion = (body[9] & 0x40) > 0 - self.tank = body[10] & 0x7F - self.water_level_set = body[15] - self.current_humidity = body[16] - self.current_temperature = (body[17] - 50) / 2 - self.swing = (body[19] & 0x20) > 0 - if self.fan_speed < 5: - self.fan_speed = 1 - - -class A1NewProtocolMessageBody(NewProtocolMessageBody): - def __init__(self, body, bt): - super().__init__(body, bt) - params = self.parse() - if NewProtocolTags.light in params: - self.light = params[NewProtocolTags.light][0] > 0 - - -class MessageA1Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: - if self.body_type in [0xB0, 0xB1, 0xB5]: - self.set_body(A1NewProtocolMessageBody(super().body, self.body_type)) - else: - self.set_body(A1GeneralMessageBody(super().body)) - elif self.message_type == MessageType.notify2 and self.body_type == 0xA0: - self.set_body(A1GeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ac/device.py b/custom_components/midea_ac_lan/midea/devices/ac/device.py deleted file mode 100644 index 87ccb1c2..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ac/device.py +++ /dev/null @@ -1,375 +0,0 @@ -import json -import logging - -from .message import ( - MessageACResponse, - MessageGeneralSet, - MessageNewProtocolQuery, - MessageNewProtocolSet, - MessagePowerQuery, - MessageQuery, - MessageSubProtocolQuery, - MessageSubProtocolSet, - MessageToggleDisplay, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - prompt_tone = "prompt_tone" - power = "power" - mode = "mode" - target_temperature = "target_temperature" - fan_speed = "fan_speed" - swing_vertical = "swing_vertical" - swing_horizontal = "swing_horizontal" - boost_mode = "boost_mode" - smart_eye = "smart_eye" - dry = "dry" - eco_mode = "eco_mode" - aux_heating = "aux_heating" - sleep_mode = "sleep_mode" - natural_wind = "natural_wind" - temp_fahrenheit = "temp_fahrenheit" - screen_display = "screen_display" - screen_display_alternate = "screen_display_alternate" - full_dust = "full_dust" - frost_protect = "frost_protect" - comfort_mode = "comfort_mode" - indoor_temperature = "indoor_temperature" - outdoor_temperature = "outdoor_temperature" - indirect_wind = "indirect_wind" - indoor_humidity = "indoor_humidity" - breezeless = "breezeless" - fresh_air_power = "fresh_air_power" - fresh_air_fan_speed = "fresh_air_fan_speed" - fresh_air_mode = "fresh_air_mode" - fresh_air_1 = "fresh_air_1" - fresh_air_2 = "fresh_air_2" - total_energy_consumption = "total_energy_consumption" - current_energy_consumption = "current_energy_consumption" - realtime_power = "realtime_power" - - -class MideaACDevice(MiedaDevice): - _fresh_air_fan_speeds = { - 0: "Off", - 20: "Silent", - 40: "Low", - 60: "Medium", - 80: "High", - 100: "Full", - } - _fresh_air_fan_speeds_rev = dict(reversed(_fresh_air_fan_speeds.items())) - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xAC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.prompt_tone: True, - DeviceAttributes.power: False, - DeviceAttributes.mode: 0, - DeviceAttributes.target_temperature: 24.0, - DeviceAttributes.fan_speed: 102, - DeviceAttributes.swing_vertical: False, - DeviceAttributes.swing_horizontal: False, - DeviceAttributes.smart_eye: False, - DeviceAttributes.dry: False, - DeviceAttributes.aux_heating: False, - DeviceAttributes.boost_mode: False, - DeviceAttributes.sleep_mode: False, - DeviceAttributes.frost_protect: False, - DeviceAttributes.comfort_mode: False, - DeviceAttributes.eco_mode: False, - DeviceAttributes.natural_wind: False, - DeviceAttributes.temp_fahrenheit: False, - DeviceAttributes.screen_display: False, - DeviceAttributes.screen_display_alternate: False, - DeviceAttributes.full_dust: False, - DeviceAttributes.indoor_temperature: None, - DeviceAttributes.outdoor_temperature: None, - DeviceAttributes.indirect_wind: False, - DeviceAttributes.indoor_humidity: None, - DeviceAttributes.breezeless: False, - DeviceAttributes.total_energy_consumption: None, - DeviceAttributes.current_energy_consumption: None, - DeviceAttributes.realtime_power: None, - DeviceAttributes.fresh_air_power: False, - DeviceAttributes.fresh_air_fan_speed: 0, - DeviceAttributes.fresh_air_mode: None, - DeviceAttributes.fresh_air_1: None, - DeviceAttributes.fresh_air_2: None, - }, - ) - self._fresh_air_version = None - self._default_temperature_step = 0.5 - self._temperature_step = None - self._used_subprotocol = False - self._bb_sn8_flag = False - self._bb_timer = False - self._power_analysis_method = None - self._default_power_analysis_method = 1 - self.set_customize(customize) - - @property - def temperature_step(self): - return self._temperature_step - - @property - def fresh_air_fan_speeds(self): - return list(MideaACDevice._fresh_air_fan_speeds.values()) - - def build_query(self): - if self._used_subprotocol: - return [ - MessageSubProtocolQuery(self._protocol_version, 0x10), - MessageSubProtocolQuery(self._protocol_version, 0x11), - MessageSubProtocolQuery(self._protocol_version, 0x30), - ] - return [ - MessageQuery(self._protocol_version), - MessageNewProtocolQuery(self._protocol_version), - MessagePowerQuery(self._protocol_version), - ] - - def process_message(self, msg): - message = MessageACResponse(msg, self._power_analysis_method) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - has_fresh_air = False - if hasattr(message, "used_subprotocol"): - self._used_subprotocol = True - if hasattr(message, "sn8_flag"): - self._bb_sn8_flag = message.sn8_flag - if hasattr(message, "timer"): - self._bb_timer = message.timer - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.fresh_air_power: - has_fresh_air = True - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - if has_fresh_air: - if self._attributes[DeviceAttributes.fresh_air_power]: - for k, v in MideaACDevice._fresh_air_fan_speeds_rev.items(): - if self._attributes[DeviceAttributes.fresh_air_fan_speed] > k: - break - else: - self._attributes[DeviceAttributes.fresh_air_mode] = v - else: - self._attributes[DeviceAttributes.fresh_air_mode] = "Off" - new_status[DeviceAttributes.fresh_air_mode.value] = self._attributes[ - DeviceAttributes.fresh_air_mode - ] - if not self._attributes[DeviceAttributes.power] or ( - DeviceAttributes.swing_vertical in new_status - and self._attributes[DeviceAttributes.swing_vertical] - ): - self._attributes[DeviceAttributes.indirect_wind] = False - new_status[DeviceAttributes.indirect_wind.value] = False - if not self._attributes[DeviceAttributes.power]: - self._attributes[DeviceAttributes.screen_display] = False - new_status[DeviceAttributes.screen_display.value] = False - if self._attributes[DeviceAttributes.fresh_air_1] is not None: - self._fresh_air_version = DeviceAttributes.fresh_air_1 - elif self._attributes[DeviceAttributes.fresh_air_2] is not None: - self._fresh_air_version = DeviceAttributes.fresh_air_2 - return new_status - - def make_message_set(self): - message = MessageGeneralSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - message.fan_speed = self._attributes[DeviceAttributes.fan_speed] - message.swing_vertical = self._attributes[DeviceAttributes.swing_vertical] - message.swing_horizontal = self._attributes[DeviceAttributes.swing_horizontal] - message.boost_mode = self._attributes[DeviceAttributes.boost_mode] - message.smart_eye = self._attributes[DeviceAttributes.smart_eye] - message.dry = self._attributes[DeviceAttributes.dry] - message.eco_mode = self._attributes[DeviceAttributes.eco_mode] - message.aux_heating = self._attributes[DeviceAttributes.aux_heating] - message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] - message.natural_wind = self._attributes[DeviceAttributes.natural_wind] - message.temp_fahrenheit = self._attributes[DeviceAttributes.temp_fahrenheit] - message.frost_protect = self._attributes[DeviceAttributes.frost_protect] - message.comfort_mode = self._attributes[DeviceAttributes.comfort_mode] - return message - - def make_subptotocol_message_set(self): - message = MessageSubProtocolSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.aux_heating = self._attributes[DeviceAttributes.aux_heating] - message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - message.fan_speed = self._attributes[DeviceAttributes.fan_speed] - message.boost_mode = self._attributes[DeviceAttributes.boost_mode] - message.dry = self._attributes[DeviceAttributes.dry] - message.eco_mode = self._attributes[DeviceAttributes.eco_mode] - message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] - message.sn8_flag = self._bb_sn8_flag - message.timer = self._bb_timer - return message - - def make_message_uniq_set(self): - if self._used_subprotocol: - message = self.make_subptotocol_message_set() - else: - message = self.make_message_set() - return message - - def set_attribute(self, attr, value): - # if nat a sensor - message = None - if attr not in [ - DeviceAttributes.indoor_temperature, - DeviceAttributes.outdoor_temperature, - DeviceAttributes.indoor_humidity, - DeviceAttributes.full_dust, - DeviceAttributes.total_energy_consumption, - DeviceAttributes.current_energy_consumption, - DeviceAttributes.realtime_power, - ]: - if attr == DeviceAttributes.prompt_tone: - self._attributes[DeviceAttributes.prompt_tone] = value - self.update_all({DeviceAttributes.prompt_tone.value: value}) - elif attr == DeviceAttributes.screen_display: - message = MessageToggleDisplay(self._protocol_version) - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - elif attr in [ - DeviceAttributes.indirect_wind, - DeviceAttributes.breezeless, - DeviceAttributes.screen_display_alternate, - ]: - message = MessageNewProtocolSet(self._protocol_version) - setattr(message, str(attr), value) - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - elif attr == DeviceAttributes.fresh_air_power: - if self._fresh_air_version is not None: - message = MessageNewProtocolSet(self._protocol_version) - setattr( - message, - str(self._fresh_air_version), - [value, self._attributes[DeviceAttributes.fresh_air_fan_speed]], - ) - elif attr == DeviceAttributes.fresh_air_mode: - if value in MideaACDevice._fresh_air_fan_speeds.values(): - speed = list(MideaACDevice._fresh_air_fan_speeds.keys())[ - list(MideaACDevice._fresh_air_fan_speeds.values()).index(value) - ] - fresh_air = ( - [True, speed] - if speed > 0 - else [ - False, - self._attributes[DeviceAttributes.fresh_air_fan_speed], - ] - ) - message = MessageNewProtocolSet(self._protocol_version) - setattr(message, str(self._fresh_air_version), fresh_air) - elif not value: - message = MessageNewProtocolSet(self._protocol_version) - setattr( - message, - str(self._fresh_air_version), - [False, self._attributes[DeviceAttributes.fresh_air_fan_speed]], - ) - elif attr == DeviceAttributes.fresh_air_fan_speed: - if self._fresh_air_version is not None: - message = MessageNewProtocolSet(self._protocol_version) - fresh_air = ( - [True, value] - if value > 0 - else [ - False, - self._attributes[DeviceAttributes.fresh_air_fan_speed], - ] - ) - setattr(message, str(self._fresh_air_version), fresh_air) - elif attr in self._attributes.keys(): - message = self.make_message_uniq_set() - if attr in [ - DeviceAttributes.boost_mode, - DeviceAttributes.sleep_mode, - DeviceAttributes.frost_protect, - DeviceAttributes.comfort_mode, - DeviceAttributes.eco_mode, - ]: - message.boost_mode = False - message.sleep_mode = False - message.comfort_mode = False - message.eco_mode = False - message.frost_protect = False - setattr(message, str(attr), value) - if attr == DeviceAttributes.mode: - setattr(message, str(DeviceAttributes.power.value), True) - if message is not None: - self.build_send(message) - - def set_target_temperature(self, target_temperature, mode): - message = self.make_message_uniq_set() - message.target_temperature = target_temperature - if mode is not None: - message.power = True - message.mode = mode - self.build_send(message) - - def set_swing(self, swing_vertical, swing_horizontal): - message = self.make_message_uniq_set() - message.swing_vertical = swing_vertical - message.swing_horizontal = swing_horizontal - self.build_send(message) - - def set_customize(self, customize): - self._temperature_step = self._default_temperature_step - self._power_analysis_method = self._default_power_analysis_method - 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 "power_analysis_method" in params: - self._power_analysis_method = params.get("power_analysis_method") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"temperature_step": self._temperature_step}) - - -class MideaAppliance(MideaACDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/ac/message.py b/custom_components/midea_ac_lan/midea/devices/ac/message.py deleted file mode 100644 index 9c5816b3..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ac/message.py +++ /dev/null @@ -1,721 +0,0 @@ -from enum import IntEnum - -from ...core.crc8 import calculate -from ...core.message import ( - MessageBody, - MessageRequest, - MessageResponse, - MessageType, - NewProtocolMessageBody, -) - -BB_AC_MODES = [0, 3, 1, 2, 4, 5] - - -class NewProtocolTags(IntEnum): - indoor_humidity = 0x0015 - screen_display = 0x0017 - breezeless = 0x0018 - prompt_tone = 0x001A - indirect_wind = 0x0042 - fresh_air_1 = 0x0233 - fresh_air_2 = 0x004B - - -class MessageACBase(MessageRequest): - _message_serial = 0 - - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xAC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - MessageACBase._message_serial += 1 - if MessageACBase._message_serial >= 254: - MessageACBase._message_serial = 1 - self._message_id = MessageACBase._message_serial - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) - body.append(calculate(body)) - return body - - -class MessageQuery(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessagePowerQuery(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray([0x21, 0x01, 0x44, 0x00, 0x01]) - - @property - def body(self): - body = bytearray([self.body_type]) + self._body - body.append(calculate(body)) - return body - - -class MessageToggleDisplay(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - self.prompt_tone = False - - @property - def _body(self): - prompt_tone = 0x40 if self.prompt_tone else 0 - return bytearray( - [ - 0x02 | prompt_tone, - 0x00, - 0xFF, - 0x02, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageNewProtocolQuery(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0xB1, - ) - - @property - def _body(self): - query_params = [ - NewProtocolTags.indirect_wind, - NewProtocolTags.breezeless, - NewProtocolTags.indoor_humidity, - NewProtocolTags.screen_display, - NewProtocolTags.fresh_air_1, - NewProtocolTags.fresh_air_2, - ] - - _body = bytearray([len(query_params)]) - for param in query_params: - _body.extend([param & 0xFF, param >> 8]) - return _body - - -class MessageSubProtocol(MessageACBase): - def __init__(self, protocol_version, message_type, subprotocol_query_type): - super().__init__( - protocol_version=protocol_version, message_type=message_type, body_type=0xAA - ) - self._subprotocol_query_type = subprotocol_query_type - - @property - def _subprotocol_body(self): - return bytes([]) - - @property - def body(self): - body = bytearray([self.body_type]) + self._body - body.append(calculate(body)) - body.append(self.checksum(body)) - return body - - @property - def _body(self): - _subprotocol_body = self._subprotocol_body - _body = bytearray( - [ - 6 - + 2 - + (len(_subprotocol_body) if _subprotocol_body is not None else 0), - 0x00, - 0xFF, - 0xFF, - self._subprotocol_query_type, - ] - ) - if _subprotocol_body is not None: - _body.extend(_subprotocol_body) - return _body - - -class MessageSubProtocolQuery(MessageSubProtocol): - def __init__(self, protocol_version, subprotocol_query_type): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - subprotocol_query_type=subprotocol_query_type, - ) - - -class MessageSubProtocolSet(MessageSubProtocol): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - subprotocol_query_type=0x20, - ) - self.power = False - self.mode = 0 - self.target_temperature = 20.0 - self.fan_speed = 102 - self.boost_mode = False - self.aux_heating = False - self.dry = False - self.eco_mode = False - self.sleep_mode = False - self.sn8_flag = False - self.timer = False - self.prompt_tone = False - - @property - def _subprotocol_body(self): - power = 0x01 if self.power else 0 - dry = 0x10 if self.power and self.dry else 0 - boost_mode = 0x20 if self.boost_mode else 0 - aux_heating = 0x40 if self.aux_heating else 0x80 - sleep_mode = 0x80 if self.sleep_mode else 0 - try: - mode = 0 if self.mode == 0 else BB_AC_MODES[self.mode] - 1 - except IndexError: - mode = 2 # set Auto if invalid mode - target_temperature = int(self.target_temperature * 2 + 30) - water_model_temperature_set = int((self.target_temperature - 1) * 2 + 50) - fan_speed = self.fan_speed - eco = 0x40 if self.eco_mode else 0 - - prompt_tone = 0x01 if self.prompt_tone else 0 - timer = 0x04 if (self.sn8_flag and self.timer) else 0 - return bytearray( - [ - 0x02 | boost_mode | power | dry, - aux_heating, - sleep_mode, - 0x00, - 0x00, - mode, - target_temperature, - fan_speed, - 0x32, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - 0x01, - 0x00, - 0x01, - water_model_temperature_set, - prompt_tone, - target_temperature, - 0x32, - 0x66, - 0x00, - eco | timer, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x08, - ] - ) - - -class MessageGeneralSet(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x40, - ) - self.power = False - self.prompt_tone = True - self.mode = 0 - self.target_temperature = 20.0 - self.fan_speed = 102 - self.swing_vertical = False - self.swing_horizontal = False - self.boost_mode = False - self.smart_eye = False - self.dry = False - self.aux_heating = False - self.eco_mode = False - self.temp_fahrenheit = False - self.sleep_mode = False - self.natural_wind = False - self.frost_protect = False - self.comfort_mode = False - - @property - def _body(self): - # Byte1, Power, prompt_tone - power = 0x01 if self.power else 0 - prompt_tone = 0x40 if self.prompt_tone else 0 - # Byte2, mode target_temperature - mode = (self.mode << 5) & 0xE0 - target_temperature = (int(self.target_temperature) & 0xF) | ( - 0x10 if int(round(self.target_temperature * 2)) % 2 != 0 else 0 - ) - # Byte 3, fan_speed - fan_speed = self.fan_speed & 0x7F - # Byte 7, swing_mode - swing_mode = ( - 0x30 - | (0x0C if self.swing_vertical else 0) - | (0x03 if self.swing_horizontal else 0) - ) - # Byte 8, turbo - boost_mode = 0x20 if self.boost_mode else 0 - # Byte 9 aux_heating eco_mode - smart_eye = 0x01 if self.smart_eye else 0 - dry = 0x04 if self.dry else 0 - aux_heating = 0x08 if self.aux_heating else 0 - eco_mode = 0x80 if self.eco_mode else 0 - # Byte 10 temp_fahrenheit - temp_fahrenheit = 0x04 if self.temp_fahrenheit else 0 - sleep_mode = 0x01 if self.sleep_mode else 0 - boost_mode_1 = 0x02 if self.boost_mode else 0 - # Byte 17 natural_wind - natural_wind = 0x40 if self.natural_wind else 0 - # Byte 21 frost_protect - frost_protect = 0x80 if self.frost_protect else 0 - # Byte 22 comfort_mode - comfort_mode = 0x01 if self.comfort_mode else 0 - - return bytearray( - [ - power | prompt_tone, - mode | target_temperature, - fan_speed, - 0x00, - 0x00, - 0x00, - swing_mode, - boost_mode, - smart_eye | dry | aux_heating | eco_mode, - temp_fahrenheit | sleep_mode | boost_mode_1, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - natural_wind, - 0x00, - 0x00, - 0x00, - frost_protect, - comfort_mode, - ] - ) - - -class MessageNewProtocolSet(MessageACBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0xB0, - ) - self.indirect_wind = None - self.prompt_tone = None - self.breezeless = None - self.screen_display_alternate = None - self.fresh_air_1 = None - self.fresh_air_2 = None - - @property - def _body(self): - pack_count = 0 - payload = bytearray([0x00]) - if self.breezeless is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.breezeless, - value=bytearray([0x01 if self.breezeless else 0x00]), - ) - ) - if self.indirect_wind is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.indirect_wind, - value=bytearray([0x02 if self.indirect_wind else 0x01]), - ) - ) - if self.prompt_tone is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.prompt_tone, - value=bytearray([0x01 if self.prompt_tone else 0x00]), - ) - ) - if self.screen_display_alternate is not None: - pack_count += 1 - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.screen_display, - value=bytearray([0x64 if self.screen_display_alternate else 0x00]), - ) - ) - if self.fresh_air_1 is not None and len(self.fresh_air_1) == 2: - pack_count += 1 - fresh_air_power = 2 if self.fresh_air_1[0] > 0 else 1 - fresh_air_fan_speed = self.fresh_air_1[1] - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.fresh_air_1, - value=bytearray( - [ - fresh_air_power, - fresh_air_fan_speed, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ), - ) - ) - if self.fresh_air_2 is not None and len(self.fresh_air_2) == 2: - pack_count += 1 - fresh_air_power = 1 if self.fresh_air_2[0] > 0 else 0 - fresh_air_fan_speed = self.fresh_air_2[1] - payload.extend( - NewProtocolMessageBody.pack( - param=NewProtocolTags.fresh_air_2, - value=bytearray([fresh_air_power, fresh_air_fan_speed, 0xFF]), - ) - ) - payload[0] = pack_count - return payload - - -class XA0MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x1) > 0 - self.target_temperature = ( - ((body[1] & 0x3E) >> 1) - 4 + 16.0 + (0.5 if body[1] & 0x40 > 0 else 0.0) - ) - self.mode = (body[2] & 0xE0) >> 5 - self.fan_speed = body[3] & 0x7F - self.swing_vertical = (body[7] & 0xC) > 0 - self.swing_horizontal = (body[7] & 0x3) > 0 - self.boost_mode = ((body[8] & 0x20) > 0) or ((body[10] & 0x2) > 0) - self.smart_eye = (body[9] & 0x01) > 0 - self.dry = (body[9] & 0x04) > 0 - self.aux_heating = (body[9] & 0x08) > 0 - self.eco_mode = (body[9] & 0x10) > 0 - self.sleep_mode = (body[10] & 0x01) > 0 - self.natural_wind = (body[10] & 0x40) > 0 - self.full_dust = (body[13] & 0x20) > 0 - self.comfort_mode = (body[14] & 0x1) > 0 if len(body) > 16 else False - - -class XA1MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[13] != 0xFF: - temp_integer = int((body[13] - 50) / 2) - temp_decimal = ((body[18] & 0xF) * 0.1) if len(body) > 20 else 0 - if body[13] > 49: - self.indoor_temperature = temp_integer + temp_decimal - else: - self.indoor_temperature = temp_integer - temp_decimal - if body[14] == 0xFF: - self.outdoor_temperature = None - else: - temp_integer = int((body[14] - 50) / 2) - temp_decimal = (((body[18] & 0xF0) >> 4) * 0.1) if len(body) > 20 else 0 - if body[14] > 49: - self.outdoor_temperature = temp_integer + temp_decimal - else: - self.outdoor_temperature = temp_integer - temp_decimal - self.indoor_humidity = body[17] - - -class XBXMessageBody(NewProtocolMessageBody): - def __init__(self, body, bt): - super().__init__(body, bt) - params = self.parse() - if NewProtocolTags.indirect_wind in params: - self.indirect_wind = params[NewProtocolTags.indirect_wind][0] == 0x02 - if NewProtocolTags.indoor_humidity in params: - self.indoor_humidity = params[NewProtocolTags.indoor_humidity][0] - if NewProtocolTags.breezeless in params: - self.breezeless = params[NewProtocolTags.breezeless][0] == 1 - if NewProtocolTags.screen_display in params: - self.screen_display_alternate = ( - params[NewProtocolTags.screen_display][0] > 0 - ) - self.screen_display_new = True - if NewProtocolTags.fresh_air_1 in params: - self.fresh_air_1 = True - data = params[NewProtocolTags.fresh_air_1] - self.fresh_air_power = data[0] == 0x02 - self.fresh_air_fan_speed = data[1] - if NewProtocolTags.fresh_air_2 in params: - self.fresh_air_2 = True - data = params[NewProtocolTags.fresh_air_2] - self.fresh_air_power = data[0] > 0 - self.fresh_air_fan_speed = data[1] - - -class XC0MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x1) > 0 - self.mode = (body[2] & 0xE0) >> 5 - self.target_temperature = ( - (body[2] & 0x0F) + 16.0 + (0.5 if body[0x02] & 0x10 > 0 else 0.0) - ) - self.fan_speed = body[3] & 0x7F - self.swing_vertical = (body[7] & 0x0C) > 0 - self.swing_horizontal = (body[7] & 0x03) > 0 - self.boost_mode = ((body[8] & 0x20) > 0) or ((body[10] & 0x2) > 0) - self.smart_eye = (body[8] & 0x40) > 0 - self.natural_wind = (body[9] & 0x2) > 0 - self.dry = (body[9] & 0x4) > 0 - self.eco_mode = (body[9] & 0x10) > 0 - self.aux_heating = (body[9] & 0x08) > 0 - self.temp_fahrenheit = (body[10] & 0x04) > 0 - self.sleep_mode = (body[10] & 0x01) > 0 - if body[11] != 0xFF: - temp_integer = int((body[11] - 50) / 2) - temp_decimal = (body[15] & 0x0F) * 0.1 - if body[11] > 49: - self.indoor_temperature = temp_integer + temp_decimal - else: - self.indoor_temperature = temp_integer - temp_decimal - if body[12] == 0xFF: - self.outdoor_temperature = None - else: - temp_integer = int((body[12] - 50) / 2) - temp_decimal = ((body[15] & 0xF0) >> 4) * 0.1 - if body[12] > 49: - self.outdoor_temperature = temp_integer + temp_decimal - else: - self.outdoor_temperature = temp_integer - temp_decimal - self.full_dust = (body[13] & 0x20) > 0 - self.screen_display = ((body[14] >> 4 & 0x7) != 0x07) and self.power - self.frost_protect = (body[21] & 0x80) > 0 if len(body) >= 22 else False - self.comfort_mode = (body[22] & 0x1) > 0 if len(body) >= 23 else False - - -class XC1MessageBody(MessageBody): - def __init__(self, body, analysis_method=3): - super().__init__(body) - if body[3] == 0x44: - self.total_energy_consumption = XC1MessageBody.parse_consumption( - analysis_method, body[4], body[5], body[6], body[7] - ) - self.current_energy_consumption = XC1MessageBody.parse_consumption( - analysis_method, body[12], body[13], body[14], body[15] - ) - self.realtime_power = XC1MessageBody.parse_power( - analysis_method, body[16], body[17], body[18] - ) - elif body[3] == 0x40: - pass - - @staticmethod - def parse_value(byte): - return (byte >> 4) * 10 + (byte & 0x0F) - - @staticmethod - def parse_power(analysis_method, byte1, byte2, byte3): - if analysis_method == 1: - return ( - float( - XC1MessageBody.parse_value(byte1) * 10000 - + XC1MessageBody.parse_value(byte2) * 100 - + XC1MessageBody.parse_value(byte3) - ) - / 10 - ) - elif analysis_method == 2: - return float((byte1 << 16) + (byte2 << 8) + byte3) / 10 - else: - return float(byte1 * 10000 + byte2 * 100 + byte3) / 10 - - @staticmethod - def parse_consumption(analysis_method, byte1, byte2, byte3, byte4): - if analysis_method == 1: - return ( - float( - XC1MessageBody.parse_value(byte1) * 1000000 - + XC1MessageBody.parse_value(byte2) * 10000 - + XC1MessageBody.parse_value(byte3) * 100 - + XC1MessageBody.parse_value(byte4) - ) - / 100 - ) - elif analysis_method == 2: - return float((byte1 << 32) + (byte2 << 16) + (byte3 << 8) + byte4) / 10 - else: - return float(byte1 * 1000000 + byte2 * 10000 + byte3 * 100 + byte4) / 100 - - -class XBBMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - subprotocol_head = body[:6] - subprotocol_body = body[6:] - data_type = subprotocol_head[-1] - subprotocol_body_len = len(subprotocol_body) - if data_type == 0x20 or data_type == 0x11: - self.power = (subprotocol_body[0] & 0x1) > 0 - self.dry = (subprotocol_body[0] & 0x10) > 0 - self.boost_mode = (subprotocol_body[0] & 0x20) > 0 - self.aux_heating = (subprotocol_body[1] & 0x40) > 0 - self.sleep_mode = (subprotocol_body[2] & 0x80) > 0 - try: - self.mode = BB_AC_MODES.index(subprotocol_body[5] + 1) - except ValueError: - self.mode = 0 - self.target_temperature = (subprotocol_body[6] - 30) / 2 - self.fan_speed = subprotocol_body[7] - self.timer = ( - (subprotocol_body[25] & 0x04) > 0 - if subprotocol_body_len > 27 - else False - ) - self.eco_mode = ( - (subprotocol_body[25] & 0x40) > 0 - if subprotocol_body_len > 27 - else False - ) - elif data_type == 0x10: - if subprotocol_body[8] & 0x80 == 0x80: - self.indoor_temperature = ( - 0 - (~(subprotocol_body[7] + subprotocol_body[8] * 256) + 1) - & 0xFFFF - ) / 100 - else: - self.indoor_temperature = ( - subprotocol_body[7] + subprotocol_body[8] * 256 - ) / 100 - self.indoor_humidity = subprotocol_body[30] - self.sn8_flag = subprotocol_body[80] == 0x31 - elif data_type == 0x12: - pass - elif data_type == 0x30: - if subprotocol_body[6] & 0x80 == 0x80: - self.outdoor_temperature = ( - 0 - (~(subprotocol_body[5] + subprotocol_body[6] * 256) + 1) - & 0xFFFF - ) / 100 - else: - self.outdoor_temperature = ( - subprotocol_body[5] + subprotocol_body[6] * 256 - ) / 100 - elif data_type == 0x13 or data_type == 0x21: - pass - - -class MessageACResponse(MessageResponse): - def __init__(self, message, power_analysis_method=3): - super().__init__(message) - if self.message_type == MessageType.notify2 and self.body_type == 0xA0: - self.set_body(XA0MessageBody(super().body)) - elif self.message_type == MessageType.notify1 and self.body_type == 0xA1: - self.set_body(XA1MessageBody(super().body)) - elif self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify2, - ] and self.body_type in [0xB0, 0xB1, 0xB5]: - self.set_body(XBXMessageBody(super().body, self.body_type)) - elif ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0xC0 - ): - self.set_body(XC0MessageBody(super().body)) - elif self.message_type == MessageType.query and self.body_type == 0xC1: - self.set_body(XC1MessageBody(super().body, power_analysis_method)) - elif ( - self.message_type - in [MessageType.set, MessageType.query, MessageType.notify2] - and self.body_type == 0xBB - and len(super().body) >= 21 - ): - self.used_subprotocol = True - self.set_body(XBBMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b0/device.py b/custom_components/midea_ac_lan/midea/devices/b0/device.py deleted file mode 100644 index 8de17bf5..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b0/device.py +++ /dev/null @@ -1,97 +0,0 @@ -import logging - -from .message import MessageB0Response, MessageQuery01 - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - door = "door" - status = "status" - time_remaining = "time_remaining" - current_temperature = "current_temperature" - tank_ejected = "tank_ejected" - water_change_reminder = "water_change_reminder" - water_shortage = "water_shortage" - - -class MideaB0Device(MiedaDevice): - _status = { - 0x01: "Standby", - 0x02: "Idle", - 0x03: "Working", - 0x04: "Finished", - 0x05: "Delay", - 0x06: "Paused", - } - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xB0, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.door: False, - DeviceAttributes.status: None, - DeviceAttributes.time_remaining: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.tank_ejected: False, - DeviceAttributes.water_change_reminder: False, - DeviceAttributes.water_shortage: False, - }, - ) - - def build_query(self): - return [MessageQuery01(self._protocol_version)] - - def process_message(self, msg): - message = MessageB0Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.status: - if value in MideaB0Device._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaB0Device._status.get(value) - ) - else: - self._attributes[DeviceAttributes.status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaB0Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/b0/message.py b/custom_components/midea_ac_lan/midea/devices/b0/message.py deleted file mode 100644 index ebed3bdd..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b0/message.py +++ /dev/null @@ -1,83 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageB0Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xB0, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery00(MessageB0Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x00, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageQuery01(MessageB0Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class B0MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if len(body) > 15: - self.door = (body[0] & 0x80) > 0 - self.status = body[0] & 0x7F - self.time_remaining = body[2] * 60 + body[3] - self.error_code = body[5] - - -class B0Message01Body(MessageBody): - def __init__(self, body): - super().__init__(body) - if len(body) > 15: - self.door = (body[32] & 0x02) > 0 - self.status = body[31] - self.time_remaining = ( - (0 if body[22] == 0xFF else body[22]) * 3600 - + (0 if body[23] == 0xFF else body[23]) * 60 - + (0 if body[24] == 0xFF else body[24]) - ) - self.current_temperature = (body[25] << 8) + (body[26]) - if self.current_temperature == 0: - self.current_temperature = (body[27] << 8) + body[28] - self.tank_ejected = (body[32] & 0x04) > 0 - self.water_shortage = (body[32] & 0x08) > 0 - self.water_change_reminder = (body[32] & 0x10) > 0 - - -class MessageB0Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.notify1, MessageType.query]: - if self.body_type == 0x01: - self.set_body(B0Message01Body(super().body)) - elif self.body_type == 0x04: - pass - else: - self.set_body(B0MessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b1/device.py b/custom_components/midea_ac_lan/midea/devices/b1/device.py deleted file mode 100644 index 9b78b67c..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b1/device.py +++ /dev/null @@ -1,97 +0,0 @@ -import logging - -from .message import MessageB1Response, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - door = "door" - status = "status" - time_remaining = "time_remaining" - current_temperature = "current_temperature" - tank_ejected = "tank_ejected" - water_change_reminder = "water_change_reminder" - water_shortage = "water_shortage" - - -class MideaB1Device(MiedaDevice): - _status = { - 0x01: "Standby", - 0x02: "Idle", - 0x03: "Working", - 0x04: "Finished", - 0x05: "Delay", - 0x06: "Paused", - } - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xB1, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.door: False, - DeviceAttributes.status: None, - DeviceAttributes.time_remaining: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.tank_ejected: False, - DeviceAttributes.water_change_reminder: False, - DeviceAttributes.water_shortage: False, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageB1Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.status: - if value in MideaB1Device._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaB1Device._status.get(value) - ) - else: - self._attributes[DeviceAttributes.status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaB1Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/b1/message.py b/custom_components/midea_ac_lan/midea/devices/b1/message.py deleted file mode 100644 index d7d6fb63..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b1/message.py +++ /dev/null @@ -1,52 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageB1Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xB1, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageB1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x00, - ) - - @property - def _body(self): - return bytearray([]) - - -class B1MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.door = (body[16] & 0x02) > 0 - self.status = body[1] - self.time_remaining = ( - (0 if body[6] == 0xFF else body[6]) * 3600 - + (0 if body[7] == 0xFF else body[7]) * 60 - + (0 if body[8] == 0xFF else body[8]) - ) - self.current_temperature = body[19] - self.tank_ejected = (body[16] & 0x04) > 0 - self.water_shortage = (body[16] & 0x08) > 0 - self.water_change_reminder = (body[16] & 0x10) > 0 - - -class MessageB1Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.notify1, MessageType.query]: - self.set_body(B1MessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b3/device.py b/custom_components/midea_ac_lan/midea/devices/b3/device.py deleted file mode 100644 index 8894dd19..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b3/device.py +++ /dev/null @@ -1,128 +0,0 @@ -import logging - -from .message import MessageB3Response, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - top_compartment_status = "top_compartment_status" - top_compartment_mode = "top_compartment_mode" - top_compartment_temperature = "top_compartment_temperature" - top_compartment_remaining = "top_compartment_remaining" - top_compartment_door = "top_compartment_door" - top_compartment_preheating = "top_compartment_preheating" - top_compartment_cooling = "top_compartment_cooling" - middle_compartment_status = "middle_compartment_status" - middle_compartment_mode = "middle_compartment_mode" - middle_compartment_temperature = "middle_compartment_temperature" - middle_compartment_remaining = "middle_compartment_remaining" - middle_compartment_door = "middle_compartment_door" - middle_compartment_preheating = "middle_compartment_preheating" - middle_compartment_cooling = "middle_compartment_cooling" - bottom_compartment_status = "bottom_compartment_status" - bottom_compartment_mode = "bottom_compartment_mode" - bottom_compartment_temperature = "bottom_compartment_temperature" - bottom_compartment_remaining = "bottom_compartment_remaining" - bottom_compartment_door = "bottom_compartment_door" - bottom_compartment_preheating = "bottom_compartment_preheating" - bottom_compartment_cooling = "bottom_compartment_cooling" - lock = "lock" - - -class MideaB2Device(MiedaDevice): - _status = { - 0x00: "Off", - 0x01: "Standby", - 0x02: "Working", - 0x03: "Delay", - 0x04: "Finished", - } - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xB3, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.top_compartment_status: None, - DeviceAttributes.top_compartment_mode: None, - DeviceAttributes.top_compartment_temperature: None, - DeviceAttributes.top_compartment_remaining: None, - DeviceAttributes.top_compartment_door: False, - DeviceAttributes.top_compartment_preheating: False, - DeviceAttributes.top_compartment_cooling: False, - DeviceAttributes.middle_compartment_status: None, - DeviceAttributes.middle_compartment_mode: None, - DeviceAttributes.middle_compartment_temperature: None, - DeviceAttributes.middle_compartment_remaining: None, - DeviceAttributes.middle_compartment_door: False, - DeviceAttributes.middle_compartment_preheating: False, - DeviceAttributes.middle_compartment_cooling: False, - DeviceAttributes.bottom_compartment_status: None, - DeviceAttributes.bottom_compartment_mode: None, - DeviceAttributes.bottom_compartment_temperature: None, - DeviceAttributes.bottom_compartment_remaining: None, - DeviceAttributes.bottom_compartment_door: False, - DeviceAttributes.bottom_compartment_preheating: False, - DeviceAttributes.bottom_compartment_cooling: False, - DeviceAttributes.lock: False, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageB3Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status in [ - DeviceAttributes.top_compartment_status, - DeviceAttributes.middle_compartment_status, - DeviceAttributes.bottom_compartment_status, - ]: - if value in MideaB2Device._status.keys(): - self._attributes[status] = MideaB2Device._status.get(value) - else: - self._attributes[status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaB2Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/b3/message.py b/custom_components/midea_ac_lan/midea/devices/b3/message.py deleted file mode 100644 index c06e22a8..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b3/message.py +++ /dev/null @@ -1,165 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageB3Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xB3, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageB3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x31, - ) - - @property - def _body(self): - return bytearray([]) - - -class B3MessageBody31(MessageBody): - def __init__(self, body): - super().__init__(body) - self.top_compartment_status = body[1] - self.top_compartment_mode = body[2] - self.top_compartment_temperature = body[3] - self.top_compartment_remaining = ( - body[23] * 3600 - if len(body) > 23 and body[23] != 0xFF - else ( - 0 + body[4] * 60 - if body[4] != 0xFF - else 0 + body[5] if body[5] != 0xFF else 0 - ) - ) - self.bottom_compartment_status = body[6] - self.bottom_compartment_mode = body[7] - self.bottom_compartment_temperature = body[8] - self.bottom_compartment_remaining = ( - body[24] * 3600 - if len(body) > 24 and body[24] != 0xFF - else ( - 0 + body[9] * 60 - if body[9] != 0xFF - else 0 + body[10] if body[10] != 0xFF else 0 - ) - ) - self.middle_compartment_status = body[17] - self.middle_compartment_mode = body[18] - self.middle_compartment_temperature = body[19] - self.middle_compartment_remaining = ( - body[25] * 3600 - if len(body) > 25 and body[25] != 0xFF - else ( - 0 + body[20] * 60 - if body[20] != 0xFF - else 0 + body[21] if body[21] != 0xFF else 0 - ) - ) - self.lock = body[11] & 0x01 > 0 - self.bottom_compartment_door = body[11] & 0x02 > 0 - self.top_compartment_door = body[11] & 0x04 > 0 - self.middle_compartment_door = body[11] & 0x10 > 0 - self.bottom_compartment_preheating = body[16] & 0x01 > 0 - self.top_compartment_preheating = body[16] & 0x02 > 0 - self.middle_compartment_preheating = body[16] & 0x10 > 0 - self.bottom_compartment_cooling = body[16] & 0x04 > 0 - self.top_compartment_cooling = body[16] & 0x08 > 0 - self.middle_compartment_cooling = body[16] & 0x20 > 0 - - -class B3MessageBody21(MessageBody): - def __init__(self, body): - super().__init__(body) - self.top_compartment_status = body[1] - self.top_compartment_mode = body[2] - self.top_compartment_temperature = body[3] - self.top_compartment_remaining = ( - body[17] * 3600 - if len(body) > 17 and body[17] != 0xFF - else ( - 0 + body[4] * 60 - if body[4] != 0xFF - else 0 + body[5] if body[5] != 0xFF else 0 - ) - ) - self.bottom_compartment_status = body[6] - self.bottom_compartment_mode = body[7] - self.bottom_compartment_temperature = body[8] - self.bottom_compartment_remaining = ( - body[18] * 3600 - if len(body) > 18 and body[18] != 0xFF - else ( - 0 + body[9] * 60 - if body[9] != 0xFF - else 0 + body[10] if body[10] != 0xFF else 0 - ) - ) - self.middle_compartment_status = body[12] - self.middle_compartment_mode = body[13] - self.middle_compartment_temperature = body[14] - self.middle_compartment_remaining = ( - body[19] * 3600 - if len(body) > 19 and body[19] != 0xFF - else ( - 0 + body[15] * 60 - if body[15] != 0xFF - else 0 + body[16] if body[16] != 0xFF else 0 - ) - ) - self.lock = body[11] & 0x01 > 0 - - -class B3MessageBody24(MessageBody): - def __init__(self, body): - super().__init__(body) - self.top_compartment_status = body[5] - self.top_compartment_mode = body[6] - self.top_compartment_temperature = body[7] - self.top_compartment_remaining = ( - body[8] * 60 if body[8] != 0xFF else 0 + body[9] if body[9] != 0xFF else 0 - ) - self.bottom_compartment_status = body[10] - self.bottom_compartment_mode = body[11] - self.bottom_compartment_temperature = body[12] - self.bottom_compartment_remaining = ( - body[13] * 60 - if body[13] != 0xFF - else 0 + body[14] if body[14] != 0xFF else 0 - ) - self.bottom_compartment_status = body[15] - self.bottom_compartment_mode = body[16] - self.bottom_compartment_temperature = body[17] - self.bottom_compartment_remaining = ( - body[18] * 60 - if body[18] != 0xFF - else 0 + body[19] if body[19] != 0xFF else 0 - ) - - -class MessageB3Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type == MessageType.query - and self.body_type == 0x31 - or self.message_type == MessageType.notify1 - and self.body_type == 0x41 - ): - self.set_body(B3MessageBody31(super().body)) - elif self.message_type == MessageType.set and self.body_type == 0x21: - self.set_body(B3MessageBody21(super().body)) - elif self.message_type == MessageType.set and self.body_type == 0x24: - self.set_body(B3MessageBody21(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b4/device.py b/custom_components/midea_ac_lan/midea/devices/b4/device.py deleted file mode 100644 index 72824244..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b4/device.py +++ /dev/null @@ -1,97 +0,0 @@ -import logging - -from .message import MessageB4Response, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - door = "door" - status = "status" - time_remaining = "time_remaining" - current_temperature = "current_temperature" - tank_ejected = "tank_ejected" - water_change_reminder = "water_change_reminder" - water_shortage = "water_shortage" - - -class MideaB4Device(MiedaDevice): - _status = { - 0x01: "Standby", - 0x02: "Idle", - 0x03: "Working", - 0x04: "Finished", - 0x05: "Delay", - 0x06: "Paused", - } - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xB4, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.door: False, - DeviceAttributes.status: None, - DeviceAttributes.time_remaining: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.tank_ejected: False, - DeviceAttributes.water_change_reminder: False, - DeviceAttributes.water_shortage: False, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageB4Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.status: - if value in MideaB4Device._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaB4Device._status.get(value) - ) - else: - self._attributes[DeviceAttributes.status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaB4Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/b4/message.py b/custom_components/midea_ac_lan/midea/devices/b4/message.py deleted file mode 100644 index 07cc1ee9..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b4/message.py +++ /dev/null @@ -1,59 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageB4Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xB4, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageB4Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class B4MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.time_remaining = ( - (0 if body[22] == 0xFF else body[22]) * 3600 - + (0 if body[23] == 0xFF else body[23]) * 60 - + (0 if body[24] == 0xFF else body[24]) - ) - self.current_temperature = (body[25] << 8) + body[26] - if self.current_temperature == 0: - self.current_temperature = (body[27] << 8) + body[28] - self.status = body[31] - self.door = (body[32] & 0x02) > 0 - self.tank_ejected = (body[16] & 0x04) > 0 - self.water_shortage = (body[16] & 0x08) > 0 - self.water_change_reminder = (body[16] & 0x10) > 0 - - -class MessageB4Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.notify1, - MessageType.query, - MessageType.set, - ]: - if self.body_type == 0x01: - self.set_body(B4MessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/b6/device.py b/custom_components/midea_ac_lan/midea/devices/b6/device.py deleted file mode 100644 index 88b50c12..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b6/device.py +++ /dev/null @@ -1,170 +0,0 @@ -import json -import logging - -from .message import MessageB6Response, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - light = "light" - mode = "mode" - fan_level = "fan_level" - fan_speed = "fan_speed" - oilcup_full = "oilcup_full" - cleaning_reminder = "cleaning_reminder" - - -class MideaB6Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xB6, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.light: None, - DeviceAttributes.mode: None, - DeviceAttributes.fan_level: 0, - DeviceAttributes.fan_speed: 0, - DeviceAttributes.oilcup_full: False, - DeviceAttributes.cleaning_reminder: False, - }, - ) - self._default_speeds = {0: "Off", 1: "Level 1", 2: "Level 2"} - self._default_power_speed = 2 - self._power_speed = self._default_power_speed - self._speeds = self._default_speeds - self.set_customize(customize) - - @property - def speed_count(self): - return len(self._speeds) - 1 - - @property - def preset_modes(self): - return list(self._speeds.values()) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageB6Response(msg) - self._protocol_version = message.protocol_version - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.fan_level: - if value in self._speeds.keys(): - self._attributes[DeviceAttributes.mode] = self._speeds.get( - value - ) - self._attributes[DeviceAttributes.fan_speed] = list( - self._speeds.keys() - ).index(value) - else: - self._attributes[DeviceAttributes.mode] = None - self._attributes[DeviceAttributes.fan_speed] = 0 - new_status[DeviceAttributes.mode.value] = self._attributes[ - DeviceAttributes.mode - ] - new_status[DeviceAttributes.fan_speed.value] = self._attributes[ - DeviceAttributes.fan_speed - ] - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - message = None - if attr == DeviceAttributes.fan_speed: - if value < len(self._speeds): - message = MessageSet(self._protocol_version) - message.fan_level = list(self._speeds.keys())[value] - elif attr == DeviceAttributes.mode: - if value in self._speeds.values(): - message = MessageSet(self._protocol_version) - message.fan_level = list(self._speeds.keys())[ - list(self._speeds.values()).index(value) - ] - elif not value: - message = MessageSet(self._protocol_version) - message.power = False - elif attr == DeviceAttributes.power: - message = MessageSet(self._protocol_version) - message.power = value - message.fan_level = self._power_speed - elif attr == DeviceAttributes.light: - message = MessageSet(self._protocol_version) - message.light = value - if message is not None: - self.build_send(message) - - def turn_on(self, fan_speed=None, mode=None): - message = MessageSet(self._protocol_version) - message.power = True - if fan_speed is not None and fan_speed < len(self._speeds): - message.fan_level = list(self._speeds.keys())[fan_speed] - else: - message.fan_level = self._power_speed - if mode is not None in self._speeds.values(): - message.fan_level = list(self._speeds.keys())[ - list(self._speeds.values()).index(mode) - ] - self.build_send(message) - - def set_customize(self, customize): - self._speeds = self._default_speeds - self._power_speed = self._default_power_speed - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params: - if "default_speed" in params: - self._power_speed = int(params.get("default_speed")) - if "speeds" in params: - self._speeds = {} - speeds = {} - for k, v in params.get("speeds").items(): - speeds[int(k)] = v - keys = sorted(speeds.keys()) - for k in keys: - self._speeds[k] = speeds[k] - self.update_all( - {"speeds": self._speeds, "default_speed": self._power_speed} - ) - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - - -class MideaAppliance(MideaB6Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/b6/message.py b/custom_components/midea_ac_lan/midea/devices/b6/message.py deleted file mode 100644 index 53a1b78e..00000000 --- a/custom_components/midea_ac_lan/midea/devices/b6/message.py +++ /dev/null @@ -1,219 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageB6Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xB6, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageB6Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x11 if protocol_version == 2 else 0x31, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageQueryTips(MessageB6Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x02, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessageSet(MessageB6Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x22 if protocol_version in [0x00, 0x01] else 0x11, - ) - self.light = None - self.power = None - self.fan_level = None - - @property - def _body(self): - if self.protocol_version in [0x00, 0x01]: - light = 0xFF - value2 = 0xFF - value3 = 0xFF - if self.light is not None: - if self.light: - light = 0x1A - else: - light = 0 - elif self.power is not None: - if self.power: - value2 = 0x02 - if self.fan_level is not None: - value3 = self.fan_level - else: - value3 = 0x01 - else: - value2 = 0x03 - elif self.fan_level is not None: - if self.fan_level == 0: - value2 = 0x03 - else: - value2 = 0x02 - value3 = self.fan_level - return bytearray( - [0x01, light, value2, value3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] - ) - else: - value13 = 0xFF - value14 = 0xFF - value15 = 0xFF - value16 = 0xFF - if self.power is not None: - value13 = 0x01 - if self.power: - value15 = 0x02 - if self.fan_level is not None: - value16 = self.fan_level - else: - value16 = 0x01 - else: - value15 = 0x01 - elif self.fan_level is not None: - value13 = 0x01 - if self.fan_level == 0: - value15 = 0x01 - else: - value15 = 0x02 - value16 = self.fan_level - elif self.light is not None: - value13 = 0x02 - value14 = 0x02 - value15 = 0x01 if self.light else 0x00 - return bytearray([0x01, value13, value14, value15, value16, 0xFF, 0xFF]) - - -class B6FeedbackBody(MessageBody): - def __init__(self, body): - super().__init__(body) - - -class B6GeneralBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[1] != 0xFF: - self.light = body[1] > 0x00 - self.power = False - fan_level = None - if body[2] != 0xFF: - self.power = body[2] in [0x02, 0x06, 0x07, 0x14, 0x15, 0x16] - if body[2] in [0x14, 0x16]: - fan_level = 0x16 - if fan_level is None and body[3] != 0xFF: - fan_level = body[3] - if fan_level > 100: - if fan_level < 130: - fan_level = 1 - elif fan_level < 140: - fan_level = 2 - elif fan_level < 170: - fan_level = 3 - else: - fan_level = 4 - else: - self.fan_level = fan_level - self.fan_level = 0 if fan_level is None else fan_level - self.oilcup_full = (body[5] & 0x01) > 0 - self.cleaning_reminder = (body[5] & 0x02) > 0 - - -class B6NewProtocolBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[1] == 0x01: - pack_bytes = body[3 : 3 + body[2]] - if pack_bytes[1] != 0xFF: - self.power = True - self.power = pack_bytes[1] not in [0x00, 0x01, 0x05, 0x07] - if pack_bytes[2] != 0xFF: - self.fan_level = pack_bytes[2] - if pack_bytes[6] != 0xFF: - self.light = pack_bytes[6] > 0 - self.oilcup_full = (pack_bytes[18] & 0x02) > 0 - self.cleaning_reminder = (pack_bytes[18] & 0x04) > 0 - - -class B6SpecialBody(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[2] != 0xFF: - self.light = body[2] > 0x00 - self.power = False - if body[3] != 0xFF: - self.power = body[3] in [0x00, 0x02, 0x04] - if body[4] != 0xFF: - self.fan_level = body[4] - - -class B6ExceptionBody(MessageBody): - def __init__(self, body): - super().__init__(body) - - -class MessageB6Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type == MessageType.set - and self.body_type == 0x22 - and super().body[1] == 0x01 - ): - self.set_body(B6SpecialBody(super().body)) - elif ( - self.message_type == MessageType.set - and self.body_type == 0x11 - and super().body[1] == 0x01 - ): - ############################# - pass - elif self.message_type == MessageType.query: - if self.body_type in [0x11, 0x31]: - if self.protocol_version in [0, 1]: - self.set_body(B6GeneralBody(super().body)) - else: - self.set_body(B6NewProtocolBody(super().body)) - elif self.body_type == 0x32 and super().body[1] == 0x01: - self.set_body(B6ExceptionBody(super().body)) - elif self.message_type == MessageType.notify1: - if self.body_type in [0x11, 0x41]: - if self.protocol_version in [0, 1]: - self.set_body(B6GeneralBody(super().body)) - else: - self.set_body(B6NewProtocolBody(super().body)) - elif self.body_type == 0x0A: - if super().body[1] == 0xA1: - self.set_body(B6ExceptionBody(super().body)) - elif super().body[1] == 0xA2: - self.oilcup_full = (super().body[2] & 0x01) > 0 - self.cleaning_reminder = (super().body[2] & 0x02) > 0 - elif self.message_type == MessageType.exception2 and self.body_type == 0xA1: - pass - - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/bf/device.py b/custom_components/midea_ac_lan/midea/devices/bf/device.py deleted file mode 100644 index d8fe2070..00000000 --- a/custom_components/midea_ac_lan/midea/devices/bf/device.py +++ /dev/null @@ -1,97 +0,0 @@ -import logging - -from .message import MessageBFResponse, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - door = "door" - status = "status" - time_remaining = "time_remaining" - current_temperature = "current_temperature" - tank_ejected = "tank_ejected" - water_change_reminder = "water_change_reminder" - water_shortage = "water_shortage" - - -class MideaBFDevice(MiedaDevice): - _status = { - 0x01: "PowerSave", - 0x02: "Standby", - 0x03: "Working", - 0x04: "Finished", - 0x05: "Delay", - 0x06: "Paused", - } - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xBF, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.door: None, - DeviceAttributes.status: None, - DeviceAttributes.time_remaining: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.tank_ejected: None, - DeviceAttributes.water_change_reminder: None, - DeviceAttributes.water_shortage: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageBFResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.status: - if value in MideaBFDevice._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaBFDevice._status.get(value) - ) - else: - self._attributes[DeviceAttributes.status] = "Unknown" - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaBFDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/bf/message.py b/custom_components/midea_ac_lan/midea/devices/bf/message.py deleted file mode 100644 index 358ce3eb..00000000 --- a/custom_components/midea_ac_lan/midea/devices/bf/message.py +++ /dev/null @@ -1,79 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageBFBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xBF, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageBFBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageBFBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x02, - ) - self.power = None - self.child_lock = None - - @property - def _body(self): - power = 0xFF if self.power is None else 0x11 if self.power else 0x01 - child_lock = ( - 0xFF if self.child_lock is None else 0x01 if self.child_lock else 0x00 - ) - return bytearray([power, child_lock] + [0xFF] * 7) - - -class MessageBFBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.status = body[31] - self.time_remaining = ( - (0 if body[22] == 0xFF else body[22]) * 3600 - + (0 if body[23] == 0xFF else body[23]) * 60 - + (0 if body[24] == 0xFF else body[24]) - ) - cur_temperature = body[25] * 256 + body[26] - if cur_temperature == 0: - cur_temperature = body[27] * 256 + body[28] - self.current_temperature = cur_temperature - self.child_lock = (body[32] & 0x01) > 0 - self.door = (body[32] & 0x02) > 0 - self.tank_ejected = (body[32] & 0x04) > 0 - self.water_state = (body[32] & 0x08) > 0 - self.water_change_reminder = (body[32] & 0x10) > 0 - - -class MessageBFResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ): - self.set_body(MessageBFBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/c2/device.py b/custom_components/midea_ac_lan/midea/devices/c2/device.py deleted file mode 100644 index 3e1a0444..00000000 --- a/custom_components/midea_ac_lan/midea/devices/c2/device.py +++ /dev/null @@ -1,153 +0,0 @@ -import json -import logging - -from .message import MessageC2Response, MessagePower, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - child_lock = "child_lock" - sensor_light = "sensor_light" - foam_shield = "foam_shield" - seat_status = "seat_status" - lid_status = "lid_status" - light_status = "light_status" - dry_level = "dry_level" - water_temp_level = "water_temp_level" - seat_temp_level = "seat_temp_level" - water_temperature = "water_temperature" - seat_temperature = "seat_temperature" - filter_life = "filter_life" - - -class MideaC2Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xC2, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.child_lock: False, - DeviceAttributes.sensor_light: False, - DeviceAttributes.foam_shield: False, - DeviceAttributes.light_status: None, - DeviceAttributes.seat_status: None, - DeviceAttributes.lid_status: None, - DeviceAttributes.dry_level: 0, - DeviceAttributes.water_temp_level: 0, - DeviceAttributes.seat_temp_level: 0, - DeviceAttributes.water_temperature: None, - DeviceAttributes.seat_temperature: None, - DeviceAttributes.filter_life: None, - }, - ) - self._max_dry_level = None - self._max_water_temp_level = None - self._max_seat_temp_level = None - self._default_max_dry_level = 3 - self._default_max_water_temp_level = 5 - self._default_max_seat_temp_level = 5 - self.set_customize(customize) - - @property - def max_dry_level(self): - return self._max_dry_level - - @property - def max_water_temp_level(self): - return self._max_water_temp_level - - @property - def max_seat_temp_level(self): - return self._max_seat_temp_level - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageC2Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - return new_status - - def set_attribute(self, attr, value): - message = None - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - elif attr in [ - DeviceAttributes.child_lock, - DeviceAttributes.sensor_light, - DeviceAttributes.foam_shield, - DeviceAttributes.water_temp_level, - DeviceAttributes.seat_temp_level, - DeviceAttributes.dry_level, - ]: - message = MessageSet(self._protocol_version) - setattr(message, attr, value) - if message: - self.build_send(message) - - def set_customize(self, customize): - self._max_dry_level = self._default_max_dry_level - self._max_water_temp_level = self._default_max_water_temp_level - self._max_seat_temp_level = self._default_max_seat_temp_level - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "max_dry_level" in params: - self._max_dry_level = params.get("max_dry_level") - if params and "max_water_temp_level" in params: - self._max_water_temp_level = params.get("max_water_temp_level") - if params and "max_seat_temp_level" in params: - self._max_seat_temp_level = params.get("max_seat_temp_level") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all( - { - "dry_level": {"max_dry_level": self._max_dry_level}, - "water_temp_level": { - "max_water_temp_level": self._max_water_temp_level - }, - "seat_temp_level": { - "max_seat_temp_level": self._max_seat_temp_level - }, - } - ) - - -class MideaAppliance(MideaC2Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/c2/message.py b/custom_components/midea_ac_lan/midea/devices/c2/message.py deleted file mode 100644 index 4ab2c023..00000000 --- a/custom_components/midea_ac_lan/midea/devices/c2/message.py +++ /dev/null @@ -1,171 +0,0 @@ -from enum import IntEnum - -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class C2MessageEnum(IntEnum): - sensor_light = 0x01 - child_lock = 0x10 - foam_shield = 0x1F - water_temp_level = 0x09 - seat_temp_level = 0x0A - dry_level = 0x0C - - -C2_MESSAGE_KEYS = { - C2MessageEnum.child_lock: {True: 0x01 << 4, False: 0x00}, - C2MessageEnum.sensor_light: {True: 0x01 << 1, False: 0x00}, - C2MessageEnum.foam_shield: {True: 0x01 << 2, False: 0x00}, - C2MessageEnum.dry_level: {0: 0x00, 1: 0x01 << 1, 2: 0x02 << 1, 3: 0x03 << 1}, - C2MessageEnum.seat_temp_level: { - 0: 0x00, - 1: 0x01 << 3, - 2: 0x02 << 3, - 3: 0x03 << 3, - 4: 0x04 << 3, - 5: 0x05 << 3, - }, - C2MessageEnum.water_temp_level: { - 0: 0x00, - 1: 0x01, - 2: 0x02, - 3: 0x03, - 4: 0x04, - 5: 0x05, - }, -} - - -class MessageC2Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xC2, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageC2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessagePower(MessageC2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x00, - ) - self.power = False - - @property - def _body(self): - if self.power: - self.body_type = 0x01 - else: - self.body_type = 0x02 - return bytearray([0x01]) - - -class MessagePowerOff(MessageC2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessageSet(MessageC2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x00, - ) - - self.child_lock = None - self.sensor_light = None - self.water_temp_level = None - self.seat_temp_level = None - self.dry_level = None - self.foam_shield = None - - @property - def _body(self): - self.body_type = 0x14 - key = 0x00 - value = 0x00 - if self.child_lock is not None: - key = C2MessageEnum.child_lock - value = self.child_lock - elif self.sensor_light is not None: - key = C2MessageEnum.sensor_light - value = self.sensor_light - elif self.water_temp_level is not None: - key = C2MessageEnum.water_temp_level - value = self.water_temp_level - elif self.seat_temp_level is not None: - key = C2MessageEnum.seat_temp_level - value = self.seat_temp_level - elif self.dry_level is not None: - key = C2MessageEnum.dry_level - value = self.dry_level - elif self.foam_shield is not None: - key = C2MessageEnum.foam_shield - value = self.foam_shield - value = C2_MESSAGE_KEYS.get(key).get(value) - return bytearray([key, value]) - - -class C2MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[2] & 0x01) > 0 - self.seat_status = (body[3] & 0x01) > 0 - self.dry_level = (body[6] & 0x7E) >> 1 - self.water_temp_level = body[9] & 0x07 - self.seat_temp_level = (body[9] & 0x38) >> 3 - self.lid_status = (body[12] & 0x40) > 0 - self.foam_shield = (body[13] & 0x80) > 0 - self.sensor_light = (body[14] & 0x01) > 0 - self.light_status = (body[14] & 0x02) > 0 - self.child_lock = (body[14] & 0x04) > 0 - self.water_temperature = body[11] - self.seat_temperature = body[11] - self.filter_life = 100 - body[19] - - -class C2Notify1MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - - -class MessageC2Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.notify1, - MessageType.query, - MessageType.set, - ]: - self.set_body(C2MessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/c3/device.py b/custom_components/midea_ac_lan/midea/devices/c3/device.py deleted file mode 100644 index c513ec4c..00000000 --- a/custom_components/midea_ac_lan/midea/devices/c3/device.py +++ /dev/null @@ -1,286 +0,0 @@ -import logging - -from .message import ( - MessageC3Response, - MessageQuery, - MessageSet, - MessageSetECO, - MessageSetSilent, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - zone1_power = "zone1_power" - zone2_power = "zone2_power" - dhw_power = "dhw_power" - zone1_curve = "zone1_curve" - zone2_curve = "zone2_curve" - disinfect = "disinfect" - fast_dhw = "fast_dhw" - zone_temp_type = "zone_temp_type" - zone1_room_temp_mode = "zone1_room_temp_mode" - zone2_room_temp_mode = "zone2_room_temp_mode" - zone1_water_temp_mode = "zone1_water_temp_mode" - zone2_water_temp_mode = "zone2_water_temp_mode" - mode = "mode" - mode_auto = "mode_auto" - zone_target_temp = "zone_target_temp" - dhw_target_temp = "dhw_target_temp" - room_target_temp = "room_target_temp" - zone_heating_temp_max = "zone_heating_temp_max" - zone_heating_temp_min = "zone_heating_temp_min" - zone_cooling_temp_max = "zone_cooling_temp_max" - zone_cooling_temp_min = "zone_cooling_temp_min" - tank_actual_temperature = "tank_actual_temperature" - room_temp_max = "room_temp_max" - room_temp_min = "room_temp_min" - dhw_temp_max = "dhw_temp_max" - dhw_temp_min = "dhw_temp_min" - target_temperature = "target_temperature" - temperature_max = "temperature_max" - temperature_min = "temperature_min" - status_heating = "status_heating" - status_dhw = "status_dhw" - status_tbh = "status_tbh" - status_ibh = "status_ibh" - total_energy_consumption = "total_energy_consumption" - total_produced_energy = "total_produced_energy" - outdoor_temperature = "outdoor_temperature" - silent_mode = "silent_mode" - eco_mode = "eco_mode" - tbh = "tbh" - error_code = "error_code" - - -class MideaC3Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xC3, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.zone1_power: False, - DeviceAttributes.zone2_power: False, - DeviceAttributes.dhw_power: False, - DeviceAttributes.zone1_curve: False, - DeviceAttributes.zone2_curve: False, - DeviceAttributes.disinfect: False, - DeviceAttributes.fast_dhw: False, - DeviceAttributes.zone_temp_type: [False, False], - DeviceAttributes.zone1_room_temp_mode: False, - DeviceAttributes.zone2_room_temp_mode: False, - DeviceAttributes.zone1_water_temp_mode: False, - DeviceAttributes.zone2_water_temp_mode: False, - DeviceAttributes.silent_mode: False, - DeviceAttributes.eco_mode: False, - DeviceAttributes.tbh: False, - DeviceAttributes.mode: 1, - DeviceAttributes.mode_auto: 1, - DeviceAttributes.zone_target_temp: [25, 25], - DeviceAttributes.dhw_target_temp: 25, - DeviceAttributes.room_target_temp: 30, - DeviceAttributes.zone_heating_temp_max: [55, 55], - DeviceAttributes.zone_heating_temp_min: [25, 25], - DeviceAttributes.zone_cooling_temp_max: [25, 25], - DeviceAttributes.zone_cooling_temp_min: [5, 5], - DeviceAttributes.room_temp_max: 60, - DeviceAttributes.room_temp_min: 34, - DeviceAttributes.dhw_temp_max: 60, - DeviceAttributes.dhw_temp_min: 20, - DeviceAttributes.tank_actual_temperature: None, - DeviceAttributes.target_temperature: [25, 25], - DeviceAttributes.temperature_max: [0, 0], - DeviceAttributes.temperature_min: [0, 0], - DeviceAttributes.total_energy_consumption: None, - DeviceAttributes.status_heating: None, - DeviceAttributes.status_dhw: None, - DeviceAttributes.status_tbh: None, - DeviceAttributes.status_ibh: None, - DeviceAttributes.total_produced_energy: None, - DeviceAttributes.outdoor_temperature: None, - DeviceAttributes.error_code: 0, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageC3Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - if "zone_temp_type" in new_status: - for zone in [0, 1]: - if self._attributes[DeviceAttributes.zone_temp_type][ - zone - ]: # Water temp mode - self._attributes[DeviceAttributes.target_temperature][zone] = ( - self._attributes[DeviceAttributes.zone_target_temp][zone] - ) - if ( - self._attributes[DeviceAttributes.mode_auto] == 2 - ): # cooling mode - self._attributes[DeviceAttributes.temperature_max][zone] = ( - self._attributes[DeviceAttributes.zone_cooling_temp_max][ - zone - ] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( - self._attributes[DeviceAttributes.zone_cooling_temp_min][ - zone - ] - ) - elif self._attributes[DeviceAttributes.mode] == 3: # heating mode - self._attributes[DeviceAttributes.temperature_max][zone] = ( - self._attributes[DeviceAttributes.zone_heating_temp_max][ - zone - ] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( - self._attributes[DeviceAttributes.zone_heating_temp_min][ - zone - ] - ) - else: # Room temp mode - self._attributes[DeviceAttributes.target_temperature][zone] = ( - self._attributes[DeviceAttributes.room_target_temp] - ) - self._attributes[DeviceAttributes.temperature_max][zone] = ( - self._attributes[DeviceAttributes.room_temp_max] - ) - self._attributes[DeviceAttributes.temperature_min][zone] = ( - self._attributes[DeviceAttributes.room_temp_min] - ) - if self._attributes[DeviceAttributes.zone1_power]: - if self._attributes[DeviceAttributes.zone_temp_type][zone]: - self._attributes[DeviceAttributes.zone1_water_temp_mode] = True - self._attributes[DeviceAttributes.zone1_room_temp_mode] = False - else: - self._attributes[DeviceAttributes.zone1_water_temp_mode] = False - self._attributes[DeviceAttributes.zone1_room_temp_mode] = True - else: - self._attributes[DeviceAttributes.zone1_water_temp_mode] = False - self._attributes[DeviceAttributes.zone1_room_temp_mode] = False - if self._attributes[DeviceAttributes.zone2_power]: - if self._attributes[DeviceAttributes.zone_temp_type][zone]: - self._attributes[DeviceAttributes.zone2_water_temp_mode] = True - self._attributes[DeviceAttributes.zone2_room_temp_mode] = False - else: - self._attributes[DeviceAttributes.zone2_water_temp_mode] = False - self._attributes[DeviceAttributes.zone2_room_temp_mode] = True - else: - self._attributes[DeviceAttributes.zone2_water_temp_mode] = False - self._attributes[DeviceAttributes.zone2_room_temp_mode] = False - new_status[DeviceAttributes.zone1_water_temp_mode.value] = self._attributes[ - DeviceAttributes.zone1_water_temp_mode - ] - new_status[DeviceAttributes.zone2_water_temp_mode.value] = self._attributes[ - DeviceAttributes.zone2_water_temp_mode - ] - new_status[DeviceAttributes.zone1_room_temp_mode.value] = self._attributes[ - DeviceAttributes.zone1_room_temp_mode - ] - new_status[DeviceAttributes.zone2_room_temp_mode.value] = self._attributes[ - DeviceAttributes.zone2_room_temp_mode - ] - - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.zone1_power = self._attributes[DeviceAttributes.zone1_power] - message.zone2_power = self._attributes[DeviceAttributes.zone2_power] - message.dhw_power = self._attributes[DeviceAttributes.dhw_power] - message.mode = self._attributes[DeviceAttributes.mode] - message.zone_target_temp = self._attributes[DeviceAttributes.zone_target_temp] - message.dhw_target_temp = self._attributes[DeviceAttributes.dhw_target_temp] - message.room_target_temp = self._attributes[DeviceAttributes.room_target_temp] - message.zone1_curve = self._attributes[DeviceAttributes.zone1_curve] - message.zone2_curve = self._attributes[DeviceAttributes.zone2_curve] - message.disinfect = self._attributes[DeviceAttributes.disinfect] - message.tbh = self._attributes[DeviceAttributes.tbh] - message.fast_dhw = self._attributes[DeviceAttributes.fast_dhw] - return message - - def set_attribute(self, attr, value): - message = None - if attr in [ - DeviceAttributes.zone1_power, - DeviceAttributes.zone2_power, - DeviceAttributes.dhw_power, - DeviceAttributes.zone1_curve, - DeviceAttributes.zone2_curve, - DeviceAttributes.disinfect, - DeviceAttributes.fast_dhw, - DeviceAttributes.dhw_target_temp, - DeviceAttributes.tbh, - ]: - message = self.make_message_set() - setattr(message, str(attr), value) - elif attr == DeviceAttributes.eco_mode: - message = MessageSetECO(self._protocol_version) - setattr(message, str(attr), value) - elif attr == DeviceAttributes.silent_mode: - message = MessageSetSilent(self._protocol_version) - setattr(message, str(attr), value) - if message is not None: - self.build_send(message) - - def set_mode(self, zone, mode): - message = self.make_message_set() - if zone == 0: - message.zone1_power = True - else: - message.zone2_power = True - message.mode = mode - self.build_send(message) - - def set_target_temperature(self, zone, target_temperature, mode): - message = self.make_message_set() - if self._attributes[DeviceAttributes.zone_temp_type][zone]: - message.zone_target_temp[zone] = target_temperature - else: - message.room_target_temp = target_temperature - if mode is not None: - if zone == 0: - message.zone1_power = True - else: - message.zone2_power = True - message.mode = mode - self.build_send(message) - - -class MideaAppliance(MideaC3Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/c3/message.py b/custom_components/midea_ac_lan/midea/devices/c3/message.py deleted file mode 100644 index 3f78f699..00000000 --- a/custom_components/midea_ac_lan/midea/devices/c3/message.py +++ /dev/null @@ -1,188 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageC3Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xC3, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageC3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageC3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - self.zone1_power = False - self.zone2_power = False - self.dhw_power = False - self.mode = 0 - self.zone_target_temp = [25, 25] - self.dhw_target_temp = 40 - self.room_target_temp = 25 - self.zone1_curve = False - self.zone2_curve = False - self.disinfect = False - self.fast_dhw = False - self.tbh = False - - @property - def _body(self): - # Byte 1 - zone1_power = 0x01 if self.zone1_power else 0x00 - zone2_power = 0x02 if self.zone2_power else 0x00 - dhw_power = 0x04 if self.dhw_power else 0x00 - # Byte 7 - zone1_curve = 0x01 if self.zone1_curve else 0x00 - zone2_curve = 0x02 if self.zone2_curve else 0x00 - disinfect = 0x04 if self.disinfect or self.tbh else 0x00 - fast_dhw = 0x08 if self.fast_dhw else 0x00 - room_target_temp = int(self.room_target_temp * 2) - zone1_target_temp = int(self.zone_target_temp[0]) - zone2_target_temp = int(self.zone_target_temp[1]) - dhw_target_temp = int(self.dhw_target_temp) - return bytearray( - [ - zone1_power | zone2_power | dhw_power, - self.mode, - zone1_target_temp, - zone2_target_temp, - dhw_target_temp, - room_target_temp, - zone1_curve | zone2_curve | disinfect | fast_dhw, - ] - ) - - -class MessageSetSilent(MessageC3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x05, - ) - self.silent_mode = False - self.super_silent = False - - @property - def _body(self): - silent_mode = 0x01 if self.silent_mode else 0 - super_silent = 0x02 if self.super_silent else 0 - - return bytearray( - [silent_mode | super_silent, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - ) - - -class MessageSetECO(MessageC3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x07, - ) - self.eco_mode = False - - @property - def _body(self): - eco_mode = 0x01 if self.eco_mode else 0 - - return bytearray([eco_mode, 0x00, 0x00, 0x00, 0x00, 0x00]) - - -class C3MessageBody(MessageBody): - def __init__(self, body, data_offset=0): - super().__init__(body) - self.zone1_power = body[data_offset + 0] & 0x01 > 0 - self.zone2_power = body[data_offset + 0] & 0x02 > 0 - self.dhw_power = body[data_offset + 0] & 0x04 > 0 - self.zone1_curve = body[data_offset + 0] & 0x08 > 0 - self.zone2_curve = body[data_offset + 0] & 0x10 > 0 - self.disinfect = body[data_offset + 0] & 0x20 > 0 - self.tbh = body[data_offset + 0] & 0x20 > 0 - self.fast_dhw = body[data_offset + 0] & 0x40 > 0 - self.zone_temp_type = [ - body[data_offset + 1] & 0x10 > 0, - body[data_offset + 1] & 0x20 > 0, - ] - self.silent_mode = body[data_offset + 2] & 0x02 > 0 - self.eco_mode = body[data_offset + 2] & 0x08 > 0 - self.mode = body[data_offset + 3] - self.mode_auto = body[data_offset + 4] - self.zone_target_temp = [body[data_offset + 5], body[data_offset + 6]] - self.dhw_target_temp = body[data_offset + 7] - self.room_target_temp = body[data_offset + 8] / 2 - self.zone_heating_temp_max = [body[data_offset + 9], body[data_offset + 13]] - self.zone_heating_temp_min = [body[data_offset + 10], body[data_offset + 14]] - self.zone_cooling_temp_max = [body[data_offset + 11], body[data_offset + 15]] - self.zone_cooling_temp_min = [body[data_offset + 12], body[data_offset + 16]] - self.room_temp_max = body[data_offset + 17] / 2 - self.room_temp_min = body[data_offset + 18] / 2 - self.dhw_temp_max = body[data_offset + 19] - self.dhw_temp_min = body[data_offset + 20] - self.tank_actual_temperature = body[data_offset + 21] - self.error_code = body[data_offset + 22] - - -class C3Notify1MessageBody(MessageBody): - def __init__(self, body, data_offset=0): - super().__init__(body) - status_byte = body[data_offset] - self.status_tbh = (status_byte & 0x08) > 0 - self.status_dhw = (status_byte & 0x04) > 0 - self.status_ibh = (status_byte & 0x02) > 0 - self.status_heating = (status_byte & 0x01) > 0 - - self.total_energy_consumption = ( - (body[data_offset + 1] << 32) - + (body[data_offset + 2] << 16) - + (body[data_offset + 3] << 8) - + (body[data_offset + 4]) - ) - - self.total_produced_energy = ( - (body[data_offset + 5] << 32) - + (body[data_offset + 6] << 16) - + (body[data_offset + 7] << 8) - + (body[data_offset + 8]) - ) - base_value = body[data_offset + 9] - self.outdoor_temperature = ( - (base_value - 256) if base_value > 127 else base_value - ) - - -class MessageC3Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ) or self.message_type == MessageType.notify2: - self.set_body(C3MessageBody(super().body, data_offset=1)) - elif self.message_type == MessageType.notify1 and self.body_type == 0x04: - self.set_body(C3Notify1MessageBody(super().body, data_offset=1)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ca/device.py b/custom_components/midea_ac_lan/midea/devices/ca/device.py deleted file mode 100644 index f169b4ff..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ca/device.py +++ /dev/null @@ -1,101 +0,0 @@ -import logging - -from .message import MessageCAResponse, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - mode = "mode" - energy_consumption = "energy_consumption" - refrigerator_actual_temp = "refrigerator_actual_temp" - freezer_actual_temp = "freezer_actual_temp" - flex_zone_actual_temp = "flex_zone_actual_temp" - right_flex_zone_actual_temp = "right_flex_zone_actual_temp" - refrigerator_setting_temp = "refrigerator_setting_temp" - freezer_setting_temp = "freezer_setting_temp" - flex_zone_setting_temp = "flex_zone_setting_temp" - right_flex_zone_setting_temp = "right_flex_zone_setting_temp" - refrigerator_door_overtime = "refrigerator_door_overtime" - freezer_door_overtime = "freezer_door_overtime" - bar_door_overtime = "bar_door_overtime" - flex_zone_door_overtime = "flex_zone_door_overtime" - refrigerator_door = "refrigerator_door" - freezer_door = "freezer_door" - bar_door = "bar_door" - flex_zone_door = "flex_zone_door" - - -class MideaCADevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xCA, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.energy_consumption: None, - DeviceAttributes.refrigerator_actual_temp: None, - DeviceAttributes.freezer_actual_temp: None, - DeviceAttributes.flex_zone_actual_temp: None, - DeviceAttributes.right_flex_zone_actual_temp: None, - DeviceAttributes.refrigerator_setting_temp: None, - DeviceAttributes.freezer_setting_temp: None, - DeviceAttributes.flex_zone_setting_temp: None, - DeviceAttributes.right_flex_zone_setting_temp: None, - DeviceAttributes.refrigerator_door_overtime: False, - DeviceAttributes.freezer_door_overtime: False, - DeviceAttributes.bar_door_overtime: False, - DeviceAttributes.flex_zone_door_overtime: False, - DeviceAttributes.refrigerator_door: False, - DeviceAttributes.freezer_door: False, - DeviceAttributes.bar_door: False, - DeviceAttributes.flex_zone_door: False, - }, - ) - self._modes = [""] - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageCAResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaCADevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/ca/message.py b/custom_components/midea_ac_lan/midea/devices/ca/message.py deleted file mode 100644 index 0611a875..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ca/message.py +++ /dev/null @@ -1,121 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageCABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCA, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageCABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x00, - ) - - @property - def _body(self): - return bytearray([]) - - -class CAGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.refrigerator_setting_temp = body[2] & 0x0F - self.freezer_setting_temp = -12 - ((body[2] & 0xF0) >> 4) - flex_zone_setting_temp = body[3] - right_flex_zone_setting_temp = body[4] - - if 1 <= flex_zone_setting_temp <= 29: - self.flex_zone_setting_temp = flex_zone_setting_temp - 19 - elif 49 <= flex_zone_setting_temp <= 54: - self.flex_zone_setting_temp = 30 - flex_zone_setting_temp - else: - self.flex_zone_setting_temp = 0 - if 1 <= right_flex_zone_setting_temp <= 29: - self.right_flex_zone_setting_temp = right_flex_zone_setting_temp - 19 - elif 49 <= right_flex_zone_setting_temp <= 54: - self.right_flex_zone_setting_temp = 30 - right_flex_zone_setting_temp - else: - self.right_flex_zone_setting_temp = 0 - - self.energy_consumption = (body[13] << 8) + body[12] - self.refrigerator_actual_temp = (body[17] - 100) / 2 - self.freezer_actual_temp = (body[18] - 100) / 2 - self.flex_zone_actual_temp = (body[19] - 100) / 2 - self.right_flex_zone_actual_temp = (body[20] - 100) / 2 - - -class CAExceptionMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.refrigerator_door_overtime = (body[1] & 0x01) > 0 - self.freezer_door_overtime = (body[1] & 0x02) > 0 - self.bar_door_overtime = (body[1] & 0x04) > 0 - self.flex_zone_door_overtime = (body[1] & 0x08) > 0 - - -class CANotify00MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.refrigerator_door = (body[1] & 0x01) > 0 - self.freezer_door = (body[1] & 0x02) > 0 - self.bar_door = (body[1] & 0x04) > 0 - self.flex_zone_door = (body[1] & 0x010) > 0 - - -class CANotify01MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.refrigerator_setting_temp = body[37] - self.freezer_setting_temp = -12 - body[38] - flex_zone_setting_temp = body[39] - right_flex_zone_setting_temp = body[40] - - if 1 <= flex_zone_setting_temp <= 29: - self.flex_zone_setting_temp = flex_zone_setting_temp - 19 - elif 49 <= flex_zone_setting_temp <= 54: - self.flex_zone_setting_temp = 30 - flex_zone_setting_temp - else: - self.flex_zone_setting_temp = 0 - if 1 <= right_flex_zone_setting_temp <= 29: - self.right_flex_zone_setting_temp = right_flex_zone_setting_temp - 19 - elif 49 <= right_flex_zone_setting_temp <= 54: - self.right_flex_zone_setting_temp = 30 - right_flex_zone_setting_temp - else: - self.right_flex_zone_setting_temp = 0 - - -class MessageCAResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x00 - ) - or (self.message_type == MessageType.notify1 and self.body_type == 0x02) - ) and len(super().body) > 20: - self.set_body(CAGeneralMessageBody(super().body)) - elif ( - self.message_type == MessageType.exception and self.body_type == 0x01 - ) or (self.message_type == 0x03 and self.body_type == 0x02): - self.set_body(CAExceptionMessageBody(super().body)) - elif self.message_type == MessageType.notify1 and self.body_type == 0x00: - self.set_body(CANotify00MessageBody(super().body)) - elif ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0x01 - ): - self.set_body(CANotify01MessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cc/device.py b/custom_components/midea_ac_lan/midea/devices/cc/device.py deleted file mode 100644 index 680a0ec9..00000000 --- a/custom_components/midea_ac_lan/midea/devices/cc/device.py +++ /dev/null @@ -1,200 +0,0 @@ -import logging - -from .message import MessageCCResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - target_temperature = "target_temperature" - fan_speed = "fan_speed" - eco_mode = "eco_mode" - sleep_mode = "sleep_mode" - night_light = "night_light" - aux_heating = "aux_heating" - swing = "swing" - ventilation = "ventilation" - temperature_precision = "temperature_precision" - fan_speed_level = "fan_speed_level" - indoor_temperature = "indoor_temperature" - aux_heat_status = "aux_heat_status" - auto_aux_heat_running = "auto_aux_heat_running" - temp_fahrenheit = "temp_fahrenheit" - - -class MideaCCDevice(MiedaDevice): - _fan_speeds_7level = { - 0x01: "Level 1", - 0x02: "Level 2", - 0x04: "Level 3", - 0x08: "Level 4", - 0x10: "Level 5", - 0x20: "Level 6", - 0x40: "Level 7", - 0x80: "Auto", - } - _fan_speeds_3level = {0x01: "Low", 0x08: "Medium", 0x40: "High", 0x80: "Auto"} - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xCC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: 1, - DeviceAttributes.target_temperature: 26.0, - DeviceAttributes.fan_speed: 0x80, - DeviceAttributes.sleep_mode: False, - DeviceAttributes.eco_mode: False, - DeviceAttributes.night_light: False, - DeviceAttributes.ventilation: False, - DeviceAttributes.aux_heating: False, - DeviceAttributes.aux_heat_status: 0, - DeviceAttributes.auto_aux_heat_running: False, - DeviceAttributes.swing: False, - DeviceAttributes.fan_speed_level: None, - DeviceAttributes.indoor_temperature: None, - DeviceAttributes.temperature_precision: 1, - DeviceAttributes.temp_fahrenheit: False, - }, - ) - self._fan_speeds = None - - @property - def fan_modes(self): - return None if self._fan_speeds is None else list(self._fan_speeds.values()) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageCCResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - fan_speed = None - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.fan_speed: - fan_speed = value - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - if ( - fan_speed is not None - and self._attributes[DeviceAttributes.fan_speed_level] is not None - ): - if self._fan_speeds is None: - if self._attributes[DeviceAttributes.fan_speed_level]: - self._fan_speeds = MideaCCDevice._fan_speeds_3level - else: - self._fan_speeds = MideaCCDevice._fan_speeds_7level - if fan_speed in self._fan_speeds.keys(): - self._attributes[DeviceAttributes.fan_speed] = self._fan_speeds.get( - fan_speed - ) - else: - self._attributes[DeviceAttributes.fan_speed] = None - new_status[DeviceAttributes.fan_speed.value] = self._attributes[ - DeviceAttributes.fan_speed - ] - aux_heating = ( - self._attributes[DeviceAttributes.aux_heat_status] == 1 - or self._attributes[DeviceAttributes.auto_aux_heat_running] - ) - if self._attributes[DeviceAttributes.aux_heating] != aux_heating: - self._attributes[DeviceAttributes.aux_heating] = aux_heating - new_status[DeviceAttributes.aux_heating.value] = self._attributes[ - DeviceAttributes.aux_heating - ] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - message.fan_speed = list(self._fan_speeds.keys())[ - list(self._fan_speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - message.eco_mode = self._attributes[DeviceAttributes.eco_mode] - message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] - message.night_light = self._attributes[DeviceAttributes.night_light] - message.aux_heat_status = self._attributes[DeviceAttributes.aux_heat_status] - message.swing = self._attributes[DeviceAttributes.swing] - return message - - def set_target_temperature(self, target_temperature, mode): - message = self.make_message_set() - message.target_temperature = target_temperature - if mode is not None: - message.power = True - message.mode = mode - self.build_send(message) - - def set_attribute(self, attr, value): - # if nat a sensor - if attr not in [ - DeviceAttributes.indoor_temperature, - DeviceAttributes.temperature_precision, - DeviceAttributes.fan_speed_level, - DeviceAttributes.aux_heat_status, - DeviceAttributes.auto_aux_heat_running, - ]: - message = self.make_message_set() - if attr == DeviceAttributes.fan_speed: - if value in self._fan_speeds.values(): - message.fan_speed = list(self._fan_speeds.keys())[ - list(self._fan_speeds.values()).index(value) - ] - else: - setattr(message, str(attr), value) - if attr == DeviceAttributes.mode: - setattr(message, str(DeviceAttributes.power.value), True) - elif attr == DeviceAttributes.eco_mode and value: - setattr(message, str(DeviceAttributes.sleep_mode.value), False) - elif attr == DeviceAttributes.sleep_mode and value: - setattr(message, str(DeviceAttributes.eco_mode.value), False) - elif attr == DeviceAttributes.aux_heating: - if value: - setattr(message, DeviceAttributes.aux_heat_status, 1) - else: - setattr(message, DeviceAttributes.aux_heat_status, 2) - self.build_send(message) - - -class MideaAppliance(MideaCCDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/cc/message.py b/custom_components/midea_ac_lan/midea/devices/cc/message.py deleted file mode 100644 index f94a7c34..00000000 --- a/custom_components/midea_ac_lan/midea/devices/cc/message.py +++ /dev/null @@ -1,143 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageCCBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageCCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([0x00] * 23) - - -class MessageSet(MessageCCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0xC3, - ) - self.power = False - self.mode = 4 - self.fan_speed = 0x80 - self.target_temperature = 26 - self.eco_mode = False - self.sleep_mode = False - self.night_light = False - self.ventilation = False - self.aux_heat_status = 0 - self.auto_aux_heat_running = False - self.swing = False - - @property - def _body(self): - # Byte1, Power Mode - power = 0x80 if self.power else 0 - mode = 1 << (self.mode - 1) - # Byte2 fan_speed - fan_speed = self.fan_speed - # Byte3 Integer of target_temperature - temperature_integer = int(self.target_temperature) & 0xFF - # Byte6 eco_mode ventilation aux_heating - eco_mode = 0x01 if self.eco_mode else 0 - if self.aux_heat_status == 1: - aux_heating = 0x10 - elif self.aux_heat_status == 2: - aux_heating = 0x20 - else: - aux_heating = 0 - swing = 0x04 if self.swing else 0 - ventilation = 0x08 if self.ventilation else 0 - # Byte8 sleep_mode night_light - sleep_mode = 0x10 if self.sleep_mode else 0 - night_light = 0x08 if self.night_light else 0 - # Byte11 Dot of target_temperature - temperature_dot = ( - int((self.target_temperature - temperature_integer) * 10) & 0xFF - ) - return bytearray( - [ - power | mode, - fan_speed, - temperature_integer, - # timer - 0x00, - 0x00, - eco_mode | ventilation | swing | aux_heating, - # non-stepless fan speed - 0xFF, - sleep_mode | night_light, - 0x00, - 0x00, - temperature_dot, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class CCGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x80) > 0 - mode = body[1] & 0x1F - self.mode = 0 - while mode >= 1: - mode /= 2 - self.mode += 1 - self.fan_speed = body[2] - self.target_temperature = body[3] + body[19] / 10 - self.indoor_temperature = (body[4] - 40) / 2 - self.eco_mode = (body[13] & 0x01) > 0 - self.sleep_mode = (body[14] & 0x10) > 0 - self.night_light = (body[14] & 0x08) > 0 - self.ventilation = (body[13] & 0x08) > 0 - self.aux_heat_status = (body[14] & 0x60) >> 5 - self.auto_aux_heat_running = (body[13] & 0x02) > 0 - self.fan_speed_level = (body[13] & 0x40) > 0 - self.temperature_precision = 1 if (body[14] & 0x80) > 0 else 0.5 - self.swing = (body[13] & 0x04) > 0 - self.temp_fahrenheit = (body[20] & 0x80) > 0 - - -class MessageCCResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - (self.message_type == MessageType.query and self.body_type == 0x01) - or ( - self.message_type in [MessageType.notify1, MessageType.notify2] - and self.body_type == 0x01 - ) - or (self.message_type == MessageType.set and self.body_type == 0xC3) - ): - self.set_body(CCGeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cd/device.py b/custom_components/midea_ac_lan/midea/devices/cd/device.py deleted file mode 100644 index a124f735..00000000 --- a/custom_components/midea_ac_lan/midea/devices/cd/device.py +++ /dev/null @@ -1,136 +0,0 @@ -import json -import logging - -from .message import MessageCDResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - max_temperature = "max_temperature" - min_temperature = "min_temperature" - target_temperature = "target_temperature" - current_temperature = "current_temperature" - outdoor_temperature = "outdoor_temperature" - condenser_temperature = "condenser_temperature" - compressor_temperature = "compressor_temperature" - compressor_status = "compressor_status" - - -class MideaCDDevice(MiedaDevice): - _modes = ["Energy-save", "Standard", "Dual", "Smart"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xCD, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: None, - DeviceAttributes.max_temperature: 65, - DeviceAttributes.min_temperature: 35, - DeviceAttributes.target_temperature: 40, - DeviceAttributes.current_temperature: None, - DeviceAttributes.outdoor_temperature: None, - DeviceAttributes.condenser_temperature: None, - DeviceAttributes.compressor_temperature: None, - DeviceAttributes.compressor_status: None, - }, - ) - self._fields = {} - self._temperature_step = None - self._default_temperature_step = 1 - self.set_customize(customize) - - @property - def temperature_step(self): - return self._temperature_step - - @property - def preset_modes(self): - return MideaCDDevice._modes - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageCDResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - if hasattr(message, "fields"): - self._fields = getattr(message, "fields") - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - self._attributes[status] = MideaCDDevice._modes[value] - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.mode, - DeviceAttributes.power, - DeviceAttributes.target_temperature, - ]: - message = MessageSet(self._protocol_version) - message.fields = self._fields - message.mode = MideaCDDevice._modes.index( - self._attributes[DeviceAttributes.mode] - ) - message.power = self._attributes[DeviceAttributes.power] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - if attr == DeviceAttributes.mode: - if value in MideaCDDevice._modes: - setattr(message, str(attr), MideaCDDevice._modes.index(value)) - else: - setattr(message, str(attr), value) - self.build_send(message) - - def set_customize(self, customize): - self._temperature_step = self._default_temperature_step - 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") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"temperature_step": self._temperature_step}) - - -class MideaAppliance(MideaCDDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/cd/message.py b/custom_components/midea_ac_lan/midea/devices/cd/message.py deleted file mode 100644 index 454cfe00..00000000 --- a/custom_components/midea_ac_lan/midea/devices/cd/message.py +++ /dev/null @@ -1,109 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageCDBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCD, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageCDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessageSet(MessageCDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - self.power = False - self.target_temperature = 0 - self.aux_heating = False - self.fields = {} - self.mode = 1 - - def read_field(self, field): - value = self.fields.get(field, 0) - return value if value else 0 - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - mode = self.mode + 1 - target_temperature = round(self.target_temperature * 2 + 30) - return bytearray( - [ - 0x01, - power, - mode, - target_temperature, - self.read_field("trValue"), - self.read_field("openPTC"), - self.read_field("ptcTemp"), - 0, # self.read_field("byte8") - ] - ) - - -class CDGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[2] & 0x01) > 0 - self.target_temperature = round((body[3] - 30) / 2) - if (body[2] & 0x02) > 0: - self.mode = 0 - elif (body[2] & 0x04) > 0: - self.mode = 1 - 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.compressor_status = (body[27] & 0x08) > 0 - if (body[28] & 0x20) > 0: - self.mode = 3 - - -class CD02MessageBody(MessageBody): - def __init__(self, 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.fields["trValue"] = body[5] - self.fields["openPTC"] = body[5] - self.fields["ptcTemp"] = body[7] - self.fields["byte8"] = body[8] - - -class MessageCDResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.query, MessageType.notify2]: - self.set_body(CDGeneralMessageBody(super().body)) - elif self.message_type == MessageType.set and self.body_type == 0x01: - self.set_body(CD02MessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ce/device.py b/custom_components/midea_ac_lan/midea/devices/ce/device.py deleted file mode 100644 index c8ac6ecf..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ce/device.py +++ /dev/null @@ -1,159 +0,0 @@ -import json -import logging - -from .message import MessageCEResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - child_lock = "child_lock" - scheduled = "scheduled" - fan_speed = "fan_speed" - pm25 = "pm25" - co2 = "co2" - current_humidity = "current_humidity" - current_temperature = "current_temperature" - hcho = "hcho" - link_to_ac = "link_to_ac" - sleep_mode = "sleep_mode" - eco_mode = "eco_mode" - aux_heating = "aux_heating'" - powerful_purify = "powerful_purify" - filter_cleaning_reminder = "filter_cleaning_reminder" - filter_change_reminder = "filter_change_reminder" - error_code = "error_code" - - -class MideaCEDevice(MiedaDevice): - _modes = ["Normal", "Sleep mode", "ECO mode"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xCE, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: None, - DeviceAttributes.child_lock: False, - DeviceAttributes.scheduled: False, - DeviceAttributes.fan_speed: 0, - DeviceAttributes.pm25: None, - DeviceAttributes.co2: None, - DeviceAttributes.current_humidity: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.hcho: None, - DeviceAttributes.link_to_ac: False, - DeviceAttributes.sleep_mode: False, - DeviceAttributes.eco_mode: False, - DeviceAttributes.aux_heating: None, - DeviceAttributes.powerful_purify: False, - DeviceAttributes.filter_cleaning_reminder: False, - DeviceAttributes.filter_change_reminder: False, - DeviceAttributes.error_code: 0, - }, - ) - self._default_speed_count = 7 - self._speed_count = self._default_speed_count - self.set_customize(customize) - - @property - def speed_count(self): - return self._speed_count - - @property - def preset_modes(self): - return self._modes - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageCEResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - if self._attributes[DeviceAttributes.sleep_mode]: - self._attributes[DeviceAttributes.mode] = "Sleep mode" - elif self._attributes[DeviceAttributes.eco_mode]: - self._attributes[DeviceAttributes.mode] = "ECO mode" - else: - self._attributes[DeviceAttributes.mode] = "None" - new_status[DeviceAttributes.mode.value] = self._attributes[ - DeviceAttributes.mode - ] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.fan_speed = self._attributes[DeviceAttributes.fan_speed] - message.link_to_ac = self._attributes[DeviceAttributes.link_to_ac] - message.sleep_mode = self._attributes[DeviceAttributes.sleep_mode] - message.eco_mode = self._attributes[DeviceAttributes.eco_mode] - message.aux_heating = self._attributes[DeviceAttributes.aux_heating] - message.powerful_purify = self._attributes[DeviceAttributes.powerful_purify] - message.scheduled = self._attributes[DeviceAttributes.scheduled] - message.child_lock = self._attributes[DeviceAttributes.child_lock] - return message - - def set_attribute(self, attr, value): - message = self.make_message_set() - if attr == DeviceAttributes.mode: - message.sleep_mode = False - message.eco_mode = False - if value == "Sleep mode": - message.sleep_mode = True - elif value == "ECO mode": - message.eco_mode = True - else: - setattr(message, str(attr), value) - self.build_send(message) - - def set_customize(self, customize): - self._speed_count = self._default_speed_count - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "speed_count" in params: - self._speed_count = params.get("speed_count") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"speed_count": self._speed_count}) - - -class MideaAppliance(MideaCEDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/ce/message.py b/custom_components/midea_ac_lan/midea/devices/ce/message.py deleted file mode 100644 index 0f6afe3f..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ce/message.py +++ /dev/null @@ -1,135 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCE, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageFABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageFABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - - self.power = False - self.fan_speed = 0 - self.link_to_ac = False - self.sleep_mode = False - self.eco_mode = False - self.aux_heating = False - self.powerful_purify = False - self.scheduled = False - self.child_lock = False - - @property - def _body(self): - power = 0x80 if self.power else 0x00 - link_to_ac = 0x01 if self.link_to_ac else 0x00 - sleep_mode = 0x02 if self.sleep_mode else 0x00 - eco_mode = 0x04 if self.eco_mode else 0x00 - aux_heating = 0x08 if self.aux_heating else 0x00 - powerful_purify = 0x10 if self.powerful_purify else 0x00 - scheduled = 0x01 if self.scheduled else 0x00 - child_lock = 0x7F if self.child_lock else 0x00 - return bytearray( - [ - power | 0x01, - self.fan_speed, - link_to_ac | sleep_mode | eco_mode | aux_heating | powerful_purify, - scheduled, - 0x00, - child_lock, - ] - ) - - -class CEGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x80) > 0 - self.child_lock = (body[1] & 0x20) > 0 - self.scheduled = (body[1] & 0x40) > 0 - self.fan_speed = body[2] - self.pm25 = (body[3] << 8) + body[4] - self.co2 = (body[5] << 8) + body[6] - if body[7] != 0xFF: - self.current_humidity = (body[7] << 8) + body[8] / 10 - else: - self.current_humidity = None - if body[9] != 0xFF: - self.current_temperature = (body[9] << 8) + (body[10] - 60) / 2 - else: - self.current_temperature = None - if body[11] != 0xFF: - self.hcho = (body[11] << 8) + body[12] / 1000 - else: - self.hcho = None - self.link_to_ac = (body[17] & 0x01) > 0 - self.sleep_mode = (body[17] & 0x02) > 0 - self.eco_mode = (body[17] & 0x04) > 0 - if (body[19] & 0x02) > 0: - self.aux_heating = (body[17] & 0x08) > 0 - else: - self.aux_heating = None - self.powerful_purify = (body[17] & 0x10) > 0 - self.filter_cleaning_reminder = (body[18] & 0x01) > 0 - self.filter_change_reminder = (body[18] & 0x02) > 0 - self.error_code = body[24] - - -class CENotifyMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.pm25 = (body[1] << 8) + body[2] - self.co2 = (body[3] << 8) + body[4] - if body[5] != 0xFF: - self.current_humidity = (body[5] << 8) + body[6] / 10 - else: - self.current_humidity = None - if body[7] != 0xFF: - self.current_temperature = (body[7] << 8) + (body[8] - 60) / 2 - else: - self.current_temperature = None - if body[9] != 0xFF: - self.hcho = (body[9] << 8) + body[10] / 1000 - else: - self.hcho = None - self.error_code = body[12] - - -class MessageCEResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x01 - ) or (self.message_type == MessageType.notify1 and self.body_type == 0x02): - self.set_body(CEGeneralMessageBody(super().body)) - elif self.message_type == MessageType.notify1 and self.body_type == 0x01: - self.set_body(CENotifyMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/cf/device.py b/custom_components/midea_ac_lan/midea/devices/cf/device.py deleted file mode 100644 index 40b5a9d5..00000000 --- a/custom_components/midea_ac_lan/midea/devices/cf/device.py +++ /dev/null @@ -1,100 +0,0 @@ -import logging - -from .message import MessageCFResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - target_temperature = "target_temperature" - aux_heating = "aux_heating" - current_temperature = "current_temperature" - max_temperature = "max_temperature" - min_temperature = "min_temperature" - - -class MideaCFDevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xCF, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: 0, - DeviceAttributes.target_temperature: None, - DeviceAttributes.aux_heating: False, - DeviceAttributes.current_temperature: 0, - DeviceAttributes.max_temperature: 55, - DeviceAttributes.min_temperature: 5, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageCFResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - return new_status - - def set_target_temperature(self, target_temperature, mode): - message = MessageSet(self._protocol_version) - message.power = True - message.mode = self._attributes[DeviceAttributes.mode] - message.target_temperature = target_temperature - if mode is not None: - message.mode = mode - self.build_send(message) - - def set_attribute(self, attr, value): - message = MessageSet(self._protocol_version) - message.power = True - message.mode = self._attributes[DeviceAttributes.mode] - if attr == DeviceAttributes.power: - message.power = value - elif attr == DeviceAttributes.mode: - message.power = True - message.mode = value - elif attr == DeviceAttributes.target_temperature: - message.target_temperature = value - elif attr == DeviceAttributes.aux_heating: - message.aux_heating = value - self.build_send(message) - - -class MideaAppliance(MideaCFDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/cf/message.py b/custom_components/midea_ac_lan/midea/devices/cf/message.py deleted file mode 100644 index 0bf0fb3f..00000000 --- a/custom_components/midea_ac_lan/midea/devices/cf/message.py +++ /dev/null @@ -1,88 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageCFBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xCF, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageCFBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageCFBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - self.power = False - self.mode = 0 # 1 自动 2 制冷 3 制热 - self.target_temperature = None - self.aux_heating = None - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - mode = self.mode - target_temperature = ( - 0xFF - if self.target_temperature is None - else (int(self.target_temperature) & 0xFF) - ) - aux_heating = ( - 0xFF if self.aux_heating is None else (0x01 if self.aux_heating else 0x00) - ) - return bytearray([power, mode, target_temperature, aux_heating]) - - -class CFMessageBody(MessageBody): - def __init__(self, body, data_offset=0): - super().__init__(body) - self.power = (body[data_offset + 0] & 0x01) > 0 - self.aux_heating = (body[data_offset + 0] & 0x02) > 0 - self.silent = (body[data_offset + 0] & 0x04) > 0 - self.mode = body[data_offset + 3] - self.target_temperature = body[data_offset + 4] - self.current_temperature = body[data_offset + 5] - if self.mode == 2: - self.max_temperature = body[data_offset + 8] - self.min_temperature = body[data_offset + 9] - elif self.mode == 3: - self.max_temperature = body[data_offset + 6] - self.min_temperature = body[data_offset + 7] - else: - self.max_temperature = body[data_offset + 6] - self.min_temperature = body[data_offset + 9] - - -class MessageCFResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.set] - and self.body_type == 0x01 - ): - self.set_body(CFMessageBody(super().body, data_offset=1)) - elif self.message_type in [MessageType.notify1, MessageType.notify2]: - self.set_body(CFMessageBody(super().body, data_offset=0)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/da/device.py b/custom_components/midea_ac_lan/midea/devices/da/device.py deleted file mode 100644 index f5506e40..00000000 --- a/custom_components/midea_ac_lan/midea/devices/da/device.py +++ /dev/null @@ -1,171 +0,0 @@ -import logging - -from .message import MessageDAResponse, MessagePower, MessageQuery, MessageStart - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - start = "start" - washing_data = "washing_data" - program = "program" - progress = "progress" - time_remaining = "time_remaining" - wash_time = "wash_time" - soak_time = "soak_time" - dehydration_time = "dehydration_time" - dehydration_speed = "dehydration_speed" - error_code = "error_code" - rinse_count = "rinse_count" - rinse_level = "rinse_level" - wash_level = "wash_level" - wash_strength = "wash_strength" - softener = "softener" - detergent = "detergent" - - -class MideaDADevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xDA, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.start: False, - DeviceAttributes.error_code: None, - DeviceAttributes.washing_data: bytearray([]), - DeviceAttributes.program: None, - DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None, - DeviceAttributes.wash_time: None, - DeviceAttributes.soak_time: None, - DeviceAttributes.dehydration_time: None, - DeviceAttributes.dehydration_speed: None, - DeviceAttributes.rinse_count: None, - DeviceAttributes.rinse_level: None, - DeviceAttributes.wash_level: None, - DeviceAttributes.wash_strength: None, - DeviceAttributes.softener: None, - DeviceAttributes.detergent: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageDAResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - progress = ["Idle", "Spin", "Rinse", "Wash", "Weight", "Unknown", "Dry", "Soak"] - program = [ - "Standard", - "Fast", - "Blanket", - "Wool", - "embathe", - "Memory", - "Child", - "Down Jacket", - "Stir", - "Mute", - "Bucket Self Clean", - "Air Dry", - ] - speed = ["-", "Low", "Medium", "High"] - strength = ["-", "Week", "Medium", "Strong"] - detergent = [ - "No", - "Less", - "Medium", - "More", - "4", - "5", - "6", - "7", - "8", - "Insufficient", - ] - softener = [ - "No", - "Intelligent", - "Programed", - "3", - "4", - "5", - "6", - "7", - "8", - "Insufficient", - ] - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.progress: - self._attributes[status] = progress[getattr(message, str(status))] - elif status == DeviceAttributes.program: - self._attributes[status] = program[getattr(message, str(status))] - elif status == DeviceAttributes.rinse_level: - temp_rinse_level = getattr(message, str(status)) - if temp_rinse_level == 15: - self._attributes[status] = "-" - else: - self._attributes[status] = temp_rinse_level - elif status == DeviceAttributes.dehydration_speed: - temp_speed = getattr(message, str(status)) - if temp_speed == 15: - self._attributes[status] = "-" - else: - self._attributes[status] = speed[temp_speed] - elif status == DeviceAttributes.detergent: - self._attributes[status] = detergent[getattr(message, str(status))] - elif status == DeviceAttributes.softener: - self._attributes[status] = softener[getattr(message, str(status))] - elif status == DeviceAttributes.wash_strength: - self._attributes[status] = strength[getattr(message, str(status))] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.start: - message = MessageStart(self._protocol_version) - message.start = value - message.washing_data = self._attributes[DeviceAttributes.washing_data] - self.build_send(message) - - -class MideaAppliance(MideaDADevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/da/message.py b/custom_components/midea_ac_lan/midea/devices/da/message.py deleted file mode 100644 index 5578d46b..00000000 --- a/custom_components/midea_ac_lan/midea/devices/da/message.py +++ /dev/null @@ -1,101 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageDABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xDA, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageDABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x03, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessagePower(MessageDABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray([power, 0xFF]) - - -class MessageStart(MessageDABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.start = False - self.washing_data = bytearray([]) - - @property - def _body(self): - if self.start: - return bytearray([0xFF, 0x01]) + self.washing_data - else: - # Stop - return bytearray([0xFF, 0x00]) - - -class DAGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.start = True if body[2] in [2, 6] else False - self.error_code = body[24] - self.program = body[4] - self.wash_time = body[9] - self.soak_time = body[12] - self.dehydration_time = (body[10] & 0xF0) >> 4 - self.dehydration_speed = (body[6] & 0xF0) >> 4 - self.rinse_count = body[10] & 0xF - self.rinse_level = (body[5] & 0xF0) >> 4 - self.wash_level = body[5] & 0xF - self.wash_strength = body[6] & 0xF - self.softener = (body[8] & 0xF0) >> 4 - self.detergent = body[8] & 0x0F - self.washing_data = body[3:15] - self.progress = 0 - for i in range(1, 7): - if (body[16] & (1 << i)) > 0: - self.progress = i - break - if self.power: - self.time_remaining = body[17] + body[18] * 60 - else: - self.time_remaining = None - - -class MessageDAResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): - self.set_body(DAGeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/db/device.py b/custom_components/midea_ac_lan/midea/devices/db/device.py deleted file mode 100644 index fe1c02e4..00000000 --- a/custom_components/midea_ac_lan/midea/devices/db/device.py +++ /dev/null @@ -1,97 +0,0 @@ -import logging - -from .message import MessageDBResponse, MessagePower, MessageQuery, MessageStart - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - start = "start" - washing_data = "washing_data" - progress = "progress" - time_remaining = "time_remaining" - - -class MideaDBDevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xDB, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.start: False, - DeviceAttributes.washing_data: bytearray([]), - DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageDBResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - progress = [ - "Idle", - "Spin", - "Rinse", - "Wash", - "Pre-wash", - "Dry", - "Weight", - "Hi-speed Spin", - "Unknown", - ] - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.progress: - self._attributes[status] = progress[getattr(message, str(status))] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.start: - message = MessageStart(self._protocol_version) - message.start = value - message.washing_data = self._attributes[DeviceAttributes.washing_data] - self.build_send(message) - - -class MideaAppliance(MideaDBDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/db/message.py b/custom_components/midea_ac_lan/midea/devices/db/message.py deleted file mode 100644 index 7a70ca55..00000000 --- a/custom_components/midea_ac_lan/midea/devices/db/message.py +++ /dev/null @@ -1,113 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageDBBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xDB, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageDBBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x03, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessagePower(MessageDBBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray( - [ - power, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - ] - ) - - -class MessageStart(MessageDBBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.start = False - self.washing_data = bytearray([]) - - @property - def _body(self): - if self.start: # Pause - return bytearray([0xFF, 0x01]) + self.washing_data - else: - # Pause - return bytearray([0xFF, 0x00]) - - -class DBGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.start = True if body[2] in [2, 6] else False - self.washing_data = body[3:16] - self.progress = 0 - for i in range(0, 7): - if (body[16] & (1 << i)) > 0: - self.progress = i + 1 - break - if self.power: - self.time_remaining = body[17] + (body[18] << 8) - else: - self.time_remaining = None - - -class MessageDBResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): - self.set_body(DBGeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/dc/device.py b/custom_components/midea_ac_lan/midea/devices/dc/device.py deleted file mode 100644 index 7e6d659f..00000000 --- a/custom_components/midea_ac_lan/midea/devices/dc/device.py +++ /dev/null @@ -1,96 +0,0 @@ -import logging - -from .message import MessageDCResponse, MessagePower, MessageQuery, MessageStart - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - start = "start" - washing_data = "washing_data" - progress = "progress" - time_remaining = "time_remaining" - - -class MideaDADevice(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xDC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.start: False, - DeviceAttributes.washing_data: bytearray([]), - DeviceAttributes.progress: "Unknown", - DeviceAttributes.time_remaining: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageDCResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - progress = [ - "Prog0", - "Prog1", - "Prog2", - "Prog3", - "Prog4", - "Prog5", - "Prog6", - "Prog7", - ] - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.progress: - self._attributes[status] = progress[getattr(message, str(status))] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.start: - message = MessageStart(self._protocol_version) - message.start = value - message.washing_data = self._attributes[DeviceAttributes.washing_data] - self.build_send(message) - - -class MideaAppliance(MideaDADevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/dc/message.py b/custom_components/midea_ac_lan/midea/devices/dc/message.py deleted file mode 100644 index 3be5d3dd..00000000 --- a/custom_components/midea_ac_lan/midea/devices/dc/message.py +++ /dev/null @@ -1,89 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageDCBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xDC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageDCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x03, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessagePower(MessageDCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray([power, 0xFF]) - - -class MessageStart(MessageDCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.start = False - self.washing_data = bytearray([]) - - @property - def _body(self): - if self.start: - return bytearray([0xFF, 0x01]) + self.washing_data - else: - # Stop - return bytearray([0xFF, 0x00]) - - -class DCGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.start = True if body[2] in [2, 6] else False - self.washing_data = body[3:15] - self.progress = 0 - for i in range(0, 7): - if (body[16] & (1 << i)) > 0: - self.progress = i + 1 - break - if self.power: - self.time_remaining = body[17] + body[18] * 60 - else: - self.time_remaining = None - - -class MessageDCResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [MessageType.query, MessageType.set] or ( - self.message_type == MessageType.notify1 and self.body_type == 0x04 - ): - self.set_body(DCGeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e1/device.py b/custom_components/midea_ac_lan/midea/devices/e1/device.py deleted file mode 100644 index 43c72ffa..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e1/device.py +++ /dev/null @@ -1,172 +0,0 @@ -import logging - -from .message import ( - MessageE1Response, - MessageLock, - MessagePower, - MessageQuery, - MessageStorage, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - status = "status" - mode = "mode" - additional = "additional" - door = "door" - rinse_aid = "rinse_aid" - salt = "salt" - child_lock = "child_lock" - uv = "uv" - dry = "dry" - dry_status = "dry_status" - storage = "storage" - storage_status = "storage_status" - time_remaining = "time_remaining" - progress = "progress" - storage_remaining = "storage_remaining" - temperature = "temperature" - humidity = "humidity" - waterswitch = "waterswitch" - water_lack = "water_lack" - error_code = "error_code" - softwater = "softwater" - wrong_operation = "wrong_operation" - bright = "bright" - - -class MideaE1Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xE1, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.status: None, - DeviceAttributes.mode: 0, - DeviceAttributes.additional: 0, - DeviceAttributes.uv: False, - DeviceAttributes.dry: False, - DeviceAttributes.dry_status: False, - DeviceAttributes.door: False, - DeviceAttributes.rinse_aid: False, - DeviceAttributes.salt: False, - DeviceAttributes.child_lock: False, - DeviceAttributes.storage: False, - DeviceAttributes.storage_status: False, - DeviceAttributes.time_remaining: None, - DeviceAttributes.progress: None, - DeviceAttributes.storage_remaining: None, - DeviceAttributes.temperature: None, - DeviceAttributes.humidity: None, - DeviceAttributes.waterswitch: False, - DeviceAttributes.water_lack: False, - DeviceAttributes.error_code: None, - DeviceAttributes.softwater: 0, - DeviceAttributes.wrong_operation: None, - DeviceAttributes.bright: 0, - }, - ) - self._modes = { - 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR - 0x1: "Auto", # BYTE_MODE_AUTO_WASH - 0x2: "Heavy", # BYTE_MODE_STRONG_WASH - 0x3: "Normal", # BYTE_MODE_STANDARD_WASH - 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH - 0x5: "Delicate", # BYTE_MODE_GLASS_WASH - 0x6: "Hour", # BYTE_MODE_HOUR_WASH - 0x7: "Quick", # BYTE_MODE_FAST_WASH - 0x8: "Rinse", # BYTE_MODE_SOAK_WASH - 0x9: "90min", # BYTE_MODE_90MIN_WASH - 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN - 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH - 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE - 0xD: "Germ", # BYTE_MODE_GERM ??? - 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH - 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM - 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH - 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH - 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH - 0x14: "Less Wash", # BYTE_MODE_LESS_WASH - 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH - 0x19: "Cloud Wash", # BYTE_MODE_CLOUD_WASH - } - self._status = ["Off", "Idle", "Delay", "Running", "Error"] - self._progress = ["Idle", "Pre-wash", "Wash", "Rinse", "Dry", "Complete"] - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageE1Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.status: - v = getattr(message, str(status)) - if v < len(self._status): - self._attributes[status] = self._status[v] - else: - self._attributes[status] = None - elif status == DeviceAttributes.progress: - v = getattr(message, str(status)) - if v < len(self._progress): - self._attributes[status] = self._progress[v] - else: - self._attributes[status] = None - elif status == DeviceAttributes.mode: - v = getattr(message, str(status)) - self._attributes[status] = self._modes[v] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.child_lock: - message = MessageLock(self._protocol_version) - message.lock = value - self.build_send(message) - elif attr == DeviceAttributes.storage: - message = MessageStorage(self._protocol_version) - message.storage = value - self.build_send(message) - - -class MideaAppliance(MideaE1Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/e1/message.py b/custom_components/midea_ac_lan/midea/devices/e1/message.py deleted file mode 100644 index 530572e8..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e1/message.py +++ /dev/null @@ -1,122 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageE1Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xE1, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessagePower(MessageE1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x08, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray([power, 0x00, 0x00, 0x00]) - - -class MessageLock(MessageE1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x83, - ) - self.lock = False - - @property - def _body(self): - lock = 0x03 if self.lock else 0x04 - return bytearray([lock]) + bytearray([0x00] * 36) - - -class MessageStorage(MessageE1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x81, - ) - self.storage = False - - @property - def _body(self): - storage = 0x01 if self.storage else 0x00 - return ( - bytearray([0x00, 0x00, 0x00, storage]) - + bytearray([0xFF] * 6) - + bytearray([0x00] * 27) - ) - - -class MessageQuery(MessageE1Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x00, - ) - - @property - def _body(self): - return bytearray([]) - - -class E1GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.status = body[1] - self.mode = body[2] - self.additional = body[3] - self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close - self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage - self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage - start_pause = (body[5] & 0x08) > 0 - if start_pause: - self.start = True - elif self.status in [2, 3]: - self.start = False - self.child_lock = (body[5] & 0x10) > 0 - self.uv = (body[4] & 0x2) > 0 - self.dry = (body[4] & 0x10) > 0 - self.dry_status = (body[4] & 0x20) > 0 - self.storage = (body[5] & 0x20) > 0 - self.storage_status = (body[5] & 0x40) > 0 - self.time_remaining = body[6] - self.progress = body[9] - self.storage_remaining = body[18] if len(body) > 18 else False - self.temperature = body[11] - self.humidity = body[33] if len(body) > 33 else None - self.waterswitch = (body[4] & 0x4) > 0 - self.water_lack = (body[5] & 0x80) > 0 - self.error_code = body[10] - self.softwater = body[13] - self.wrong_operation = body[16] - self.bright = body[24] if len(body) > 24 else None - - -class MessageE1Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0 - ): - self.set_body(E1GeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e2/device.py b/custom_components/midea_ac_lan/midea/devices/e2/device.py deleted file mode 100644 index b261a44f..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e2/device.py +++ /dev/null @@ -1,141 +0,0 @@ -import json -import logging - -from .message import ( - MessageE2Response, - MessageNewProtocolSet, - MessagePower, - MessageQuery, - MessageSet, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - heating = "heating" - keep_warm = "keep_warm" - protection = "protection" - current_temperature = "current_temperature" - target_temperature = "target_temperature" - whole_tank_heating = "whole_tank_heating" - variable_heating = "variable_heating" - heating_time_remaining = "heating_time_remaining" - water_consumption = "water_consumption" - heating_power = "heating_power" - - -class MideaE2Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xE2, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.heating: False, - DeviceAttributes.keep_warm: False, - DeviceAttributes.protection: False, - DeviceAttributes.current_temperature: None, - DeviceAttributes.target_temperature: 40, - DeviceAttributes.whole_tank_heating: False, - DeviceAttributes.variable_heating: False, - DeviceAttributes.heating_time_remaining: 0, - DeviceAttributes.water_consumption: None, - DeviceAttributes.heating_power: None, - }, - ) - self._default_old_protocol = "auto" - self._old_protocol = self._default_old_protocol - self.set_customize(customize) - - def old_protocol(self): - return self.subtype <= 82 or self.subtype == 85 or self.subtype == 36353 - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageE2Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = getattr(message, str(status)) - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.protection = self._attributes[DeviceAttributes.protection] - message.whole_tank_heating = self._attributes[ - DeviceAttributes.whole_tank_heating - ] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - message.variable_heating = self._attributes[DeviceAttributes.variable_heating] - return message - - def set_attribute(self, attr, value): - if attr not in [ - DeviceAttributes.heating, - DeviceAttributes.keep_warm, - DeviceAttributes.current_temperature, - ]: - if self._old_protocol is not None and self._old_protocol != "auto": - old_protocol = self._old_protocol - else: - old_protocol = self.old_protocol() - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - elif old_protocol: - message = self.make_message_set() - setattr(message, str(attr), value) - else: - message = MessageNewProtocolSet(self._protocol_version) - setattr(message, str(attr), value) - self.build_send(message) - - def set_customize(self, customize): - self._old_protocol = self._default_old_protocol - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "old_protocol" in params: - self._old_protocol = params.get("old_protocol") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"old_protocol": self._old_protocol}) - - -class MideaAppliance(MideaE2Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/e2/message.py b/custom_components/midea_ac_lan/midea/devices/e2/message.py deleted file mode 100644 index a70c6cc9..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e2/message.py +++ /dev/null @@ -1,150 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageE2Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xE2, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageE2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessagePower(MessageE2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - if self.power: - self.body_type = 0x01 - else: - self.body_type = 0x02 - return bytearray([0x01]) - - -class MessageNewProtocolSet(MessageE2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x14, - ) - self.target_temperature = None - self.variable_heating = None - self.whole_tank_heating = None - - @property - def _body(self): - byte1 = 0x00 - byte2 = 0x00 - if self.target_temperature is not None: - byte1 = 0x07 - byte2 = int(self.target_temperature) & 0xFF - elif self.whole_tank_heating is not None: - byte1 = 0x04 - byte2 = 0x02 if self.whole_tank_heating else 0x01 - elif self.variable_heating is not None: - byte1 = 0x10 - byte2 = 0x01 if self.variable_heating else 0x00 - return bytearray([byte1, byte2]) - - -class MessageSet(MessageE2Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x04, - ) - self.target_temperature = 0 - self.variable_heating = False - self.whole_tank_heating = False - self.protection = False - - @property - def _body(self): - # Byte 4 whole_tank_heating, protection - protection = 0x04 if self.protection else 0x00 - whole_tank_heating = 0x02 if self.whole_tank_heating else 0x01 - # Byte 5 target_temperature - target_temperature = self.target_temperature & 0xFF - # Byte 9 variable_heating - variable_heating = 0x10 if self.variable_heating else 0x00 - return bytearray( - [ - 0x01, - 0x00, - 0x80, - whole_tank_heating | protection, - target_temperature, - 0x00, - 0x00, - 0x00, - variable_heating, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class E2GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[2] & 0x01) > 0 - self.heating = (body[2] & 0x04) > 0 - self.keep_warm = (body[2] & 0x08) > 0 - self.variable_heating = (body[2] & 0x80) > 0 - self.current_temperature = body[4] - self.whole_tank_heating = (body[7] & 0x08) > 0 - self.heating_time_remaining = body[9] * 60 + body[10] - self.target_temperature = body[11] - self.protection = ((body[22] & 0x02) > 0) if len(body) > 22 else False - if len(body) > 25: - self.water_consumption = body[24] + (body[25] << 8) - if len(body) > 34: - self.heating_power = body[34] * 100 - - -class MessageE2Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0x01 - ) or ( - self.message_type == MessageType.set - and self.body_type in [0x01, 0x02, 0x04, 0x14] - ): - self.set_body(E2GeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e3/device.py b/custom_components/midea_ac_lan/midea/devices/e3/device.py deleted file mode 100644 index 250076d1..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e3/device.py +++ /dev/null @@ -1,142 +0,0 @@ -import json -import logging - -from .message import ( - MessageE3Response, - MessageNewProtocolSet, - MessagePower, - MessageQuery, - MessageSet, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - burning_state = "burning_state" - zero_cold_water = "zero_cold_water" - protection = "protection" - zero_cold_pulse = "zero_cold_pulse" - smart_volume = "smart_volume" - current_temperature = "current_temperature" - target_temperature = "target_temperature" - - -class MideaE3Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xE3, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.burning_state: False, - DeviceAttributes.zero_cold_water: False, - DeviceAttributes.protection: False, - DeviceAttributes.zero_cold_pulse: False, - DeviceAttributes.smart_volume: False, - DeviceAttributes.current_temperature: None, - DeviceAttributes.target_temperature: 40, - }, - ) - self._old_subtypes = [32, 33, 34, 35, 36, 37, 40, 43, 48, 49, 80] - self._precision_halves = None - self._default_precision_halves = False - self.set_customize(customize) - - @property - def precision_halves(self): - return self._precision_halves - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageE3Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if self._precision_halves and status in [ - DeviceAttributes.current_temperature, - DeviceAttributes.target_temperature, - ]: - self._attributes[status] = getattr(message, str(status)) / 2 - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.zero_cold_water = self._attributes[DeviceAttributes.zero_cold_water] - message.protection = self._attributes[DeviceAttributes.protection] - message.zero_clod_pulse = self._attributes[DeviceAttributes.zero_cold_pulse] - message.smart_volume = self._attributes[DeviceAttributes.smart_volume] - message.target_temperature = self._attributes[ - DeviceAttributes.target_temperature - ] - return message - - def set_attribute(self, attr, value): - if attr not in [ - DeviceAttributes.burning_state, - DeviceAttributes.current_temperature, - DeviceAttributes.protection, - ]: - if self._precision_halves and attr == DeviceAttributes.target_temperature: - value = int(value * 2) - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - elif self.subtype in self._old_subtypes: - message = self.make_message_set() - setattr(message, str(attr), value) - else: - message = MessageNewProtocolSet(self._protocol_version) - setattr(message, "key", str(attr)) - setattr(message, "value", value) - self.build_send(message) - - def set_customize(self, customize): - self._precision_halves = self._default_precision_halves - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "precision_halves" in params: - self._precision_halves = params.get("precision_halves") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"precision_halves": self._precision_halves}) - - -class MideaAppliance(MideaE3Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/e3/message.py b/custom_components/midea_ac_lan/midea/devices/e3/message.py deleted file mode 100644 index 4b6952a2..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e3/message.py +++ /dev/null @@ -1,173 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - -NEW_PROTOCOL_PARAMS = { - "zero_cold_water": 0x03, - # "zero_cold_master": 0x12, - "zero_cold_pulse": 0x04, - "smart_volume": 0x07, - "target_temperature": 0x08, -} - - -class MessageE3Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xE3, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageE3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessagePower(MessageE3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x02, - ) - self.power = False - - @property - def _body(self): - if self.power: - self.body_type = 0x01 - else: - self.body_type = 0x02 - return bytearray([0x01]) - - -class MessageSet(MessageE3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x04, - ) - - self.target_temperature = 0 - self.zero_cold_water = False - self.bathtub_volume = 0 - self.protection = False - self.zero_cold_pulse = False - self.smart_volume = False - - @property - def _body(self): - # Byte 2 zero_cold_water mode - zero_cold_water = 0x01 if self.zero_cold_water else 0x00 - # Byte 3 - protection = 0x08 if self.protection else 0x00 - zero_cold_pulse = 0x10 if self.zero_cold_pulse else 0x00 - smart_volume = 0x20 if self.smart_volume else 0x00 - # Byte 5 target_temperature - target_temperature = self.target_temperature & 0xFF - - return bytearray( - [ - 0x01, - zero_cold_water | 0x02, - protection | zero_cold_pulse | smart_volume, - 0x00, - target_temperature, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageNewProtocolSet(MessageE3Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x14, - ) - self.key = None - self.value = None - - @property - def _body(self): - key = NEW_PROTOCOL_PARAMS.get(self.key) - if self.key == "target_temperature": - value = self.value - else: - value = 0x01 if self.value else 0x00 - return bytearray( - [ - key, - value, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class E3GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[2] & 0x01) > 0 - self.burning_state = (body[2] & 0x02) > 0 - self.zero_cold_water = (body[2] & 0x04) > 0 - self.current_temperature = body[5] - self.target_temperature = body[6] - self.protection = (body[8] & 0x08) > 0 - self.zero_cold_pulse = (body[20] & 0x01) > 0 if len(body) > 20 else False - self.smart_volume = (body[20] & 0x02) > 0 if len(body) > 20 else False - - -class MessageE3Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - (self.message_type == MessageType.query and self.body_type == 0x01) - or ( - self.message_type == MessageType.set - and self.body_type in [0x01, 0x02, 0x04, 0x14] - ) - or ( - self.message_type == MessageType.notify1 - and self.body_type in [0x00, 0x01] - ) - ): - self.set_body(E3GeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e6/device.py b/custom_components/midea_ac_lan/midea/devices/e6/device.py deleted file mode 100644 index 82368b40..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e6/device.py +++ /dev/null @@ -1,93 +0,0 @@ -import logging - -from .message import MessageE6Response, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - main_power = "main_power" - heating_power = "heating_power" - heating_working = "heating_working" - bathing_working = "bathing_working" - min_temperature = "temperature_min" - max_temperature = "temperature_max" - heating_temperature = "heating_temperature" - bathing_temperature = "bathing_temperature" - heating_leaving_temperature = "heating_leaving_temperature" - bathing_leaving_temperature = "bathing_leaving_temperature" - - -class MideaE6Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xE6, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.main_power: False, - DeviceAttributes.heating_power: True, - DeviceAttributes.heating_working: None, - DeviceAttributes.bathing_working: None, - DeviceAttributes.min_temperature: [30, 35], - DeviceAttributes.max_temperature: [80, 60], - DeviceAttributes.heating_temperature: 50, - DeviceAttributes.bathing_temperature: 40, - DeviceAttributes.heating_leaving_temperature: None, - DeviceAttributes.bathing_leaving_temperature: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageE6Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.main_power, - DeviceAttributes.heating_power, - DeviceAttributes.heating_temperature, - DeviceAttributes.bathing_temperature, - ]: - message = MessageSet(self._protocol_version) - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaE6Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/e6/message.py b/custom_components/midea_ac_lan/midea/devices/e6/message.py deleted file mode 100644 index 230e2d5d..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e6/message.py +++ /dev/null @@ -1,79 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageE6Base(MessageRequest): - def __init__(self, protocol_version, message_type): - super().__init__( - device_type=0xE6, - protocol_version=protocol_version, - message_type=message_type, - body_type=None, - ) - - @property - def body(self): - return self._body - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageE6Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, message_type=MessageType.query - ) - - @property - def _body(self): - return bytearray([0x01, 0x01] + [0] * 28) - - -class MessageSet(MessageE6Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, message_type=MessageType.set - ) - self.main_power = None - self.heating_temperature = None - self.bathing_temperature = None - self.heating_power = None - - @property - def _body(self): - body = [] - if self.main_power is not None: - main_power = 0x01 if self.main_power else 0x02 - body = [main_power, 0x01] - elif self.heating_temperature is not None: - body = [0x04, 0x13, self.heating_temperature] - elif self.bathing_temperature is not None: - body = [0x04, 0x12, self.bathing_temperature] - elif self.heating_power is not None: - heating_power = 0x01 if self.heating_power else 0x02 - body = [0x04, 0x01, heating_power] - body_len = len(body) - return bytearray(body + [0] * (30 - body_len)) - - -class E6GeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.main_power = (body[2] & 0x04) > 0 - self.heating_working = (body[2] & 0x10) > 0 - self.bathing_working = (body[2] & 0x20) > 0 - self.heating_power = (body[4] & 0x01) > 0 - self.min_temperature = [body[16], body[11]] - self.max_temperature = [body[15], body[10]] - self.heating_temperature = body[17] - self.bathing_temperature = body[12] - self.heating_leaving_temperature = body[14] - self.bathing_leaving_temperature = body[8] - - -class MessageE6Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - self.set_body(E6GeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/e8/device.py b/custom_components/midea_ac_lan/midea/devices/e8/device.py deleted file mode 100644 index 813941a2..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e8/device.py +++ /dev/null @@ -1,99 +0,0 @@ -import logging - -from .message import MessageE8Response, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - status = "status" - time_remaining = "time_remaining" - keep_warm_remaining = "keep_warm_remaining" - working_time = "working_time" - target_temperature = "target_temperature" - current_temperature = "current_temperature" - finished = "finished" - water_shortage = "water_shortage" - - -class MideaE8Device(MiedaDevice): - _status = { - 0x00: "Standby", - 0x01: "Delay", - 0x02: "Working", - 0x03: "Paused", - 0x04: "Keep-Warming", - 0xFF: "Error", - } - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xB1, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.status: None, - DeviceAttributes.time_remaining: None, - DeviceAttributes.keep_warm_remaining: None, - DeviceAttributes.working_time: None, - DeviceAttributes.target_temperature: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.finished: None, - DeviceAttributes.water_shortage: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageE8Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.status: - if value in MideaE8Device._status.keys(): - self._attributes[DeviceAttributes.status] = ( - MideaE8Device._status.get(value) - ) - else: - self._attributes[DeviceAttributes.status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaE8Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/e8/message.py b/custom_components/midea_ac_lan/midea/devices/e8/message.py deleted file mode 100644 index 6fe81409..00000000 --- a/custom_components/midea_ac_lan/midea/devices/e8/message.py +++ /dev/null @@ -1,55 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageE8Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xE8, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageE8Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0xAA, - ) - - @property - def _body(self): - return bytearray([0x55, 0x00, 0x01, 0x00, 0x00]) - - -class E8MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.status = body[11] - self.time_remaining = body[16] * 3600 + body[17] * 60 + body[18] - self.keep_warm_remaining = body[19] * 3600 + body[20] * 60 + body[21] - self.working_time = body[28] * 3600 + body[29] * 60 + body[30] - self.target_temperature = body[39] - self.current_temperature = body[39] - self.finished = (body[41] & 0x01) > 0 - self.water_shortage = body[43] > 0 - - -class MessageE8Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if len(super().body) > 6: - sub_cmd = super().body[6] - if ( - (self.message_type == MessageType.set and sub_cmd in [0x02, 0x04, 0x06]) - or self.message_type in [MessageType.query, MessageType.notify1] - and sub_cmd == 2 - ): - self.set_body(E8MessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ea/device.py b/custom_components/midea_ac_lan/midea/devices/ea/device.py deleted file mode 100644 index 722d12d8..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ea/device.py +++ /dev/null @@ -1,197 +0,0 @@ -import logging - -from .message import MessageEAResponse, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - cooking = "cooking" - keep_warm = "keep_warm" - mode = "mode" - time_remaining = "time_remaining" - keep_warm_time = "keep_warm_time" - top_temperature = "top_temperature" - bottom_temperature = "bottom_temperature" - progress = "progress" - - -class MideaEADevice(MiedaDevice): - _mode_list = ( - [ - "smart", - "reserve", - "cook_rice", - "fast_cook_rice", - "standard_cook_rice", - "gruel", - "cook_congee", - "stew_soup", - "stewing", - "heat_rice", - "make_cake", - "yoghourt", - "soup_rice", - "coarse_rice", - "five_ceeals_rice", - "eight_treasures_rice", - "crispy_rice", - "shelled_rice", - "eight_treasures_congee", - "infant_congee", - "older_rice", - "rice_soup", - "rice_paste", - "egg_custard", - "warm_milk", - "hot_spring_egg", - "millet_congee", - "firewood_rice", - "few_rice", - "red_potato", - "corn", - "quick_freeze_bun", - "steam_ribs", - "steam_egg", - "coarse_congee", - "steep_rice", - "appetizing_congee", - "corn_congee", - "sprout_rice", - "luscious_rice", - "luscious_boiled", - "fast_rice", - "fast_boil", - "bean_rice_congee", - "fast_congee", - "baby_congee", - "cook_soup", - "congee_coup", - "steam_corn", - "steam_red_potato", - "boil_congee", - "delicious_steam", - "boil_egg", - "rice_wine", - "fruit_vegetable_paste", - "vegetable_porridge", - "pork_porridge", - "fragrant_rice", - "assorte_rice", - "steame_fish", - "baby_rice", - "essence_rice", - "fragrant_dense_congee", - "one_two_cook", - "original_steame", - "hot_fast_rice", - "online_celebrity_rice", - "sushi_rice", - "stone_bowl_rice", - "no_water_treat", - "keep_fresh", - "low_sugar_rice", - "black_buckwheat_rice", - "resveratrol_rice", - "yellow_wheat_rice", - "green_buckwheat_rice", - "roughage_rice", - "millet_mixed_rice", - "iron_pan_rice", - "olla_pan_rice", - "vegetable_rice", - "baby_side", - "regimen_congee", - "earthen_pot_congee", - "regimen_soup", - "pottery_jar_soup", - "canton_soup", - "nutrition_stew", - "northeast_stew", - "uncap_boil", - "trichromatic_coarse_grain", - "four_color_vegetables", - "egg", - "chop", - ] - + ["unknown"] * 98 - + ["clean"] - + ["unknown"] * 5 - + ["keep_warm"] - ) - _progress = ["Idle", "Delay", "Cooking", "Keep-warm"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xEA, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.cooking: False, - DeviceAttributes.keep_warm: False, - DeviceAttributes.mode: 0, - DeviceAttributes.time_remaining: None, - DeviceAttributes.top_temperature: None, - DeviceAttributes.bottom_temperature: None, - DeviceAttributes.keep_warm_time: None, - DeviceAttributes.progress: "Unknown", - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageEAResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.progress: - if value < len(MideaEADevice._progress): - self._attributes[status] = MideaEADevice._progress[value] - else: - self._attributes[status] = "Unknown" - elif status == DeviceAttributes.mode: - if value < len(MideaEADevice._mode_list): - self._attributes[status] = MideaEADevice._mode_list[value] - else: - self._attributes[status] = "Cloud" - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaEADevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/ea/message.py b/custom_components/midea_ac_lan/midea/devices/ea/message.py deleted file mode 100644 index 49347767..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ea/message.py +++ /dev/null @@ -1,116 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageEABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xEA, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageEABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=None, - ) - - @property - def body(self): - return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00]) - - @property - def _body(self): - return bytearray([]) - - -class EABody1(MessageBody): - def __init__(self, body): - super().__init__(body) - self.mode = body[6] + (body[7] << 8) - self.progress = body[14] - self.cooking = self.progress == 2 - self.keep_warm = self.progress == 3 - self.top_temperature = body[18] - self.bottom_temperature = body[19] - self.time_remaining = body[22] * 60 + body[23] - self.keep_warm_time = body[26] * 60 + body[27] - - -class EABody2(MessageBody): - def __init__(self, body): - super().__init__(body) - self.progress = body[9] - self.cooking = self.progress == 2 - self.keep_warm = self.progress == 3 - self.mode = body[58] + (body[59] << 8) - self.time_remaining = body[50] * 60 + body[51] - self.keep_warm_time = body[54] * 60 + body[55] - self.top_temperature = body[21] - self.bottom_temperature = body[20] - - -class EABody3(MessageBody): - def __init__(self, body): - super().__init__(body) - self.mode = body[4] + (body[5] << 8) - self.progress = body[8] - self.cooking = self.progress == 2 - self.keep_warm = self.progress == 3 - self.time_remaining = body[12] * 60 + body[13] - self.top_temperature = body[20] - self.bottom_temperature = body[21] - self.keep_warm_time = body[22] * 60 + body[23] - - -class EABodyNew(MessageBody): - def __init__(self, body): - super().__init__(body) - if body[6] in [2, 4, 6, 8, 10, 0x62]: - self.mode = body[7] + (body[8] << 8) - self.progress = body[11] - self.cooking = self.progress == 2 - self.keep_warm = self.progress == 3 - self.time_remaining = body[16] * 60 + body[17] - self.top_temperature = body[60] - self.bottom_temperature = body[61] - self.keep_warm_time = body[19] * 60 + body[20] - - -class MessageEAResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type == MessageType.notify1 and super().body[3] == 0x01: - self.set_body(EABodyNew(super().body)) - elif self.protocol_version == 0: - if self.message_type == MessageType.set and super().body[5] == 0x16: # 381 - self.set_body(EABody1(super().body)) - elif self.message_type == MessageType.query: - if super().body[6] == 0x52 and super().body[7] == 0xC3: # 404 - self.set_body(EABody2(super().body)) - elif super().body[5] == 0x3D: # 420 - self.set_body(EABody1(super().body)) - elif ( - self.message_type == MessageType.notify1 and super().body[5] == 0x3D - ): # 463 - self.set_body(EABody1(super().body)) - else: - if ( - (self.message_type == MessageType.set and super().body[3] == 0x02) - or (self.message_type == MessageType.query and super().body[3] == 0x03) - or ( - self.message_type == MessageType.notify1 and super().body[3] == 0x04 - ) - ): # 351 - self.set_body(EABody3(super().body)) - elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: - self.mode = super().body[4] + (super().body[5] << 8) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ec/device.py b/custom_components/midea_ac_lan/midea/devices/ec/device.py deleted file mode 100644 index a87e9955..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ec/device.py +++ /dev/null @@ -1,211 +0,0 @@ -import logging - -from .message import MessageECResponse, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - cooking = "cooking" - mode = "mode" - time_remaining = "time_remaining" - keep_warm_time = "keep_warm_time" - top_temperature = "top_temperature" - bottom_temperature = "bottom_temperature" - progress = "progress" - with_pressure = "with_pressure" - - -class MideaECDevice(MiedaDevice): - _mode_list = ( - [ - "smart", - "reserve", - "cook_rice", - "fast_cook_rice", - "standard_cook_rice", - "gruel", - "cook_congee", - "stew_soup", - "stewing", - "heat_rice", - "make_cake", - "yoghourt", - "soup_rice", - "coarse_rice", - "five_ceeals_rice", - "eight_treasures_rice", - "crispy_rice", - "shelled_rice", - "eight_treasures_congee", - "infant_congee", - "older_rice", - "rice_soup", - "rice_paste", - "egg_custard", - "warm_milk", - "hot_spring_egg", - "millet_congee", - "firewood_rice", - "few_rice", - "red_potato", - "corn", - "quick_freeze_bun", - "steam_ribs", - "steam_egg", - "coarse_congee", - "steep_rice", - "appetizing_congee", - "corn_congee", - "sprout_rice", - "luscious_rice", - "luscious_boiled", - "fast_rice", - "fast_boil", - "bean_rice_congee", - "fast_congee", - "baby_congee", - "cook_soup", - "congee_coup", - "steam_corn", - "steam_red_potato", - "boil_congee", - "delicious_steam", - "boil_egg", - "rice_wine", - "fruit_vegetable_paste", - "vegetable_porridge", - "pork_porridge", - "fragrant_rice", - "assorte_rice", - "steame_fish", - "baby_rice", - "essence_rice", - "fragrant_dense_congee", - "one_two_cook", - "original_steame", - "hot_fast_rice", - "online_celebrity_rice", - "sushi_rice", - "stone_bowl_rice", - "no_water_treat", - "keep_fresh", - "low_sugar_rice", - "black_buckwheat_rice", - "resveratrol_rice", - "yellow_wheat_rice", - "green_buckwheat_rice", - "roughage_rice", - "millet_mixed_rice", - "iron_pan_rice", - "olla_pan_rice", - "vegetable_rice", - "baby_side", - "regimen_congee", - "earthen_pot_congee", - "regimen_soup", - "pottery_jar_soup", - "canton_soup", - "nutrition_stew", - "northeast_stew", - "uncap_boil", - "trichromatic_coarse_grain", - "four_color_vegetables", - "egg", - "chop", - ] - + ["unknown"] * 98 - + ["clean"] - + ["unknown"] * 5 - + ["keep_warm", "diy"] - ) - _progress = [ - "Idle", - "Cooking", - "Delay", - "Keep-warm", - "Lid-open", - "Relieving", - "Keep-pressure", - "Relieving", - "Cooking", - "Relieving", - "Lid-open", - ] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xEC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.cooking: False, - DeviceAttributes.mode: 0, - DeviceAttributes.time_remaining: None, - DeviceAttributes.top_temperature: None, - DeviceAttributes.bottom_temperature: None, - DeviceAttributes.keep_warm_time: None, - DeviceAttributes.progress: "Unknown", - DeviceAttributes.with_pressure: None, - }, - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageECResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.progress: - if value < len(MideaECDevice._progress): - self._attributes[status] = MideaECDevice._progress[ - getattr(message, str(status)) - ] - else: - self._attributes[status] = "Unknown" - elif status == DeviceAttributes.mode: - if value < len(MideaECDevice._mode_list): - self._attributes[status] = MideaECDevice._mode_list[value] - else: - self._attributes[status] = "Cloud" - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - pass - - -class MideaAppliance(MideaECDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/ec/message.py b/custom_components/midea_ac_lan/midea/devices/ec/message.py deleted file mode 100644 index 00c4aefe..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ec/message.py +++ /dev/null @@ -1,74 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageECBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xEC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageECBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=None, - ) - - @property - def body(self): - return bytearray([0xAA, 0x55, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) - - @property - def _body(self): - return bytearray([]) - - -class ECGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.mode = body[4] + (body[5] << 8) - self.progress = body[8] - self.cooking = self.progress == 1 - self.time_remaining = body[12] * 60 + body[13] - self.keep_warm_time = body[16] * 60 + body[17] - self.top_temperature = body[21] - self.bottom_temperature = body[22] - self.with_pressure = (body[23] & 0x04) > 0 - - -class ECBodyNew(MessageBody): - def __init__(self, body): - super().__init__(body) - self.progress = body[11] - self.cooking = self.progress == 1 - self.time_remaining = body[16] * 60 + body[17] - self.keep_warm_time = body[19] * 60 + body[20] - self.top_temperature = body[48] - self.bottom_temperature = body[49] - self.with_pressure = body[33] > 0 - - -class MessageECResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type == MessageType.notify1 and super().body[3] == 0x01: - self.set_body(ECBodyNew(super().body)) - elif ( - (self.message_type == MessageType.set and super().body[3] == 0x02) - or (self.message_type == MessageType.query and super().body[3] == 0x03) - or (self.message_type == MessageType.notify1 and super().body[3] == 0x04) - or (self.message_type == MessageType.notify1 and super().body[3] == 0x3D) - ): - self.set_body(ECGeneralMessageBody(super().body)) - elif self.message_type == MessageType.notify1 and super().body[3] == 0x06: - self.mode = super().body[4] + (super().body[5] << 8) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/ed/device.py b/custom_components/midea_ac_lan/midea/devices/ed/device.py deleted file mode 100644 index a25872e5..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ed/device.py +++ /dev/null @@ -1,103 +0,0 @@ -import logging - -from .message import MessageEDResponse, MessageNewSet, MessageOldSet, MessageQuery - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - water_consumption = "water_consumption" - in_tds = "in_tds" - out_tds = "out_tds" - filter1 = "filter1" - filter2 = "filter2" - filter3 = "filter3" - life1 = "life1" - life2 = "life2" - life3 = "life3" - child_lock = "child_lock" - - -class MideaEDDevice(MiedaDevice): - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xED, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.water_consumption: None, - DeviceAttributes.in_tds: None, - DeviceAttributes.out_tds: None, - DeviceAttributes.filter1: None, - DeviceAttributes.filter2: None, - DeviceAttributes.filter3: None, - DeviceAttributes.life1: None, - DeviceAttributes.life2: None, - DeviceAttributes.life3: None, - DeviceAttributes.child_lock: False, - }, - ) - self._device_class = 0 - - def _use_new_set(self): - return True # if (self.sub_type > 342 or self.sub_type == 340) else False - - def build_query(self): - return [MessageQuery(self._protocol_version, self._device_class)] - - def process_message(self, msg): - message = MessageEDResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - if hasattr(message, "device_class"): - self._device_class = message.device_class - for status in self._attributes.keys(): - if hasattr(message, str(status)): - new_status[str(status)] = getattr(message, str(status)) - self._attributes[status] = getattr(message, str(status)) - return new_status - - def set_attribute(self, attr, value): - message = None - if self._use_new_set(): - if attr in [DeviceAttributes.power, DeviceAttributes.child_lock]: - message = MessageNewSet(self._protocol_version) - else: - if attr in []: - message = MessageOldSet(self._protocol_version) - if message is not None: - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaEDDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/ed/message.py b/custom_components/midea_ac_lan/midea/devices/ed/message.py deleted file mode 100644 index e843d8af..00000000 --- a/custom_components/midea_ac_lan/midea/devices/ed/message.py +++ /dev/null @@ -1,200 +0,0 @@ -from enum import IntEnum - -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class NewSetTags(IntEnum): - power = 0x0100 - lock = 0x0201 - - -class EDNewSetParamPack: - @staticmethod - def pack(param, value, addition=0): - return bytearray( - [param & 0xFF, param >> 8, value, addition & 0xFF, addition >> 8] - ) - - -class MessageEDBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xED, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageEDBase): - def __init__(self, protocol_version, device_class): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=device_class, - ) - - @property - def _body(self): - return bytearray([0x01]) - - -class MessageNewSet(MessageEDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x15, - ) - self.power = None - self.lock = None - - @property - def _body(self): - pack_count = 0 - payload = bytearray([0x01, 0x00]) - if self.power is not None: - pack_count += 1 - payload.extend( - EDNewSetParamPack.pack( - param=NewSetTags.power, value=0x01 if self.power else 0x00 # power - ) - ) - if self.lock is not None: - pack_count += 1 - payload.extend( - EDNewSetParamPack.pack( - param=NewSetTags.lock, value=0x01 if self.lock else 0x00 # lock - ) - ) - payload[1] = pack_count - return payload - - -class MessageOldSet(MessageEDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=None, - ) - - @property - def body(self): - return bytearray([]) - - @property - def _body(self): - return bytearray([]) - - -class EDMessageBody01(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[2] & 0x01) > 0 - self.water_consumption = body[7] + (body[8] << 8) - self.in_tds = body[36] + (body[37] << 8) - self.out_tds = body[38] + (body[39] << 8) - self.child_lock = body[15] > 0 - self.filter1 = round((body[25] + (body[26] << 8)) / 24) - self.filter2 = round((body[27] + (body[28] << 8)) / 24) - self.filter3 = round((body[29] + (body[30] << 8)) / 24) - self.life1 = body[16] - self.life2 = body[17] - self.life3 = body[18] - - -class EDMessageBody03(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[51] & 0x01) > 0 - self.child_lock = (body[51] & 0x08) > 0 - self.water_consumption = body[20] + (body[21] << 8) - self.life1 = body[22] - self.life2 = body[23] - self.life3 = body[24] - self.in_tds = body[27] + (body[28] << 8) - self.out_tds = body[29] + (body[30] << 8) - - -class EDMessageBody05(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[51] & 0x01) > 0 - self.child_lock = (body[51] & 0x08) > 0 - self.water_consumption = body[20] + (body[21] << 8) - - -class EDMessageBody06(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[51] & 0x01) > 0 - self.child_lock = (body[51] & 0x08) > 0 - self.water_consumption = body[25] + (body[26] << 8) - - -class EDMessageBody07(MessageBody): - def __init__(self, body): - super().__init__(body) - self.water_consumption = (body[21] << 8) + body[20] - self.power = (body[51] & 0x01) > 0 - self.child_lock = (body[51] & 0x08) > 0 - - -class EDMessageBodyFF(MessageBody): - def __init__(self, body): - super().__init__(body) - data_offset = 2 - while True: - length = (body[data_offset + 2] >> 4) + 2 - attr = ((body[data_offset + 2] % 16) << 8) + body[data_offset + 1] - if attr == 0x000: - self.child_lock = (body[data_offset + 5] & 0x01) > 0 - self.power = (body[data_offset + 6] & 0x01) > 0 - elif attr == 0x011: - self.water_consumption = ( - float( - ( - body[data_offset + 3] - + (body[data_offset + 4] << 8) - + (body[data_offset + 5] << 16) - + (body[data_offset + 6] << 24) - ) - ) - / 1000 - ) - elif attr == 0x013: - self.in_tds = body[data_offset + 3] + (body[data_offset + 4] << 8) - self.out_tds = body[data_offset + 5] + (body[data_offset + 6] << 8) - elif attr == 0x10: - self.life1 = body[data_offset + 3] - self.life2 = body[data_offset + 4] - self.life3 = body[data_offset + 5] - # fix index out of range error - if data_offset + length + 6 > len(body): - break - data_offset += length - - -class MessageEDResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self._message_type in [MessageType.query, MessageType.notify1]: - self.device_class = self._body_type - if self._body_type in [0x00, 0xFF]: - self.set_body(EDMessageBodyFF(super().body)) - if self.body_type == 0x01: - self.set_body(EDMessageBody01(super().body)) - elif self.body_type in [0x03, 0x04]: - self.set_body(EDMessageBody03(super().body)) - elif self.body_type == 0x05: - self.set_body(EDMessageBody05(super().body)) - elif self.body_type == 0x06: - self.set_body(EDMessageBody06(super().body)) - elif self.body_type == 0x07: - self.set_body(EDMessageBody07(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fa/device.py b/custom_components/midea_ac_lan/midea/devices/fa/device.py deleted file mode 100644 index 38772c93..00000000 --- a/custom_components/midea_ac_lan/midea/devices/fa/device.py +++ /dev/null @@ -1,327 +0,0 @@ -import json -import logging - -from .message import MessageFAResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - child_lock = "child_lock" - mode = "mode" - fan_speed = "fan_speed" - oscillate = "oscillate" - oscillation_angle = "oscillation_angle" - tilting_angle = "tilting_angle" - oscillation_mode = "oscillation_mode" - - -class MideaFADevice(MiedaDevice): - _oscillation_angles = ["Off", "30", "60", "90", "120", "180", "360"] - _tilting_angles = ["Off", "30", "60", "90", "120", "180", "360", "+60", "-60", "40"] - _oscillation_modes = [ - "Off", - "Oscillation", - "Tilting", - "Curve-W", - "Curve-8", - "Reserved", - "Both", - ] - _modes = [ - "Normal", - "Natural", - "Sleep", - "Comfort", - "Silent", - "Baby", - "Induction", - "Circulation", - "Strong", - "Soft", - "Customize", - "Warm", - "Smart", - ] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xFA, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.child_lock: False, - DeviceAttributes.mode: 0, - DeviceAttributes.fan_speed: 0, - DeviceAttributes.oscillate: False, - DeviceAttributes.oscillation_angle: None, - DeviceAttributes.tilting_angle: None, - DeviceAttributes.oscillation_mode: None, - }, - ) - self._default_speed_count = 3 - self._speed_count = self._default_speed_count - self.set_customize(customize) - - @property - def speed_count(self): - return self._speed_count - - @property - def oscillation_angles(self): - return MideaFADevice._oscillation_angles - - @property - def tilting_angles(self): - return MideaFADevice._tilting_angles - - @property - def oscillation_modes(self): - return MideaFADevice._oscillation_modes - - @property - def preset_modes(self): - return self._modes - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageFAResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.oscillation_angle: - if value < len(MideaFADevice._oscillation_angles): - self._attributes[status] = MideaFADevice._oscillation_angles[ - value - ] - else: - self._attributes[status] = None - elif status == DeviceAttributes.tilting_angle: - if value < len(MideaFADevice._tilting_angles): - self._attributes[status] = MideaFADevice._tilting_angles[value] - else: - self._attributes[status] = None - elif status == DeviceAttributes.oscillation_mode: - if value < len(MideaFADevice._oscillation_modes): - self._attributes[status] = MideaFADevice._oscillation_modes[ - value - ] - else: - self._attributes[status] = None - elif status == DeviceAttributes.mode: - if value < len(MideaFADevice._modes): - self._attributes[status] = MideaFADevice._modes[value] - else: - self._attributes[status] = None - elif status == DeviceAttributes.power: - self._attributes[status] = value - if not value: - self._attributes[DeviceAttributes.fan_speed] = 0 - elif ( - status == DeviceAttributes.fan_speed - and not self._attributes[DeviceAttributes.power] - ): - self._attributes[status] = 0 - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_oscillation(self, attr, value): - message = None - if self._attributes[attr] != value: - if attr == DeviceAttributes.oscillate: - message = MessageSet(self._protocol_version, self.subtype) - message.oscillate = value - if value: - message.oscillation_angle = 3 # 90 - message.oscillation_mode = 1 # Oscillation - elif attr == DeviceAttributes.oscillation_mode and ( - value in MideaFADevice._oscillation_modes or not value - ): - message = MessageSet(self._protocol_version, self.subtype) - if value == "Off" or not value: - message.oscillate = False - else: - message.oscillate = True - message.oscillation_mode = MideaFADevice._oscillation_modes.index( - value - ) - if value == "Oscillation": - if ( - self._attributes[DeviceAttributes.oscillation_angle] - == "Off" - ): - message.oscillation_angle = 3 # 90 - else: - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) - ) - elif value == "Tilting": - if self._attributes[DeviceAttributes.tilting_angle] == "Off": - message.tilting_angle = 3 # 90 - else: - message.tilting_angle = MideaFADevice._tilting_angles.index( - self._attributes[DeviceAttributes.tilting_angle] - ) - else: - if ( - self._attributes[DeviceAttributes.oscillation_angle] - == "Off" - ): - message.oscillation_angle = 3 # 90 - else: - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) - ) - if self._attributes[DeviceAttributes.tilting_angle] == "Off": - message.tilting_angle = 3 # 90 - else: - message.tilting_angle = MideaFADevice._tilting_angles.index( - self._attributes[DeviceAttributes.tilting_angle] - ) - elif attr == DeviceAttributes.oscillation_angle and ( - value in MideaFADevice._oscillation_angles or not value - ): - message = MessageSet(self._protocol_version, self.subtype) - if value == "Off" or not value: - if self._attributes[DeviceAttributes.tilting_angle] == "Off": - message.oscillate = False - else: - message.oscillate = True - message.oscillation_mode = 2 - message.tilting_angle = MideaFADevice._tilting_angles.index( - self._attributes[DeviceAttributes.tilting_angle] - ) - else: - message.oscillation_angle = MideaFADevice._oscillation_angles.index( - value - ) - message.oscillate = True - if self._attributes[DeviceAttributes.tilting_angle] == "Off": - message.oscillation_mode = 1 - elif ( - self._attributes[DeviceAttributes.oscillation_mode] == "Tilting" - ): - message.oscillation_mode = 6 - message.tilting_angle = MideaFADevice._tilting_angles.index( - self._attributes[DeviceAttributes.tilting_angle] - ) - elif attr == DeviceAttributes.tilting_angle and ( - value in MideaFADevice._tilting_angles or not value - ): - message = MessageSet(self._protocol_version, self.subtype) - if value == "Off" or not value: - if self._attributes[DeviceAttributes.oscillation_angle] == "Off": - message.oscillate = False - else: - message.oscillate = True - message.oscillation_mode = 1 - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) - ) - else: - message.tilting_angle = MideaFADevice._tilting_angles.index(value) - message.oscillate = True - if self._attributes[DeviceAttributes.oscillation_angle] == "Off": - message.oscillation_mode = 2 - elif ( - self._attributes[DeviceAttributes.oscillation_mode] - == "Oscillation" - ): - message.oscillation_mode = 6 - message.oscillation_angle = ( - MideaFADevice._oscillation_angles.index( - self._attributes[DeviceAttributes.oscillation_angle] - ) - ) - return message - - def set_attribute(self, attr, value): - message = None - if attr in [ - DeviceAttributes.oscillate, - DeviceAttributes.oscillation_mode, - DeviceAttributes.oscillation_angle, - DeviceAttributes.tilting_angle, - ]: - message = self.set_oscillation(attr, value) - elif ( - attr == DeviceAttributes.fan_speed - and value > 0 - and not self._attributes[DeviceAttributes.power] - ): - message = MessageSet(self._protocol_version, self.subtype) - message.fan_speed = value - message.power = True - elif attr == DeviceAttributes.mode: - if value in MideaFADevice._modes: - message = MessageSet(self._protocol_version, self.subtype) - message.mode = MideaFADevice._modes.index(value) - elif not (attr == DeviceAttributes.fan_speed and value == 0): - message = MessageSet(self._protocol_version, self.subtype) - setattr(message, str(attr), value) - if message is not None: - self.build_send(message) - - def turn_on(self, fan_speed=None, mode=None): - message = MessageSet(self._protocol_version, self.subtype) - message.power = True - if fan_speed is not None: - message.fan_speed = fan_speed - if mode is None: - message.mode = mode - self.build_send(message) - - def set_customize(self, customize): - self._speed_count = self._default_speed_count - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "speed_count" in params: - self._speed_count = params.get("speed_count") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"speed_count": self._speed_count}) - - -class MideaAppliance(MideaFADevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/fa/message.py b/custom_components/midea_ac_lan/midea/devices/fa/message.py deleted file mode 100644 index 6e11a307..00000000 --- a/custom_components/midea_ac_lan/midea/devices/fa/message.py +++ /dev/null @@ -1,197 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFABase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xFA, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageFABase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=None, - ) - - @property - def body(self): - return bytearray([]) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageFABase): - def __init__(self, protocol_version, subtype): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x00, - ) - self._subtype = subtype - self.power = None - self.lock = None - self.mode = None - self.fan_speed = None - self.oscillate = None - self.oscillation_angle = None - self.oscillation_mode = None - self.tilting_angle = None - - @property - def _body(self): - if 1 <= self._subtype <= 10 or self._subtype == 161: - _body_return = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - if self._subtype != 10: - _body_return[13] = 0xFF - else: - _body_return = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x80, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - if self.power is not None: - if self.power: - _body_return[3] = 1 - else: - _body_return[3] = 0 - if self.lock is not None: - if self.lock: - _body_return[2] = 1 - else: - _body_return[2] = 2 - if self.mode is not None: - _body_return[3] = 1 | (((self.mode + 1) << 1) & 0x1E) - if self.fan_speed is not None and 1 <= self.fan_speed <= 26: - _body_return[4] = self.fan_speed - if self.oscillate is not None: - if self.oscillate: - _body_return[7] = 1 - else: - _body_return[7] = 0 - if self.oscillation_angle is not None: - _body_return[7] = ( - 1 | _body_return[7] | ((self.oscillation_angle << 4) & 0x70) - ) - if self.oscillation_mode is not None: - _body_return[7] = ( - 1 | _body_return[7] | ((self.oscillation_mode << 1) & 0x0E) - ) - if self.tilting_angle is not None and len(_body_return) > 24: - _body_return[24] = self.tilting_angle - return _body_return - - -class FAGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - lock = body[3] & 0x03 - if lock == 1: - self.child_lock = True - else: - self.child_lock = False - self.power = (body[4] & 0x01) > 0 - mode = (body[4] & 0x1E) >> 1 - if mode > 0: - self.mode = mode - 1 - fan_speed = body[5] - if 1 <= fan_speed <= 26: - self.fan_speed = fan_speed - else: - self.fan_speed = 0 - self.oscillate = (body[8] & 0x01) > 0 - self.oscillation_angle = (body[8] & 0x70) >> 4 - self.oscillation_mode = (body[8] & 0x0E) >> 1 - self.tilting_angle = body[25] if len(body) > 25 else 0 - - -class MessageFAResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: - self.set_body(FAGeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fb/device.py b/custom_components/midea_ac_lan/midea/devices/fb/device.py deleted file mode 100644 index c5ae1887..00000000 --- a/custom_components/midea_ac_lan/midea/devices/fb/device.py +++ /dev/null @@ -1,109 +0,0 @@ -import logging - -from .message import MessageFBResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - heating_level = "heating_level" - target_temperature = "target_temperature" - current_temperature = "current_temperature" - child_lock = "child_lock" - - -class MideaFBDevice(MiedaDevice): - _modes = { - 0x01: "Auto", - 0x02: "ECO", - 0x03: "Sleep", - 0x04: "Anti-freezing", - 0x05: "Comfort", - 0x06: "Constant-temperature", - 0x07: "Normal", - 0x08: "Fast-heating", - 0x10: "Standby", - } - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xFB, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: None, - DeviceAttributes.heating_level: 0, - DeviceAttributes.target_temperature: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.child_lock: False, - }, - ) - - @property - def modes(self): - return list(MideaFBDevice._modes.values()) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageFBResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - if value in MideaFBDevice._modes.keys(): - self._attributes[status] = MideaFBDevice._modes.get(value) - else: - self._attributes[status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.mode: - message = MessageSet(self._protocol_version, self.subtype) - if value in MideaFBDevice._modes.values(): - message.mode = list(MideaFBDevice._modes.keys())[ - list(MideaFBDevice._modes.values()).index(value) - ] - else: - message = MessageSet(self._protocol_version, self.subtype) - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaFBDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/fb/message.py b/custom_components/midea_ac_lan/midea/devices/fb/message.py deleted file mode 100644 index 57e613d9..00000000 --- a/custom_components/midea_ac_lan/midea/devices/fb/message.py +++ /dev/null @@ -1,134 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFBBase(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xFB, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(MessageFBBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=None, - ) - - @property - def body(self): - return bytearray([]) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(MessageFBBase): - def __init__(self, protocol_version, subtype): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x00, - ) - self._subtype = subtype - self.power = None - self.mode = None - self.heating_level = None - self.target_temperature = None - self.child_lock = None - - @property - def body(self): - power = 0 if self.power is None else (0x01 if self.power else 0x02) - mode = 0 if self.mode is None else self.mode - heating_level = ( - 0 - if self.heating_level is None - else ( - int(self.heating_level if 1 <= self.heating_level <= 10 else 0) & 0xFF - ) - ) - target_temperature = ( - 0 - if self.target_temperature is None - else ( - int( - (self.target_temperature + 41) - if -40 <= self.target_temperature <= 50 - else (0x80 if self.target_temperature in [0x80, 87] else 0) - ) - & 0xFF - ) - ) - child_lock = ( - 0xFF if self.child_lock is None else (0x01 if self.child_lock else 0x00) - ) - _return_body = bytearray( - [ - power, - 0x00, - 0x00, - 0x00, - mode, - heating_level, - target_temperature, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - child_lock, - 0x00, - ] - ) - if self._subtype > 5: - _return_body += bytearray([0x00, 0x00, 0x00]) - return _return_body - - @property - def _body(self): - return bytearray([]) - - -class FBGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[0] & 0x01) not in [0, 2] - self.mode = body[4] - self.heating_level = body[5] - self.target_temperature = body[6] - 41 - if 1 <= body[7] <= 100: - self.target_humidity = body[7] - self.current_humidity = body[12] - self.current_temperature = body[13] - 20 - if len(body) > 18: - self.child_lock = (body[18] & 0x01) > 0 - if len(body) > 21: - self.energy_consumption = (body[21] << 8) + body[20] - - -class MessageFBResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: - self.set_body(FBGeneralMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fc/device.py b/custom_components/midea_ac_lan/midea/devices/fc/device.py deleted file mode 100644 index fad4f23d..00000000 --- a/custom_components/midea_ac_lan/midea/devices/fc/device.py +++ /dev/null @@ -1,238 +0,0 @@ -import json -import logging - -from .message import MessageFCResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - mode = "mode" - fan_speed = "fan_speed" - anion = "anion" - screen_display = "screen_display" - detect_mode = "detect_mode" - pm25 = "pm25" - tvoc = "tvoc" - hcho = "hcho" - child_lock = "child_lock" - prompt_tone = "prompt_tone" - filter1_life = "filter1_life" - filter2_life = "filter2_life" - standby = "standby" - - -class MideaFCDevice(MiedaDevice): - _modes = { - 0x00: "Standby", - 0x10: "Auto", - 0x20: "Manual", - 0x30: "Sleep", - 0x40: "Fast", - 0x50: "Smoke", - } - _speeds = {1: "Auto", 4: "Standby", 39: "Low", 59: "Medium", 80: "High"} - _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} - _detect_modes = ["Off", "PM 2.5", "Methanal"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xFC, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.mode: None, - DeviceAttributes.fan_speed: None, - DeviceAttributes.anion: False, - DeviceAttributes.standby: False, - DeviceAttributes.screen_display: None, - DeviceAttributes.detect_mode: None, - DeviceAttributes.pm25: None, - DeviceAttributes.tvoc: None, - DeviceAttributes.hcho: None, - DeviceAttributes.child_lock: False, - DeviceAttributes.prompt_tone: True, - DeviceAttributes.filter1_life: None, - DeviceAttributes.filter2_life: None, - }, - ) - - self._standby_detect_default = [40, 20] - self._standby_detect = self._standby_detect_default - self.set_customize(customize) - - @property - def modes(self): - return list(MideaFCDevice._modes.values()) - - @property - def fan_speeds(self): - return list(MideaFCDevice._speeds.values()) - - @property - def screen_displays(self): - return list(MideaFCDevice._screen_displays.values()) - - @property - def detect_modes(self): - return self._detect_modes - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = MessageFCResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - if value in MideaFCDevice._modes.keys(): - self._attributes[status] = MideaFCDevice._modes.get(value) - else: - self._attributes[status] = None - elif status == DeviceAttributes.fan_speed: - if value in MideaFCDevice._speeds.keys(): - self._attributes[status] = MideaFCDevice._speeds.get(value) - else: - self._attributes[status] = None - elif status == DeviceAttributes.screen_display: - if value in MideaFCDevice._screen_displays.keys(): - self._attributes[status] = MideaFCDevice._screen_displays.get( - value - ) - else: - self._attributes[status] = None - elif status == DeviceAttributes.detect_mode: - if value < len(MideaFCDevice._detect_modes): - self._attributes[status] = MideaFCDevice._detect_modes[value] - else: - self._attributes[status] = None - else: - - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.child_lock = self._attributes[DeviceAttributes.child_lock] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.anion = self._attributes[DeviceAttributes.anion] - message.standby = self._attributes[DeviceAttributes.standby] - message.screen_display = self._attributes[DeviceAttributes.screen_display] - message.detect_mode = ( - 0 - if self._attributes[DeviceAttributes.detect_mode] is None - else MideaFCDevice._detect_modes.index( - self._attributes[DeviceAttributes.detect_mode] - ) - ) - message.mode = ( - 0x10 - if self._attributes[DeviceAttributes.mode] is None - else list(MideaFCDevice._modes.keys())[ - list(MideaFCDevice._modes.values()).index( - self._attributes[DeviceAttributes.mode] - ) - ] - ) - message.fan_speed = ( - 39 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(MideaFCDevice._speeds.keys())[ - list(MideaFCDevice._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) - message.screen_display = ( - 0 - if self._attributes[DeviceAttributes.screen_display] is None - else list(MideaFCDevice._screen_displays.keys())[ - list(MideaFCDevice._screen_displays.values()).index( - self._attributes[DeviceAttributes.screen_display] - ) - ] - ) - message.standby_detect = self._standby_detect - return message - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.prompt_tone: - self._attributes[DeviceAttributes.prompt_tone] = value - self.update_all({DeviceAttributes.prompt_tone.value: value}) - else: - message = self.make_message_set() - if attr == DeviceAttributes.mode: - if value in MideaFCDevice._modes.values(): - message.mode = list(MideaFCDevice._modes.keys())[ - list(MideaFCDevice._modes.values()).index(value) - ] - elif attr == DeviceAttributes.fan_speed: - if value in MideaFCDevice._speeds.values(): - message.fan_speed = list(MideaFCDevice._speeds.keys())[ - list(MideaFCDevice._speeds.values()).index(value) - ] - elif attr == DeviceAttributes.screen_display: - if value in MideaFCDevice._screen_displays.values(): - message.screen_display = list( - MideaFCDevice._screen_displays.keys() - )[list(MideaFCDevice._screen_displays.values()).index(value)] - elif not value: - message.screen_display = 7 - elif attr == DeviceAttributes.detect_mode: - if value in MideaFCDevice._detect_modes: - message.detect_mode = MideaFCDevice._detect_modes.index(value) - elif not value: - message.detect_mode = 0 - else: - setattr(message, str(attr), value) - self.build_send(message) - - def set_customize(self, customize): - self._standby_detect = self._standby_detect_default - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "standby_detect" in params: - settings = params.get("standby_detect") - if len(settings) == 2 and settings[0] > settings[1]: - self._standby_detect = settings - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"standby_detect": self._standby_detect}) - - -class MideaAppliance(MideaFCDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/fc/message.py b/custom_components/midea_ac_lan/midea/devices/fc/message.py deleted file mode 100644 index db391f8e..00000000 --- a/custom_components/midea_ac_lan/midea/devices/fc/message.py +++ /dev/null @@ -1,209 +0,0 @@ -from ...core.crc8 import calculate -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFCBase(MessageRequest): - _message_serial = 0 - - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xFC, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - MessageFCBase._message_serial += 1 - if MessageFCBase._message_serial >= 254: - MessageFCBase._message_serial = 1 - self._message_id = MessageFCBase._message_serial - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) - body.append(calculate(body)) - return body - - -class MessageQuery(MessageFCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray( - [ - 0x00, - 0x00, - 0xFF, - 0x03, - 0x00, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageSet(MessageFCBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x48, - ) - self.power = False - self.mode = 0 - self.fan_speed = 0 - self.child_lock = False - self.prompt_tone = False - self.anion = False - self.standby = False - self.screen_display = 0 - self.detect_mode = 0 - self.standby_detect = [40, 20] - - @property - def _body(self): - # byte1 power - power = 0x01 if self.power else 0x00 - detect = 0x08 if self.detect_mode > 0 else 0x00 - detect_mode = (self.detect_mode - 1) if self.detect_mode > 0 else 0 - # byte2 mode - # byte3 fan_speed - # byte 8 child_lock - child_lock = 0x80 if self.child_lock else 0x00 - # byte 9 anion - anion = 0x20 if self.anion else 0x00 - # byte 10 prompt_tone - prompt_tone = 0x40 if self.prompt_tone else 0x00 - # byte 15/16/17 standby - if self.standby: - standby = 0x04 - standby_detect_high = self.standby_detect[0] - standby_detect_low = self.standby_detect[1] - else: - standby = 0x08 - standby_detect_high = 0 - standby_detect_low = 0 - return bytearray( - [ - power | prompt_tone | detect | 0x02, - self.mode, - self.fan_speed, - 0x00, - 0x00, - 0x00, - 0x00, - child_lock, - self.screen_display, - anion, - 0x00, - 0x00, - 0x00, - detect_mode, - standby, - standby_detect_high, - standby_detect_low, - 0x00, - 0x00, - 0x00, - ] - ) - - -class FCGeneralMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.mode = body[2] & 0xF0 - self.fan_speed = body[3] & 0x7F - self.screen_display = body[9] & 0x07 - if len(body) > 14 and body[14] != 0xFF: - self.pm25 = body[13] + (body[14] << 8) - else: - self.pm25 = None - if len(body) > 15 and body[15] != 0xFF: - self.tvoc = body[15] - else: - self.tvoc = None - self.anion = (body[19] & 0x40 > 0) if len(body) > 19 else False - self.standby = ((body[34] & 0xFF) == 0x14) if len(body) > 34 else False - self.child_lock = (body[8] & 0x80 > 0) if len(body) > 8 else False - if len(body) > 23: - self.filter1_life = body[23] - if len(body) > 24: - self.filter2_life = body[24] - if len(body) > 29: - if (body[1] & 0x08) > 0: - self.detect_mode = body[29] + 1 - else: - self.detect_mode = 0 - if len(body) > 38 and body[38] != 0xFF: - self.hcho = body[37] + (body[38] << 8) - else: - self.hcho = None - - -class FCNotifyMessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.mode = body[2] & 0xF0 - self.fan_speed = body[3] & 0x7F - self.screen_display = body[9] & 0x07 - if len(body) > 14 and body[14] != 0xFF: - self.pm25 = body[13] + (body[14] << 8) - else: - self.pm25 = None - if len(body) > 15 and body[15] != 0xFF: - self.tvoc = body[15] - else: - self.tvoc = None - self.anion = (body[10] & 0x20 > 0) if len(body) > 10 else False - self.standby = (body[27] & 0x14 == 0xFF) if len(body) > 27 else False - self.child_lock = (body[10] & 0x10 > 0) if len(body) > 10 else False - if len(body) > 22: - if (body[1] & 0x08) > 0: - self.detect_mode = body[22] + 1 - else: - self.detect_mode = 0 - if len(body) > 31 and body[31] != 0xFF: - self.hcho = body[30] + (body[31] << 8) - else: - self.hcho = None - - -class MessageFCResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.body_type in [0xB0, 0xB1]: - pass - else: - if ( - self.message_type - in [MessageType.query, MessageType.set, MessageType.notify1] - and self.body_type == 0xC8 - ): - self.set_body(FCGeneralMessageBody(super().body)) - elif self.message_type == MessageType.notify1 and self.body_type == 0xA0: - self.set_body(FCNotifyMessageBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/fd/device.py b/custom_components/midea_ac_lan/midea/devices/fd/device.py deleted file mode 100644 index 82dfc063..00000000 --- a/custom_components/midea_ac_lan/midea/devices/fd/device.py +++ /dev/null @@ -1,207 +0,0 @@ -import logging - -from .message import MessageFDResponse, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - fan_speed = "fan_speed" - prompt_tone = "prompt_tone" - target_humidity = "target_humidity" - current_humidity = "current_humidity" - current_temperature = "current_temperature" - tank = "tank" - mode = "mode" - screen_display = "screen_display" - disinfect = "disinfect" - - -class MideaFDDevice(MiedaDevice): - _modes = [ - "Manual", - "Auto", - "Continuous", - "Living-Room", - "Bed-Room", - "Kitchen", - "Sleep", - ] - _speeds_old = { - 1: "Lowest", - 40: "Low", - 60: "Medium", - 80: "High", - 102: "Auto", - 127: "Off", - } - _speeds_new = { - 1: "Lowest", - 39: "Low", - 59: "Medium", - 80: "High", - 101: "Auto", - 127: "Off", - } - _screen_displays = {0: "Bright", 6: "Dim", 7: "Off"} - _detect_modes = ["Off", "PM 2.5", "Methanal"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0xFD, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.fan_speed: None, - DeviceAttributes.prompt_tone: True, - DeviceAttributes.target_humidity: 60, - DeviceAttributes.current_humidity: None, - DeviceAttributes.current_temperature: None, - DeviceAttributes.tank: 0, - DeviceAttributes.mode: None, - DeviceAttributes.screen_display: None, - DeviceAttributes.disinfect: None, - }, - ) - if self.subtype > 5: - self._speeds = MideaFDDevice._speeds_new - else: - self._speeds = MideaFDDevice._speeds_old - - @property - def modes(self): - return list(MideaFDDevice._modes) - - @property - def fan_speeds(self): - return list(self._speeds.values()) - - @property - def screen_displays(self): - return list(MideaFDDevice._screen_displays.values()) - - @property - def detect_modes(self): - return self._detect_modes - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - - message = MessageFDResponse(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - if value <= len(MideaFDDevice._modes): - self._attributes[status] = MideaFDDevice._modes[value - 1] - else: - self._attributes[status] = None - elif status == DeviceAttributes.fan_speed: - if value in self._speeds.keys(): - self._attributes[status] = self._speeds.get(value) - else: - self._attributes[status] = None - elif status == DeviceAttributes.screen_display: - if value in MideaFDDevice._screen_displays.keys(): - self._attributes[status] = MideaFDDevice._screen_displays.get( - value - ) - else: - self._attributes[status] = None - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def make_message_set(self): - message = MessageSet(self._protocol_version) - message.power = self._attributes[DeviceAttributes.power] - message.prompt_tone = self._attributes[DeviceAttributes.prompt_tone] - message.screen_display = self._attributes[DeviceAttributes.screen_display] - message.disinfect = self._attributes[DeviceAttributes.disinfect] - if self._attributes[DeviceAttributes.mode] in MideaFDDevice._modes: - message.mode = ( - MideaFDDevice._modes.index(self._attributes[DeviceAttributes.mode]) + 1 - ) - else: - message.mode = 1 - message.fan_speed = ( - 40 - if self._attributes[DeviceAttributes.fan_speed] is None - else list(self._speeds.keys())[ - list(self._speeds.values()).index( - self._attributes[DeviceAttributes.fan_speed] - ) - ] - ) - message.screen_display = ( - 0 - if self._attributes[DeviceAttributes.screen_display] is None - else list(MideaFDDevice._screen_displays.keys())[ - list(MideaFDDevice._screen_displays.values()).index( - self._attributes[DeviceAttributes.screen_display] - ) - ] - ) - return message - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.prompt_tone: - self._attributes[DeviceAttributes.prompt_tone] = value - self.update_all({DeviceAttributes.prompt_tone.value: value}) - else: - message = self.make_message_set() - if attr == DeviceAttributes.mode: - if value in MideaFDDevice._modes: - message.mode = MideaFDDevice._modes.index(value) + 1 - elif attr == DeviceAttributes.fan_speed: - if value in self._speeds.values(): - message.fan_speed = list(self._speeds.keys())[ - list(self._speeds.values()).index(value) - ] - elif attr == DeviceAttributes.screen_display: - if value in MideaFDDevice._screen_displays.values(): - message.screen_display = list( - MideaFDDevice._screen_displays.keys() - )[list(MideaFDDevice._screen_displays.values()).index(value)] - elif not value: - message.screen_display = 7 - else: - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(MideaFDDevice): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/fd/message.py b/custom_components/midea_ac_lan/midea/devices/fd/message.py deleted file mode 100644 index 39aa6ec1..00000000 --- a/custom_components/midea_ac_lan/midea/devices/fd/message.py +++ /dev/null @@ -1,171 +0,0 @@ -from ...core.crc8 import calculate -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class MessageFDBase(MessageRequest): - _message_serial = 0 - - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0xFD, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - MessageFDBase._message_serial += 1 - if MessageFDBase._message_serial >= 254: - MessageFDBase._message_serial = 1 - self._message_id = MessageFDBase._message_serial - - @property - def _body(self): - raise NotImplementedError - - @property - def body(self): - body = bytearray([self.body_type]) + self._body + bytearray([self._message_id]) - body.append(calculate(body)) - return body - - -class MessageQuery(MessageFDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x41, - ) - - @property - def _body(self): - return bytearray( - [ - 0x81, - 0x00, - 0xFF, - 0x03, - 0x00, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class MessageSet(MessageFDBase): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x48, - ) - self.power = False - self.fan_speed = 0 - self.target_humidity = 50 - self.prompt_tone = False - self.screen_display = 0x07 - self.mode = 0x01 - self.disinfect = None - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - prompt_tone = 0x40 if self.prompt_tone else 0x00 - disinfect = 0 if self.disinfect is None else (1 if self.disinfect else 2) - return bytearray( - [ - power | prompt_tone | 0x02, - 0x00, - self.fan_speed, - 0x00, - 0x00, - 0x00, - self.target_humidity, - 0x00, - self.screen_display, - self.mode, - 0x00, - 0x00, - 0x00, - 0x00, - disinfect, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - - -class FDC8MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.fan_speed = body[3] & 0x7F - self.target_humidity = body[7] - self.current_humidity = body[16] - self.current_temperature = (body[17] - 50) / 2 - self.tank = body[10] - self.mode = (body[8] & 0x70) >> 4 - self.screen_display = body[9] & 0x07 - if len(body) > 36: - disinfect = body[34] & 0x03 - if disinfect == 1: - self.disinfect = True - elif disinfect == 2: - self.disinfect = False - - -class FDA0MessageBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = (body[1] & 0x01) > 0 - self.fan_speed = body[3] & 0x7F - self.target_humidity = body[7] - self.current_humidity = body[16] - self.current_temperature = (body[17] - 50) / 2 - self.tank = body[10] - self.mode = body[10] & 0x07 - self.screen_display = body[9] & 0x07 - if len(body) > 29: - disinfect = body[27] & 0x03 - if disinfect == 1: - self.disinfect = True - elif disinfect == 2: - self.disinfect = False - - -class MessageFDResponse(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.message_type in [ - MessageType.query, - MessageType.set, - MessageType.notify1, - ]: - if self.body_type in [0xB0, 0xB1]: - pass - elif self.body_type == 0xA0: - self.set_body(FDA0MessageBody(super().body)) - elif self.body_type == 0xC8: - self.set_body(FDC8MessageBody(super().body)) - self.set_attr() - if ( - hasattr(self, "fan_speed") - and self.fan_speed is not None - and self.fan_speed < 5 - ): - self.fan_speed = 1 diff --git a/custom_components/midea_ac_lan/midea/devices/x13/device.py b/custom_components/midea_ac_lan/midea/devices/x13/device.py deleted file mode 100644 index cc019d20..00000000 --- a/custom_components/midea_ac_lan/midea/devices/x13/device.py +++ /dev/null @@ -1,137 +0,0 @@ -import json -import logging - -from .message import Message13Response, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - brightness = "brightness" - color_temperature = "color_temperature" - rgb_color = "rgb_color" - effect = "effect" - power = "power" - - -class Midea13Device(MiedaDevice): - _effects = ["Manual", "Living", "Reading", "Mildly", "Cinema", "Night"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0x13, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.brightness: None, - DeviceAttributes.color_temperature: None, - DeviceAttributes.rgb_color: None, - DeviceAttributes.effect: None, - DeviceAttributes.power: False, - }, - ) - self._color_temp_range = None - self._default_color_temp_range = [2700, 6500] - self.set_customize(customize) - - @property - def effects(self): - return Midea13Device._effects - - @property - def color_temp_range(self): - return self._color_temp_range - - def kelvin_to_midea(self, kelvin): - return round( - (kelvin - self._color_temp_range[0]) - / (self._color_temp_range[1] - self._color_temp_range[0]) - * 255 - ) - - def midea_to_kelvin(self, midea): - return ( - round((self._color_temp_range[1] - self._color_temp_range[0]) / 255 * midea) - + self._color_temp_range[0] - ) - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = Message13Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - if hasattr(message, "control_success"): - new_status = {"control_success", message.control_success} - if message.control_success: - self.refresh_status() - else: - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.effect: - self._attributes[status] = Midea13Device._effects[value] - elif status == DeviceAttributes.color_temperature: - self._attributes[status] = self.midea_to_kelvin(value) - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.brightness, - DeviceAttributes.color_temperature, - DeviceAttributes.effect, - DeviceAttributes.power, - ]: - message = MessageSet(self._protocol_version) - if attr == DeviceAttributes.effect and value in self._effects: - setattr(message, str(attr), Midea13Device._effects.index(value)) - elif attr == DeviceAttributes.color_temperature: - setattr(message, str(attr), self.kelvin_to_midea(value)) - else: - setattr(message, str(attr), value) - self.build_send(message) - - def set_customize(self, customize): - self._color_temp_range = self._default_color_temp_range - if customize and len(customize) > 0: - try: - params = json.loads(customize) - if params and "color_temp_range_kelvin" in params: - self._color_temp_range = params.get("color_temp_range_kelvin") - except Exception as e: - _LOGGER.error(f"[{self.device_id}] Set customize error: {repr(e)}") - self.update_all({"color_temp_range": self._color_temp_range}) - - -class MideaAppliance(Midea13Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/x13/message.py b/custom_components/midea_ac_lan/midea/devices/x13/message.py deleted file mode 100644 index fb208a11..00000000 --- a/custom_components/midea_ac_lan/midea/devices/x13/message.py +++ /dev/null @@ -1,90 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class Message13Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0x13, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(Message13Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x24, - ) - - @property - def _body(self): - return bytearray([0x00, 0x00, 0x00, 0x00]) - - -class MessageSet(Message13Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x00, - ) - self.brightness = None - self.color_temperature = None - self.effect = None - self.power = None - - @property - def _body(self): - body_byte = 0x00 - if self.power is not None: - self.body_type = 0x01 - body_byte = 0x01 if self.power else 0x00 - elif self.effect is not None and self.effect in range(1, 6): - self.body_type = 0x02 - body_byte = self.effect + 1 - elif self.color_temperature is not None: - self.body_type = 0x03 - body_byte = self.color_temperature - elif self.brightness is not None: - self.body_type = 0x04 - body_byte = self.brightness - return bytearray([body_byte, 0x00, 0x00, 0x00]) - - -class MessageMainLightBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.brightness = self.read_byte(body, 1) - self.color_temperature = self.read_byte(body, 2) - self.effect = self.read_byte(body, 3) - 1 - if self.effect > 5: - self.effect = 1 - """ - self.rgb_color = [self.read_byte(body, 5), - self.read_byte(body, 6), - self.read_byte(body, 7)] - """ - self.power = self.read_byte(body, 8) > 0 - - -class MessageMainLightResponseBody(MessageBody): - def __init__(self, body): - super().__init__(body) - self.control_success = body[1] > 0 - - -class Message13Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if self.body_type == 0xA4: - self.set_body(MessageMainLightBody(super().body)) - elif self.message_type == MessageType.set and self.body_type > 0x80: - self.set_body(MessageMainLightResponseBody(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/x26/device.py b/custom_components/midea_ac_lan/midea/devices/x26/device.py deleted file mode 100644 index b01c67b5..00000000 --- a/custom_components/midea_ac_lan/midea/devices/x26/device.py +++ /dev/null @@ -1,145 +0,0 @@ -import logging -import math - -from .message import Message26Response, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - main_light = "main_light" - night_light = "night_light" - mode = "mode" - direction = "direction" - current_humidity = "current_humidity" - current_radar = "current_radar" - current_temperature = "current_temperature" - - -class Midea26Device(MiedaDevice): - _modes = ["Off", "Heat(high)", "Heat(low)", "Bath", "Blow", "Ventilation", "Dry"] - _directions = ["60", "70", "80", "90", "100", "110", "120", "Oscillate"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0x26, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.main_light: False, - DeviceAttributes.night_light: False, - DeviceAttributes.mode: None, - DeviceAttributes.direction: None, - DeviceAttributes.current_humidity: None, - DeviceAttributes.current_radar: None, - DeviceAttributes.current_temperature: None, - }, - ) - self._fields = {} - - @staticmethod - def _convert_to_midea_direction(direction): - if direction == "Oscillate": - result = 0xFD - else: - result = ( - Midea26Device._directions.index(direction) * 10 + 60 - if direction in Midea26Device._directions - else 0xFD - ) - return result - - @staticmethod - def _convert_from_midea_direction(direction): - if direction > 120 or direction < 60: - result = 7 - else: - result = math.floor((direction - 60 + 5) / 10) - return result - - @property - def preset_modes(self): - return Midea26Device._modes - - @property - def directions(self): - return Midea26Device._directions - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = Message26Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - self._fields = getattr(message, "fields") - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.mode: - self._attributes[status] = Midea26Device._modes[value] - elif status == DeviceAttributes.direction: - self._attributes[status] = Midea26Device._directions[ - self._convert_from_midea_direction(value) - ] - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.main_light, - DeviceAttributes.night_light, - DeviceAttributes.mode, - DeviceAttributes.direction, - ]: - message = MessageSet(self._protocol_version) - message.fields = self._fields - message.main_light = self._attributes[DeviceAttributes.main_light] - message.night_light = self._attributes[DeviceAttributes.night_light] - message.mode = Midea26Device._modes.index( - self._attributes[DeviceAttributes.mode] - ) - message.direction = self._convert_to_midea_direction( - self._attributes[DeviceAttributes.direction] - ) - if attr in [DeviceAttributes.main_light, DeviceAttributes.night_light]: - message.main_light = False - message.night_light = False - setattr(message, str(attr), value) - elif attr == DeviceAttributes.mode: - message.mode = Midea26Device._modes.index(value) - elif attr == DeviceAttributes.direction: - message.direction = self._convert_to_midea_direction(value) - self.build_send(message) - - -class MideaAppliance(Midea26Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/x26/message.py b/custom_components/midea_ac_lan/midea/devices/x26/message.py deleted file mode 100644 index 51026d3a..00000000 --- a/custom_components/midea_ac_lan/midea/devices/x26/message.py +++ /dev/null @@ -1,179 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class Message26Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0x26, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(Message26Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(Message26Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - self.fields = {} - self.main_light = False - self.night_light = False - self.mode = 0 - self.direction = 0xFD - - def read_field(self, field): - value = self.fields.get(field, 0) - return value if value else 0 - - @property - def _body(self): - return bytearray( - [ - 1 if self.main_light else 0, - self.read_field("MAIN_LIGHT_BRIGHTNESS"), - 1 if self.night_light else 0, - self.read_field("NIGHT_LIGHT_BRIGHTNESS"), - self.read_field("RADAR_INDUCTION_ENABLE"), - self.read_field("RADAR_INDUCTION_CLOSING_TIME"), - self.read_field("LIGHT_INTENSITY_THRESHOLD"), - self.read_field("RADAR_SENSITIVITY"), - 1 if self.mode == 1 or self.mode == 2 else 0, - ( - 0 - if not (self.mode == 1 or self.mode == 2) - else 55 if self.mode == 1 else 30 - ), - self.read_field("HEATING_SPEED"), - self.direction, - 1 if self.mode == 3 else 0, - self.read_field("BATH_HEATING_TIME"), - self.read_field("BATH_TEMPERATURE"), - self.read_field("BATH_SPEED"), - self.direction, - 1 if self.mode == 5 else 0, - self.read_field("VENTILATION_SPEED"), - self.direction, - 1 if self.mode == 6 else 0, - self.read_field("DRYING_TIME"), - self.read_field("DRYING_TEMPERATURE"), - self.read_field("DRYING_SPEED"), - self.direction, - 1 if self.mode == 4 else 0, - self.read_field("BLOWING_SPEED"), - self.direction, - self.read_field("DELAY_ENABLE"), - self.read_field("DELAY_TIME"), - self.read_field("SOFT_WIND_ENABLE"), - self.read_field("SOFT_WIND_TIME"), - self.read_field("SOFT_WIND_TEMPERATURE"), - self.read_field("SOFT_WIND_SPEED"), - self.read_field("SOFT_WIND_DIRECTION"), - self.read_field("WINDLESS_ENABLE"), - self.read_field("ANION_ENABLE"), - self.read_field("SMELLY_ENABLE"), - self.read_field("SMELLY_THRESHOLD"), - ] - ) - - -class Message26Body(MessageBody): - def __init__(self, body): - super().__init__(body) - self.fields = {} - self.main_light = self.read_byte(body, 1) > 0 - self.fields["MAIN_LIGHT_BRIGHTNESS"] = self.read_byte(body, 2) - self.night_light = self.read_byte(body, 3) > 0 - self.fields["NIGHT_LIGHT_BRIGHTNESS"] = self.read_byte(body, 4) - self.fields["RADAR_INDUCTION_ENABLE"] = self.read_byte(body, 5) - self.fields["RADAR_INDUCTION_CLOSING_TIME"] = self.read_byte(body, 6) - self.fields["LIGHT_INTENSITY_THRESHOLD"] = self.read_byte(body, 7) - self.fields["RADAR_SENSITIVITY"] = self.read_byte(body, 8) - heat_mode = self.read_byte(body, 9) > 0 - heat_temperature = self.read_byte(body, 10) - self.fields["HEATING_SPEED"] = self.read_byte(body, 11) - heat_direction = self.read_byte(body, 12) - bath_mode = self.read_byte(body, 13) > 0 - self.fields["BATH_HEATING_TIME"] = self.read_byte(body, 14) - self.fields["BATH_TEMPERATURE"] = self.read_byte(body, 15) - self.fields["BATH_SPEED"] = self.read_byte(body, 16) - bath_direction = self.read_byte(body, 17) - ventilation_mode = self.read_byte(body, 18) > 0 - self.fields["VENTILATION_SPEED"] = self.read_byte(body, 19) - ventilation_direction = self.read_byte(body, 20) - dry_mode = self.read_byte(body, 21) > 0 - self.fields["DRYING_TIME"] = self.read_byte(body, 22) - self.fields["DRYING_TEMPERATURE"] = self.read_byte(body, 23) - self.fields["DRYING_SPEED"] = self.read_byte(body, 24) - dry_direction = self.read_byte(body, 25) - blow_mode = self.read_byte(body, 26) > 0 - self.fields["BLOWING_SPEED"] = self.read_byte(body, 27) - blow_direction = self.read_byte(body, 28) - self.fields["DELAY_ENABLE"] = self.read_byte(body, 29) - self.fields["DELAY_TIME"] = self.read_byte(body, 30) - if self.read_byte(body, 31) != 0xFF: - self.current_humidity = self.read_byte(body, 31) - if self.read_byte(body, 32) != 0xFF: - self.current_radar = self.read_byte(body, 32) - if self.read_byte(body, 33) != 0xFF: - self.current_temperature = self.read_byte(body, 33) - self.fields["SOFT_WIND_ENABLE"] = self.read_byte(body, 38) - self.fields["SOFT_WIND_TIME"] = self.read_byte(body, 39) - self.fields["SOFT_WIND_TEMPERATURE"] = self.read_byte(body, 40) - self.fields["SOFT_WIND_SPEED"] = self.read_byte(body, 41) - self.fields["SOFT_WIND_DIRECTION"] = self.read_byte(body, 42) - self.fields["WINDLESS_ENABLE"] = self.read_byte(body, 43) - self.fields["ANION_ENABLE"] = self.read_byte(body, 44) - self.fields["SMELLY_ENABLE"] = self.read_byte(body, 45) - self.fields["SMELLY_THRESHOLD"] = self.read_byte(body, 46) - self.mode = 0 - self.direction = 0xFD - if heat_mode: - if heat_temperature > 50: - self.mode = 1 - else: - self.mode = 2 - self.direction = heat_direction - elif bath_mode: - self.mode = 3 - self.direction = bath_direction - elif blow_mode: - self.mode = 4 - self.direction = blow_direction - elif ventilation_mode: - self.mode = 5 - self.direction = ventilation_direction - elif dry_mode: - self.mode = 6 - self.direction = dry_direction - - -class Message26Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ): - self.set_body(Message26Body(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/x34/device.py b/custom_components/midea_ac_lan/midea/devices/x34/device.py deleted file mode 100644 index 76e8f303..00000000 --- a/custom_components/midea_ac_lan/midea/devices/x34/device.py +++ /dev/null @@ -1,172 +0,0 @@ -import logging - -from .message import ( - Message34Response, - MessageLock, - MessagePower, - MessageQuery, - MessageStorage, -) - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - power = "power" - status = "status" - mode = "mode" - additional = "additional" - door = "door" - rinse_aid = "rinse_aid" - salt = "salt" - child_lock = "child_lock" - uv = "uv" - dry = "dry" - dry_status = "dry_status" - storage = "storage" - storage_status = "storage_status" - time_remaining = "time_remaining" - progress = "progress" - storage_remaining = "storage_remaining" - temperature = "temperature" - humidity = "humidity" - waterswitch = "waterswitch" - water_lack = "water_lack" - error_code = "error_code" - softwater = "softwater" - wrong_operation = "wrong_operation" - bright = "bright" - - -class Midea34Device(MiedaDevice): - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0x34, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.power: False, - DeviceAttributes.status: None, - DeviceAttributes.mode: 0, - DeviceAttributes.additional: 0, - DeviceAttributes.uv: False, - DeviceAttributes.dry: False, - DeviceAttributes.dry_status: False, - DeviceAttributes.door: False, - DeviceAttributes.rinse_aid: False, - DeviceAttributes.salt: False, - DeviceAttributes.child_lock: False, - DeviceAttributes.storage: False, - DeviceAttributes.storage_status: False, - DeviceAttributes.time_remaining: None, - DeviceAttributes.progress: None, - DeviceAttributes.storage_remaining: None, - DeviceAttributes.temperature: None, - DeviceAttributes.humidity: None, - DeviceAttributes.waterswitch: False, - DeviceAttributes.water_lack: False, - DeviceAttributes.error_code: None, - DeviceAttributes.softwater: 0, - DeviceAttributes.wrong_operation: None, - DeviceAttributes.bright: 0, - }, - ) - self._modes = { - 0x0: "Neutral Gear", # BYTE_MODE_NEUTRAL_GEAR - 0x1: "Auto", # BYTE_MODE_AUTO_WASH - 0x2: "Heavy", # BYTE_MODE_STRONG_WASH - 0x3: "Normal", # BYTE_MODE_STANDARD_WASH - 0x4: "Energy Saving", # BYTE_MODE_ECO_WASH - 0x5: "Delicate", # BYTE_MODE_GLASS_WASH - 0x6: "Hour", # BYTE_MODE_HOUR_WASH - 0x7: "Quick", # BYTE_MODE_FAST_WASH - 0x8: "Rinse", # BYTE_MODE_SOAK_WASH - 0x9: "90min", # BYTE_MODE_90MIN_WASH - 0xA: "Self Clean", # BYTE_MODE_SELF_CLEAN - 0xB: "Fruit Wash", # BYTE_MODE_FRUIT_WASH - 0xC: "Self Define", # BYTE_MODE_SELF_DEFINE - 0xD: "Germ", # BYTE_MODE_GERM ??? - 0xE: "Bowl Wash", # BYTE_MODE_BOWL_WASH - 0xF: "Kill Germ", # BYTE_MODE_KILL_GERM - 0x10: "Sea Food Wash", # BYTE_MODE_SEA_FOOD_WASH - 0x12: "Hot Pot Wash", # BYTE_MODE_HOT_POT_WASH - 0x13: "Quiet", # BYTE_MODE_QUIET_NIGHT_WASH - 0x14: "Less Wash", # BYTE_MODE_LESS_WASH - 0x16: "Oil Net Wash", # BYTE_MODE_OIL_NET_WASH - 0x19: "Cloud Wash", # BYTE_MODE_CLOUD_WASH - } - self._status = ["Off", "Idle", "Delay", "Running", "Error"] - self._progress = ["Idle", "Pre-wash", "Wash", "Rinse", "Dry", "Complete"] - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = Message34Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - for status in self._attributes.keys(): - if hasattr(message, str(status)): - if status == DeviceAttributes.status: - v = getattr(message, str(status)) - if v < len(self._status): - self._attributes[status] = self._status[v] - else: - self._attributes[status] = None - elif status == DeviceAttributes.progress: - v = getattr(message, str(status)) - if v < len(self._progress): - self._attributes[status] = self._progress[v] - else: - self._attributes[status] = None - elif status == DeviceAttributes.mode: - v = getattr(message, str(status)) - self._attributes[status] = self._modes[v] - else: - self._attributes[status] = getattr(message, str(status)) - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr == DeviceAttributes.power: - message = MessagePower(self._protocol_version) - message.power = value - self.build_send(message) - elif attr == DeviceAttributes.child_lock: - message = MessageLock(self._protocol_version) - message.lock = value - self.build_send(message) - elif attr == DeviceAttributes.storage: - message = MessageStorage(self._protocol_version) - message.storage = value - self.build_send(message) - - -class MideaAppliance(Midea34Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/x34/message.py b/custom_components/midea_ac_lan/midea/devices/x34/message.py deleted file mode 100644 index c107acd2..00000000 --- a/custom_components/midea_ac_lan/midea/devices/x34/message.py +++ /dev/null @@ -1,122 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class Message34Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0x34, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(Message34Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x00, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessagePower(Message34Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x08, - ) - self.power = False - - @property - def _body(self): - power = 0x01 if self.power else 0x00 - return bytearray([power, 0x00, 0x00, 0x00]) - - -class MessageLock(Message34Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x83, - ) - self.lock = False - - @property - def _body(self): - lock = 0x03 if self.lock else 0x04 - return bytearray([lock]) + bytearray([0x00] * 36) - - -class MessageStorage(Message34Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x81, - ) - self.storage = False - - @property - def _body(self): - storage = 0x01 if self.storage else 0x00 - return ( - bytearray([0x00, 0x00, 0x00, storage]) - + bytearray([0xFF] * 6) - + bytearray([0x00] * 27) - ) - - -class Message34Body(MessageBody): - def __init__(self, body): - super().__init__(body) - self.power = body[1] > 0 - self.status = body[1] - self.mode = body[2] - self.additional = body[3] - self.door = (body[5] & 0x01) == 0 # 0 - open, 1 - close - self.rinse_aid = (body[5] & 0x02) > 0 # 0 - enough, 1 - shortage - self.salt = (body[5] & 0x04) > 0 # 0 - enough, 1 - shortage - start_pause = (body[5] & 0x08) > 0 - if start_pause: - self.start = True - elif self.status in [2, 3]: - self.start = False - self.child_lock = (body[5] & 0x10) > 0 - self.uv = (body[4] & 0x2) > 0 - self.dry = (body[4] & 0x10) > 0 - self.dry_status = (body[4] & 0x20) > 0 - self.storage = (body[5] & 0x20) > 0 - self.storage_status = (body[5] & 0x40) > 0 - self.time_remaining = body[6] - self.progress = body[9] - self.storage_remaining = body[18] if len(body) > 18 else False - self.temperature = body[11] - self.humidity = body[33] if len(body) > 33 else None - self.waterswitch = (body[4] & 0x4) > 0 - self.water_lack = (body[5] & 0x80) > 0 - self.error_code = body[10] - self.softwater = body[13] - self.wrong_operation = body[16] - self.bright = body[24] - - -class Message34Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if (self.message_type == MessageType.set and 0 <= self.body_type <= 7) or ( - self.message_type in [MessageType.query, MessageType.notify1] - and self.body_type == 0 - ): - self.set_body(Message34Body(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea/devices/x40/device.py b/custom_components/midea_ac_lan/midea/devices/x40/device.py deleted file mode 100644 index 6115e00c..00000000 --- a/custom_components/midea_ac_lan/midea/devices/x40/device.py +++ /dev/null @@ -1,135 +0,0 @@ -import logging -import math - -from .message import Message40Response, MessageQuery, MessageSet - -try: - from enum import StrEnum -except ImportError: - from ...backports.myenum import StrEnum - -from ...core.device import MiedaDevice - -_LOGGER = logging.getLogger(__name__) - - -class DeviceAttributes(StrEnum): - light = "light" - fan_speed = "fan_speed" - direction = "direction" - ventilation = "ventilation" - smelly_sensor = "smelly_sensor" - current_temperature = "current_temperature" - - -class Midea40Device(MiedaDevice): - _directions = ["60", "70", "80", "90", "100", "Oscillate"] - - def __init__( - self, - name: str, - device_id: int, - ip_address: str, - port: int, - token: str, - key: str, - protocol: int, - model: str, - subtype: int, - customize: str, - ): - super().__init__( - name=name, - device_id=device_id, - device_type=0x40, - ip_address=ip_address, - port=port, - token=token, - key=key, - protocol=protocol, - model=model, - subtype=subtype, - attributes={ - DeviceAttributes.light: False, - DeviceAttributes.fan_speed: 0, - DeviceAttributes.direction: False, - DeviceAttributes.ventilation: False, - DeviceAttributes.smelly_sensor: False, - DeviceAttributes.current_temperature: None, - }, - ) - self._fields = {} - - @property - def directions(self): - return Midea40Device._directions - - @staticmethod - def _convert_to_midea_direction(direction): - if direction == "Oscillate": - result = 0xFD - else: - result = ( - Midea40Device._directions.index(direction) * 10 + 60 - if direction in Midea40Device._directions - else 0xFD - ) - return result - - @staticmethod - def _convert_from_midea_direction(direction): - if direction > 100 or direction < 60: - result = 5 - else: - result = math.floor((direction - 60 + 5) / 10) - return result - - def build_query(self): - return [MessageQuery(self._protocol_version)] - - def process_message(self, msg): - message = Message40Response(msg) - _LOGGER.debug(f"[{self.device_id}] Received: {message}") - new_status = {} - self._fields = getattr(message, "fields") - for status in self._attributes.keys(): - if hasattr(message, str(status)): - value = getattr(message, str(status)) - if status == DeviceAttributes.direction: - self._attributes[status] = Midea40Device._directions[ - self._convert_from_midea_direction(value) - ] - else: - self._attributes[status] = value - new_status[str(status)] = self._attributes[status] - return new_status - - def set_attribute(self, attr, value): - if attr in [ - DeviceAttributes.light, - DeviceAttributes.fan_speed, - DeviceAttributes.direction, - DeviceAttributes.ventilation, - DeviceAttributes.smelly_sensor, - ]: - message = MessageSet(self._protocol_version) - message.fields = self._fields - message.light = self._attributes[DeviceAttributes.light] - message.ventilation = self._attributes[DeviceAttributes.ventilation] - message.smelly_sensor = self._attributes[DeviceAttributes.smelly_sensor] - message.fan_speed = self._attributes[DeviceAttributes.fan_speed] - message.direction = self._convert_to_midea_direction( - self._attributes[DeviceAttributes.direction] - ) - if attr == DeviceAttributes.direction: - message.direction = self._convert_to_midea_direction(value) - elif attr == DeviceAttributes.ventilation and message.fan_speed == 2: - message.fan_speed = 1 - message.ventilation = value - else: - setattr(message, str(attr), value) - self.build_send(message) - - -class MideaAppliance(Midea40Device): - pass diff --git a/custom_components/midea_ac_lan/midea/devices/x40/message.py b/custom_components/midea_ac_lan/midea/devices/x40/message.py deleted file mode 100644 index fba44d2e..00000000 --- a/custom_components/midea_ac_lan/midea/devices/x40/message.py +++ /dev/null @@ -1,164 +0,0 @@ -from ...core.message import MessageBody, MessageRequest, MessageResponse, MessageType - - -class Message40Base(MessageRequest): - def __init__(self, protocol_version, message_type, body_type): - super().__init__( - device_type=0x40, - protocol_version=protocol_version, - message_type=message_type, - body_type=body_type, - ) - - @property - def _body(self): - raise NotImplementedError - - -class MessageQuery(Message40Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.query, - body_type=0x01, - ) - - @property - def _body(self): - return bytearray([]) - - -class MessageSet(Message40Base): - def __init__(self, protocol_version): - super().__init__( - protocol_version=protocol_version, - message_type=MessageType.set, - body_type=0x01, - ) - self.fields = {} - self.light = False - self.fan_speed = 0 - self.direction = False - self.ventilation = False - self.smelly_sensor = False - - def read_field(self, field): - value = self.fields.get(field, 0) - return value if value else 0 - - @property - def _body(self): - light = 1 if self.light else 0 - blow = 1 if self.fan_speed > 0 else 0 - fan_speed = 0xFF if self.fan_speed == 0 else 30 if self.fan_speed == 1 else 100 - ventilation = 1 if self.ventilation else 0 - direction = self.direction - smelly_sensor = 1 if self.smelly_sensor else 0 - return bytearray( - [ - light, - self.read_field("MAIN_LIGHT_BRIGHTNESS"), - self.read_field("NIGHT_LIGHT_ENABLE"), - self.read_field("NIGHT_LIGHT_BRIGHTNESS"), - self.read_field("RADAR_INDUCTION_ENABLE"), - self.read_field("RADAR_INDUCTION_CLOSING_TIME"), - self.read_field("LIGHT_INTENSITY_THRESHOLD"), - self.read_field("RADAR_SENSITIVITY"), - self.read_field("HEATING_ENABLE"), - self.read_field("HEATING_TEMPERATURE"), - self.read_field("HEATING_SPEED"), - self.read_field("HEATING_DIRECTION"), - self.read_field("BATH_ENABLE"), - self.read_field("BATH_HEATING_TIME"), - self.read_field("BATH_TEMPERATURE"), - self.read_field("BATH_SPEED"), - self.read_field("BATH_DIRECTION"), - ventilation, - self.read_field("VENTILATION_SPEED"), - self.read_field("VENTILATION_DIRECTION"), - self.read_field("DRYING_ENABLE"), - self.read_field("DRYING_TIME"), - self.read_field("DRYING_TEMPERATURE"), - self.read_field("DRYING_SPEED"), - self.read_field("DRYING_DIRECTION"), - blow, - fan_speed, - direction, - self.read_field("DELAY_ENABLE"), - self.read_field("DELAY_TIME"), - self.read_field("SOFT_WIND_ENABLE"), - self.read_field("SOFT_WIND_TIME"), - self.read_field("SOFT_WIND_TEMPERATURE"), - self.read_field("SOFT_WIND_SPEED"), - self.read_field("SOFT_WIND_DIRECTION"), - self.read_field("WINDLESS_ENABLE"), - self.read_field("ANION_ENABLE"), - smelly_sensor, - self.read_field("SMELLY_THRESHOLD"), - ] - ) - - -class Message40Body(MessageBody): - def __init__(self, body): - super().__init__(body) - self.fields = {} - self.light = body[1] > 0 - self.fields["MAIN_LIGHT_BRIGHTNESS"] = body[2] - self.fields["NIGHT_LIGHT_ENABLE"] = body[3] - self.fields["NIGHT_LIGHT_BRIGHTNESS"] = body[4] - self.fields["RADAR_INDUCTION_ENABLE"] = body[5] - self.fields["RADAR_INDUCTION_CLOSING_TIME"] = body[6] - self.fields["LIGHT_INTENSITY_THRESHOLD"] = body[7] - self.fields["RADAR_SENSITIVITY"] = body[8] - self.fields["HEATING_ENABLE"] = body[9] - self.fields["HEATING_TEMPERATURE"] = body[10] - self.fields["HEATING_SPEED"] = body[11] - self.fields["HEATING_DIRECTION"] = body[12] - self.fields["BATH_ENABLE"] = body[13] > 0 - self.fields["BATH_HEATING_TIME"] = body[14] - self.fields["BATH_TEMPERATURE"] = body[15] - self.fields["BATH_SPEED"] = body[16] - self.fields["BATH_DIRECTION"] = body[17] - self.ventilation = body[18] > 0 - self.fields["VENTILATION_SPEED"] = body[19] - self.fields["VENTILATION_DIRECTION"] = body[20] - self.fields["DRYING_ENABLE"] = body[21] > 0 - self.fields["DRYING_TIME"] = body[22] - self.fields["DRYING_TEMPERATURE"] = body[23] - self.fields["DRYING_SPEED"] = body[24] - self.fields["DRYING_DIRECTION"] = body[25] - blow = body[26] > 0 - blow_speed = body[27] - self.direction = body[28] - self.fields["DELAY_ENABLE"] = body[29] - self.fields["DELAY_TIME"] = body[30] - self.current_temperature = body[33] - self.fields["SOFT_WIND_ENABLE"] = body[38] - self.fields["SOFT_WIND_TIME"] = body[39] - self.fields["SOFT_WIND_TEMPERATURE"] = body[40] - self.fields["SOFT_WIND_SPEED"] = body[41] - self.fields["SOFT_WIND_DIRECTION"] = body[42] - self.fields["WINDLESS_ENABLE"] = body[43] - self.fields["ANION_ENABLE"] = body[44] - self.smelly_sensor = body[45] - self.fields["SMELLY_THRESHOLD"] = body[46] - if blow: - if blow_speed <= 30: - self.fan_speed = 1 - else: - self.fan_speed = 2 - else: - self.fan_speed = 0 - - -class Message40Response(MessageResponse): - def __init__(self, message): - super().__init__(message) - if ( - self.message_type - in [MessageType.set, MessageType.notify1, MessageType.query] - and self.body_type == 0x01 - ): - self.set_body(Message40Body(super().body)) - self.set_attr() diff --git a/custom_components/midea_ac_lan/midea_devices.py b/custom_components/midea_ac_lan/midea_devices.py index 38b6717d..a66755cc 100644 --- a/custom_components/midea_ac_lan/midea_devices.py +++ b/custom_components/midea_ac_lan/midea_devices.py @@ -1,2191 +1,2190 @@ -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass -from homeassistant.const import ( - CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - CONCENTRATION_PARTS_PER_MILLION, - PERCENTAGE, - Platform, - UnitOfEnergy, - UnitOfPower, - UnitOfTemperature, - UnitOfTime, - UnitOfVolume, -) - -from .midea.devices.a1.device import DeviceAttributes as A1Attributes -from .midea.devices.ac.device import DeviceAttributes as ACAttributes -from .midea.devices.b0.device import DeviceAttributes as B0Attributes -from .midea.devices.b1.device import DeviceAttributes as B1Attributes -from .midea.devices.b3.device import DeviceAttributes as B3Attributes -from .midea.devices.b4.device import DeviceAttributes as B4Attributes -from .midea.devices.b6.device import DeviceAttributes as B6Attributes -from .midea.devices.bf.device import DeviceAttributes as BFAttributes -from .midea.devices.c2.device import DeviceAttributes as C2Attributes -from .midea.devices.c3.device import DeviceAttributes as C3Attributes -from .midea.devices.ca.device import DeviceAttributes as CAAttributes -from .midea.devices.cc.device import DeviceAttributes as CCAttributes -from .midea.devices.cd.device import DeviceAttributes as CDAttributes -from .midea.devices.ce.device import DeviceAttributes as CEAttributes -from .midea.devices.cf.device import DeviceAttributes as CFAttributes -from .midea.devices.da.device import DeviceAttributes as DAAttributes -from .midea.devices.db.device import DeviceAttributes as DBAttributes -from .midea.devices.dc.device import DeviceAttributes as DCAttributes -from .midea.devices.e1.device import DeviceAttributes as E1Attributes -from .midea.devices.e2.device import DeviceAttributes as E2Attributes -from .midea.devices.e3.device import DeviceAttributes as E3Attributes -from .midea.devices.e6.device import DeviceAttributes as E6Attributes -from .midea.devices.e8.device import DeviceAttributes as E8Attributes -from .midea.devices.ea.device import DeviceAttributes as EAAttributes -from .midea.devices.ec.device import DeviceAttributes as ECAttributes -from .midea.devices.ed.device import DeviceAttributes as EDAttributes -from .midea.devices.fa.device import DeviceAttributes as FAAttributes -from .midea.devices.fb.device import DeviceAttributes as FBAttributes -from .midea.devices.fc.device import DeviceAttributes as FCAttributes -from .midea.devices.fd.device import DeviceAttributes as FDAttributes -from .midea.devices.x26.device import DeviceAttributes as X26Attributes -from .midea.devices.x34.device import DeviceAttributes as X34Attributes -from .midea.devices.x40.device import DeviceAttributes as X40Attributes - -MIDEA_DEVICES = { - 0x13: { - "name": "Light", - "entities": { - "light": {"type": Platform.LIGHT, "icon": "mdi:lightbulb", "default": True} - }, - }, - 0x26: { - "name": "Bathroom Master", - "entities": { - X26Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - X26Attributes.current_humidity: { - "type": Platform.SENSOR, - "name": "Current Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - X26Attributes.current_radar: { - "type": Platform.BINARY_SENSOR, - "name": "Occupancy Status", - "device_class": BinarySensorDeviceClass.MOVING, - }, - X26Attributes.main_light: { - "type": Platform.SWITCH, - "name": "Main Light", - "icon": "mdi:lightbulb", - }, - X26Attributes.night_light: { - "type": Platform.SWITCH, - "name": "Night Light", - "icon": "mdi:lightbulb", - }, - X26Attributes.mode: { - "type": Platform.SELECT, - "name": "Mode", - "options": "preset_modes", - "icon": "mdi:fan", - }, - X26Attributes.direction: { - "type": Platform.SELECT, - "name": "Direction", - "options": "directions", - "icon": "mdi:arrow-split-vertical", - }, - }, - }, - 0x34: { - "name": "Sink Dishwasher", - "entities": { - X34Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - X34Attributes.rinse_aid: { - "type": Platform.BINARY_SENSOR, - "name": "Rinse Aid Shortage", - "icon": "mdi:bottle-tonic", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - X34Attributes.salt: { - "type": Platform.BINARY_SENSOR, - "name": "Salt Shortage", - "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - X34Attributes.humidity: { - "type": Platform.SENSOR, - "name": "Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - X34Attributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - X34Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - X34Attributes.storage_remaining: { - "type": Platform.SENSOR, - "name": "Storage Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.HOURS, - "state_class": SensorStateClass.MEASUREMENT, - }, - X34Attributes.temperature: { - "type": Platform.SENSOR, - "name": "Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - X34Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - X34Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - X34Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - X34Attributes.storage: { - "type": Platform.SWITCH, - "name": "Storage", - "icon": "mdi:repeat-variant", - }, - X34Attributes.mode: { - "type": Platform.SENSOR, - "name": "Working Mode", - "icon": "mdi:dishwasher", - }, - X34Attributes.error_code: { - "type": Platform.SENSOR, - "name": "Error Code", - "icon": "mdi:alert-box", - }, - X34Attributes.softwater: { - "type": Platform.SENSOR, - "name": "Softwater Level", - "icon": "mdi:shaker-outline", - }, - X34Attributes.bright: { - "type": Platform.SENSOR, - "name": "Bright Level", - "icon": "mdi:star-four-points", - }, - }, - }, - 0x40: { - "name": "Integrated Ceiling Fan", - "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, - X40Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - X40Attributes.light: { - "type": Platform.SWITCH, - "name": "Light", - "icon": "mdi:lightbulb", - }, - X40Attributes.ventilation: { - "type": Platform.SWITCH, - "name": "Ventilation", - "icon": "mdi:air-filter", - }, - X40Attributes.smelly_sensor: { - "type": Platform.SWITCH, - "name": "Smelly Sensor", - "icon": "mdi:scent", - }, - X40Attributes.direction: { - "type": Platform.SELECT, - "name": "Direction", - "options": "directions", - "icon": "mdi:arrow-split-vertical", - }, - }, - }, - 0xA1: { - "name": "Dehumidifier", - "entities": { - "humidifier": { - "type": Platform.HUMIDIFIER, - "icon": "mdi:air-humidifier", - "default": True, - }, - A1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - A1Attributes.anion: { - "type": Platform.SWITCH, - "name": "Anion", - "icon": "mdi:vanish", - }, - A1Attributes.prompt_tone: { - "type": Platform.SWITCH, - "name": "Prompt Tone", - "icon": "mdi:bell", - }, - A1Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - A1Attributes.swing: { - "type": Platform.SWITCH, - "name": "swing", - "icon": "mdi:pan-horizontal", - }, - A1Attributes.fan_speed: { - "type": Platform.SELECT, - "name": "Fan Speed", - "options": "fan_speeds", - "icon": "mdi:fan", - }, - A1Attributes.water_level_set: { - "type": Platform.SELECT, - "name": "Water Level Setting", - "options": "water_level_sets", - "icon": "mdi:cup-water", - }, - A1Attributes.current_humidity: { - "type": Platform.SENSOR, - "name": "Current Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - A1Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - A1Attributes.tank: { - "type": Platform.SENSOR, - "name": "Tank", - "icon": "mdi:cup-water", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - A1Attributes.tank_full: { - "type": Platform.BINARY_SENSOR, - "name": "Tank status", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - }, - }, - 0xAC: { - "name": "Air Conditioner", - "entities": { - "climate": { - "type": Platform.CLIMATE, - "icon": "mdi:air-conditioner", - "default": True, - }, - "fresh_air": {"type": Platform.FAN, "icon": "mdi:fan", "name": "Fresh Air"}, - ACAttributes.aux_heating: { - "type": Platform.SWITCH, - "name": "Aux Heating", - "icon": "mdi:heat-wave", - }, - ACAttributes.boost_mode: { - "type": Platform.SWITCH, - "name": "Boost Mode", - "icon": "mdi:turbine", - }, - ACAttributes.breezeless: { - "type": Platform.SWITCH, - "name": "Breezeless", - "icon": "mdi:tailwind", - }, - ACAttributes.comfort_mode: { - "type": Platform.SWITCH, - "name": "Comfort Mode", - "icon": "mdi:alpha-c-circle", - }, - ACAttributes.dry: { - "type": Platform.SWITCH, - "name": "Dry", - "icon": "mdi:air-filter", - }, - ACAttributes.eco_mode: { - "type": Platform.SWITCH, - "name": "ECO Mode", - "icon": "mdi:leaf-circle", - }, - ACAttributes.frost_protect: { - "type": Platform.SWITCH, - "name": "Frost Protect", - "icon": "mdi:snowflake-alert", - }, - ACAttributes.indirect_wind: { - "type": Platform.SWITCH, - "name": "Indirect Wind", - "icon": "mdi:tailwind", - }, - ACAttributes.natural_wind: { - "type": Platform.SWITCH, - "name": "Natural Wind", - "icon": "mdi:tailwind", - }, - ACAttributes.prompt_tone: { - "type": Platform.SWITCH, - "name": "Prompt Tone", - "icon": "mdi:bell", - }, - ACAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - ACAttributes.screen_display: { - "type": Platform.SWITCH, - "name": "Screen Display", - "icon": "mdi:television-ambient-light", - }, - ACAttributes.screen_display_alternate: { - "type": Platform.SWITCH, - "name": "Screen Display Alternate", - "icon": "mdi:television-ambient-light", - }, - ACAttributes.sleep_mode: { - "type": Platform.SWITCH, - "name": "Sleep Mode", - "icon": "mdi:power-sleep", - }, - ACAttributes.smart_eye: { - "type": Platform.SWITCH, - "name": "Smart Eye", - "icon": "mdi:eye", - }, - ACAttributes.swing_horizontal: { - "type": Platform.SWITCH, - "name": "Swing Horizontal", - "icon": "mdi:arrow-split-vertical", - }, - ACAttributes.swing_vertical: { - "type": Platform.SWITCH, - "name": "Swing Vertical", - "icon": "mdi:arrow-split-horizontal", - }, - ACAttributes.full_dust: { - "type": Platform.BINARY_SENSOR, - "name": "Full of Dust", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - ACAttributes.indoor_humidity: { - "type": Platform.SENSOR, - "name": "Indoor Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - ACAttributes.indoor_temperature: { - "type": Platform.SENSOR, - "name": "Indoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - ACAttributes.outdoor_temperature: { - "type": Platform.SENSOR, - "name": "Outdoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - ACAttributes.total_energy_consumption: { - "type": Platform.SENSOR, - "name": "Total Energy Consumption", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - ACAttributes.current_energy_consumption: { - "type": Platform.SENSOR, - "name": "Current Energy Consumption", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - ACAttributes.realtime_power: { - "type": Platform.SENSOR, - "name": "Realtime Power", - "device_class": SensorDeviceClass.POWER, - "unit": UnitOfPower.WATT, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB0: { - "name": "Microwave Oven", - "entities": { - B0Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B0Attributes.tank_ejected: { - "type": Platform.BINARY_SENSOR, - "name": "Tank Ejected", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B0Attributes.water_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Water Change Reminder", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B0Attributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B0Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B0Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - B0Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB1: { - "name": "Electric Oven", - "entities": { - B1Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B1Attributes.tank_ejected: { - "type": Platform.BINARY_SENSOR, - "name": "Tank ejected", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B1Attributes.water_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Water Change Reminder", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B1Attributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B1Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B1Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - B1Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB3: { - "name": "Dish Sterilizer", - "entities": { - B3Attributes.top_compartment_door: { - "type": Platform.BINARY_SENSOR, - "name": "Top Compartment Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B3Attributes.top_compartment_preheating: { - "type": Platform.BINARY_SENSOR, - "name": "Top Compartment Preheating", - "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.top_compartment_cooling: { - "type": Platform.BINARY_SENSOR, - "name": "Top Compartment Cooling", - "icon": "snowflake-variant", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.middle_compartment_door: { - "type": Platform.BINARY_SENSOR, - "name": "Middle Compartment Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B3Attributes.middle_compartment_preheating: { - "type": Platform.BINARY_SENSOR, - "name": "Middle Compartment Preheating", - "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.middle_compartment_cooling: { - "type": Platform.BINARY_SENSOR, - "name": "Middle Compartment Cooling", - "icon": "snowflake-variant", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.bottom_compartment_door: { - "type": Platform.BINARY_SENSOR, - "name": "Bottom Compartment Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B3Attributes.bottom_compartment_preheating: { - "type": Platform.BINARY_SENSOR, - "name": "Bottom Compartment Preheating", - "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.bottom_compartment_cooling: { - "type": Platform.BINARY_SENSOR, - "name": "Bottom Compartment Cooling", - "icon": "snowflake-variant", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - B3Attributes.top_compartment_status: { - "type": Platform.SENSOR, - "name": "Top Compartment Status", - "icon": "mdi:information", - }, - B3Attributes.top_compartment_temperature: { - "type": Platform.SENSOR, - "name": "Top Compartment Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.top_compartment_remaining: { - "type": Platform.SENSOR, - "name": "Top Compartment Remaining", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.middle_compartment_status: { - "type": Platform.SENSOR, - "name": "Middle Compartment Status", - "icon": "mdi:information", - }, - B3Attributes.middle_compartment_temperature: { - "type": Platform.SENSOR, - "name": "Middle Compartment Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.middle_compartment_remaining: { - "type": Platform.SENSOR, - "name": "Middle Compartment Remaining", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.bottom_compartment_status: { - "type": Platform.SENSOR, - "name": "Bottom Compartment Status", - "icon": "mdi:information", - }, - B3Attributes.bottom_compartment_temperature: { - "type": Platform.SENSOR, - "name": "Bottom Compartment Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B3Attributes.bottom_compartment_remaining: { - "type": Platform.SENSOR, - "name": "Bottom Compartment Remaining", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB4: { - "name": "Toaster", - "entities": { - B4Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - B4Attributes.tank_ejected: { - "type": Platform.BINARY_SENSOR, - "name": "Tank ejected", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B4Attributes.water_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Water Change Reminder", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B4Attributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B4Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - B4Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - B4Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xB6: { - "name": "Range Hood", - "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, - B6Attributes.light: { - "type": Platform.SWITCH, - "name": "Light", - "icon": "mdi:lightbulb", - }, - B6Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - B6Attributes.cleaning_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Cleaning Reminder", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B6Attributes.oilcup_full: { - "type": Platform.BINARY_SENSOR, - "name": "Oil-cup Full", - "icon": "mdi:cup", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - B6Attributes.fan_level: { - "type": Platform.SENSOR, - "name": "Fan level", - "icon": "mdi:fan", - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xBF: { - "name": "Microwave Steam Oven", - "entities": { - BFAttributes.tank_ejected: { - "type": Platform.BINARY_SENSOR, - "name": "Tank ejected", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - BFAttributes.water_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Water Change Reminder", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - BFAttributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - BFAttributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:cup-water", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - BFAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - BFAttributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - BFAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xC2: { - "name": "Toilet", - "entities": { - C2Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - C2Attributes.sensor_light: { - "type": Platform.SWITCH, - "name": "Sensor Light", - "icon": "mdi:lightbulb", - }, - C2Attributes.foam_shield: { - "type": Platform.SWITCH, - "name": "Foam Shield", - "icon": "mdi:chart-bubble", - }, - C2Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - C2Attributes.seat_status: { - "type": Platform.BINARY_SENSOR, - "name": "Seat Status", - "icon": "mdi:seat-legroom-normal", - }, - C2Attributes.lid_status: { - "type": Platform.BINARY_SENSOR, - "name": "Lid Status", - "icon": "mdi:toilet", - }, - C2Attributes.light_status: { - "type": Platform.BINARY_SENSOR, - "name": "Light Status", - "icon": "mdi:lightbulb", - "device_class": BinarySensorDeviceClass.LIGHT, - }, - C2Attributes.water_temperature: { - "type": Platform.SENSOR, - "name": "Water Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - C2Attributes.seat_temperature: { - "type": Platform.SENSOR, - "name": "Seat Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - C2Attributes.filter_life: { - "type": Platform.SENSOR, - "name": "Filter Life", - "icon": "mdi:toilet", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - C2Attributes.dry_level: { - "type": Platform.NUMBER, - "name": "Dry Level", - "icon": "mdi:fire", - "max": "max_dry_level", - "min": 0, - "step": 1, - }, - C2Attributes.water_temp_level: { - "type": Platform.NUMBER, - "name": "Water Temperature Level", - "icon": "mdi:fire", - "max": "max_water_temp_level", - "min": 0, - "step": 1, - }, - C2Attributes.seat_temp_level: { - "type": Platform.NUMBER, - "name": "Seat Temperature Level", - "icon": "mdi:fire", - "max": "max_seat_temp_level", - "min": 0, - "step": 1, - }, - }, - }, - 0xC3: { - "name": "Heat Pump Wi-Fi Controller", - "entities": { - "climate_zone1": { - "type": Platform.CLIMATE, - "icon": "mdi:air-conditioner", - "name": "Zone1 Thermostat", - "zone": 0, - "default": True, - }, - "climate_zone2": { - "type": Platform.CLIMATE, - "icon": "mdi:air-conditioner", - "name": "Zone2 Thermostat", - "zone": 1, - "default": False, - }, - "water_heater": { - "type": Platform.WATER_HEATER, - "icon": "mdi:heat-pump", - "name": "Domestic hot water", - "default": True, - }, - C3Attributes.disinfect: { - "type": Platform.SWITCH, - "name": "Disinfect", - "icon": "mdi:water-plus-outline", - }, - C3Attributes.dhw_power: { - "type": Platform.SWITCH, - "name": "DHW Power", - "icon": "mdi:power", - }, - C3Attributes.eco_mode: { - "type": Platform.SWITCH, - "name": "ECO Mode", - "icon": "mdi:leaf-circle", - }, - C3Attributes.fast_dhw: { - "type": Platform.SWITCH, - "name": "Fast DHW", - "icon": "mdi:rotate-orbit", - }, - C3Attributes.silent_mode: { - "type": Platform.SWITCH, - "name": "Silent Mode", - "icon": "mdi:fan-remove", - }, - C3Attributes.tbh: { - "type": Platform.SWITCH, - "name": "TBH", - "icon": "mdi:water-boiler", - }, - C3Attributes.zone1_curve: { - "type": Platform.SWITCH, - "name": "Zone1 Curve", - "icon": "mdi:chart-bell-curve-cumulative", - }, - C3Attributes.zone2_curve: { - "type": Platform.SWITCH, - "name": "Zone2 Curve", - "icon": "mdi:chart-bell-curve-cumulative", - }, - C3Attributes.zone1_power: { - "type": Platform.SWITCH, - "name": "Zone1 Power", - "icon": "mdi:power", - }, - C3Attributes.zone2_power: { - "type": Platform.SWITCH, - "name": "Zone2 Power", - "icon": "mdi:power", - }, - C3Attributes.zone1_water_temp_mode: { - "type": Platform.BINARY_SENSOR, - "name": "Zone1 Water-temperature Mode", - "icon": "mdi:coolant-temperature", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.zone2_water_temp_mode: { - "type": Platform.BINARY_SENSOR, - "name": "Zone2 Water-temperature Mode", - "icon": "mdi:coolant-temperature", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.zone1_room_temp_mode: { - "type": Platform.BINARY_SENSOR, - "name": "Zone1 Room-temperature Mode", - "icon": "mdi:home-thermometer-outline", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.zone2_room_temp_mode: { - "type": Platform.BINARY_SENSOR, - "name": "Zone2 Room-temperature Mode", - "icon": "mdi:home-thermometer-outline", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.error_code: { - "type": Platform.SENSOR, - "name": "Error Code", - "icon": "mdi:alpha-e-circle", - }, - C3Attributes.tank_actual_temperature: { - "type": Platform.SENSOR, - "name": "Tank Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - C3Attributes.status_dhw: { - "type": Platform.BINARY_SENSOR, - "name": "DHW status", - "icon": "mdi:heat-pump", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.status_tbh: { - "type": Platform.BINARY_SENSOR, - "name": "TBH status", - "icon": "mdi:water-boiler", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.status_ibh: { - "type": Platform.BINARY_SENSOR, - "name": "IBH status", - "icon": "mdi:coolant-temperature", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.status_heating: { - "type": Platform.BINARY_SENSOR, - "name": "Heating status", - "icon": "mdi:heat-pump", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - C3Attributes.total_energy_consumption: { - "type": Platform.SENSOR, - "name": "Total energy consumption", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - C3Attributes.total_produced_energy: { - "type": Platform.SENSOR, - "name": "Total produced energy", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - C3Attributes.outdoor_temperature: { - "type": Platform.SENSOR, - "name": "Outdoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xCA: { - "name": "Refrigerator", - "entities": { - CAAttributes.bar_door: { - "type": Platform.BINARY_SENSOR, - "name": "Bar Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - CAAttributes.bar_door_overtime: { - "type": Platform.BINARY_SENSOR, - "name": "Bar Door Overtime", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.flex_zone_door: { - "type": Platform.BINARY_SENSOR, - "name": "Flex Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - CAAttributes.flex_zone_door_overtime: { - "type": Platform.BINARY_SENSOR, - "name": "Flex Zone Door", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.freezer_door: { - "type": Platform.BINARY_SENSOR, - "name": "Freezer Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - CAAttributes.freezer_door_overtime: { - "type": Platform.BINARY_SENSOR, - "name": "Freezer Door Overtime", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.refrigerator_door: { - "type": Platform.BINARY_SENSOR, - "name": "Refrigerator Door", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.refrigerator_door_overtime: { - "type": Platform.BINARY_SENSOR, - "name": "Refrigerator Door Overtime", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CAAttributes.flex_zone_actual_temp: { - "type": Platform.SENSOR, - "name": "Flex Zone Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.flex_zone_setting_temp: { - "type": Platform.SENSOR, - "name": "Flex Zone Setting Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.freezer_actual_temp: { - "type": Platform.SENSOR, - "name": "Freezer Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.freezer_setting_temp: { - "type": Platform.SENSOR, - "name": "Freezer Setting Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.energy_consumption: { - "type": Platform.SENSOR, - "name": "Energy Consumption", - "device_class": SensorDeviceClass.ENERGY, - "unit": UnitOfEnergy.KILO_WATT_HOUR, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - CAAttributes.refrigerator_actual_temp: { - "type": Platform.SENSOR, - "name": "Refrigerator Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.refrigerator_setting_temp: { - "type": Platform.SENSOR, - "name": "Refrigerator Setting Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.right_flex_zone_actual_temp: { - "type": Platform.SENSOR, - "name": "Right Flex Zone Actual Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CAAttributes.right_flex_zone_setting_temp: { - "type": Platform.SENSOR, - "name": "Right Flex Zone Setting Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xCC: { - "name": "MDV Wi-Fi Controller", - "entities": { - "climate": { - "type": Platform.CLIMATE, - "icon": "hass:air-conditioner", - "default": True, - }, - CCAttributes.aux_heating: { - "type": Platform.SWITCH, - "name": "Aux Heating", - "icon": "mdi:heat-wave", - }, - CCAttributes.eco_mode: { - "type": Platform.SWITCH, - "name": "ECO Mode", - "icon": "mdi:leaf-circle", - }, - CCAttributes.night_light: { - "type": Platform.SWITCH, - "name": "Night Light", - "icon": "mdi:lightbulb", - }, - CCAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - CCAttributes.sleep_mode: { - "type": Platform.SWITCH, - "name": "Sleep Mode", - "icon": "mdi:power-sleep", - }, - CCAttributes.swing: { - "type": Platform.SWITCH, - "name": "Swing", - "icon": "mdi:arrow-split-horizontal", - }, - CCAttributes.indoor_temperature: { - "type": Platform.SENSOR, - "name": "Indoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xCD: { - "name": "Heat Pump Water Heater", - "entities": { - "water_heater": { - "type": Platform.WATER_HEATER, - "icon": "mdi:heat-pump", - "default": True, - }, - CDAttributes.compressor_status: { - "type": Platform.BINARY_SENSOR, - "name": "Compressor Status", - "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - CDAttributes.compressor_temperature: { - "type": Platform.SENSOR, - "name": "Compressor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CDAttributes.condenser_temperature: { - "type": Platform.SENSOR, - "name": "Condenser Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CDAttributes.outdoor_temperature: { - "type": Platform.SENSOR, - "name": "Outdoor Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CDAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - }, - }, - 0xCE: { - "name": "Fresh Air Appliance", - "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, - CEAttributes.filter_cleaning_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Filter Cleaning Reminder", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CEAttributes.filter_change_reminder: { - "type": Platform.BINARY_SENSOR, - "name": "Filter Change Reminder", - "icon": "mdi:alert-circle", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - CEAttributes.current_humidity: { - "type": Platform.SENSOR, - "name": "Current Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.co2: { - "type": Platform.SENSOR, - "name": "Carbon Dioxide", - "device_class": SensorDeviceClass.CO2, - "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.hcho: { - "type": Platform.SENSOR, - "name": "Methanal", - "icon": "mdi:molecule", - "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.pm25: { - "type": Platform.SENSOR, - "name": "PM 2.5", - "device_class": SensorDeviceClass.PM25, - "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, - }, - CEAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - CEAttributes.aux_heating: { - "type": Platform.SWITCH, - "name": "Aux Heating", - "icon": "mdi:heat-wave", - }, - CEAttributes.eco_mode: { - "type": Platform.SWITCH, - "name": "ECO Mode", - "icon": "mdi:leaf-circle", - }, - CEAttributes.link_to_ac: { - "type": Platform.SWITCH, - "name": "Link to AC", - "icon": "mdi:link", - }, - CEAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - CEAttributes.powerful_purify: { - "type": Platform.SWITCH, - "name": "Powerful Purification", - "icon": "mdi:turbine", - }, - CEAttributes.sleep_mode: { - "type": Platform.SWITCH, - "name": "Sleep Mode", - "icon": "mdi:power-sleep", - }, - }, - }, - 0xCF: { - "name": "Heat Pump", - "entities": { - "climate": { - "type": Platform.CLIMATE, - "icon": "hass:air-conditioner", - "default": True, - }, - CFAttributes.aux_heating: { - "type": Platform.SWITCH, - "name": "Aux Heating", - "icon": "mdi:heat-wave", - }, - CFAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - CFAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xDA: { - "name": "Top Load Washer", - "entities": { - DAAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DAAttributes.wash_time: { - "type": Platform.SENSOR, - "name": "wash time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DAAttributes.soak_time: { - "type": Platform.SENSOR, - "name": "soak time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DAAttributes.dehydration_time: { - "type": Platform.SENSOR, - "name": "dehydration time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DAAttributes.dehydration_speed: { - "type": Platform.SENSOR, - "name": "dehydration speed", - "icon": "mdi:speedometer", - }, - DAAttributes.error_code: { - "type": Platform.SENSOR, - "name": "error code", - "icon": "mdi:washing-machine-alert", - }, - DAAttributes.rinse_count: { - "type": Platform.SENSOR, - "name": "rinse count", - "icon": "mdi:water-sync", - }, - DAAttributes.rinse_level: { - "type": Platform.SENSOR, - "name": "rinse level", - "icon": "mdi:hydraulic-oil-level", - }, - DAAttributes.wash_level: { - "type": Platform.SENSOR, - "name": "rinse count", - "icon": "mdi:hydraulic-oil-level", - }, - DAAttributes.wash_strength: { - "type": Platform.SENSOR, - "name": "wash strength", - "icon": "mdi:network-strength-4-cog", - }, - DAAttributes.softener: { - "type": Platform.SENSOR, - "name": "softener", - "icon": "mdi:tshirt-crew", - }, - DAAttributes.detergent: { - "type": Platform.SENSOR, - "name": "detergent", - "icon": "mdi:spray-bottle", - }, - DAAttributes.program: { - "type": Platform.SENSOR, - "name": "Program", - "icon": "mdi:progress-wrench", - }, - DAAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - DAAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - DAAttributes.start: { - "type": Platform.SWITCH, - "name": "Start", - "icon": "mdi:motion-play-outline", - }, - }, - }, - 0xDB: { - "name": "Front Load Washer", - "entities": { - DBAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DBAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - DBAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - DBAttributes.start: { - "type": Platform.SWITCH, - "name": "Start", - "icon": "mdi:motion-play-outline", - }, - }, - }, - 0xDC: { - "name": "Clothes Dryer", - "entities": { - DCAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - DCAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - DCAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - DCAttributes.start: { - "type": Platform.SWITCH, - "name": "Start", - "icon": "mdi:motion-play-outline", - }, - }, - }, - 0xE1: { - "name": "Dishwasher", - "entities": { - E1Attributes.door: { - "type": Platform.BINARY_SENSOR, - "name": "Door", - "icon": "mdi:box-shadow", - "device_class": BinarySensorDeviceClass.DOOR, - }, - E1Attributes.rinse_aid: { - "type": Platform.BINARY_SENSOR, - "name": "Rinse Aid Shortage", - "icon": "mdi:bottle-tonic", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - E1Attributes.salt: { - "type": Platform.BINARY_SENSOR, - "name": "Salt Shortage", - "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - E1Attributes.humidity: { - "type": Platform.SENSOR, - "name": "Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - E1Attributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - E1Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - E1Attributes.storage_remaining: { - "type": Platform.SENSOR, - "name": "Storage Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.HOURS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E1Attributes.temperature: { - "type": Platform.SENSOR, - "name": "Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E1Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - E1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - E1Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - E1Attributes.storage: { - "type": Platform.SWITCH, - "name": "Storage", - "icon": "mdi:repeat-variant", - }, - E1Attributes.mode: { - "type": Platform.SENSOR, - "name": "Working Mode", - "icon": "mdi:dishwasher", - }, - E1Attributes.error_code: { - "type": Platform.SENSOR, - "name": "Error Code", - "icon": "mdi:alert-box", - }, - E1Attributes.softwater: { - "type": Platform.SENSOR, - "name": "Softwater Level", - "icon": "mdi:shaker-outline", - }, - E1Attributes.bright: { - "type": Platform.SENSOR, - "name": "Bright Level", - "icon": "mdi:star-four-points", - }, - }, - }, - 0xE2: { - "name": "Electric Water Heater", - "entities": { - "water_heater": { - "type": Platform.WATER_HEATER, - "icon": "mdi:meter-electric-outline", - "default": True, - }, - E2Attributes.heating: { - "type": Platform.BINARY_SENSOR, - "name": "Heating", - "icon": "mdi:heat-wave", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E2Attributes.keep_warm: { - "type": Platform.BINARY_SENSOR, - "name": "Keep Warm", - "icon": "mdi:menu", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E2Attributes.protection: { - "type": Platform.BINARY_SENSOR, - "name": "Protection", - "icon": "mdi:shield-check", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E2Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E2Attributes.heating_time_remaining: { - "type": Platform.SENSOR, - "name": "Heating Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - E2Attributes.heating_power: { - "type": Platform.SENSOR, - "name": "Heating Power", - "device_class": SensorDeviceClass.POWER, - "unit": UnitOfPower.WATT, - "state_class": SensorStateClass.MEASUREMENT, - }, - E2Attributes.water_consumption: { - "type": Platform.SENSOR, - "name": "Water Consumption", - "icon": "mdi:water", - "unit": UnitOfVolume.LITERS, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - E2Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - E2Attributes.variable_heating: { - "type": Platform.SWITCH, - "name": "Variable Heating", - "icon": "mdi:waves", - }, - E2Attributes.whole_tank_heating: { - "type": Platform.SWITCH, - "name": "Whole Tank Heating", - "icon": "mdi:restore", - }, - }, - }, - 0xE3: { - "name": "Gas Water Heater", - "entities": { - "water_heater": { - "type": Platform.WATER_HEATER, - "icon": "mdi:meter-gas", - "default": True, - }, - E3Attributes.burning_state: { - "type": Platform.BINARY_SENSOR, - "name": "Burning State", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E3Attributes.protection: { - "type": Platform.BINARY_SENSOR, - "name": "Protection", - "icon": "mdi:shield-check", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E3Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E3Attributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - E3Attributes.smart_volume: { - "type": Platform.SWITCH, - "name": "Smart Volume", - "icon": "mdi:recycle", - }, - E3Attributes.zero_cold_water: { - "type": Platform.SWITCH, - "name": "Zero Cold Water", - "icon": "mdi:restore", - }, - E3Attributes.zero_cold_pulse: { - "type": Platform.SWITCH, - "name": "Zero Cold Water (Pulse)", - "icon": "mdi:restore-alert", - }, - }, - }, - 0xE6: { - "name": "Gas Boilers", - "entities": { - "water_heater_heating": { - "type": Platform.WATER_HEATER, - "icon": "mdi:meter-gas", - "name": "Heating", - "use": 0, - "default": True, - }, - "water_heater_bathing": { - "type": Platform.WATER_HEATER, - "icon": "mdi:meter-gas", - "name": "Bathing", - "use": 1, - "default": True, - }, - E6Attributes.heating_working: { - "type": Platform.BINARY_SENSOR, - "name": "Heating Working Status", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E6Attributes.bathing_working: { - "type": Platform.BINARY_SENSOR, - "name": "Bathing Working Status", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - E6Attributes.heating_leaving_temperature: { - "type": Platform.SENSOR, - "name": "Heating Leaving Water Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E6Attributes.bathing_leaving_temperature: { - "type": Platform.SENSOR, - "name": "Bathing Leaving Water Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E6Attributes.main_power: { - "type": Platform.SWITCH, - "name": "Main Power", - "icon": "mdi:power", - }, - E6Attributes.heating_power: { - "type": Platform.SWITCH, - "name": "Heating Power", - "icon": "mdi:heating-coil", - }, - }, - }, - 0xE8: { - "name": "Electric Slow Cooker", - "entities": { - E8Attributes.finished: { - "type": Platform.BINARY_SENSOR, - "name": "Finished", - "icon": "", - }, - E8Attributes.water_shortage: { - "type": Platform.BINARY_SENSOR, - "name": "Water Shortage", - "icon": "mdi:drag", - "device_class": BinarySensorDeviceClass.PROBLEM, - }, - E8Attributes.status: { - "type": Platform.SENSOR, - "name": "Status", - "icon": "mdi:information", - }, - E8Attributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E8Attributes.keep_warm_remaining: { - "type": Platform.SENSOR, - "name": "Keep Warm Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E8Attributes.working_time: { - "type": Platform.SENSOR, - "name": "Working Time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.SECONDS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E8Attributes.target_temperature: { - "type": Platform.SENSOR, - "name": "Target Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - E8Attributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xEA: { - "name": "Electric Rice Cooker", - "entities": { - EAAttributes.cooking: { - "type": Platform.BINARY_SENSOR, - "name": "Cooking", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - EAAttributes.keep_warm: { - "type": Platform.BINARY_SENSOR, - "name": "Keep Warm", - "icon": "mdi:menu", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - EAAttributes.bottom_temperature: { - "type": Platform.SENSOR, - "name": "Bottom Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - EAAttributes.keep_warm_time: { - "type": Platform.SENSOR, - "name": "Keep Warm Time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - EAAttributes.mode: { - "type": Platform.SENSOR, - "name": "Mode", - "icon": "mdi:orbit", - }, - EAAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - EAAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - EAAttributes.top_temperature: { - "type": Platform.SENSOR, - "name": "Top Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xEC: { - "name": "Electric Pressure Cooker", - "entities": { - ECAttributes.cooking: { - "type": Platform.BINARY_SENSOR, - "name": "Cooking", - "icon": "mdi:fire", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - ECAttributes.with_pressure: { - "type": Platform.BINARY_SENSOR, - "name": "With Pressure", - "icon": "mdi:information", - "device_class": BinarySensorDeviceClass.RUNNING, - }, - ECAttributes.bottom_temperature: { - "type": Platform.SENSOR, - "name": "Bottom Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - ECAttributes.keep_warm_time: { - "type": Platform.SENSOR, - "name": "Keep Warm Time", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - ECAttributes.mode: { - "type": Platform.SENSOR, - "name": "Mode", - "icon": "mdi:orbit", - }, - ECAttributes.progress: { - "type": Platform.SENSOR, - "name": "Progress", - "icon": "mdi:rotate-360", - }, - ECAttributes.time_remaining: { - "type": Platform.SENSOR, - "name": "Time Remaining", - "icon": "mdi:progress-clock", - "unit": UnitOfTime.MINUTES, - "state_class": SensorStateClass.MEASUREMENT, - }, - ECAttributes.top_temperature: { - "type": Platform.SENSOR, - "name": "Top Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xED: { - "name": "Water Drinking Appliance", - "entities": { - EDAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - EDAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - EDAttributes.filter1: { - "type": Platform.SENSOR, - "name": "Filter1 Available Days", - "icon": "mdi:air-filter", - "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.filter2: { - "type": Platform.SENSOR, - "name": "Filter2 Available Days", - "icon": "mdi:air-filter", - "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.filter3: { - "type": Platform.SENSOR, - "name": "Filter3 Available Days", - "icon": "mdi:air-filter", - "unit": UnitOfTime.DAYS, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.life1: { - "type": Platform.SENSOR, - "name": "Filter1 Life Level", - "icon": "mdi:percent", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.life2: { - "type": Platform.SENSOR, - "name": "Filter2 Life Level", - "icon": "mdi:percent", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.life3: { - "type": Platform.SENSOR, - "name": "Filter3 Life Level", - "icon": "mdi:percent", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.in_tds: { - "type": Platform.SENSOR, - "name": "In TDS", - "icon": "mdi:water", - "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.out_tds: { - "type": Platform.SENSOR, - "name": "Out TDS", - "icon": "mdi:water-plus", - "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, - }, - EDAttributes.water_consumption: { - "type": Platform.SENSOR, - "name": "Water Consumption", - "icon": "mdi:water-pump", - "unit": UnitOfVolume.LITERS, - "state_class": SensorStateClass.TOTAL_INCREASING, - }, - }, - }, - 0xFA: { - "name": "Fan", - "entities": { - "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, - FAAttributes.oscillation_mode: { - "type": Platform.SELECT, - "name": "Oscillation Mode", - "options": "oscillation_modes", - "icon": "mdi:swap-horizontal-variant", - }, - FAAttributes.oscillation_angle: { - "type": Platform.SELECT, - "name": "Oscillation Angle", - "options": "oscillation_angles", - "icon": "mdi:pan-horizontal", - }, - FAAttributes.tilting_angle: { - "type": Platform.SELECT, - "name": "Tilting Angle", - "options": "tilting_angles", - "icon": "mdi:pan-vertical", - }, - FAAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - FAAttributes.oscillate: { - "type": Platform.SWITCH, - "name": "Oscillate", - "icon": "mdi:swap-horizontal-bold", - }, - FAAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - }, - }, - 0xFB: { - "name": "Electric Heater", - "entities": { - "climate": { - "type": Platform.CLIMATE, - "icon": "mdi:air-conditioner", - "default": True, - }, - FBAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - FBAttributes.heating_level: { - "type": Platform.NUMBER, - "name": "Heating Level", - "icon": "mdi:fire", - "max": 10, - "min": 1, - "step": 1, - }, - FBAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - FBAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xFC: { - "name": "Air Purifier", - "entities": { - FCAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, - FCAttributes.anion: { - "type": Platform.SWITCH, - "name": "Anion", - "icon": "mdi:vanish", - }, - FCAttributes.prompt_tone: { - "type": Platform.SWITCH, - "name": "Prompt Tone", - "icon": "mdi:bell", - }, - FCAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - FCAttributes.standby: { - "type": Platform.SWITCH, - "name": "Standby", - "icon": "mdi:smoke-detector-variant", - }, - FCAttributes.detect_mode: { - "type": Platform.SELECT, - "name": "Detect Mode", - "options": "detect_modes", - "icon": "mdi:smoke-detector-variant", - }, - FCAttributes.mode: { - "type": Platform.SELECT, - "name": "Mode", - "options": "modes", - "icon": "mdi:rotate-360", - }, - FCAttributes.fan_speed: { - "type": Platform.SELECT, - "name": "Fan Speed", - "options": "fan_speeds", - "icon": "mdi:fan", - }, - FCAttributes.screen_display: { - "type": Platform.SELECT, - "name": "Screen Display", - "options": "screen_displays", - "icon": "mdi:television-ambient-light", - }, - FCAttributes.pm25: { - "type": Platform.SENSOR, - "name": "PM 2.5", - "device_class": SensorDeviceClass.PM25, - "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, - }, - FCAttributes.tvoc: { - "type": Platform.SENSOR, - "name": "TVOC", - "icon": "mdi:heat-wave", - "unit": CONCENTRATION_PARTS_PER_MILLION, - "state_class": SensorStateClass.MEASUREMENT, - }, - FCAttributes.hcho: { - "type": Platform.SENSOR, - "name": "Methanal", - "icon": "mdi:molecule", - "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - "state_class": SensorStateClass.MEASUREMENT, - }, - FCAttributes.filter1_life: { - "type": Platform.SENSOR, - "name": "Filter1 Life Level", - "icon": "mdi:air-filter", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - FCAttributes.filter2_life: { - "type": Platform.SENSOR, - "name": "Filter2 Life Level", - "icon": "mdi:air-filter", - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, - 0xFD: { - "name": "Humidifier", - "entities": { - Platform.HUMIDIFIER: { - "type": Platform.HUMIDIFIER, - "icon": "mdi:air-humidifier", - "default": True, - }, - FDAttributes.disinfect: { - "type": Platform.SWITCH, - "name": "Disinfect", - "icon": "mdi:water-plus-outline", - }, - FDAttributes.prompt_tone: { - "type": Platform.SWITCH, - "name": "Prompt Tone", - "icon": "mdi:bell", - }, - FDAttributes.power: { - "type": Platform.SWITCH, - "name": "Power", - "icon": "mdi:power", - }, - FDAttributes.fan_speed: { - "type": Platform.SELECT, - "name": "Fan Speed", - "options": "fan_speeds", - "icon": "mdi:fan", - }, - FDAttributes.screen_display: { - "type": Platform.SELECT, - "name": "Screen Display", - "options": "screen_displays", - "icon": "mdi:television-ambient-light", - }, - FDAttributes.current_humidity: { - "type": Platform.SENSOR, - "name": "Current Humidity", - "device_class": SensorDeviceClass.HUMIDITY, - "unit": PERCENTAGE, - "state_class": SensorStateClass.MEASUREMENT, - }, - FDAttributes.current_temperature: { - "type": Platform.SENSOR, - "name": "Current Temperature", - "device_class": SensorDeviceClass.TEMPERATURE, - "unit": UnitOfTemperature.CELSIUS, - "state_class": SensorStateClass.MEASUREMENT, - }, - }, - }, -} +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass +from homeassistant.const import ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION, + PERCENTAGE, + Platform, + UnitOfEnergy, + UnitOfPower, + UnitOfTemperature, + UnitOfTime, + UnitOfVolume, +) +from midealocal.devices.a1 import DeviceAttributes as A1Attributes +from midealocal.devices.ac import DeviceAttributes as ACAttributes +from midealocal.devices.b0 import DeviceAttributes as B0Attributes +from midealocal.devices.b1 import DeviceAttributes as B1Attributes +from midealocal.devices.b3 import DeviceAttributes as B3Attributes +from midealocal.devices.b4 import DeviceAttributes as B4Attributes +from midealocal.devices.b6 import DeviceAttributes as B6Attributes +from midealocal.devices.bf import DeviceAttributes as BFAttributes +from midealocal.devices.c2 import DeviceAttributes as C2Attributes +from midealocal.devices.c3 import DeviceAttributes as C3Attributes +from midealocal.devices.ca import DeviceAttributes as CAAttributes +from midealocal.devices.cc import DeviceAttributes as CCAttributes +from midealocal.devices.cd import DeviceAttributes as CDAttributes +from midealocal.devices.ce import DeviceAttributes as CEAttributes +from midealocal.devices.cf import DeviceAttributes as CFAttributes +from midealocal.devices.da import DeviceAttributes as DAAttributes +from midealocal.devices.db import DeviceAttributes as DBAttributes +from midealocal.devices.dc import DeviceAttributes as DCAttributes +from midealocal.devices.e1 import DeviceAttributes as E1Attributes +from midealocal.devices.e2 import DeviceAttributes as E2Attributes +from midealocal.devices.e3 import DeviceAttributes as E3Attributes +from midealocal.devices.e6 import DeviceAttributes as E6Attributes +from midealocal.devices.e8 import DeviceAttributes as E8Attributes +from midealocal.devices.ea import DeviceAttributes as EAAttributes +from midealocal.devices.ec import DeviceAttributes as ECAttributes +from midealocal.devices.ed import DeviceAttributes as EDAttributes +from midealocal.devices.fa import DeviceAttributes as FAAttributes +from midealocal.devices.fb import DeviceAttributes as FBAttributes +from midealocal.devices.fc import DeviceAttributes as FCAttributes +from midealocal.devices.fd import DeviceAttributes as FDAttributes +from midealocal.devices.x26 import DeviceAttributes as X26Attributes +from midealocal.devices.x34 import DeviceAttributes as X34Attributes +from midealocal.devices.x40 import DeviceAttributes as X40Attributes + +MIDEA_DEVICES = { + 0x13: { + "name": "Light", + "entities": { + "light": {"type": Platform.LIGHT, "icon": "mdi:lightbulb", "default": True} + }, + }, + 0x26: { + "name": "Bathroom Master", + "entities": { + X26Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X26Attributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + X26Attributes.current_radar: { + "type": Platform.BINARY_SENSOR, + "name": "Occupancy Status", + "device_class": BinarySensorDeviceClass.MOVING, + }, + X26Attributes.main_light: { + "type": Platform.SWITCH, + "name": "Main Light", + "icon": "mdi:lightbulb", + }, + X26Attributes.night_light: { + "type": Platform.SWITCH, + "name": "Night Light", + "icon": "mdi:lightbulb", + }, + X26Attributes.mode: { + "type": Platform.SELECT, + "name": "Mode", + "options": "preset_modes", + "icon": "mdi:fan", + }, + X26Attributes.direction: { + "type": Platform.SELECT, + "name": "Direction", + "options": "directions", + "icon": "mdi:arrow-split-vertical", + }, + }, + }, + 0x34: { + "name": "Sink Dishwasher", + "entities": { + X34Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + X34Attributes.rinse_aid: { + "type": Platform.BINARY_SENSOR, + "name": "Rinse Aid Shortage", + "icon": "mdi:bottle-tonic", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + X34Attributes.salt: { + "type": Platform.BINARY_SENSOR, + "name": "Salt Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + X34Attributes.humidity: { + "type": Platform.SENSOR, + "name": "Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + X34Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + X34Attributes.storage_remaining: { + "type": Platform.SENSOR, + "name": "Storage Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.HOURS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.temperature: { + "type": Platform.SENSOR, + "name": "Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + X34Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + X34Attributes.storage: { + "type": Platform.SWITCH, + "name": "Storage", + "icon": "mdi:repeat-variant", + }, + X34Attributes.mode: { + "type": Platform.SENSOR, + "name": "Working Mode", + "icon": "mdi:dishwasher", + }, + X34Attributes.error_code: { + "type": Platform.SENSOR, + "name": "Error Code", + "icon": "mdi:alert-box", + }, + X34Attributes.softwater: { + "type": Platform.SENSOR, + "name": "Softwater Level", + "icon": "mdi:shaker-outline", + }, + X34Attributes.bright: { + "type": Platform.SENSOR, + "name": "Bright Level", + "icon": "mdi:star-four-points", + }, + }, + }, + 0x40: { + "name": "Integrated Ceiling Fan", + "entities": { + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + X40Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X40Attributes.light: { + "type": Platform.SWITCH, + "name": "Light", + "icon": "mdi:lightbulb", + }, + X40Attributes.ventilation: { + "type": Platform.SWITCH, + "name": "Ventilation", + "icon": "mdi:air-filter", + }, + X40Attributes.smelly_sensor: { + "type": Platform.SWITCH, + "name": "Smelly Sensor", + "icon": "mdi:scent", + }, + X40Attributes.direction: { + "type": Platform.SELECT, + "name": "Direction", + "options": "directions", + "icon": "mdi:arrow-split-vertical", + }, + }, + }, + 0xA1: { + "name": "Dehumidifier", + "entities": { + "humidifier": { + "type": Platform.HUMIDIFIER, + "icon": "mdi:air-humidifier", + "default": True, + }, + A1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + A1Attributes.anion: { + "type": Platform.SWITCH, + "name": "Anion", + "icon": "mdi:vanish", + }, + A1Attributes.prompt_tone: { + "type": Platform.SWITCH, + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + A1Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + A1Attributes.swing: { + "type": Platform.SWITCH, + "name": "swing", + "icon": "mdi:pan-horizontal", + }, + A1Attributes.fan_speed: { + "type": Platform.SELECT, + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + A1Attributes.water_level_set: { + "type": Platform.SELECT, + "name": "Water Level Setting", + "options": "water_level_sets", + "icon": "mdi:cup-water", + }, + A1Attributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.tank: { + "type": Platform.SENSOR, + "name": "Tank", + "icon": "mdi:cup-water", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.tank_full: { + "type": Platform.BINARY_SENSOR, + "name": "Tank status", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + }, + }, + 0xAC: { + "name": "Air Conditioner", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "default": True, + }, + "fresh_air": {"type": Platform.FAN, "icon": "mdi:fan", "name": "Fresh Air"}, + ACAttributes.aux_heating: { + "type": Platform.SWITCH, + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + ACAttributes.boost_mode: { + "type": Platform.SWITCH, + "name": "Boost Mode", + "icon": "mdi:turbine", + }, + ACAttributes.breezeless: { + "type": Platform.SWITCH, + "name": "Breezeless", + "icon": "mdi:tailwind", + }, + ACAttributes.comfort_mode: { + "type": Platform.SWITCH, + "name": "Comfort Mode", + "icon": "mdi:alpha-c-circle", + }, + ACAttributes.dry: { + "type": Platform.SWITCH, + "name": "Dry", + "icon": "mdi:air-filter", + }, + ACAttributes.eco_mode: { + "type": Platform.SWITCH, + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + ACAttributes.frost_protect: { + "type": Platform.SWITCH, + "name": "Frost Protect", + "icon": "mdi:snowflake-alert", + }, + ACAttributes.indirect_wind: { + "type": Platform.SWITCH, + "name": "Indirect Wind", + "icon": "mdi:tailwind", + }, + ACAttributes.natural_wind: { + "type": Platform.SWITCH, + "name": "Natural Wind", + "icon": "mdi:tailwind", + }, + ACAttributes.prompt_tone: { + "type": Platform.SWITCH, + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + ACAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + ACAttributes.screen_display: { + "type": Platform.SWITCH, + "name": "Screen Display", + "icon": "mdi:television-ambient-light", + }, + ACAttributes.screen_display_alternate: { + "type": Platform.SWITCH, + "name": "Screen Display Alternate", + "icon": "mdi:television-ambient-light", + }, + ACAttributes.sleep_mode: { + "type": Platform.SWITCH, + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + ACAttributes.smart_eye: { + "type": Platform.SWITCH, + "name": "Smart Eye", + "icon": "mdi:eye", + }, + ACAttributes.swing_horizontal: { + "type": Platform.SWITCH, + "name": "Swing Horizontal", + "icon": "mdi:arrow-split-vertical", + }, + ACAttributes.swing_vertical: { + "type": Platform.SWITCH, + "name": "Swing Vertical", + "icon": "mdi:arrow-split-horizontal", + }, + ACAttributes.full_dust: { + "type": Platform.BINARY_SENSOR, + "name": "Full of Dust", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + ACAttributes.indoor_humidity: { + "type": Platform.SENSOR, + "name": "Indoor Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.indoor_temperature: { + "type": Platform.SENSOR, + "name": "Indoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.outdoor_temperature: { + "type": Platform.SENSOR, + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.total_energy_consumption: { + "type": Platform.SENSOR, + "name": "Total Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + ACAttributes.current_energy_consumption: { + "type": Platform.SENSOR, + "name": "Current Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + ACAttributes.realtime_power: { + "type": Platform.SENSOR, + "name": "Realtime Power", + "device_class": SensorDeviceClass.POWER, + "unit": UnitOfPower.WATT, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB0: { + "name": "Microwave Oven", + "entities": { + B0Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B0Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "name": "Tank Ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B0Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + B0Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB1: { + "name": "Electric Oven", + "entities": { + B1Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B1Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B1Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + B1Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB3: { + "name": "Dish Sterilizer", + "entities": { + B3Attributes.top_compartment_door: { + "type": Platform.BINARY_SENSOR, + "name": "Top Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.top_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "name": "Top Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.top_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "name": "Top Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.middle_compartment_door: { + "type": Platform.BINARY_SENSOR, + "name": "Middle Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.middle_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "name": "Middle Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.middle_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "name": "Middle Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.bottom_compartment_door: { + "type": Platform.BINARY_SENSOR, + "name": "Bottom Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.bottom_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "name": "Bottom Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.bottom_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "name": "Bottom Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.top_compartment_status: { + "type": Platform.SENSOR, + "name": "Top Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.top_compartment_temperature: { + "type": Platform.SENSOR, + "name": "Top Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.top_compartment_remaining: { + "type": Platform.SENSOR, + "name": "Top Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.middle_compartment_status: { + "type": Platform.SENSOR, + "name": "Middle Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.middle_compartment_temperature: { + "type": Platform.SENSOR, + "name": "Middle Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.middle_compartment_remaining: { + "type": Platform.SENSOR, + "name": "Middle Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.bottom_compartment_status: { + "type": Platform.SENSOR, + "name": "Bottom Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.bottom_compartment_temperature: { + "type": Platform.SENSOR, + "name": "Bottom Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.bottom_compartment_remaining: { + "type": Platform.SENSOR, + "name": "Bottom Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB4: { + "name": "Toaster", + "entities": { + B4Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B4Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B4Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + B4Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB6: { + "name": "Range Hood", + "entities": { + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + B6Attributes.light: { + "type": Platform.SWITCH, + "name": "Light", + "icon": "mdi:lightbulb", + }, + B6Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + B6Attributes.cleaning_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Cleaning Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B6Attributes.oilcup_full: { + "type": Platform.BINARY_SENSOR, + "name": "Oil-cup Full", + "icon": "mdi:cup", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B6Attributes.fan_level: { + "type": Platform.SENSOR, + "name": "Fan level", + "icon": "mdi:fan", + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xBF: { + "name": "Microwave Steam Oven", + "entities": { + BFAttributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + BFAttributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + BFAttributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + BFAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xC2: { + "name": "Toilet", + "entities": { + C2Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + C2Attributes.sensor_light: { + "type": Platform.SWITCH, + "name": "Sensor Light", + "icon": "mdi:lightbulb", + }, + C2Attributes.foam_shield: { + "type": Platform.SWITCH, + "name": "Foam Shield", + "icon": "mdi:chart-bubble", + }, + C2Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + C2Attributes.seat_status: { + "type": Platform.BINARY_SENSOR, + "name": "Seat Status", + "icon": "mdi:seat-legroom-normal", + }, + C2Attributes.lid_status: { + "type": Platform.BINARY_SENSOR, + "name": "Lid Status", + "icon": "mdi:toilet", + }, + C2Attributes.light_status: { + "type": Platform.BINARY_SENSOR, + "name": "Light Status", + "icon": "mdi:lightbulb", + "device_class": BinarySensorDeviceClass.LIGHT, + }, + C2Attributes.water_temperature: { + "type": Platform.SENSOR, + "name": "Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.seat_temperature: { + "type": Platform.SENSOR, + "name": "Seat Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.filter_life: { + "type": Platform.SENSOR, + "name": "Filter Life", + "icon": "mdi:toilet", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.dry_level: { + "type": Platform.NUMBER, + "name": "Dry Level", + "icon": "mdi:fire", + "max": "max_dry_level", + "min": 0, + "step": 1, + }, + C2Attributes.water_temp_level: { + "type": Platform.NUMBER, + "name": "Water Temperature Level", + "icon": "mdi:fire", + "max": "max_water_temp_level", + "min": 0, + "step": 1, + }, + C2Attributes.seat_temp_level: { + "type": Platform.NUMBER, + "name": "Seat Temperature Level", + "icon": "mdi:fire", + "max": "max_seat_temp_level", + "min": 0, + "step": 1, + }, + }, + }, + 0xC3: { + "name": "Heat Pump Wi-Fi Controller", + "entities": { + "climate_zone1": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "name": "Zone1 Thermostat", + "zone": 0, + "default": True, + }, + "climate_zone2": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "name": "Zone2 Thermostat", + "zone": 1, + "default": False, + }, + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:heat-pump", + "name": "Domestic hot water", + "default": True, + }, + C3Attributes.disinfect: { + "type": Platform.SWITCH, + "name": "Disinfect", + "icon": "mdi:water-plus-outline", + }, + C3Attributes.dhw_power: { + "type": Platform.SWITCH, + "name": "DHW Power", + "icon": "mdi:power", + }, + C3Attributes.eco_mode: { + "type": Platform.SWITCH, + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + C3Attributes.fast_dhw: { + "type": Platform.SWITCH, + "name": "Fast DHW", + "icon": "mdi:rotate-orbit", + }, + C3Attributes.silent_mode: { + "type": Platform.SWITCH, + "name": "Silent Mode", + "icon": "mdi:fan-remove", + }, + C3Attributes.tbh: { + "type": Platform.SWITCH, + "name": "TBH", + "icon": "mdi:water-boiler", + }, + C3Attributes.zone1_curve: { + "type": Platform.SWITCH, + "name": "Zone1 Curve", + "icon": "mdi:chart-bell-curve-cumulative", + }, + C3Attributes.zone2_curve: { + "type": Platform.SWITCH, + "name": "Zone2 Curve", + "icon": "mdi:chart-bell-curve-cumulative", + }, + C3Attributes.zone1_power: { + "type": Platform.SWITCH, + "name": "Zone1 Power", + "icon": "mdi:power", + }, + C3Attributes.zone2_power: { + "type": Platform.SWITCH, + "name": "Zone2 Power", + "icon": "mdi:power", + }, + C3Attributes.zone1_water_temp_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Zone1 Water-temperature Mode", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone2_water_temp_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Zone2 Water-temperature Mode", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone1_room_temp_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Zone1 Room-temperature Mode", + "icon": "mdi:home-thermometer-outline", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone2_room_temp_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Zone2 Room-temperature Mode", + "icon": "mdi:home-thermometer-outline", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.error_code: { + "type": Platform.SENSOR, + "name": "Error Code", + "icon": "mdi:alpha-e-circle", + }, + C3Attributes.tank_actual_temperature: { + "type": Platform.SENSOR, + "name": "Tank Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C3Attributes.status_dhw: { + "type": Platform.BINARY_SENSOR, + "name": "DHW status", + "icon": "mdi:heat-pump", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_tbh: { + "type": Platform.BINARY_SENSOR, + "name": "TBH status", + "icon": "mdi:water-boiler", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_ibh: { + "type": Platform.BINARY_SENSOR, + "name": "IBH status", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_heating: { + "type": Platform.BINARY_SENSOR, + "name": "Heating status", + "icon": "mdi:heat-pump", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.total_energy_consumption: { + "type": Platform.SENSOR, + "name": "Total energy consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + C3Attributes.total_produced_energy: { + "type": Platform.SENSOR, + "name": "Total produced energy", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + C3Attributes.outdoor_temperature: { + "type": Platform.SENSOR, + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xCA: { + "name": "Refrigerator", + "entities": { + CAAttributes.bar_door: { + "type": Platform.BINARY_SENSOR, + "name": "Bar Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.bar_door_overtime: { + "type": Platform.BINARY_SENSOR, + "name": "Bar Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.flex_zone_door: { + "type": Platform.BINARY_SENSOR, + "name": "Flex Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.flex_zone_door_overtime: { + "type": Platform.BINARY_SENSOR, + "name": "Flex Zone Door", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.freezer_door: { + "type": Platform.BINARY_SENSOR, + "name": "Freezer Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.freezer_door_overtime: { + "type": Platform.BINARY_SENSOR, + "name": "Freezer Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.refrigerator_door: { + "type": Platform.BINARY_SENSOR, + "name": "Refrigerator Door", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.refrigerator_door_overtime: { + "type": Platform.BINARY_SENSOR, + "name": "Refrigerator Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.flex_zone_actual_temp: { + "type": Platform.SENSOR, + "name": "Flex Zone Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.flex_zone_setting_temp: { + "type": Platform.SENSOR, + "name": "Flex Zone Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.freezer_actual_temp: { + "type": Platform.SENSOR, + "name": "Freezer Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.freezer_setting_temp: { + "type": Platform.SENSOR, + "name": "Freezer Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.energy_consumption: { + "type": Platform.SENSOR, + "name": "Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + CAAttributes.refrigerator_actual_temp: { + "type": Platform.SENSOR, + "name": "Refrigerator Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.refrigerator_setting_temp: { + "type": Platform.SENSOR, + "name": "Refrigerator Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.right_flex_zone_actual_temp: { + "type": Platform.SENSOR, + "name": "Right Flex Zone Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.right_flex_zone_setting_temp: { + "type": Platform.SENSOR, + "name": "Right Flex Zone Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xCC: { + "name": "MDV Wi-Fi Controller", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "hass:air-conditioner", + "default": True, + }, + CCAttributes.aux_heating: { + "type": Platform.SWITCH, + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CCAttributes.eco_mode: { + "type": Platform.SWITCH, + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + CCAttributes.night_light: { + "type": Platform.SWITCH, + "name": "Night Light", + "icon": "mdi:lightbulb", + }, + CCAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + CCAttributes.sleep_mode: { + "type": Platform.SWITCH, + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + CCAttributes.swing: { + "type": Platform.SWITCH, + "name": "Swing", + "icon": "mdi:arrow-split-horizontal", + }, + CCAttributes.indoor_temperature: { + "type": Platform.SENSOR, + "name": "Indoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xCD: { + "name": "Heat Pump Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:heat-pump", + "default": True, + }, + CDAttributes.compressor_status: { + "type": Platform.BINARY_SENSOR, + "name": "Compressor Status", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + CDAttributes.compressor_temperature: { + "type": Platform.SENSOR, + "name": "Compressor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.condenser_temperature: { + "type": Platform.SENSOR, + "name": "Condenser Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.outdoor_temperature: { + "type": Platform.SENSOR, + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + }, + }, + 0xCE: { + "name": "Fresh Air Appliance", + "entities": { + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + CEAttributes.filter_cleaning_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Filter Cleaning Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CEAttributes.filter_change_reminder: { + "type": Platform.BINARY_SENSOR, + "name": "Filter Change Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CEAttributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.co2: { + "type": Platform.SENSOR, + "name": "Carbon Dioxide", + "device_class": SensorDeviceClass.CO2, + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.hcho: { + "type": Platform.SENSOR, + "name": "Methanal", + "icon": "mdi:molecule", + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.pm25: { + "type": Platform.SENSOR, + "name": "PM 2.5", + "device_class": SensorDeviceClass.PM25, + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + CEAttributes.aux_heating: { + "type": Platform.SWITCH, + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CEAttributes.eco_mode: { + "type": Platform.SWITCH, + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + CEAttributes.link_to_ac: { + "type": Platform.SWITCH, + "name": "Link to AC", + "icon": "mdi:link", + }, + CEAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + CEAttributes.powerful_purify: { + "type": Platform.SWITCH, + "name": "Powerful Purification", + "icon": "mdi:turbine", + }, + CEAttributes.sleep_mode: { + "type": Platform.SWITCH, + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + }, + }, + 0xCF: { + "name": "Heat Pump", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "hass:air-conditioner", + "default": True, + }, + CFAttributes.aux_heating: { + "type": Platform.SWITCH, + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CFAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + CFAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xDA: { + "name": "Top Load Washer", + "entities": { + DAAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.wash_time: { + "type": Platform.SENSOR, + "name": "wash time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.soak_time: { + "type": Platform.SENSOR, + "name": "soak time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.dehydration_time: { + "type": Platform.SENSOR, + "name": "dehydration time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.dehydration_speed: { + "type": Platform.SENSOR, + "name": "dehydration speed", + "icon": "mdi:speedometer", + }, + DAAttributes.error_code: { + "type": Platform.SENSOR, + "name": "error code", + "icon": "mdi:washing-machine-alert", + }, + DAAttributes.rinse_count: { + "type": Platform.SENSOR, + "name": "rinse count", + "icon": "mdi:water-sync", + }, + DAAttributes.rinse_level: { + "type": Platform.SENSOR, + "name": "rinse level", + "icon": "mdi:hydraulic-oil-level", + }, + DAAttributes.wash_level: { + "type": Platform.SENSOR, + "name": "rinse count", + "icon": "mdi:hydraulic-oil-level", + }, + DAAttributes.wash_strength: { + "type": Platform.SENSOR, + "name": "wash strength", + "icon": "mdi:network-strength-4-cog", + }, + DAAttributes.softener: { + "type": Platform.SENSOR, + "name": "softener", + "icon": "mdi:tshirt-crew", + }, + DAAttributes.detergent: { + "type": Platform.SENSOR, + "name": "detergent", + "icon": "mdi:spray-bottle", + }, + DAAttributes.program: { + "type": Platform.SENSOR, + "name": "Program", + "icon": "mdi:progress-wrench", + }, + DAAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DAAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + DAAttributes.start: { + "type": Platform.SWITCH, + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + }, + }, + 0xDB: { + "name": "Front Load Washer", + "entities": { + DBAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DBAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DBAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + DBAttributes.start: { + "type": Platform.SWITCH, + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + }, + }, + 0xDC: { + "name": "Clothes Dryer", + "entities": { + DCAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DCAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DCAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + DCAttributes.start: { + "type": Platform.SWITCH, + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + }, + }, + 0xE1: { + "name": "Dishwasher", + "entities": { + E1Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + E1Attributes.rinse_aid: { + "type": Platform.BINARY_SENSOR, + "name": "Rinse Aid Shortage", + "icon": "mdi:bottle-tonic", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E1Attributes.salt: { + "type": Platform.BINARY_SENSOR, + "name": "Salt Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E1Attributes.humidity: { + "type": Platform.SENSOR, + "name": "Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + E1Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + E1Attributes.storage_remaining: { + "type": Platform.SENSOR, + "name": "Storage Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.HOURS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.temperature: { + "type": Platform.SENSOR, + "name": "Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + E1Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + E1Attributes.storage: { + "type": Platform.SWITCH, + "name": "Storage", + "icon": "mdi:repeat-variant", + }, + E1Attributes.mode: { + "type": Platform.SENSOR, + "name": "Working Mode", + "icon": "mdi:dishwasher", + }, + E1Attributes.error_code: { + "type": Platform.SENSOR, + "name": "Error Code", + "icon": "mdi:alert-box", + }, + E1Attributes.softwater: { + "type": Platform.SENSOR, + "name": "Softwater Level", + "icon": "mdi:shaker-outline", + }, + E1Attributes.bright: { + "type": Platform.SENSOR, + "name": "Bright Level", + "icon": "mdi:star-four-points", + }, + }, + }, + 0xE2: { + "name": "Electric Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-electric-outline", + "default": True, + }, + E2Attributes.heating: { + "type": Platform.BINARY_SENSOR, + "name": "Heating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.keep_warm: { + "type": Platform.BINARY_SENSOR, + "name": "Keep Warm", + "icon": "mdi:menu", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.protection: { + "type": Platform.BINARY_SENSOR, + "name": "Protection", + "icon": "mdi:shield-check", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.heating_time_remaining: { + "type": Platform.SENSOR, + "name": "Heating Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.heating_power: { + "type": Platform.SENSOR, + "name": "Heating Power", + "device_class": SensorDeviceClass.POWER, + "unit": UnitOfPower.WATT, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.water_consumption: { + "type": Platform.SENSOR, + "name": "Water Consumption", + "icon": "mdi:water", + "unit": UnitOfVolume.LITERS, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + E2Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + E2Attributes.variable_heating: { + "type": Platform.SWITCH, + "name": "Variable Heating", + "icon": "mdi:waves", + }, + E2Attributes.whole_tank_heating: { + "type": Platform.SWITCH, + "name": "Whole Tank Heating", + "icon": "mdi:restore", + }, + }, + }, + 0xE3: { + "name": "Gas Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-gas", + "default": True, + }, + E3Attributes.burning_state: { + "type": Platform.BINARY_SENSOR, + "name": "Burning State", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E3Attributes.protection: { + "type": Platform.BINARY_SENSOR, + "name": "Protection", + "icon": "mdi:shield-check", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E3Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E3Attributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + E3Attributes.smart_volume: { + "type": Platform.SWITCH, + "name": "Smart Volume", + "icon": "mdi:recycle", + }, + E3Attributes.zero_cold_water: { + "type": Platform.SWITCH, + "name": "Zero Cold Water", + "icon": "mdi:restore", + }, + E3Attributes.zero_cold_pulse: { + "type": Platform.SWITCH, + "name": "Zero Cold Water (Pulse)", + "icon": "mdi:restore-alert", + }, + }, + }, + 0xE6: { + "name": "Gas Boilers", + "entities": { + "water_heater_heating": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-gas", + "name": "Heating", + "use": 0, + "default": True, + }, + "water_heater_bathing": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-gas", + "name": "Bathing", + "use": 1, + "default": True, + }, + E6Attributes.heating_working: { + "type": Platform.BINARY_SENSOR, + "name": "Heating Working Status", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E6Attributes.bathing_working: { + "type": Platform.BINARY_SENSOR, + "name": "Bathing Working Status", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E6Attributes.heating_leaving_temperature: { + "type": Platform.SENSOR, + "name": "Heating Leaving Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E6Attributes.bathing_leaving_temperature: { + "type": Platform.SENSOR, + "name": "Bathing Leaving Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E6Attributes.main_power: { + "type": Platform.SWITCH, + "name": "Main Power", + "icon": "mdi:power", + }, + E6Attributes.heating_power: { + "type": Platform.SWITCH, + "name": "Heating Power", + "icon": "mdi:heating-coil", + }, + }, + }, + 0xE8: { + "name": "Electric Slow Cooker", + "entities": { + E8Attributes.finished: { + "type": Platform.BINARY_SENSOR, + "name": "Finished", + "icon": "", + }, + E8Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "name": "Water Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E8Attributes.status: { + "type": Platform.SENSOR, + "name": "Status", + "icon": "mdi:information", + }, + E8Attributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.keep_warm_remaining: { + "type": Platform.SENSOR, + "name": "Keep Warm Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.working_time: { + "type": Platform.SENSOR, + "name": "Working Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.target_temperature: { + "type": Platform.SENSOR, + "name": "Target Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xEA: { + "name": "Electric Rice Cooker", + "entities": { + EAAttributes.cooking: { + "type": Platform.BINARY_SENSOR, + "name": "Cooking", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + EAAttributes.keep_warm: { + "type": Platform.BINARY_SENSOR, + "name": "Keep Warm", + "icon": "mdi:menu", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + EAAttributes.bottom_temperature: { + "type": Platform.SENSOR, + "name": "Bottom Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.keep_warm_time: { + "type": Platform.SENSOR, + "name": "Keep Warm Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.mode: { + "type": Platform.SENSOR, + "name": "Mode", + "icon": "mdi:orbit", + }, + EAAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + EAAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.top_temperature: { + "type": Platform.SENSOR, + "name": "Top Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xEC: { + "name": "Electric Pressure Cooker", + "entities": { + ECAttributes.cooking: { + "type": Platform.BINARY_SENSOR, + "name": "Cooking", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ECAttributes.with_pressure: { + "type": Platform.BINARY_SENSOR, + "name": "With Pressure", + "icon": "mdi:information", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ECAttributes.bottom_temperature: { + "type": Platform.SENSOR, + "name": "Bottom Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.keep_warm_time: { + "type": Platform.SENSOR, + "name": "Keep Warm Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.mode: { + "type": Platform.SENSOR, + "name": "Mode", + "icon": "mdi:orbit", + }, + ECAttributes.progress: { + "type": Platform.SENSOR, + "name": "Progress", + "icon": "mdi:rotate-360", + }, + ECAttributes.time_remaining: { + "type": Platform.SENSOR, + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.top_temperature: { + "type": Platform.SENSOR, + "name": "Top Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xED: { + "name": "Water Drinking Appliance", + "entities": { + EDAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + EDAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + EDAttributes.filter1: { + "type": Platform.SENSOR, + "name": "Filter1 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.filter2: { + "type": Platform.SENSOR, + "name": "Filter2 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.filter3: { + "type": Platform.SENSOR, + "name": "Filter3 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life1: { + "type": Platform.SENSOR, + "name": "Filter1 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life2: { + "type": Platform.SENSOR, + "name": "Filter2 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life3: { + "type": Platform.SENSOR, + "name": "Filter3 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.in_tds: { + "type": Platform.SENSOR, + "name": "In TDS", + "icon": "mdi:water", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.out_tds: { + "type": Platform.SENSOR, + "name": "Out TDS", + "icon": "mdi:water-plus", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.water_consumption: { + "type": Platform.SENSOR, + "name": "Water Consumption", + "icon": "mdi:water-pump", + "unit": UnitOfVolume.LITERS, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + }, + }, + 0xFA: { + "name": "Fan", + "entities": { + "fan": {"type": Platform.FAN, "icon": "mdi:fan", "default": True}, + FAAttributes.oscillation_mode: { + "type": Platform.SELECT, + "name": "Oscillation Mode", + "options": "oscillation_modes", + "icon": "mdi:swap-horizontal-variant", + }, + FAAttributes.oscillation_angle: { + "type": Platform.SELECT, + "name": "Oscillation Angle", + "options": "oscillation_angles", + "icon": "mdi:pan-horizontal", + }, + FAAttributes.tilting_angle: { + "type": Platform.SELECT, + "name": "Tilting Angle", + "options": "tilting_angles", + "icon": "mdi:pan-vertical", + }, + FAAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + FAAttributes.oscillate: { + "type": Platform.SWITCH, + "name": "Oscillate", + "icon": "mdi:swap-horizontal-bold", + }, + FAAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + }, + }, + 0xFB: { + "name": "Electric Heater", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "default": True, + }, + FBAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + FBAttributes.heating_level: { + "type": Platform.NUMBER, + "name": "Heating Level", + "icon": "mdi:fire", + "max": 10, + "min": 1, + "step": 1, + }, + FBAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + FBAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xFC: { + "name": "Air Purifier", + "entities": { + FCAttributes.child_lock: {"type": Platform.LOCK, "name": "Child Lock"}, + FCAttributes.anion: { + "type": Platform.SWITCH, + "name": "Anion", + "icon": "mdi:vanish", + }, + FCAttributes.prompt_tone: { + "type": Platform.SWITCH, + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + FCAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + FCAttributes.standby: { + "type": Platform.SWITCH, + "name": "Standby", + "icon": "mdi:smoke-detector-variant", + }, + FCAttributes.detect_mode: { + "type": Platform.SELECT, + "name": "Detect Mode", + "options": "detect_modes", + "icon": "mdi:smoke-detector-variant", + }, + FCAttributes.mode: { + "type": Platform.SELECT, + "name": "Mode", + "options": "modes", + "icon": "mdi:rotate-360", + }, + FCAttributes.fan_speed: { + "type": Platform.SELECT, + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + FCAttributes.screen_display: { + "type": Platform.SELECT, + "name": "Screen Display", + "options": "screen_displays", + "icon": "mdi:television-ambient-light", + }, + FCAttributes.pm25: { + "type": Platform.SENSOR, + "name": "PM 2.5", + "device_class": SensorDeviceClass.PM25, + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.tvoc: { + "type": Platform.SENSOR, + "name": "TVOC", + "icon": "mdi:heat-wave", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.hcho: { + "type": Platform.SENSOR, + "name": "Methanal", + "icon": "mdi:molecule", + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.filter1_life: { + "type": Platform.SENSOR, + "name": "Filter1 Life Level", + "icon": "mdi:air-filter", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.filter2_life: { + "type": Platform.SENSOR, + "name": "Filter2 Life Level", + "icon": "mdi:air-filter", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xFD: { + "name": "Humidifier", + "entities": { + Platform.HUMIDIFIER: { + "type": Platform.HUMIDIFIER, + "icon": "mdi:air-humidifier", + "default": True, + }, + FDAttributes.disinfect: { + "type": Platform.SWITCH, + "name": "Disinfect", + "icon": "mdi:water-plus-outline", + }, + FDAttributes.prompt_tone: { + "type": Platform.SWITCH, + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + FDAttributes.power: { + "type": Platform.SWITCH, + "name": "Power", + "icon": "mdi:power", + }, + FDAttributes.fan_speed: { + "type": Platform.SELECT, + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + FDAttributes.screen_display: { + "type": Platform.SELECT, + "name": "Screen Display", + "options": "screen_displays", + "icon": "mdi:television-ambient-light", + }, + FDAttributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + FDAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, +} diff --git a/custom_components/midea_ac_lan/water_heater.py b/custom_components/midea_ac_lan/water_heater.py index 20d3959c..db88040e 100644 --- a/custom_components/midea_ac_lan/water_heater.py +++ b/custom_components/midea_ac_lan/water_heater.py @@ -16,11 +16,11 @@ Platform, UnitOfTemperature, ) +from midealocal.devices.c3 import DeviceAttributes as C3Attributes +from midealocal.devices.cd import DeviceAttributes as CDAttributes +from midealocal.devices.e6 import DeviceAttributes as E6Attributes from .const import DEVICES, DOMAIN -from .midea.devices.c3.device import DeviceAttributes as C3Attributes -from .midea.devices.cd.device import DeviceAttributes as CDAttributes -from .midea.devices.e6.device import DeviceAttributes as E6Attributes from .midea_devices import MIDEA_DEVICES from .midea_entity import MideaEntity diff --git a/requirements.txt b/requirements.txt index 73256096..8b6be90e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ homeassistant==2024.5.1 -pycryptodome==3.20.0 \ No newline at end of file +pycryptodome==3.20.0 +midea-local==1.0.3