forked from HomeAssistant/midea-meiju-codec
v0.0.4
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import logging
|
||||
import os
|
||||
import base64
|
||||
from importlib import import_module
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.util.json import load_json
|
||||
try:
|
||||
from homeassistant.helpers.json import save_json
|
||||
except ImportError:
|
||||
from homeassistant.util.json import save_json
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import (
|
||||
Platform,
|
||||
@@ -20,7 +22,7 @@ from homeassistant.const import (
|
||||
CONF_DEVICE,
|
||||
CONF_ENTITIES
|
||||
)
|
||||
from .device_map.device_mapping import DEVICE_MAPPING
|
||||
from .core.logger import MideaLogger
|
||||
from .core.device import MiedaDevice
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
@@ -35,35 +37,56 @@ ALL_PLATFORM = [
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.CLIMATE,
|
||||
Platform.SELECT,
|
||||
Platform.WATER_HEATER,
|
||||
Platform.FAN
|
||||
]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def get_sn8_used(hass: HomeAssistant, sn8):
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
count = 0
|
||||
for entry in entries:
|
||||
if sn8 == entry.data.get("sn8"):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def load_device_config(hass, device_type, sn8):
|
||||
def remove_device_config(hass: HomeAssistant, sn8):
|
||||
config_file = hass.config.path(f"{CONFIG_PATH}/{sn8}.json")
|
||||
try:
|
||||
os.remove(config_file)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def load_device_config(hass: HomeAssistant, device_type, sn8):
|
||||
os.makedirs(hass.config.path(CONFIG_PATH), exist_ok=True)
|
||||
config_file = hass.config.path(f"{CONFIG_PATH}/{sn8}.json")
|
||||
json_data = load_json(config_file, default={})
|
||||
d_type = "0x%02X" % device_type
|
||||
if len(json_data) >0:
|
||||
if len(json_data) > 0:
|
||||
json_data = json_data.get(sn8)
|
||||
elif d_type in DEVICE_MAPPING:
|
||||
if sn8 in DEVICE_MAPPING[d_type]:
|
||||
json_data = DEVICE_MAPPING[d_type][sn8]
|
||||
save_data = {sn8: json_data}
|
||||
save_json(config_file, save_data)
|
||||
elif "default" in DEVICE_MAPPING[d_type]:
|
||||
json_data = DEVICE_MAPPING[d_type]["default"]
|
||||
save_data = {sn8: json_data}
|
||||
save_json(config_file, save_data)
|
||||
else:
|
||||
device_path = f".device_mapping.{'T0x%02X' % device_type}"
|
||||
try:
|
||||
mapping_module = import_module(device_path, __package__)
|
||||
if sn8 in mapping_module.DEVICE_MAPPING.keys():
|
||||
json_data = mapping_module.DEVICE_MAPPING[sn8]
|
||||
elif "default" in mapping_module.DEVICE_MAPPING:
|
||||
json_data = mapping_module.DEVICE_MAPPING["default"]
|
||||
if len(json_data) > 0:
|
||||
save_data = {sn8: json_data}
|
||||
save_json(config_file, save_data)
|
||||
except ModuleNotFoundError:
|
||||
MideaLogger.warning(f"Can't load mapping file for type {'T0x%02X' % device_type}")
|
||||
return json_data
|
||||
|
||||
|
||||
async def update_listener(hass, config_entry):
|
||||
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, hass_config: dict):
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType):
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
cjson = os.getcwd() + "/cjson.lua"
|
||||
bit = os.getcwd() + "/bit.lua"
|
||||
@@ -80,7 +103,7 @@ async def async_setup(hass: HomeAssistant, hass_config: dict):
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry):
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
device_type = config_entry.data.get(CONF_TYPE)
|
||||
if device_type == CONF_ACCOUNT:
|
||||
return True
|
||||
@@ -100,7 +123,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry):
|
||||
sn8 = config_entry.data.get("sn8")
|
||||
lua_file = config_entry.data.get("lua_file")
|
||||
if protocol == 3 and (key is None or key is None):
|
||||
_LOGGER.error("For V3 devices, the key and the token is required.")
|
||||
MideaLogger.error("For V3 devices, the key and the token is required.")
|
||||
return False
|
||||
device = MiedaDevice(
|
||||
name=name,
|
||||
@@ -144,15 +167,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry):
|
||||
return False
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry):
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
device_id = config_entry.data.get(CONF_DEVICE_ID)
|
||||
lua_file = config_entry.data.get("lua_file")
|
||||
os.remove(lua_file)
|
||||
if device_id is not None:
|
||||
device = hass.data[DOMAIN][DEVICES][device_id][CONF_DEVICE]
|
||||
if device is not None:
|
||||
config_file = hass.config.path(f"{CONFIG_PATH}/{device.sn8}.json")
|
||||
os.remove(config_file)
|
||||
if get_sn8_used(hass, device.sn8) == 1:
|
||||
lua_file = config_entry.data.get("lua_file")
|
||||
os.remove(lua_file)
|
||||
remove_device_config(hass, device.sn8)
|
||||
device.close()
|
||||
hass.data[DOMAIN][DEVICES].pop(device_id)
|
||||
for platform in ALL_PLATFORM:
|
||||
|
@@ -8,6 +8,7 @@ from homeassistant.const import (
|
||||
CONF_DEVICE,
|
||||
CONF_ENTITIES
|
||||
)
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DEVICES
|
||||
@@ -19,11 +20,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
device_id = config_entry.data.get(CONF_DEVICE_ID)
|
||||
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
|
||||
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
|
||||
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
|
||||
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.BINARY_SENSOR)
|
||||
devs = [MideaDeviceStatusSensorEntity(device, manufacturer,"Status", {})]
|
||||
devs = [MideaDeviceStatusSensorEntity(device, manufacturer, rationale,"Status", {})]
|
||||
if entities is not None:
|
||||
for entity_key, config in entities.items():
|
||||
devs.append(MideaBinarySensorEntity(device, manufacturer, entity_key, config))
|
||||
devs.append(MideaBinarySensorEntity(device, manufacturer, rationale, entity_key, config))
|
||||
async_add_entities(devs)
|
||||
|
||||
|
||||
|
@@ -1,15 +1,21 @@
|
||||
from homeassistant.components.climate import *
|
||||
from homeassistant.components.climate import (
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
ATTR_HVAC_MODE,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
Platform,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_ENTITIES,
|
||||
CONF_DEVICE,
|
||||
ATTR_TEMPERATURE
|
||||
)
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DEVICES
|
||||
)
|
||||
from .core.logger import MideaLogger
|
||||
from .midea_entities import MideaEntity, Rationale
|
||||
|
||||
|
||||
@@ -17,34 +23,31 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
device_id = config_entry.data.get(CONF_DEVICE_ID)
|
||||
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
|
||||
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
|
||||
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
|
||||
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.CLIMATE)
|
||||
devs = []
|
||||
if entities is not None:
|
||||
for entity_key, config in entities.items():
|
||||
devs.append(MideaClimateEntity(device, manufacturer, entity_key, config))
|
||||
devs.append(MideaClimateEntity(device, manufacturer, rationale, entity_key, config))
|
||||
async_add_entities(devs)
|
||||
|
||||
|
||||
class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
def __init__(self, device, manufacturer, entity_key, config):
|
||||
super().__init__(device, manufacturer, entity_key, config)
|
||||
def __init__(self, device, manufacturer, rationale, entity_key, config):
|
||||
super().__init__(device, manufacturer, rationale, entity_key, config)
|
||||
self._key_power = self._config.get("power")
|
||||
self._key_hvac_modes = self._config.get("hvac_modes")
|
||||
self._key_preset_modes = self._config.get("preset_modes")
|
||||
self._key_aux_heat = self._config.get("aux_heat")
|
||||
self._key_swing_modes = self._config.get("swing_modes")
|
||||
self._key_fan_modes = self._config.get("fan_modes")
|
||||
self._key_current_temperature_low = self._config.get("current_temperature_low")
|
||||
self._key_min_temp = self._config.get("min_temp")
|
||||
self._key_max_temp = self._config.get("max_temp")
|
||||
self._key_current_temperature = self._config.get("current_temperature")
|
||||
self._key_target_temperature = self._config.get("target_temperature")
|
||||
self._attr_temperature_unit = self._config.get("temperature_unit")
|
||||
self._attr_precision = self._config.get("precision")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self.hvac_mode
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
@@ -62,7 +65,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
return self._device.get_attribute("indoor_temperature")
|
||||
return self._device.get_attribute(self._key_current_temperature)
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
@@ -103,7 +106,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
return self.get_mode(self._key_preset_modes)
|
||||
return self._dict_get_selected(self._key_preset_modes)
|
||||
|
||||
@property
|
||||
def fan_modes(self):
|
||||
@@ -111,7 +114,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def fan_mode(self):
|
||||
return self.get_mode(self._key_fan_modes, Rationale.LESS)
|
||||
return self._dict_get_selected(self._key_fan_modes, Rationale.LESS)
|
||||
|
||||
@property
|
||||
def swing_modes(self):
|
||||
@@ -119,7 +122,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def swing_mode(self):
|
||||
return self.get_mode(self._key_swing_modes)
|
||||
return self._dict_get_selected(self._key_swing_modes)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
@@ -127,7 +130,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
return self.get_mode(self._key_hvac_modes)
|
||||
return self._dict_get_selected(self._key_hvac_modes)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
@@ -135,13 +138,13 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
|
||||
@property
|
||||
def is_aux_heat(self):
|
||||
return self._device.get_attribute(self._key_aux_heat) == "on"
|
||||
return self._get_status_on_off(self._key_aux_heat)
|
||||
|
||||
def turn_on(self):
|
||||
self._device.set_attribute(attribute=self._key_power, value="on")
|
||||
self._set_status_on_off(self._key_power, True)
|
||||
|
||||
def turn_off(self):
|
||||
self._device.set_attribute(attribute=self._key_power, value="off")
|
||||
self._set_status_on_off(self._key_power, False)
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
if ATTR_TEMPERATURE not in kwargs:
|
||||
@@ -159,16 +162,15 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
new_status[self._key_target_temperature[1]] = temp_dec
|
||||
else:
|
||||
new_status[self._key_target_temperature] = temperature
|
||||
MideaLogger.error(new_status)
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def set_fan_mode(self, fan_mode: str):
|
||||
new_statis = self._key_fan_modes.get(fan_mode)
|
||||
self._device.set_attributes(new_statis)
|
||||
new_status = self._key_fan_modes.get(fan_mode)
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def set_preset_mode(self, preset_mode: str):
|
||||
new_statis = self._key_preset_modes.get(preset_mode)
|
||||
self._device.set_attributes(new_statis)
|
||||
new_status = self._key_preset_modes.get(preset_mode)
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def set_hvac_mode(self, hvac_mode: str):
|
||||
new_status = self._key_hvac_modes.get(hvac_mode)
|
||||
@@ -179,10 +181,10 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def turn_aux_heat_on(self) -> None:
|
||||
self._device.set_attribute(attr=self._key_aux_heat, value="on")
|
||||
self._set_status_on_off(self._key_aux_heat, True)
|
||||
|
||||
def turn_aux_heat_off(self) -> None:
|
||||
self._device.set_attribute(attr=self._key_aux_heat, value="off")
|
||||
self._set_status_on_off(self._key_aux_heat, False)
|
||||
|
||||
def update_state(self, status):
|
||||
try:
|
||||
|
@@ -122,15 +122,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
device_list = {}
|
||||
for device in devices:
|
||||
if not self._device_configured(int(device.get("applianceCode"))):
|
||||
try:
|
||||
subtype = int(device.get("modelNumber")) if device.get("modelNumber") is not None else 0
|
||||
except ValueError:
|
||||
subtype = 0
|
||||
self._device_list[int(device.get("applianceCode"))] = {
|
||||
"device_id": int(device.get("applianceCode")),
|
||||
"name": device.get("name"),
|
||||
"type": int(device.get("type"), 16),
|
||||
"sn8": device.get("sn8"),
|
||||
"sn8": device.get("sn8", "00000000"),
|
||||
"sn": device.get("sn"),
|
||||
"model": device.get("productModel"),
|
||||
"subtype": int(device.get("modelNumber")) if device.get("modelNumber") is not None else 0,
|
||||
"enterprise_code": device.get("enterpriseCode"),
|
||||
"model": device.get("productModel", "0"),
|
||||
"subtype": subtype,
|
||||
"enterprise_code": device.get("enterpriseCode","0000"),
|
||||
"online": device.get("onlineStatus") == "1"
|
||||
}
|
||||
device_list[int(device.get("applianceCode"))] = \
|
||||
|
@@ -35,7 +35,7 @@ class MideaCloudBase:
|
||||
self.security = security
|
||||
self.server = server
|
||||
|
||||
async def api_request(self, endpoint, args=None, data=None):
|
||||
async def api_request(self, endpoint, args=None, data=None) -> dict | None:
|
||||
args = args or {}
|
||||
headers = {}
|
||||
if data is None:
|
||||
@@ -127,7 +127,7 @@ class MideaCloudBase:
|
||||
udpid = CloudSecurity.get_udpid(device_id.to_bytes(6, "big"))
|
||||
else:
|
||||
udpid = CloudSecurity.get_udpid(device_id.to_bytes(6, "little"))
|
||||
_LOGGER.error(f"The udpid of deivce [{device_id}] generated "
|
||||
_LOGGER.debug(f"The udpid of deivce [{device_id}] generated "
|
||||
f"with byte order '{'big' if byte_order_big else 'little'}': {udpid}")
|
||||
response = await self.api_request(
|
||||
"/v1/iot/secure/getToken",
|
||||
@@ -171,11 +171,12 @@ class MeijuCloudExtend(MideaCloudBase):
|
||||
response = await self.api_request("/v1/appliance/home/list/get", args={
|
||||
'homegroupId': home
|
||||
})
|
||||
for h in response.get("homeList") or []:
|
||||
for r in h.get("roomList") or []:
|
||||
for a in r.get("applianceList"):
|
||||
a["sn"] = CloudSecurity.decrypt(bytes.fromhex(a["sn"]), self.key).decode()
|
||||
devices.append(a)
|
||||
if response:
|
||||
for h in response.get("homeList") or []:
|
||||
for r in h.get("roomList") or []:
|
||||
for a in r.get("applianceList"):
|
||||
a["sn"] = CloudSecurity.decrypt(bytes.fromhex(a["sn"]), self.key).decode()
|
||||
devices.append(a)
|
||||
return devices
|
||||
|
||||
async def get_lua(self, sn, device_type, path, enterprise_code=None):
|
||||
@@ -183,7 +184,7 @@ class MeijuCloudExtend(MideaCloudBase):
|
||||
"/v1/appliance/protocol/lua/luaGet",
|
||||
data={
|
||||
"applianceSn": sn,
|
||||
"applianceType": f"0x{'%02X' % device_type}",
|
||||
"applianceType": "0x%02X" % device_type,
|
||||
"applianceMFCode": enterprise_code if enterprise_code else "0000",
|
||||
'version': "0",
|
||||
"iotAppId": "900",
|
||||
|
@@ -60,6 +60,7 @@ class MiedaDevice(threading.Thread):
|
||||
self._sn = sn
|
||||
self._sn8 = sn8
|
||||
self._attributes = {
|
||||
"device_type": "T0x%02X" % device_type,
|
||||
"sn": sn,
|
||||
"sn8": sn8,
|
||||
"subtype": subtype
|
||||
@@ -135,8 +136,8 @@ class MiedaDevice(threading.Thread):
|
||||
for attr in self._centralized:
|
||||
new_status[attr] = self._attributes.get(attr)
|
||||
new_status[attribute] = value
|
||||
set_cmd = self._lua_runtime.build_control(new_status)
|
||||
self.build_send(set_cmd)
|
||||
if set_cmd := self._lua_runtime.build_control(new_status):
|
||||
self.build_send(set_cmd)
|
||||
|
||||
def set_attributes(self, attributes):
|
||||
new_status = {}
|
||||
@@ -148,8 +149,8 @@ class MiedaDevice(threading.Thread):
|
||||
has_new = True
|
||||
new_status[attribute] = value
|
||||
if has_new:
|
||||
set_cmd = self._lua_runtime.build_control(new_status)
|
||||
self.build_send(set_cmd)
|
||||
if set_cmd := self._lua_runtime.build_control(new_status):
|
||||
self.build_send(set_cmd)
|
||||
|
||||
@staticmethod
|
||||
def fetch_v2_message(msg):
|
||||
@@ -231,8 +232,8 @@ class MiedaDevice(threading.Thread):
|
||||
|
||||
def refresh_status(self):
|
||||
for query in self._queries:
|
||||
query_cmd = self._lua_runtime.build_query(query)
|
||||
self.build_send(query_cmd)
|
||||
if query_cmd := self._lua_runtime.build_query(query):
|
||||
self.build_send(query_cmd)
|
||||
|
||||
def parse_message(self, msg):
|
||||
if self._protocol == 3:
|
||||
@@ -255,16 +256,16 @@ class MiedaDevice(threading.Thread):
|
||||
decrypted = self._security.aes_decrypt(cryptographic)
|
||||
MideaLogger.debug(f"Received: {decrypted.hex()}")
|
||||
# 这就是最终消息
|
||||
status = self._lua_runtime.decode_status(decrypted.hex())
|
||||
MideaLogger.debug(f"Decoded: {status}")
|
||||
new_status = {}
|
||||
for single in status.keys():
|
||||
value = status.get(single)
|
||||
if single not in self._attributes or self._attributes[single] != value:
|
||||
self._attributes[single] = value
|
||||
new_status[single] = value
|
||||
if len(new_status) > 0:
|
||||
self.update_all(new_status)
|
||||
if status := self._lua_runtime.decode_status(decrypted.hex()):
|
||||
MideaLogger.debug(f"Decoded: {status}")
|
||||
new_status = {}
|
||||
for single in status.keys():
|
||||
value = status.get(single)
|
||||
if single not in self._attributes or self._attributes[single] != value:
|
||||
self._attributes[single] = value
|
||||
new_status[single] = value
|
||||
if len(new_status) > 0:
|
||||
self.update_all(new_status)
|
||||
return ParseMessageResult.SUCCESS
|
||||
|
||||
def send_heartbeat(self):
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import lupa
|
||||
import threading
|
||||
import json
|
||||
|
||||
from .logger import MideaLogger
|
||||
lupa.LuaMemoryError
|
||||
|
||||
class LuaRuntime:
|
||||
def __init__(self, file):
|
||||
@@ -44,22 +45,34 @@ class MideaCodec(LuaRuntime):
|
||||
query_dict = self._build_base_dict()
|
||||
query_dict["query"] = {} if append is None else append
|
||||
json_str = json.dumps(query_dict)
|
||||
result = self.json_to_data(json_str)
|
||||
return result
|
||||
try:
|
||||
result = self.json_to_data(json_str)
|
||||
return result
|
||||
except lupa.LuaError as e:
|
||||
MideaLogger.error(f"LuaRuntimeError in build_query {json_str}: {repr(e)}")
|
||||
return None
|
||||
|
||||
def build_control(self, append=None):
|
||||
query_dict = self._build_base_dict()
|
||||
query_dict["control"] = {} if append is None else append
|
||||
json_str = json.dumps(query_dict)
|
||||
result = self.json_to_data(json_str)
|
||||
return result
|
||||
try:
|
||||
result = self.json_to_data(json_str)
|
||||
return result
|
||||
except lupa.LuaError as e:
|
||||
MideaLogger.error(f"LuaRuntimeError in build_control {json_str}: {repr(e)}")
|
||||
return None
|
||||
|
||||
def build_status(self, append=None):
|
||||
query_dict = self._build_base_dict()
|
||||
query_dict["status"] = {} if append is None else append
|
||||
json_str = json.dumps(query_dict)
|
||||
result = self.json_to_data(json_str)
|
||||
return result
|
||||
try:
|
||||
result = self.json_to_data(json_str)
|
||||
return result
|
||||
except lupa.LuaError as e:
|
||||
MideaLogger.error(f"LuaRuntimeError in build_status {json_str}: {repr(e)}")
|
||||
return None
|
||||
|
||||
def decode_status(self, data: str):
|
||||
data_dict = self._build_base_dict()
|
||||
@@ -67,6 +80,11 @@ class MideaCodec(LuaRuntime):
|
||||
"data": data
|
||||
}
|
||||
json_str = json.dumps(data_dict)
|
||||
result = self.data_to_json(json_str)
|
||||
status = json.loads(result)
|
||||
return status.get("status")
|
||||
try:
|
||||
result = self.data_to_json(json_str)
|
||||
status = json.loads(result)
|
||||
return status.get("status")
|
||||
except lupa.LuaError as e:
|
||||
MideaLogger.error(f"LuaRuntimeError in decode_status {data}: {repr(e)}")
|
||||
return None
|
||||
|
||||
|
@@ -1,106 +0,0 @@
|
||||
from homeassistant.const import *
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
from homeassistant.components.climate import (
|
||||
HVACMode,
|
||||
PRESET_NONE,
|
||||
PRESET_ECO,
|
||||
PRESET_COMFORT,
|
||||
PRESET_SLEEP,
|
||||
PRESET_BOOST,
|
||||
SWING_OFF,
|
||||
SWING_BOTH,
|
||||
SWING_VERTICAL,
|
||||
SWING_HORIZONTAL,
|
||||
FAN_AUTO,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
FAN_HIGH,
|
||||
)
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"0xAC": {
|
||||
"default": {
|
||||
"manufacturer": "美的",
|
||||
"queries": [{}, {"query_type": "prevent_straight_wind"}],
|
||||
"centralized": ["power", "temperature", "small_temperature", "mode", "eco", "comfort_power_save",
|
||||
"comfort_sleep", "strong_wind", "wind_swing_lr", "wind_swing_lr", "wind_speed",
|
||||
"ptc", "dry"],
|
||||
"entities": {
|
||||
Platform.CLIMATE: {
|
||||
"thermostat": {
|
||||
"name": "Thermostat",
|
||||
"power": "power",
|
||||
"target_temperature": ["temperature", "small_temperature"],
|
||||
"hvac_modes": {
|
||||
HVACMode.OFF: {"power": "off"},
|
||||
HVACMode.HEAT: {"power": "on", "mode": "heat"},
|
||||
HVACMode.COOL: {"power": "on", "mode": "cool"},
|
||||
HVACMode.AUTO: {"power": "on", "mode": "auto"},
|
||||
HVACMode.DRY: {"power": "on", "mode": "dry"},
|
||||
HVACMode.FAN_ONLY: {"power": "on", "mode": "fan"}
|
||||
},
|
||||
"preset_modes": {
|
||||
PRESET_NONE: {
|
||||
"eco": "off",
|
||||
"comfort_power_save": "off",
|
||||
"comfort_sleep": "off",
|
||||
"strong_wind": "off"
|
||||
},
|
||||
PRESET_ECO: {"eco": "on"},
|
||||
PRESET_COMFORT: {"comfort_power_save": "on"},
|
||||
PRESET_SLEEP: {"comfort_sleep": "on"},
|
||||
PRESET_BOOST: {"strong_wind": "on"}
|
||||
},
|
||||
"swing_modes": {
|
||||
SWING_OFF: {"wind_swing_lr": "off", "wind_swing_ud": "off"},
|
||||
SWING_BOTH: {"wind_swing_lr": "on", "wind_swing_ud": "on"},
|
||||
SWING_HORIZONTAL: {"wind_swing_lr": "on", "wind_swing_ud": "off"},
|
||||
SWING_VERTICAL: {"wind_swing_lr": "off", "wind_swing_ud": "on"},
|
||||
},
|
||||
"fan_modes": {
|
||||
"silent": {"wind_speed": 20},
|
||||
FAN_LOW: {"wind_speed": 40},
|
||||
FAN_MEDIUM: {"wind_speed": 60},
|
||||
FAN_HIGH: {"wind_speed": 80},
|
||||
"full": {"wind_speed": 100},
|
||||
FAN_AUTO: {"wind_speed": 102}
|
||||
},
|
||||
"current_temperature": "indoor_temperature",
|
||||
"aux_heat": "ptc",
|
||||
"min_temp": 17,
|
||||
"max_temp": 30,
|
||||
"temperature_unit": TEMP_CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"dry": {
|
||||
"name": "Dry",
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"prevent_straight_wind": {
|
||||
"binary_rationale": [1, 2]
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"indoor_temperature": {
|
||||
"name": "室内温度",
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit": TEMP_CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"outdoor_temperature": {
|
||||
"name": "室外机温度",
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit": TEMP_CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
},
|
||||
Platform.BINARY_SENSOR: {
|
||||
"power": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
152
custom_components/midea_meiju_codec/device_mapping/T0xAC.py
Normal file
152
custom_components/midea_meiju_codec/device_mapping/T0xAC.py
Normal file
@@ -0,0 +1,152 @@
|
||||
from homeassistant.const import *
|
||||
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
|
||||
from homeassistant.components.switch import SwitchDeviceClass
|
||||
|
||||
DEVICE_MAPPING = {
|
||||
"default": {
|
||||
"manufacturer": "美的",
|
||||
"rationale": ["off", "on"],
|
||||
"queries": [{}, {"query_type": "prevent_straight_wind"}],
|
||||
"centralized": ["power", "temperature", "small_temperature", "mode", "eco", "comfort_power_save",
|
||||
"comfort_sleep", "strong_wind", "wind_swing_lr", "wind_swing_lr", "wind_speed",
|
||||
"ptc", "dry"],
|
||||
"entities": {
|
||||
Platform.CLIMATE: {
|
||||
"thermostat": {
|
||||
"name": "Thermostat",
|
||||
"power": "power",
|
||||
"hvac_modes": {
|
||||
"off": {"power": "off"},
|
||||
"heat": {"power": "on", "mode": "heat"},
|
||||
"cool": {"power": "on", "mode": "cool"},
|
||||
"auto": {"power": "on", "mode": "auto"},
|
||||
"dry": {"power": "on", "mode": "dry"},
|
||||
"fan_only": {"power": "on", "mode": "fan"}
|
||||
},
|
||||
"preset_modes": {
|
||||
"none": {
|
||||
"eco": "off",
|
||||
"comfort_power_save": "off",
|
||||
"comfort_sleep": "off",
|
||||
"strong_wind": "off"
|
||||
},
|
||||
"eco": {"eco": "on"},
|
||||
"comfort": {"comfort_power_save": "on"},
|
||||
"sleep": {"comfort_sleep": "on"},
|
||||
"boost": {"strong_wind": "on"}
|
||||
},
|
||||
"swing_modes": {
|
||||
"off": {"wind_swing_lr": "off", "wind_swing_ud": "off"},
|
||||
"both": {"wind_swing_lr": "on", "wind_swing_ud": "on"},
|
||||
"horizontal": {"wind_swing_lr": "on", "wind_swing_ud": "off"},
|
||||
"vertical": {"wind_swing_lr": "off", "wind_swing_ud": "on"},
|
||||
},
|
||||
"fan_modes": {
|
||||
"silent": {"wind_speed": 20},
|
||||
"low": {"wind_speed": 40},
|
||||
"medium": {"wind_speed": 60},
|
||||
"high": {"wind_speed": 80},
|
||||
"full": {"wind_speed": 100},
|
||||
"auto": {"wind_speed": 102}
|
||||
},
|
||||
"target_temperature": ["temperature", "small_temperature"],
|
||||
"current_temperature": "indoor_temperature",
|
||||
"aux_heat": "ptc",
|
||||
"min_temp": 17,
|
||||
"max_temp": 30,
|
||||
"temperature_unit": TEMP_CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.SWITCH: {
|
||||
"dry": {
|
||||
"name": "Dry",
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
},
|
||||
"prevent_straight_wind": {
|
||||
"name": "防直吹",
|
||||
"device_class": SwitchDeviceClass.SWITCH,
|
||||
"rationale": [1, 2]
|
||||
}
|
||||
},
|
||||
Platform.SENSOR: {
|
||||
"indoor_temperature": {
|
||||
"name": "室内温度",
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
"outdoor_temperature": {
|
||||
"name": "室外机温度",
|
||||
"device_class": SensorDeviceClass.TEMPERATURE,
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
"state_class": SensorStateClass.MEASUREMENT
|
||||
},
|
||||
},
|
||||
Platform.BINARY_SENSOR: {
|
||||
"power": {}
|
||||
},
|
||||
Platform.SELECT: {
|
||||
"preset_modes": {
|
||||
"options": {
|
||||
"none": {
|
||||
"eco": "off",
|
||||
"comfort_power_save": "off",
|
||||
"comfort_sleep": "off",
|
||||
"strong_wind": "off"
|
||||
},
|
||||
"eco": {"eco": "on"},
|
||||
"comfort": {"comfort_power_save": "on"},
|
||||
"sleep": {"comfort_sleep": "on"},
|
||||
"boost": {"strong_wind": "on"}
|
||||
}
|
||||
},
|
||||
"hvac_modes": {
|
||||
"options": {
|
||||
"off": {"power": "off"},
|
||||
"heat": {"power": "on", "mode": "heat"},
|
||||
"cool": {"power": "on", "mode": "cool"},
|
||||
"auto": {"power": "on", "mode": "auto"},
|
||||
"dry": {"power": "on", "mode": "dry"},
|
||||
"fan_only": {"power": "on", "mode": "fan"}
|
||||
}
|
||||
}
|
||||
},
|
||||
Platform.WATER_HEATER:{
|
||||
"water_heater": {
|
||||
"name": "热水器",
|
||||
"power": "power",
|
||||
"operation_list": {
|
||||
"off": {"power": "off"},
|
||||
"heat": {"power": "on", "mode": "heat"},
|
||||
"cool": {"power": "on", "mode": "cool"},
|
||||
"auto": {"power": "on", "mode": "auto"},
|
||||
"dry": {"power": "on", "mode": "dry"},
|
||||
"fan_only": {"power": "on", "mode": "fan"}
|
||||
},
|
||||
"target_temperature": ["temperature", "small_temperature"],
|
||||
"current_temperature": "indoor_temperature",
|
||||
"min_temp": 17,
|
||||
"max_temp": 30,
|
||||
"temperature_unit": TEMP_CELSIUS,
|
||||
"precision": PRECISION_HALVES,
|
||||
}
|
||||
},
|
||||
Platform.FAN: {
|
||||
"fan": {
|
||||
"power": "power",
|
||||
"preset_modes": {
|
||||
"off": {"power": "off"},
|
||||
"heat": {"power": "on", "mode": "heat"},
|
||||
"cool": {"power": "on", "mode": "cool"},
|
||||
"auto": {"power": "on", "mode": "auto"},
|
||||
"dry": {"power": "on", "mode": "dry"},
|
||||
"fan_only": {"power": "on", "mode": "fan"}
|
||||
},
|
||||
"oscillate": "wind_swing_lr",
|
||||
"speeds": list({"wind_speed": value + 1} for value in range(0, 100)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
custom_components/midea_meiju_codec/fan.py
Normal file
112
custom_components/midea_meiju_codec/fan.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.const import (
|
||||
Platform,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DEVICE,
|
||||
CONF_ENTITIES,
|
||||
)
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DEVICES
|
||||
)
|
||||
from .midea_entities import MideaEntity
|
||||
from .core.logger import MideaLogger
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
device_id = config_entry.data.get(CONF_DEVICE_ID)
|
||||
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
|
||||
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
|
||||
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
|
||||
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.FAN)
|
||||
devs = []
|
||||
if entities is not None:
|
||||
for entity_key, config in entities.items():
|
||||
devs.append(MideaFanEntity(device, manufacturer, rationale, entity_key, config))
|
||||
async_add_entities(devs)
|
||||
|
||||
|
||||
class MideaFanEntity(MideaEntity, FanEntity):
|
||||
def __init__(self, device, manufacturer, rationale, entity_key, config):
|
||||
super().__init__(device, manufacturer, rationale, entity_key, config)
|
||||
self._key_power = self._config.get("power")
|
||||
self._key_preset_modes = self._config.get("preset_modes")
|
||||
self._key_speeds = self._config.get("speeds")
|
||||
self._key_oscillate = self._config.get("oscillate")
|
||||
self._key_directions = self._config.get("directions")
|
||||
self._attr_speed_count = len(self._key_speeds) if self._key_speeds else 0
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
if self._key_preset_modes is not None and len(self._key_preset_modes) > 0:
|
||||
features |= FanEntityFeature.PRESET_MODE
|
||||
if self._key_speeds is not None and len(self._key_speeds) > 0:
|
||||
features |= FanEntityFeature.SET_SPEED
|
||||
if self._key_oscillate is not None:
|
||||
features |= FanEntityFeature.OSCILLATE
|
||||
if self._key_directions is not None and len(self._key_directions) > 0:
|
||||
features |= FanEntityFeature.DIRECTION
|
||||
return features
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
return self._get_status_on_off(self._key_power)
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
return list(self._key_preset_modes.keys())
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
return self._dict_get_selected(self._key_preset_modes)
|
||||
|
||||
@property
|
||||
def percentage(self):
|
||||
index = self._list_get_selected(self._key_speeds)
|
||||
if index is None:
|
||||
return None
|
||||
return round((index + 1) * 100 / self._attr_speed_count)
|
||||
|
||||
@property
|
||||
def oscillating(self):
|
||||
return self._get_status_on_off(self._key_oscillate)
|
||||
|
||||
def turn_on(
|
||||
self,
|
||||
percentage: int | None = None,
|
||||
preset_mode: str | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
if preset_mode is not None:
|
||||
new_status = self._key_preset_modes.get(preset_mode)
|
||||
else:
|
||||
new_status = {}
|
||||
if percentage is not None:
|
||||
index = round(percentage * self._attr_speed_count / 100) - 1
|
||||
new_status.update(self._key_speeds[index])
|
||||
new_status[self._key_power] = self._rationale[1]
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def turn_off(self):
|
||||
self._set_status_on_off(self._key_power, False)
|
||||
|
||||
def set_percentage(self, percentage: int):
|
||||
index = round(percentage * self._attr_speed_count / 100)
|
||||
if 0 < index < len(self._key_speeds):
|
||||
new_status = self._key_speeds[index - 1]
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def set_preset_mode(self, preset_mode: str):
|
||||
new_status = self._key_preset_modes.get(preset_mode)
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def oscillate(self, oscillating: bool):
|
||||
if self.oscillating != oscillating:
|
||||
self._set_status_on_off(self._key_oscillate, oscillating)
|
||||
|
||||
def update_state(self, status):
|
||||
try:
|
||||
self.schedule_update_ha_state()
|
||||
except Exception as e:
|
||||
pass
|
@@ -13,20 +13,25 @@ class Rationale(IntEnum):
|
||||
GREATER = 1
|
||||
LESS = 2
|
||||
|
||||
|
||||
class MideaEntity(Entity):
|
||||
def __init__(self, device, manufacturer: str | None, entity_key: str, config: dict):
|
||||
def __init__(self, device, manufacturer: str | None, rationale: list | None, entity_key: str, config: dict):
|
||||
self._device = device
|
||||
self._device.register_update(self.update_state)
|
||||
self._entity_key = entity_key
|
||||
self._config = config
|
||||
self._device_name = self._device.device_name
|
||||
self._attr_native_unit_of_measurement = self._config.get("unit")
|
||||
self._rationale = rationale
|
||||
rationale = config.get("rationale")
|
||||
if rationale:
|
||||
self._rationale = rationale
|
||||
if self._rationale is None:
|
||||
self._rationale = ["off", "on"]
|
||||
self._attr_native_unit_of_measurement = self._config.get("unit_of_measurement")
|
||||
self._attr_device_class = self._config.get("device_class")
|
||||
self._attr_state_class = self._config.get("state_class")
|
||||
self._attr_unit_of_measurement = self._config.get("unit")
|
||||
self._attr_icon = self._config.get("icon")
|
||||
self._attr_unique_id = f"{DOMAIN}.{self._device.device_id}_{self._entity_key}"
|
||||
MideaLogger.debug(self._attr_unique_id)
|
||||
self._attr_device_info = {
|
||||
"manufacturer": "Midea" if manufacturer is None else manufacturer,
|
||||
"model": f"{self._device.model}",
|
||||
@@ -43,16 +48,47 @@ class MideaEntity(Entity):
|
||||
def should_poll(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return self._device.connected
|
||||
|
||||
def get_mode(self, key_of_modes, rationale: Rationale = Rationale.EQUALLY):
|
||||
for mode, status in key_of_modes.items():
|
||||
def _get_status_on_off(self, status_key: str):
|
||||
result = False
|
||||
status = self._device.get_attribute(status_key)
|
||||
if status is not None:
|
||||
try:
|
||||
result = bool(self._rationale.index(status))
|
||||
except ValueError:
|
||||
MideaLogger.error(f"The value of attribute {status_key} ('{status}') "
|
||||
f"is not in rationale {self._rationale}")
|
||||
return result
|
||||
|
||||
def _set_status_on_off(self, status_key: str, turn_on: bool):
|
||||
self._device.set_attribute(status_key, self._rationale[int(turn_on)])
|
||||
|
||||
def _list_get_selected(self, key_of_list: list, rationale: Rationale = Rationale.EQUALLY):
|
||||
for index in range(0, len(key_of_list)):
|
||||
match = True
|
||||
for attr, value in key_of_list[index].items():
|
||||
state_value = self._device.get_attribute(attr)
|
||||
if state_value is None:
|
||||
match = False
|
||||
break
|
||||
if rationale is Rationale.EQUALLY and state_value != value:
|
||||
match = False
|
||||
break
|
||||
if rationale is Rationale.GREATER and state_value < value:
|
||||
match = False
|
||||
break
|
||||
if rationale is Rationale.LESS and state_value > value:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
return index
|
||||
return None
|
||||
|
||||
def _dict_get_selected(self, key_of_dict: dict, rationale: Rationale = Rationale.EQUALLY):
|
||||
for mode, status in key_of_dict.items():
|
||||
match = True
|
||||
for attr, value in status.items():
|
||||
state_value = self._device.get_attribute(attr)
|
||||
@@ -74,7 +110,6 @@ class MideaEntity(Entity):
|
||||
|
||||
def update_state(self, status):
|
||||
if self._entity_key in status or "connected" in status:
|
||||
|
||||
try:
|
||||
self.schedule_update_ha_state()
|
||||
except Exception as e:
|
||||
@@ -82,10 +117,6 @@ class MideaEntity(Entity):
|
||||
|
||||
|
||||
class MideaBinaryBaseEntity(MideaEntity):
|
||||
def __init__(self, device, manufacturer: str | None, entity_key: str, config: dict):
|
||||
super().__init__(device, manufacturer, entity_key, config)
|
||||
binary_rationale = config.get("binary_rationale")
|
||||
self._binary_rationale = binary_rationale if binary_rationale is not None else ["off", "on"]
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@@ -93,4 +124,4 @@ class MideaBinaryBaseEntity(MideaEntity):
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
return self._device.get_attribute(self._entity_key) == self._binary_rationale[1]
|
||||
return self._get_status_on_off(self._entity_key)
|
50
custom_components/midea_meiju_codec/select.py
Normal file
50
custom_components/midea_meiju_codec/select.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.const import (
|
||||
Platform,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DEVICE,
|
||||
CONF_ENTITIES,
|
||||
)
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DEVICES
|
||||
)
|
||||
from .midea_entities import MideaEntity
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
device_id = config_entry.data.get(CONF_DEVICE_ID)
|
||||
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
|
||||
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
|
||||
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
|
||||
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.SELECT)
|
||||
devs = []
|
||||
if entities is not None:
|
||||
for entity_key, config in entities.items():
|
||||
devs.append(MideaSelectEntity(device, manufacturer, rationale, entity_key, config))
|
||||
async_add_entities(devs)
|
||||
|
||||
|
||||
class MideaSelectEntity(MideaEntity, SelectEntity):
|
||||
def __init__(self, device, manufacturer, rationale, entity_key, config):
|
||||
super().__init__(device, manufacturer, rationale, entity_key, config)
|
||||
self._key_options = self._config.get("options")
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return list(self._key_options.keys())
|
||||
|
||||
@property
|
||||
def current_option(self):
|
||||
return self._dict_get_selected(self._key_options)
|
||||
|
||||
def select_option(self, option: str):
|
||||
new_status = self._key_options.get(option)
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def update_state(self, status):
|
||||
try:
|
||||
self.schedule_update_ha_state()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
@@ -16,16 +16,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
device_id = config_entry.data.get(CONF_DEVICE_ID)
|
||||
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
|
||||
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
|
||||
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
|
||||
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.SENSOR)
|
||||
devs = []
|
||||
if entities is not None:
|
||||
for entity_key, config in entities.items():
|
||||
devs.append(MideaSensorEntity(device, manufacturer, entity_key, config))
|
||||
devs.append(MideaSensorEntity(device, manufacturer, rationale, entity_key, config))
|
||||
async_add_entities(devs)
|
||||
|
||||
|
||||
class MideaSensorEntity(MideaEntity, SensorEntity):
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def native_value(self):
|
||||
return self._device.get_attribute(self._entity_key)
|
||||
|
@@ -4,8 +4,6 @@ from homeassistant.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DEVICE,
|
||||
CONF_ENTITIES,
|
||||
STATE_ON,
|
||||
STATE_OFF
|
||||
)
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
@@ -18,18 +16,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
device_id = config_entry.data.get(CONF_DEVICE_ID)
|
||||
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
|
||||
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
|
||||
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
|
||||
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.SWITCH)
|
||||
devs = []
|
||||
if entities is not None:
|
||||
for entity_key, config in entities.items():
|
||||
devs.append(MideaSwitchEntity(device, manufacturer, entity_key, config))
|
||||
devs.append(MideaSwitchEntity(device, manufacturer, rationale, entity_key, config))
|
||||
async_add_entities(devs)
|
||||
|
||||
|
||||
class MideaSwitchEntity(MideaBinaryBaseEntity, SwitchEntity):
|
||||
|
||||
def turn_on(self):
|
||||
self._device.set_attribute(attribute=self._entity_key, value=self._binary_rationale[1])
|
||||
self._set_status_on_off(self._entity_key, True)
|
||||
|
||||
def turn_off(self):
|
||||
self._device.set_attribute(attribute=self._entity_key, value=self._binary_rationale[0])
|
||||
self._set_status_on_off(self._entity_key, False)
|
||||
|
128
custom_components/midea_meiju_codec/water_heater.py
Normal file
128
custom_components/midea_meiju_codec/water_heater.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from homeassistant.components.water_heater import WaterHeaterEntity, WaterHeaterEntityFeature
|
||||
from homeassistant.const import (
|
||||
Platform,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DEVICE,
|
||||
CONF_ENTITIES,
|
||||
ATTR_TEMPERATURE
|
||||
)
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DEVICES
|
||||
)
|
||||
from .midea_entities import MideaEntity
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
device_id = config_entry.data.get(CONF_DEVICE_ID)
|
||||
device = hass.data[DOMAIN][DEVICES][device_id].get(CONF_DEVICE)
|
||||
manufacturer = hass.data[DOMAIN][DEVICES][device_id].get("manufacturer")
|
||||
rationale = hass.data[DOMAIN][DEVICES][device_id].get("rationale")
|
||||
entities = hass.data[DOMAIN][DEVICES][device_id].get(CONF_ENTITIES).get(Platform.WATER_HEATER)
|
||||
devs = []
|
||||
if entities is not None:
|
||||
for entity_key, config in entities.items():
|
||||
devs.append(MideaWaterHeaterEntityEntity(device, manufacturer, rationale, entity_key, config))
|
||||
async_add_entities(devs)
|
||||
|
||||
|
||||
class MideaWaterHeaterEntityEntity(MideaEntity, WaterHeaterEntity):
|
||||
def __init__(self, device, manufacturer, rationale, entity_key, config):
|
||||
super().__init__(device, manufacturer, rationale, entity_key, config)
|
||||
self._key_power = self._config.get("power")
|
||||
self._key_operation_list = self._config.get("operation_list")
|
||||
self._key_min_temp = self._config.get("min_temp")
|
||||
self._key_max_temp = self._config.get("max_temp")
|
||||
self._key_current_temperature = self._config.get("current_temperature")
|
||||
self._key_target_temperature = self._config.get("target_temperature")
|
||||
self._attr_temperature_unit = self._config.get("temperature_unit")
|
||||
self._attr_precision = self._config.get("precision")
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
features = 0
|
||||
if self._key_target_temperature is not None:
|
||||
features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
if self._key_operation_list is not None:
|
||||
features |= WaterHeaterEntityFeature.OPERATION_MODE
|
||||
return features
|
||||
|
||||
@property
|
||||
def operation_list(self):
|
||||
return list(self._key_operation_list.keys())
|
||||
|
||||
@property
|
||||
def current_operation(self):
|
||||
return self._dict_get_selected(self._key_operation_list)
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
return self._device.get_attribute(self._key_current_temperature)
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
if isinstance(self._key_target_temperature, list):
|
||||
temp_int = self._device.get_attribute(self._key_target_temperature[0])
|
||||
tem_dec = self._device.get_attribute(self._key_target_temperature[1])
|
||||
if temp_int is not None and tem_dec is not None:
|
||||
return temp_int + tem_dec
|
||||
return None
|
||||
else:
|
||||
return self._device.get_attribute(self._key_target_temperature)
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
if isinstance(self._key_min_temp, str):
|
||||
return float(self._device.get_attribute(self._key_min_temp))
|
||||
else:
|
||||
return float(self._key_min_temp)
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
if isinstance(self._key_max_temp, str):
|
||||
return float(self._device.get_attribute(self._key_max_temp))
|
||||
else:
|
||||
return float(self._key_max_temp)
|
||||
|
||||
@property
|
||||
def target_temperature_low(self):
|
||||
return self.min_temp
|
||||
|
||||
@property
|
||||
def target_temperature_high(self):
|
||||
return self.max_temp
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
return self._get_status_on_off(self._key_power)
|
||||
|
||||
def turn_on(self):
|
||||
self._set_status_on_off(self._key_power, True)
|
||||
|
||||
def turn_off(self):
|
||||
self._set_status_on_off(self._key_power, False)
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
if ATTR_TEMPERATURE not in kwargs:
|
||||
return
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
temp_int, temp_dec = divmod(temperature, 1)
|
||||
temp_int = int(temp_int)
|
||||
new_status = {}
|
||||
if isinstance(self._key_target_temperature, list):
|
||||
new_status[self._key_target_temperature[0]] = temp_int
|
||||
new_status[self._key_target_temperature[1]] = temp_dec
|
||||
else:
|
||||
new_status[self._key_target_temperature] = temperature
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def set_operation_mode(self, operation_mode: str) -> None:
|
||||
new_status = self._key_operation_list.get(operation_mode)
|
||||
self._device.set_attributes(new_status)
|
||||
|
||||
def update_state(self, status):
|
||||
try:
|
||||
self.schedule_update_ha_state()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
Reference in New Issue
Block a user