16 Commits

Author SHA1 Message Date
sususweet
15ac35a887 fix: async import module. 2025-11-21 09:49:35 +08:00
sususweet
2858e991ae feat: add support for T0xBF. Fix #36. 2025-11-21 09:39:42 +08:00
Yingqi Tang
fe1a5b315b fix: file async read 2025-11-21 01:16:55 +00:00
Yingqi Tang
849280676f feat: add follow_body_sense for T0xAC. Fixes #51 2025-11-21 01:10:45 +00:00
Yingqi Tang
3edd94d790 fix: refresh status fallback to lua when cloud api is down. Fixes #52. 2025-11-21 01:05:55 +00:00
sususweet
730f7d0c7b feat: update version 2025-11-19 02:11:39 +08:00
sususweet
b2a2c80133 feat: update wash function for T0xED. 2025-11-19 02:06:47 +08:00
sususweet
cbaf99592f fix: get_status attributes missing. 2025-11-19 01:59:39 +08:00
sususweet
6062b0534f feat: add support for MSmartHome App. 2025-11-19 01:48:11 +08:00
sususweet
5369e6c13d feat: version 0.1.23 2025-11-14 21:15:14 +08:00
sususweet
2a91ce9914 fix: add entities for T0xB6. Ref #45. 2025-11-14 21:14:11 +08:00
sususweet
52c10f7d0d fix: multi-point login and control for meiju app. 2025-11-14 20:52:03 +08:00
sususweet
c090edfb3d fix: bubble attribute for T0xE3. 2025-11-14 20:51:46 +08:00
sususweet
e3a1812f9a feat: fix cold water single loop support for T0xE3. 2025-11-13 10:09:40 +08:00
sususweet
cc5ec70819 feat: add support for T0xFC. Fix #47. 2025-11-11 15:34:30 +08:00
sususweet
37ace3d764 feat: add cloud api for SmartHome app 2025-11-11 14:24:10 +08:00
19 changed files with 526 additions and 165 deletions

View File

@@ -32,6 +32,7 @@ Get devices from MSmartHome/Midea Meiju homes through the network and control th
- T0xB6 Range Hood
- T0xB7 Gas Stove
- T0xB8 Smart Robot Vacuum
- T0xBF Microwave Steam Oven
- T0xCA French Door Refrigerator
- T0xCC Central Air Conditioning (Ducted) Wi-Fi Controller
- T0xCD Air Energy Water Heater
@@ -48,6 +49,7 @@ Get devices from MSmartHome/Midea Meiju homes through the network and control th
- T0xED Water Softener
- T0xFA Electric Fan
- T0xFB Electric Heater
- T0xFC Air Purifier
- T0xFD Humidifier
Welcome to collaborate on adding support for more devices.

View File

@@ -32,6 +32,7 @@
- T0xB6 抽油烟机
- T0xB7 燃气灶
- T0xB8 智能扫地机器人
- T0xBF 微波炉
- T0xCA 对开门冰箱
- T0xCC 中央空调(风管机)Wi-Fi线控器
- T0xCD 空气能热水器
@@ -48,6 +49,7 @@
- T0xED 软水机
- T0xFA 电风扇
- T0xFB 电暖器
- T0xFC 空气净化器
- T0xFD 加湿器
欢迎合作开发添加更多设备支持。

View File

@@ -1,3 +1,4 @@
import asyncio
import os
import base64
from importlib import import_module
@@ -62,6 +63,9 @@ PLATFORMS: list[Platform] = [
Platform.BUTTON
]
async def import_module_async(module_name):
# 在线程池中执行导入操作
return await asyncio.to_thread(import_module, module_name, __package__)
def get_sn8_used(hass: HomeAssistant, sn8):
entries = hass.config_entries.async_entries(DOMAIN)
@@ -102,7 +106,7 @@ async def load_device_config(hass: HomeAssistant, device_type, sn8):
# if not json_data:
device_path = f".device_mapping.{'T0x%02X' % device_type}"
try:
mapping_module = import_module(device_path, __package__)
mapping_module = await import_module_async(device_path)
for key, config in mapping_module.DEVICE_MAPPING.items():
# support tuple & regular expression pattern to support multiple sn8 sharing one mapping
if (key == sn8) or (isinstance(key, tuple) and sn8 in key) or (isinstance(key, str) and re.match(key, sn8)):
@@ -258,6 +262,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
protocol=info.get(CONF_PROTOCOL) or 2,
model=info.get(CONF_MODEL),
subtype=info.get(CONF_MODEL_NUMBER),
manufacturer_code=info.get(CONF_MANUFACTURER_CODE),
sn=info.get(CONF_SN),
sn8=info.get(CONF_SN8),
lua_file=file,

View File

@@ -85,6 +85,7 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
self._key_target_humidity = self._config.get("target_humidity")
self._attr_temperature_unit = self._config.get("temperature_unit")
self._attr_precision = self._config.get("precision")
self._attr_target_temperature_step = self._config.get("precision")
@property
def supported_features(self):

View File

@@ -4,6 +4,7 @@ import datetime
import json
import base64
import asyncio
import aiofiles
import requests
from aiohttp import ClientSession
from secrets import token_hex
@@ -27,6 +28,7 @@ clouds = {
"app_key": "ac21b9f9cbfe4ca5a88562ef25e2b768",
"iot_key": bytes.fromhex(format(7882822598523843940, 'x')).decode(),
"hmac_key": bytes.fromhex(format(117390035944627627450677220413733956185864939010425, 'x')).decode(),
# "api_url": "https://mp-eu-prod.appsmb.com/mas/v5/app/proxy?alias=",
"api_url": "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=",
},
}
@@ -88,12 +90,10 @@ class MideaCloud:
"accesstoken": self._access_token
})
response:dict = {"code": -1}
_LOGGER.debug(f"Midea cloud API header: {header}")
_LOGGER.debug(f"Midea cloud API dump_data: {dump_data}")
try:
r = await self._session.request(method, url, headers=header, data=dump_data, timeout=5)
raw = await r.read()
_LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}")
_LOGGER.debug(f"Midea cloud API url: {url}, header: {header}, data: {data}, response: {raw}")
response = json.loads(raw)
except Exception as e:
_LOGGER.debug(f"API request attempt failed: {e}")
@@ -161,28 +161,6 @@ class MideaCloud:
async def login(self) -> bool:
raise NotImplementedError()
async def send_cloud(self, appliance_id: int, data: bytearray):
appliance_code = str(appliance_id)
params = {
'applianceCode': appliance_code,
'order': self._security.aes_encrypt(bytes_to_dec_string(data)).hex(),
'timestamp': 'true',
"isFull": "false"
}
if response := await self._api_request(
endpoint='/v1/appliance/transparent/send',
data=params,
):
if response and response.get('reply'):
_LOGGER.debug("[%s] Cloud command response: %s", appliance_code, response)
reply_data = self._security.aes_decrypt(bytes.fromhex(response['reply']))
return reply_data
else:
_LOGGER.warning("[%s] Cloud command failed: %s", appliance_code, response)
return None
async def list_home(self) -> dict | None:
return {1: "My home"}
@@ -197,10 +175,6 @@ class MideaCloud:
manufacturer_code: str = "0000",
):
raise NotImplementedError()
async def send_device_control(self, appliance_code: int, control: dict, status: dict | None = None) -> bool:
"""Send control to a device via cloud. Subclasses should implement if supported."""
raise NotImplementedError()
async def send_central_ac_control(self, appliance_code: int, nodeid: str, modelid: str, idtype: int, control: dict) -> bool:
"""Send control to central AC subdevice. Subclasses should implement if supported."""
@@ -322,6 +296,28 @@ class MeijuCloud(MideaCloud):
return appliances
return None
async def send_cloud(self, appliance_id: int, data: bytearray):
appliance_code = str(appliance_id)
params = {
'applianceCode': appliance_code,
'order': self._security.aes_encrypt(bytes_to_dec_string(data)).hex(),
'timestamp': 'true',
"isFull": "false"
}
if response := await self._api_request(
endpoint='/v1/appliance/transparent/send',
data=params,
):
if response and response.get('reply'):
_LOGGER.debug("[%s] Cloud command response: %s", appliance_code, response)
reply_data = self._security.aes_decrypt(bytes.fromhex(response['reply']))
return reply_data
else:
_LOGGER.warning("[%s] Cloud command failed: %s", appliance_code, response)
return None
async def get_device_status(self, appliance_code: int, query: dict) -> dict | None:
data = {
"applianceCode": str(appliance_code),
@@ -470,8 +466,8 @@ class MeijuCloud(MideaCloud):
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)
async with aiofiles.open(fnm, "w") as fp:
await fp.write(stream)
return fnm
@@ -632,10 +628,105 @@ class MSmartHomeCloud(MideaCloud):
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)
async with aiofiles.open(fnm, "w") as fp:
await fp.write(stream)
return fnm
async def send_cloud(self, appliance_code: int, data: bytearray):
appliance_code = str(appliance_code)
params = {
"clientType": "1",
"appId": self.APP_ID,
"format": "2",
"deviceId": self._device_id,
"applianceCode": appliance_code,
'order': self._security.aes_encrypt(bytes_to_dec_string(data)).hex(),
'timestamp': 'true',
"isFull": "false"
}
if response := await self._api_request(
endpoint='/v1/appliance/transparent/send',
data=params,
):
if response and response.get('reply'):
_LOGGER.debug("[%s] Cloud command response: %s", appliance_code, response)
reply_data = self._security.aes_decrypt(bytes.fromhex(response['reply']))
return reply_data
else:
_LOGGER.warning("[%s] Cloud command failed: %s", appliance_code, response)
return None
async def get_device_status(
self,
appliance_code: int,
device_type: int,
sn: str,
model_number: str | None,
manufacturer_code: str = "0000",
query: dict = {}
) -> dict | None:
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",
"applianceCode": appliance_code,
"command": {
"query": query
}
}
if response := await self._api_request(
endpoint="/v1/device/status/lua/get",
data=data
):
# 预期返回形如 { ... 状态键 ... }
return response
return None
async def send_device_control(
self,
appliance_code: int,
device_type: int,
sn: str,
model_number: str | None,
manufacturer_code: str = "0000",
control: dict | None = None,
status: dict | None = None
) -> bool:
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",
"applianceCode": appliance_code,
"command": {
"control": control
}
}
if status and isinstance(status, dict):
data["command"]["status"] = status
response = await self._api_request(
endpoint="/v1/device/lua/control",
data=data
)
return response is not None
def get_midea_cloud(cloud_name: str, session: ClientSession, account: str, password: str) -> MideaCloud | None:
cloud = None

View File

@@ -3,7 +3,7 @@ import socket
import traceback
from enum import IntEnum
from .cloud import MideaCloud
from .cloud import MideaCloud, MSmartHomeCloud, MeijuCloud
from .security import LocalSecurity, MSGTYPE_HANDSHAKE_REQUEST, MSGTYPE_ENCRYPTED_REQUEST
from .packet_builder import PacketBuilder
from .message import MessageQuestCustom
@@ -42,6 +42,7 @@ class MiedaDevice(threading.Thread):
protocol: int,
model: str | None,
subtype: int | None,
manufacturer_code: str | None,
connected: bool,
sn: str | None,
sn8: str | None,
@@ -65,6 +66,7 @@ class MiedaDevice(threading.Thread):
self._subtype = subtype
self._sn = sn
self._sn8 = sn8
self._manufacturer_code = manufacturer_code
self._attributes = {
"device_type": "T0x%02X" % device_type,
"sn": sn,
@@ -178,7 +180,17 @@ class MiedaDevice(threading.Thread):
cloud = self._cloud
if cloud and hasattr(cloud, "send_device_control"):
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
if isinstance(cloud, MSmartHomeCloud):
await cloud.send_device_control(
appliance_code=self._device_id,
device_type=self.device_type,
sn=self.sn,
model_number=self.subtype,
manufacturer_code=self._manufacturer_code,
control=nested_status,
status=self._attributes)
elif isinstance(cloud, MeijuCloud):
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
async def set_attributes(self, attributes):
new_status = {}
@@ -205,7 +217,17 @@ class MiedaDevice(threading.Thread):
cloud = self._cloud
if cloud and hasattr(cloud, "send_device_control"):
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
if isinstance(cloud, MSmartHomeCloud):
await cloud.send_device_control(
appliance_code=self._device_id,
device_type=self.device_type,
sn=self.sn,
model_number=self.subtype,
manufacturer_code=self._manufacturer_code,
control=nested_status,
status=self._attributes)
elif isinstance(cloud, MeijuCloud):
await cloud.send_device_control(self._device_id, control=nested_status, status=self._attributes)
def set_ip_address(self, ip_address):
MideaLogger.debug(f"Update IP address to {ip_address}")
@@ -269,48 +291,72 @@ class MiedaDevice(threading.Thread):
async def refresh_status(self):
for query in self._queries:
if self._lua_runtime is not None:
if query_cmd := self._lua_runtime.build_query(query):
await self._build_send(query_cmd)
cloud = self._cloud
if cloud and hasattr(cloud, "get_device_status"):
if isinstance(cloud, MSmartHomeCloud):
if status := await cloud.get_device_status(
appliance_code=self._device_id,
device_type=self.device_type,
sn=self.sn,
model_number=self.subtype,
manufacturer_code=self._manufacturer_code,
query=query
):
self._parse_cloud_message(status)
else:
if self._lua_runtime is not None:
if query_cmd := self._lua_runtime.build_query(query):
await self._build_send(query_cmd)
def _parse_cloud_message(self, decrypted):
elif isinstance(cloud, MeijuCloud):
if status := await cloud.get_device_status(
appliance_code=self._device_id,
query=query
):
self._parse_cloud_message(status)
else:
if self._lua_runtime is not None:
if query_cmd := self._lua_runtime.build_query(query):
await self._build_send(query_cmd)
def _parse_cloud_message(self, status):
# MideaLogger.debug(f"Received: {decrypted}")
if status := self._lua_runtime.decode_status(dec_string_to_bytes(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:
for c in self._calculate_get:
lvalue = c.get("lvalue")
rvalue = c.get("rvalue")
if lvalue and rvalue:
calculate = False
for s, v in new_status.items():
if rvalue.find(f"[{s}]") >= 0:
calculate = True
break
if calculate:
calculate_str1 = \
(f"{lvalue.replace('[', 'self._attributes[').replace("]", "\"]")} = "
f"{rvalue.replace('[', 'float(self._attributes[').replace(']', "\"])")}") \
.replace("[", "[\"")
calculate_str2 = \
(f"{lvalue.replace('[', 'new_status[').replace("]", "\"]")} = "
f"{rvalue.replace('[', 'float(self._attributes[').replace(']', "\"])")}") \
.replace("[", "[\"")
try:
exec(calculate_str1)
exec(calculate_str2)
except Exception as e:
traceback.print_exc()
MideaLogger.warning(
f"Calculation Error: {lvalue} = {rvalue}, calculate_str1: {calculate_str1}, calculate_str2: {calculate_str2}", self._device_id
)
self._update_all(new_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:
for c in self._calculate_get:
lvalue = c.get("lvalue")
rvalue = c.get("rvalue")
if lvalue and rvalue:
calculate = False
for s, v in new_status.items():
if rvalue.find(f"[{s}]") >= 0:
calculate = True
break
if calculate:
calculate_str1 = \
(f"{lvalue.replace('[', 'self._attributes[').replace("]", "\"]")} = "
f"{rvalue.replace('[', 'float(self._attributes[').replace(']', "\"])")}") \
.replace("[", "[\"")
calculate_str2 = \
(f"{lvalue.replace('[', 'new_status[').replace("]", "\"]")} = "
f"{rvalue.replace('[', 'float(self._attributes[').replace(']', "\"])")}") \
.replace("[", "[\"")
try:
exec(calculate_str1)
exec(calculate_str2)
except Exception as e:
traceback.print_exc()
MideaLogger.warning(
f"Calculation Error: {lvalue} = {rvalue}, calculate_str1: {calculate_str1}, calculate_str2: {calculate_str2}",
self._device_id
)
self._update_all(new_status)
return ParseMessageResult.SUCCESS
def _parse_message(self, msg):
@@ -372,11 +418,13 @@ class MiedaDevice(threading.Thread):
async def _send_message(self, data):
if reply := await self._cloud.send_cloud(self._device_id, data):
result = self._parse_cloud_message(reply)
if result == ParseMessageResult.ERROR:
MideaLogger.debug(f"Message 'ERROR' received")
elif result == ParseMessageResult.SUCCESS:
timeout_counter = 0
if reply_dec := self._lua_runtime.decode_status(dec_string_to_bytes(reply).hex()):
MideaLogger.debug(f"Decoded: {reply_dec}")
result = self._parse_cloud_message(reply_dec)
if result == ParseMessageResult.ERROR:
MideaLogger.debug(f"Message 'ERROR' received")
elif result == ParseMessageResult.SUCCESS:
timeout_counter = 0
# if self._protocol == 3:
# self._send_message_v3(data, msg_type=MSGTYPE_ENCRYPTED_REQUEST)

View File

@@ -107,7 +107,10 @@ DEVICE_MAPPING = {
},
"aux_heat": {
"device_class": SwitchDeviceClass.SWITCH,
}
},
"follow_body_sense": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.SENSOR: {
"mode": {
@@ -252,16 +255,13 @@ DEVICE_MAPPING = {
},
Platform.SWITCH: {
"dry": {
"name": "干燥",
"device_class": SwitchDeviceClass.SWITCH,
},
"prevent_straight_wind": {
"name": "防直吹",
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [1, 2]
},
"aux_heat": {
"name": "电辅热",
"device_class": SwitchDeviceClass.SWITCH,
}
},
@@ -357,7 +357,6 @@ DEVICE_MAPPING = {
},
Platform.SWITCH: {
"power": {
"name": "电源",
"device_class": SwitchDeviceClass.SWITCH,
},
},

View File

@@ -1,4 +1,5 @@
from homeassistant.const import Platform
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.const import Platform, UnitOfPower, UnitOfElectricPotential
from homeassistant.components.switch import SwitchDeviceClass
DEVICE_MAPPING = {
@@ -6,6 +7,14 @@ DEVICE_MAPPING = {
"rationale": ["off", "on"],
"queries": [{}],
"centralized": [],
"calculate": {
"get": [
{
"lvalue": "[b7_vbattery]",
"rvalue": "[b7_vbatt] / 1000.0"
},
],
},
"entities": {
Platform.SWITCH: {
"power": {
@@ -18,6 +27,25 @@ DEVICE_MAPPING = {
"device_class": SwitchDeviceClass.SWITCH,
}
},
Platform.SENSOR: {
"error_code": {
"device_class": SensorDeviceClass.ENUM
},
"b7_left_status": {
"device_class": SensorDeviceClass.ENUM,
"translation_key": "left_status",
},
"b7_right_status": {
"device_class": SensorDeviceClass.ENUM,
"translation_key": "right_status",
},
"b7_vbattery":{
"device_class": SensorDeviceClass.VOLTAGE,
"unit_of_measurement": UnitOfElectricPotential.VOLT,
"state_class": SensorStateClass.MEASUREMENT,
"translation_key": "battery_voltage",
}
},
Platform.SELECT: {
"wind_pressure": {
"options": {
@@ -28,6 +56,15 @@ DEVICE_MAPPING = {
"extreme": {"wind_pressure": "4"},
}
},
"gear": {
"options": {
"off": {"gear": 0},
"low": {"gear": 1},
"medium": {"gear": 2},
"high": {"gear": 3},
"extreme": {"gear": 4},
}
},
},
}
}

View File

@@ -0,0 +1,65 @@
from homeassistant.const import Platform, UnitOfTime, UnitOfArea, UnitOfTemperature
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
DEVICE_MAPPING = {
"default": {
"rationale": ["off", "on"],
"queries": [{}],
"centralized": [],
"calculate": {
"get": [
{
"lvalue": "[work_time]",
"rvalue": "[work_second] + 60 * [work_minute] + 3600 * [work_hour]"
},
{
"lvalue": "[set_time]",
"rvalue": "[second_set] + 60 * [minute_set] + 3600 * [hour_set]"
}
],
"set": [
]
},
"entities": {
Platform.BINARY_SENSOR: {
"lack_water": {
"device_class": BinarySensorDeviceClass.RUNNING,
"rationale": [0, 1]
},
"door_open": {
"device_class": BinarySensorDeviceClass.RUNNING,
},
"change_water": {
"device_class": BinarySensorDeviceClass.RUNNING,
"rationale": [0, 1]
}
},
Platform.SENSOR: {
"work_status": {
"device_class": SensorDeviceClass.ENUM
},
"cur_temperature_above": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"cur_temperature_underside": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"work_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.SECONDS,
"state_class": SensorStateClass.MEASUREMENT
},
"set_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.SECONDS,
"state_class": SensorStateClass.MEASUREMENT
},
}
}
}
}

View File

@@ -1,4 +1,5 @@
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, \
CONCENTRATION_PARTS_PER_MILLION
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
@@ -107,19 +108,19 @@ DEVICE_MAPPING = {
},
"co2_value": {
"device_class": SensorDeviceClass.CO2,
"unit_of_measurement": "ppm",
"unit_of_measurement": CONCENTRATION_PARTS_PER_MILLION,
"state_class": SensorStateClass.MEASUREMENT,
"attribute": "co2.value"
},
"hcho_value": {
"device_class": SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
"unit_of_measurement": "μg/m³",
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT,
"attribute": "hcho.value"
},
"pm25_value": {
"device_class": SensorDeviceClass.PM25,
"unit_of_measurement": "μg/m³",
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT,
"attribute": "pm2_5.value"
},

View File

@@ -1,4 +1,5 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, \
CONCENTRATION_PARTS_PER_MILLION
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
@@ -168,12 +169,12 @@ DEVICE_MAPPING = {
},
"pm25_value": {
"device_class": SensorDeviceClass.PM25,
"unit_of_measurement": "µg/m³",
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT
},
"co2_value": {
"device_class": SensorDeviceClass.CO2,
"unit_of_measurement": "ppm",
"unit_of_measurement": CONCENTRATION_PARTS_PER_MILLION,
"state_class": SensorStateClass.MEASUREMENT
},
"machine_type": {

View File

@@ -1,4 +1,4 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PERCENTAGE, PRECISION_HALVES
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PRECISION_WHOLE
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
@@ -15,10 +15,6 @@ DEVICE_MAPPING = {
"min": 0,
"max": 3,
"step": 1
},
"cur_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"state_class": SensorStateClass.MEASUREMENT,
}
},
Platform.CLIMATE: {
@@ -31,9 +27,9 @@ DEVICE_MAPPING = {
"target_temperature": "temperature",
"current_temperature": "cur_temperature",
"min_temp": 30,
"max_temp": 75,
"max_temp": 80,
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_HALVES,
"precision": PRECISION_WHOLE,
}
},
Platform.SWITCH: {
@@ -61,25 +57,8 @@ DEVICE_MAPPING = {
"water_flow": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.SELECT: {
"func_select": {
"options": {
"low": {"func_select": "low"},
"medium": {"func_select": "medium"}
}
},
"type_select": {
"options": {
"normal": {"type_select": "normal"},
"valve": {"type_select": "valve"},
}
},
"machine": {
"options": {
"real_machine": {"machine": "real_machine"},
"virtual_machine": {"machine": "virtual_machine"}
}
"sterilization": {
"device_class": SwitchDeviceClass.SWITCH,
}
},
Platform.SENSOR: {
@@ -111,22 +90,18 @@ DEVICE_MAPPING = {
"passwater_lowbyte": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "L",
"state_class": SensorStateClass.MEASUREMENT
"state_class": SensorStateClass.TOTAL
},
"passwater_highbyte": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "L",
"state_class": SensorStateClass.MEASUREMENT
"state_class": SensorStateClass.TOTAL
},
"rate": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "L/min",
"state_class": SensorStateClass.MEASUREMENT
"device_class": SensorDeviceClass.ENUM
},
"cur_rate": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "L/min",
"state_class": SensorStateClass.MEASUREMENT
"device_class": SensorDeviceClass.ENUM
},
"sterilize_left_days": {
"device_class": SensorDeviceClass.DURATION,
@@ -154,19 +129,13 @@ DEVICE_MAPPING = {
"state_class": SensorStateClass.MEASUREMENT
},
"tds_value": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "ppm",
"state_class": SensorStateClass.MEASUREMENT
"device_class": SensorDeviceClass.ENUM
},
"heat_water_level": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "%",
"state_class": SensorStateClass.MEASUREMENT
"device_class": SensorDeviceClass.ENUM
},
"flow": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "L/min",
"state_class": SensorStateClass.MEASUREMENT
"device_class": SensorDeviceClass.ENUM
},
"end_time_hour": {
"device_class": SensorDeviceClass.DURATION,
@@ -201,19 +170,17 @@ DEVICE_MAPPING = {
"state_class": SensorStateClass.MEASUREMENT
},
"mg_remain": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "mg",
"state_class": SensorStateClass.MEASUREMENT
"device_class": SensorDeviceClass.ENUM
},
"waterday_lowbyte": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "L",
"state_class": SensorStateClass.MEASUREMENT
"state_class": SensorStateClass.TOTAL
},
"waterday_highbyte": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "L",
"state_class": SensorStateClass.MEASUREMENT
"state_class": SensorStateClass.TOTAL
}
},
Platform.BINARY_SENSOR: {

View File

@@ -15,7 +15,9 @@ DEVICE_MAPPING = {
},
"bubble": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [0, 1]
},
"cold_water": {
"device_class": SwitchDeviceClass.SWITCH,
},
"cold_water_master": {
"device_class": SwitchDeviceClass.SWITCH,

View File

@@ -17,9 +17,6 @@ DEVICE_MAPPING = {
"heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"antifreeze": {
"device_class": SwitchDeviceClass.SWITCH,
},
"lock": {
"device_class": SwitchDeviceClass.SWITCH,
},
@@ -29,22 +26,13 @@ DEVICE_MAPPING = {
"keep_warm": {
"device_class": SwitchDeviceClass.SWITCH,
},
"vacation": {
"device_class": SwitchDeviceClass.SWITCH,
},
"germicidal": {
"device_class": SwitchDeviceClass.SWITCH,
},
"lack_water": {
"device_class": SwitchDeviceClass.SWITCH,
},
"drainage": {
"device_class": SwitchDeviceClass.SWITCH,
},
"wash_enable": {
"device_class": SwitchDeviceClass.SWITCH,
},
"water_way": {
"wash": {
"device_class": SwitchDeviceClass.SWITCH,
},
"soften": {
@@ -65,14 +53,14 @@ DEVICE_MAPPING = {
"cl_sterilization": {
"device_class": SwitchDeviceClass.SWITCH,
},
"holiday_mode": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.BINARY_SENSOR: {
"heat_status": {
"device_class": BinarySensorDeviceClass.RUNNING,
},
"lack_water": {
"device_class": BinarySensorDeviceClass.PROBLEM,
},
"standby_status": {
"device_class": BinarySensorDeviceClass.RUNNING,
},

View File

@@ -1,4 +1,5 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PERCENTAGE, DEGREE
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PERCENTAGE, DEGREE, \
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
@@ -94,7 +95,7 @@ DEVICE_MAPPING = {
},
"pm25": {
"device_class": SensorDeviceClass.PM25,
"unit_of_measurement": "µg/m³",
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT
},
"ud_swing_angle": {

View File

@@ -0,0 +1,103 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfVolume, UnitOfTime, PERCENTAGE, PRECISION_HALVES, \
UnitOfEnergy, UnitOfPower, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
DEVICE_MAPPING = {
"default": {
"rationale": ["off", "on"],
"queries": [{}],
"centralized": [],
"entities": {
Platform.SWITCH: {
"power": {
"device_class": SwitchDeviceClass.SWITCH,
},
"anion": {
"device_class": SwitchDeviceClass.SWITCH,
},
"buzzer": {
"device_class": SwitchDeviceClass.SWITCH,
},
"lock": {
"device_class": SwitchDeviceClass.SWITCH,
},
"waterions":{
"device_class": SwitchDeviceClass.SWITCH,
}
},
Platform.SELECT: {
"mode": {
"options": {
"constant_humidity": {"mode": "constant_humidity"},
"manual": {"mode": "manual"},
"sleep": {"mode": "sleep"},
"fast": {"mode": "fast"},
"auto": {"mode": "auto"},
}
},
"bias_gear":{
"options": {
"瑜伽静修场景": {"mode": "auto", "sub_mode": "denoise", "bias_gear": -20},
"室内对话场景": {"mode": "auto", "sub_mode": "denoise", "bias_gear": -10}
}
},
"bright": {
"options": {
"全亮": {"bright": 0},
"半亮": {"bright": 6},
"熄灭": {"bright": 7}
}
},
"gear": {
"options": {
"low": {"wind_speed": 1},
"medium": {"wind_speed": 2},
"high": {"wind_speed": 3}
}
},
"humidity": {
"options": {
"40%": {"humidity": 40},
"50%": {"humidity": 50},
"60%": {"humidity": 60},
"70%": {"humidity": 70}
}
},
},
Platform.SENSOR: {
"temperature_feedback": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"humidify_feedback": {
"device_class": SensorDeviceClass.HUMIDITY,
"unit_of_measurement": "%",
"state_class": SensorStateClass.MEASUREMENT
},
"hcho":{
"device_class": SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT
},
"pm1":{
"device_class": SensorDeviceClass.PM1,
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT
},
"pm25":{
"device_class": SensorDeviceClass.PM25,
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT
},
"pm10":{
"device_class": SensorDeviceClass.PM10,
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT
}
}
}
}
}

View File

@@ -7,5 +7,5 @@
"iot_class": "cloud_push",
"issue_tracker": "https://github.com/sususweet/midea-meiju-codec/issues",
"requirements": ["lupa>=2.0"],
"version": "v0.1.20"
"version": "v0.1.25"
}

View File

@@ -349,6 +349,15 @@
}
},
"select": {
"bright": {
"name": "Brightness"
},
"bias_gear": {
"name": "Bias Gear"
},
"humidity": {
"name": "Humidity"
},
"b6_power": {
"name": "Smoke Machine Power"
},
@@ -642,6 +651,9 @@
"move_direction": {
"name": "Move Direction"
},
"work_status": {
"name": "Work Status"
},
"sub_work_status": {
"name": "Sub Work Status"
},
@@ -1197,6 +1209,9 @@
"work_time": {
"name": "Work Time"
},
"set_time": {
"name": "Set Time"
},
"zero_cold_tem": {
"name": "Zero Cold Temperature"
},
@@ -1434,6 +1449,9 @@
"left_status": {
"name": "Left Status"
},
"right_status": {
"name": "Right Status"
},
"eq_recipe_action": {
"name": "Eq Recipe Action"
},
@@ -1562,6 +1580,12 @@
}
},
"switch": {
"follow_body_sense": {
"name": "Follow Body Sense"
},
"waterions": {
"name": "Disinfection"
},
"mute": {
"name": "Mute"
},
@@ -1815,7 +1839,7 @@
"name": "CO2 Check Enable"
},
"cold_water": {
"name": "Cold Water"
"name": "Cold Water Single Loop"
},
"cold_water_ai": {
"name": "Cold Water AI"

View File

@@ -353,6 +353,15 @@
}
},
"select": {
"bright": {
"name": "亮度"
},
"bias_gear": {
"name": "降噪设置"
},
"humidity": {
"name": "设定湿度"
},
"b6_power": {
"name": "烟机开关"
},
@@ -646,6 +655,9 @@
"move_direction": {
"name": "移动方向"
},
"work_status": {
"name": "工作状态"
},
"sub_work_status": {
"name": "子工作状态"
},
@@ -1201,6 +1213,9 @@
"work_time": {
"name": "工作时间"
},
"set_time": {
"name": "设置工作时间"
},
"zero_cold_tem": {
"name": "零冷水温度"
},
@@ -1438,6 +1453,9 @@
"left_status": {
"name": "左侧状态"
},
"right_status": {
"name": "右侧状态"
},
"eq_recipe_action": {
"name": "等量食谱动作"
},
@@ -1566,6 +1584,12 @@
}
},
"switch": {
"follow_body_sense": {
"name": "随身感"
},
"waterions": {
"name": "消杀"
},
"mute": {
"name": "静音"
},
@@ -1819,7 +1843,7 @@
"name": "CO2检测启用"
},
"cold_water": {
"name": "零冷水"
"name": "零冷水单次循环"
},
"cold_water_ai": {
"name": "AI零冷水"