52 Commits

Author SHA1 Message Date
sususweet
b8104a8643 feat: add support for new wind. 2025-11-28 00:23:45 +08:00
sususweet
98ebac5516 feat: Follow body sense function fixed. Fix #51. 2025-11-27 23:24:01 +08:00
sususweet
bf7345d680 fix:translation key error. 2025-11-27 23:08:35 +08:00
sususweet
df01bc7cb5 fix:remove plugin download in msmart app. 2025-11-27 23:05:35 +08:00
sususweet
61b33e4ceb feat: add support for T0xC3 heat pump. Fix #46. 2025-11-27 23:03:35 +08:00
sususweet
8714549f90 feat: add support for fan-light. Fix #56. 2025-11-27 21:40:56 +08:00
sususweet
266419e3a9 refactor: remove inner number type conversion in attr calculate. 2025-11-27 21:39:07 +08:00
sususweet
7fee90b1fd feat: add plugin download for devices. 2025-11-27 16:17:58 +08:00
sususweet
a0c1423933 feat: add support for Integrated Oven. Fixed #55. 2025-11-27 16:05:44 +08:00
sususweet
7fac466875 feat: add support for colmo ground warmer, sn 17497071. Fix #54. 2025-11-27 11:12:50 +08:00
sususweet
e5bbcfca94 fix: device humidity and temperature for T0xAC. 2025-11-23 01:12:44 +08:00
sususweet
ff39911d1e fix: life unit changed to percentage. 2025-11-23 00:43:55 +08:00
sususweet
e5dbd87802 feat: version 0.1.26 2025-11-22 00:32:52 +08:00
sususweet
00f493d06a feat: add support for T0xE6 and T0xED. 2025-11-22 00:31:33 +08:00
sususweet
55f7bdbda8 fix: extend timeout for request to avoid slow api response. 2025-11-22 00:12:22 +08:00
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
sususweet
7476d6ad1d feat: add support for T0xCD 2025-11-07 01:08:38 +08:00
sususweet
883369184b feat: fix support for T0xE2 2025-11-06 22:31:08 +08:00
sususweet
4fe689217a feat: add support for washing machine T0xDA 2025-11-05 20:58:00 +08:00
sususweet
a680cd43b8 feat: update device mapping for T0xFA 2025-11-04 16:03:17 +08:00
sususweet
05ad0c3a15 feat: version 0.1.20 2025-11-03 11:38:57 +08:00
sususweet
aea43aad0c feat: change entity for water heater. 2025-11-03 11:38:11 +08:00
sususweet
329fbce3dd feat: add support for T0x9B. Fixed #35. 2025-11-02 16:13:33 +08:00
sususweet
f9a1f57d31 feat: add support for T0x9C. 2025-11-02 16:02:21 +08:00
sususweet
ee6c5b18af feat: update central ac control protocol 2025-11-02 00:37:32 +08:00
sususweet
6166ebf4f7 feat: version 0.1.16 2025-10-31 23:25:56 +08:00
sususweet
39cc28b4dd fix: lua base library location error. 2025-10-31 23:21:39 +08:00
sususweet
e4b939780f feat: update support for device T0x15. 2025-10-31 23:01:09 +08:00
Yingqi Tang
c12e95f2a1 Fix duplicate import of 'os' in lua_runtime.py
Removed duplicate import of 'os' in lua_runtime.py
2025-10-31 22:56:07 +08:00
Yingqi Tang
6129248fed Merge pull request #41 from happyhaha1/master
feat(lua): enhance runtime environment with resilient module loading
2025-10-31 22:54:17 +08:00
Yingqi Tang
3b2d817dd6 Merge branch 'master' into master 2025-10-31 22:54:04 +08:00
sususweet
e9f8f95826 feat: add support for device T0x15. 2025-10-31 22:47:43 +08:00
sususweet
7a28c62ac5 fix: fix lua library error. 2025-10-31 22:46:52 +08:00
sususweet
f2735fd729 feat: add new entity include button and number. 2025-10-31 22:46:29 +08:00
happyhaha1
05e30ab414 feat(lua): enhance runtime environment with resilient module loading
Introduce robust Lua module deployment strategy that ensures proper library
availability across different system configurations. The implementation now
prioritizes Home Assistant's configuration directory for persistent storage,
with automatic failover to temporary locations when permission restrictions
occur. Additionally, the runtime now validates module dependencies during
initialization and provides clear diagnostic warnings for any loading failures.
2025-10-31 10:44:42 +08:00
Yingqi Tang
af6f2d1789 Update README.md 2025-10-28 22:58:02 +08:00
sususweet
665763bb22 feat: update readme 2025-10-28 22:53:26 +08:00
37 changed files with 2524 additions and 420 deletions

View File

@@ -3,7 +3,7 @@
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration)
[![Stable](https://img.shields.io/github/v/release/sususweet/midea-meiju-codec)](https://github.com/sususweet/midea-meiju-codec/releases/latest)
English | [简体中文](README_zh_CN.md)
English | [简体中文](README_hans.md)
Get devices from MSmartHome/Midea Meiju homes through the network and control them via Midea's cloud API.
@@ -19,9 +19,12 @@ Get devices from MSmartHome/Midea Meiju homes through the network and control th
## Currently Supported Device Types
- T0x13 Electric Light
- T0x15 Water Heater
- T0x21 Central Air Conditioning Gateway
- T0x26 Bath Heater
- T0x3D Water Heater
- T0x9B Steam oven
- T0x9C Integrated Gas Stove
- T0xA1 Dehumidifier
- T0xAC Air Conditioner
- T0xB2 Electric Steamer
@@ -29,20 +32,26 @@ 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
- T0xC3 Heat Pump
- T0xCA French Door Refrigerator
- T0xCC Central Air Conditioning (Ducted) Wi-Fi Controller
- T0xCD Air Energy Water Heater
- T0xCE Fresh Air System
- T0xCF Central Air Conditioning Heating
- T0xD9 Twin Tub Washing Machine
- T0xDB Front Load Washing Machine
- T0xDA Impeller Washing Machine
- T0xDB Cylinder Washing Machine
- T0xDC Clothes Dryer
- T0xE1 Dishwasher
- T0xE2 Electric Water Heater
- T0xE3 Constant Temperature Gas Water Heater
- T0xE6 Wall hanging furnace
- T0xEA Rice Cooker
- T0xED Water Softener
- T0xFA Electric Fan
- T0xFB Electric Heater
- T0xFC Air Purifier
- T0xFD Humidifier
Welcome to collaborate on adding support for more devices.
@@ -53,7 +62,7 @@ Collaboration method: After adding this plugin, find devices that are not correc
Expand the `Attributes` card below and submit these fields with the issue. Pay special attention to the `Device type` and `Subtype` fields, as these are the basis for obtaining the corresponding lua files for device control.
Then go to the Home Assistant installation directory, find the device's corresponding T_0000_`Device type`_`Subtype`_***.lua file in the `.storage/midea_auto_cloud/lua/` directory, and wait for adaptation.
Then go to the Home Assistant installation directory, find the device's corresponding T_0000_`Device type`_`Subtype`_***.lua file in the `.storage/midea_auto_cloud/lua/` directory and `zip` file in the `.storage/midea_auto_cloud/plugin/` directory, and wait for adaptation.
![img_1.png](./img/img_1.png)
@@ -72,4 +81,4 @@ The example configuration `22012227` demonstrates how to map device attributes t
## Acknowledgments
Thanks to the [midea-meiju-codec](https://github.com/MattedBroadSky/midea-meiju-codec) project for providing prior knowledge.
Thanks to the [midea-meiju-codec](https://github.com/MattedBroadSky/midea-meiju-codec) project for providing prior knowledge.

View File

@@ -19,9 +19,12 @@
## 目前支持的设备类型
- T0x13 电灯
- T0x15 养生壶
- T0x21 中央空调网关
- T0x26 浴霸
- T0x3D 电热水瓶
- T0x9B 蒸烤箱
- T0x9C 集成灶
- T0xA1 除湿机
- T0xAC 空调
- T0xB2 电蒸箱
@@ -29,20 +32,26 @@
- T0xB6 抽油烟机
- T0xB7 燃气灶
- T0xB8 智能扫地机器人
- T0xBF 微波炉
- T0xC3 热泵
- T0xCA 对开门冰箱
- T0xCC 中央空调(风管机)Wi-Fi线控器
- T0xCD 空气能热水器
- T0xCE 新风机
- T0xCF 中央空调暖家
- T0xD9 复式洗衣机
- T0xDA 波轮洗衣机
- T0xDB 滚筒洗衣机
- T0xDC 干衣机
- T0xE1 洗碗机
- T0xE2 电热水器
- T0xE3 恒温式燃气热水器
- T0xE6 壁挂炉
- T0xEA 电饭锅
- T0xED 软水机
- T0xFA 电风扇
- T0xFB 电暖器
- T0xFC 空气净化器
- T0xFD 加湿器
欢迎合作开发添加更多设备支持。
@@ -53,7 +62,7 @@
展开下面的`属性`卡片把里面这些字段随issue提交。 着重关注Device type、Subtype这两个字段这是后续获得设备控制对应lua文件的基础。
再进入Homeassistant的安装目录`.storage/midea_auto_cloud/lua/`目录下找到设备对应的T_0000_`Device type`_`Subtype`_***.lua文件等待适配就可以了。
再进入Homeassistant的安装目录`.storage/midea_auto_cloud/lua/`目录下找到设备对应的T_0000_`Device type`_`Subtype`_***.lua文件以及`.storage/midea_auto_cloud/plugin/`目录下的`zip`文件,等待适配就可以了。
![img_1.png](./img/img_1.png)

View File

@@ -1,5 +1,7 @@
import asyncio
import os
import base64
import traceback
from importlib import import_module
import re
from homeassistant.config_entries import ConfigEntry
@@ -43,7 +45,7 @@ from .const import (
CONF_SN,
CONF_MODEL_NUMBER,
CONF_SERVERS, STORAGE_PATH, CONF_MANUFACTURER_CODE,
CONF_SELECTED_HOMES
CONF_SELECTED_HOMES, CONF_SMART_PRODUCT_ID, STORAGE_PLUGIN_PATH
)
# 账号型:登录云端、获取设备列表,并为每台设备建立协调器(无本地控制)
from .const import CONF_PASSWORD as CONF_PASSWORD_KEY, CONF_SERVER as CONF_SERVER_KEY
@@ -57,9 +59,14 @@ PLATFORMS: list[Platform] = [
Platform.WATER_HEATER,
Platform.FAN,
Platform.LIGHT,
Platform.HUMIDIFIER
Platform.HUMIDIFIER,
Platform.NUMBER,
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)
@@ -100,7 +107,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)):
@@ -138,20 +145,46 @@ async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry):
async def async_setup(hass: HomeAssistant, config: ConfigType):
hass.data.setdefault(DOMAIN, {})
cjson = os.getcwd() + "/cjson.lua"
bit = os.getcwd() + "/bit.lua"
# if not os.path.exists(cjson):
from .const import CJSON_LUA
cjson_lua = base64.b64decode(CJSON_LUA.encode("utf-8")).decode("utf-8")
with open(cjson, "wt") as fp:
fp.write(cjson_lua)
# if not os.path.exists(bit):
from .const import BIT_LUA
bit_lua = base64.b64decode(BIT_LUA.encode("utf-8")).decode("utf-8")
with open(bit, "wt") as fp:
fp.write(bit_lua)
return True
os.makedirs(hass.config.path(STORAGE_PATH), exist_ok=True)
lua_path = hass.config.path(STORAGE_PATH)
cjson = os.path.join(lua_path, "cjson.lua")
bit = os.path.join(lua_path, "bit.lua")
# 只有文件不存在时才创建
if not os.path.exists(cjson):
from .const import CJSON_LUA
cjson_lua = base64.b64decode(CJSON_LUA.encode("utf-8")).decode("utf-8")
try:
with open(cjson, "wt") as fp:
fp.write(cjson_lua)
except PermissionError as e:
MideaLogger.error(f"Failed to create cjson.lua at {cjson}: {e}")
# 如果无法创建文件,尝试使用临时目录
import tempfile
temp_dir = tempfile.gettempdir()
cjson = os.path.join(temp_dir, "cjson.lua")
with open(cjson, "wt") as fp:
fp.write(cjson_lua)
MideaLogger.warning(f"Using temporary file for cjson.lua: {cjson}")
if not os.path.exists(bit):
from .const import BIT_LUA
bit_lua = base64.b64decode(BIT_LUA.encode("utf-8")).decode("utf-8")
try:
with open(bit, "wt") as fp:
fp.write(bit_lua)
except PermissionError as e:
MideaLogger.error(f"Failed to create bit.lua at {bit}: {e}")
# 如果无法创建文件,尝试使用临时目录
import tempfile
temp_dir = tempfile.gettempdir()
bit = os.path.join(temp_dir, "bit.lua")
with open(bit, "wt") as fp:
fp.write(bit_lua)
MideaLogger.warning(f"Using temporary file for bit.lua: {bit}")
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
device_type = config_entry.data.get(CONF_TYPE)
@@ -217,6 +250,22 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
model_number=info.get(CONF_MODEL_NUMBER),
manufacturer_code=info.get(CONF_MANUFACTURER_CODE),
)
try:
os.makedirs(hass.config.path(STORAGE_PLUGIN_PATH), exist_ok=True)
plugin_path = hass.config.path(STORAGE_PLUGIN_PATH)
await cloud.download_plugin(
path=plugin_path,
appliance_code=appliance_code,
smart_product_id=info.get(CONF_SMART_PRODUCT_ID),
device_type=info.get(CONF_TYPE),
sn=info.get(CONF_SN),
sn8=info.get(CONF_SN8),
model_number=info.get(CONF_MODEL_NUMBER),
manufacturer_code=info.get(CONF_MANUFACTURER_CODE),
)
except Exception as e:
traceback.print_exc()
try:
device = MiedaDevice(
name=info.get(CONF_NAME),
@@ -230,6 +279,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

@@ -0,0 +1,99 @@
from homeassistant.components.button import ButtonEntity
from homeassistant.const import Platform
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .core.logger import MideaLogger
from .midea_entity import MideaEntity
from . import load_device_config
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up button entities for Midea devices."""
account_bucket = hass.data.get(DOMAIN, {}).get("accounts", {}).get(config_entry.entry_id)
if not account_bucket:
async_add_entities([])
return
device_list = account_bucket.get("device_list", {})
coordinator_map = account_bucket.get("coordinator_map", {})
devs = []
for device_id, info in device_list.items():
device_type = info.get("type")
sn8 = info.get("sn8")
config = await load_device_config(hass, device_type, sn8) or {}
entities_cfg = (config.get("entities") or {}).get(Platform.BUTTON, {})
manufacturer = config.get("manufacturer")
rationale = config.get("rationale")
coordinator = coordinator_map.get(device_id)
device = coordinator.device if coordinator else None
for entity_key, ecfg in entities_cfg.items():
devs.append(MideaButtonEntity(
coordinator, device, manufacturer, rationale, entity_key, ecfg
))
async_add_entities(devs)
class MideaButtonEntity(MideaEntity, ButtonEntity):
"""Midea button entity."""
def __init__(self, coordinator, device, manufacturer, rationale, entity_key, config):
super().__init__(
coordinator,
device.device_id,
device.device_name,
f"T0x{device.device_type:02X}",
device.sn,
device.sn8,
device.model,
entity_key,
device=device,
manufacturer=manufacturer,
rationale=rationale,
config=config,
)
async def async_press(self) -> None:
"""Handle the button press."""
# 从配置中获取要执行的命令或操作
command = self._config.get("command")
attribute = self._config.get("attribute", self._entity_key)
value = self._config.get("value")
# 判断是否为中央空调设备T0x21
is_central_ac = self._device.device_type == 0x21 if self._device else False
if command:
# 如果配置中指定了命令,执行该命令
if isinstance(command, dict):
# 如果是字典,可能需要发送多个属性
await self.async_set_attributes(command)
elif isinstance(command, str):
# 如果是字符串,可能是特殊命令类型
await self._async_execute_command(command)
elif value is not None:
# 如果配置中指定了值,设置该属性值
await self.async_set_attribute(attribute, value)
else:
# 默认行为:如果没有指定命令或值,记录警告
MideaLogger.warning(
f"Button {self._entity_key} has no command or value configured"
)
async def _async_execute_command(self, command: str) -> None:
"""Execute a special command."""
# 这里可以处理特殊的命令类型
# 例如:重启、重置、测试等
if command == "reset" or command == "restart":
# 可以在这里实现重置或重启逻辑
MideaLogger.debug(f"Executing {command} command for button {self._entity_key}")
else:
# 对于其他命令,可以通过 coordinator 发送
await self.coordinator.async_send_command(0, command)

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):
@@ -107,13 +108,23 @@ class MideaClimateEntity(MideaEntity, ClimateEntity):
@property
def current_temperature(self):
temp = self._get_nested_value(self._key_current_temperature)
if temp is not None:
try:
return float(temp)
except (ValueError, TypeError):
return None
return None
if isinstance(self._key_current_temperature, list):
temp_int = self._get_nested_value(self._key_current_temperature[0])
tem_dec = self._get_nested_value(self._key_current_temperature[1])
if temp_int is not None and tem_dec is not None:
try:
return float(temp_int) + float(tem_dec)
except (ValueError, TypeError):
return None
return None
else:
temp = self._get_nested_value(self._key_current_temperature)
if temp is not None:
try:
return float(temp)
except (ValueError, TypeError):
return None
return None
@property
def target_temperature(self):

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,9 @@ import time
import datetime
import json
import base64
import asyncio
import traceback
import os
import aiofiles
import requests
from aiohttp import ClientSession
from secrets import token_hex
@@ -27,7 +29,8 @@ clouds = {
"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=",
"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,15 +91,14 @@ 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}")
_LOGGER.debug(f"Midea cloud API url: {url}, header: {header}, data: {data}")
try:
r = await self._session.request(method, url, headers=header, data=dump_data, timeout=5)
r = await self._session.request(method, url, headers=header, data=dump_data, timeout=30)
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}")
traceback.print_exc()
if int(response["code"]) == 0:
if "data" in response:
@@ -139,7 +141,7 @@ class MideaCloud:
_LOGGER.debug(f"Midea cloud API url: {url}, data: {data}, response: {raw}")
response = json.loads(raw)
except Exception as e:
_LOGGER.debug(f"API request attempt failed: {e}")
traceback.print_exc()
if int(response["code"]) == 0 and "data" in response:
return response["data"]
@@ -161,28 +163,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"}
@@ -198,10 +178,18 @@ class MideaCloud:
):
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."""
async def download_plugin(
self, path: str,
appliance_code: str,
smart_product_id: str,
device_type: int,
sn: str,
sn8: str,
model_number: str | None,
manufacturer_code: str = "0000",
):
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."""
raise NotImplementedError()
@@ -309,6 +297,7 @@ class MeijuCloud(MideaCloud):
"type": int(appliance.get("type"), 16),
"sn": self._security.aes_decrypt(appliance.get("sn")) if appliance.get("sn") else "",
"sn8": appliance.get("sn8", "00000000"),
"smart_product_id": appliance.get("smartProductId", "0"),
"model_number": appliance.get("modelNumber", "0"),
"manufacturer_code": appliance.get("enterpriseCode", "0000"),
"model": appliance.get("productModel"),
@@ -322,6 +311,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),
@@ -361,11 +372,8 @@ class MeijuCloud(MideaCloud):
command_data = {
"nodeid": nodeid,
"acattri_ctrl": {
"aclist": [{
"nodeid": nodeid,
"modelid": modelid,
"type": idtype
}],
"nodeid": nodeid,
"modelid": modelid, "type": idtype, "aclist_data": nodeid[-2:],
"event": control
}
}
@@ -473,11 +481,98 @@ 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
async def download_plugin(
self, path: str,
appliance_code: str,
smart_product_id: str,
device_type: int,
sn: str,
sn8: str,
model_number: str | None,
manufacturer_code: str = "0000",
):
# 构建 applianceList根据传入的参数动态生成
appliance_info = {
"appModel": sn8,
"appEnterprise": manufacturer_code,
"appType": f"0x{device_type:02X}",
"applianceCode": str(appliance_code) if isinstance(appliance_code, int) else appliance_code,
"smartProductId": str(smart_product_id) if isinstance(smart_product_id, int) else smart_product_id,
"modelNumber": model_number or "0",
"versionCode": 0
}
appliance_list = [appliance_info]
data = {
"applianceList": json.dumps(appliance_list),
"iotAppId": self.APP_ID,
"match": "1",
"clientType": "1",
"clientVersion": 201
}
fnm = None
if response := await self._api_request(
endpoint="/v1/plugin/update/getPluginV3",
data=data
):
# response 是 {"list": [...]}
plugin_list = response.get("list", [])
if not plugin_list:
MideaLogger.warning(f"No plugin found for device type 0x{device_type:02X}, sn: {sn}")
return None
# 找到匹配的设备(优先匹配 applianceCode其次匹配 appType
matched_plugin = None
# 首先尝试精确匹配 applianceCode
for plugin in plugin_list:
if plugin.get("applianceCode") == sn and plugin.get("appType") == f"0x{device_type:02X}":
matched_plugin = plugin
break
# 如果没有精确匹配,使用第一个匹配 appType 的
if not matched_plugin:
for plugin in plugin_list:
if plugin.get("appType") == f"0x{device_type:02X}":
matched_plugin = plugin
break
if not matched_plugin:
MideaLogger.warning(f"No matching plugin found for device type 0x{device_type:02X}, sn: {sn}")
return None
# 下载 zip 文件
zip_url = matched_plugin.get("url")
zip_title = matched_plugin.get("title", f"plugin_0x{device_type:02X}.zip")
if not zip_url:
MideaLogger.warning(f"No download URL found for plugin: {zip_title}")
return None
try:
# 确保目录存在
os.makedirs(path, exist_ok=True)
res = await self._session.get(zip_url)
if res.status == 200:
zip_data = await res.read()
if zip_data:
fnm = f"{path}/{zip_title}"
async with aiofiles.open(fnm, "wb") as fp:
await fp.write(zip_data)
MideaLogger.info(f"Downloaded plugin file: {fnm}")
else:
MideaLogger.warning(f"Downloaded zip file is empty: {zip_url}")
else:
MideaLogger.warning(f"Failed to download plugin, status: {res.status}, url: {zip_url}")
except Exception as e:
MideaLogger.error(f"Error downloading plugin: {e}")
traceback.print_exc()
return fnm
class MSmartHomeCloud(MideaCloud):
APP_ID = "1010"
SRC = "10"
@@ -547,7 +642,7 @@ class MSmartHomeCloud(MideaCloud):
self._api_url = api_url
async def login(self) -> bool:
await self._re_route()
# await self._re_route()
if login_id := await self._get_login_id():
self._login_id = login_id
iot_data = self._make_general_data()
@@ -635,10 +730,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,47 +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[')} = "
f"{rvalue.replace('[', 'self._attributes[')}") \
.replace("[", "[\"").replace("]", "\"]")
calculate_str2 = \
(f"{lvalue.replace('[', 'new_status[')} = "
f"{rvalue.replace('[', 'self._attributes[')}") \
.replace("[", "[\"").replace("]", "\"]")
try:
exec(calculate_str1)
exec(calculate_str2)
except Exception:
MideaLogger.warning(
f"Calculation Error: {lvalue} = {rvalue}", 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('[', 'self._attributes[').replace(']', "\"]")}") \
.replace("[", "[\"")
calculate_str2 = \
(f"{lvalue.replace('[', 'new_status[').replace("]", "\"]")} = "
f"{rvalue.replace('[', '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):
@@ -371,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

@@ -7,6 +7,7 @@ class MideaLogType(IntEnum):
DEBUG = 1
WARN = 2
ERROR = 3
INFO = 4
class MideaLogger:
@@ -18,6 +19,8 @@ class MideaLogger:
log = f"[{device_id}] {log}"
if log_type == MideaLogType.DEBUG:
logging.getLogger(mod.__name__).debug(log)
elif log_type == MideaLogType.INFO:
logging.getLogger(mod.__name__).info(log)
elif log_type == MideaLogType.WARN:
logging.getLogger(mod.__name__).warning(log)
elif log_type == MideaLogType.ERROR:
@@ -27,6 +30,10 @@ class MideaLogger:
def debug(log, device_id=None):
MideaLogger._log(MideaLogType.DEBUG, log, device_id)
@staticmethod
def info(log, device_id=None):
MideaLogger._log(MideaLogType.INFO, log, device_id)
@staticmethod
def warning(log, device_id=None):
MideaLogger._log(MideaLogType.WARN, log, device_id)

View File

@@ -1,5 +1,5 @@
import os
import traceback
import lupa
import threading
import json
@@ -9,6 +9,23 @@ from .logger import MideaLogger
class LuaRuntime:
def __init__(self, file):
self._runtimes = lupa.lua51.LuaRuntime()
# 设置Lua路径包含cjson.lua和bit.lua的目录
lua_dir = os.path.dirname(os.path.abspath(file))
self._runtimes.execute(f'package.path = package.path .. ";{lua_dir}/?.lua"')
# 加载必需的Lua库
try:
self._runtimes.execute('require "cjson"')
except Exception as e:
MideaLogger.warning(f"Failed to load cjson: {e}")
try:
self._runtimes.execute('require "bit"')
except Exception as e:
MideaLogger.warning(f"Failed to load bit: {e}")
# 加载设备特定的Lua文件
string = f'dofile("{file}")'
self._runtimes.execute(string)
self._lock = threading.Lock()
@@ -70,6 +87,17 @@ class MideaCodec(LuaRuntime):
query_dict["control"]["bucket"] = prefix
else:
query_dict["control"]["bucket"] = "db"
# 针对T0x9C集成灶特殊处理
elif self._device_type == "T0x9C":
control_keys = list(append.keys())
if len(control_keys) > 0:
# 从第一个键名中提取前缀,例如从 'db_power' 中提取 'db'
first_key = control_keys[0]
prefix = first_key.split("_")[0]
else:
prefix = "total"
query_dict["control"]["type"] = prefix
json_str = json.dumps(query_dict)
MideaLogger.debug(f"LuaRuntime json_str {json_str}")
try:

View File

@@ -1,9 +1,11 @@
"""Data coordinator for Midea Auto Cloud integration."""
import logging
import traceback
from datetime import datetime, timedelta
from typing import NamedTuple
from attr import attributes
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.event import async_call_later
@@ -184,14 +186,35 @@ class MideaDataUpdateCoordinator(DataUpdateCoordinator[MideaDeviceData]):
async def async_set_attribute(self, attribute: str, value) -> None:
"""Set a device attribute."""
# 云端控制:构造 control 与 status携带当前状态作为上下文
await self.device.set_attribute(attribute, value)
self.device.attributes[attribute] = value
self.mute_state_update_for_a_while()
self.async_update_listeners()
attributes = {}
attributes[attribute] = value
await self.async_set_attributes(attributes)
async def async_set_attributes(self, attributes: dict) -> None:
"""Set multiple device attributes."""
# 云端控制:构造 control 与 status携带当前状态作为上下文
for c in self.device._calculate_set:
lvalue = c.get("lvalue")
rvalue = c.get("rvalue")
if lvalue and rvalue:
calculate = False
for s, v in attributes.items():
if rvalue.find(f"[{s}]") >= 0:
calculate = True
break
if calculate:
calculate_str1 = \
(f"{lvalue.replace('[', 'attributes[').replace("]", "\"]")} = "
f"{rvalue.replace('[', 'attributes[').replace(']', "\"]")}") \
.replace("[", "[\"")
try:
exec(calculate_str1)
except Exception as e:
traceback.print_exc()
MideaLogger.warning(
f"Calculation Error: {lvalue} = {rvalue}, calculate_str1: {calculate_str1}",
self._device_id
)
await self.device.set_attributes(attributes)
self.device.attributes.update(attributes)
self.mute_state_update_for_a_while()

View File

@@ -1,4 +1,5 @@
from homeassistant.const import Platform
from homeassistant.components.fan import DIRECTION_FORWARD, DIRECTION_REVERSE
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_WHOLE
DEVICE_MAPPING = {
"default": {
@@ -45,14 +46,7 @@ DEVICE_MAPPING = {
Platform.FAN: {
"fan": {
"power": "fan_power",
"speeds": [
{"fan_speed": "1"},
{"fan_speed": "2"},
{"fan_speed": "3"},
{"fan_speed": "4"},
{"fan_speed": "5"},
{"fan_speed": "6"},
],
"speeds": list({"fan_speed": value + 1} for value in range(0, 6)),
"preset_modes": {
"breathing_wind": {"fan_scene": "breathing_wind"},
"const_temperature": {"fan_scene": "const_temperature"},
@@ -64,5 +58,73 @@ DEVICE_MAPPING = {
}
}
}
},
"79010863": {
"rationale": ["off", "on"],
"queries": [{}],
"centralized": [],
"calculate": {
"get": [
{
"lvalue": "[device_fan_speed]",
"rvalue": "int((int([fan_speed])) / 20) + 1"
},
],
"set": [
{
"lvalue": "[fan_speed]",
"rvalue": "min(int((int([device_fan_speed]) - 1) * 20 + 1), 100)"
},
]
},
"entities": {
Platform.LIGHT: {
"light": {
"power": "led_power",
"brightness": {"brightness": [1, 100]},
"color_temp": {"color_temperature": [2700, 6500]},
"preset_modes": {
"work": {"led_scene_light": "work"},
"eating": {"led_scene_light": "eating"},
"film": {"led_scene_light": "film"},
"night": {"led_scene_light": "night"},
"ledmanual": {"led_scene_light": "ledmanual"},
}
}
},
Platform.FAN: {
"fan": {
"power": "fan_power",
"speeds": list({"device_fan_speed": value + 1} for value in range(0, 6)),
"preset_modes": {
"breathing_wind": {"fan_scene": "breathing_wind"},
"const_temperature": {"fan_scene": "const_temperature"},
"fanmanual": {"fan_scene": "fanmanual"},
"baby_wind": {"fan_scene": "baby_wind"},
"sleep_wind": {"fan_scene": "sleep_wind"},
"forest_wind": {"fan_scene": "forest_wind"}
},
"directions": {
DIRECTION_FORWARD: {"arround_dir": "1"},
DIRECTION_REVERSE: {"arround_dir": "0"},
}
}
},
Platform.CLIMATE: {
"thermostat": {
"power": "fan_power",
"hvac_modes": {
"off": {"fan_scene": "fanmanual"},
"auto": {"fan_scene": "const_temperature"},
},
"target_temperature": "const_temperature_value",
"current_temperature": "indoor_temperature",
"min_temp": 20,
"max_temp": 35,
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_WHOLE,
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfVolume, UnitOfTime, PERCENTAGE, PRECISION_HALVES, \
UnitOfEnergy, UnitOfPower, PRECISION_WHOLE
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": ["warm_target_temp", "boil_target_temp", "meate_select", "max_work_time", "warm_time_min"],
"entities": {
Platform.BINARY_SENSOR: {
"islack_water": {
"device_class": BinarySensorDeviceClass.PROBLEM,
}
},
Platform.NUMBER: {
"warm_time_min": {
"min": 0,
"max": 480,
"step": 60
},
"max_work_time": {
"min": 0,
"max": 12,
"step": 1
},
"warm_target_temp": {
"min": 0,
"max": 100,
"step": 1
},
"boil_target_temp": {
"min": 0,
"max": 100,
"step": 1
},
},
Platform.SELECT: {
"work_mode": {
"options": {
"取消": {"work_mode": "0", "work_switch": "cancel"},
"烧水": {"work_mode": "1", "work_switch": "start"},
"除氯": {"work_mode": "2", "work_switch": "start"},
"花草茶": {"work_mode": "4", "work_switch": "start"},
"养生汤": {"work_mode": "5", "work_switch": "start"},
}
}
},
Platform.SENSOR: {
"current_temp": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfVolume, UnitOfTime, PERCENTAGE, PRECISION_HALVES, \
UnitOfEnergy, UnitOfPower, PRECISION_WHOLE, UnitOfPressure
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.SENSOR: {
"work_status": {
"device_class": SensorDeviceClass.ENUM,
}
}
}
}
}

View File

@@ -0,0 +1,116 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfVolume, UnitOfTime, PERCENTAGE, PRECISION_HALVES, \
UnitOfEnergy, UnitOfPower, PRECISION_WHOLE, UnitOfPressure
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.NUMBER: {
"b6_lightness": {
"min": 0,
"max": 100,
"step": 1
}
},
Platform.SWITCH: {
"total_power": {
"device_class": SwitchDeviceClass.SWITCH,
"translation_key": "power"
},
"total_lock": {
"device_class": SwitchDeviceClass.SWITCH,
"translation_key": "lock"
},
"b6_light": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.SELECT: {
"b3_function_control":{
"options": {
"关闭": {"b3_work_cabinet_control": 1, "b3_function_control": 1},
"消毒": {"b3_work_cabinet_control": 1, "b3_function_control": 2, "b3_work_destination_time": 60},
"烘干": {"b3_work_cabinet_control": 1, "b3_function_control": 4, "b3_work_destination_time": 120},
}
},
"b6_gear": {
"options": {
"": {"b6_gear": "0"},
"": {"b6_gear": "1"},
"": {"b6_gear": "2"},
"": {"b6_gear": "3"},
"爆炒": {"b6_gear": "4"}
}
},
"b6_power_on_light": {
"options": {
"": {"b6_power_on_light": "off", "b6_setting": "power_on_light"},
"": {"b6_power_on_light": "on", "b6_setting": "power_on_light"}
}
},
"b6_lock": {
"options": {
"": {"b6_lock": "off", "b6_setting": "lock"},
"": {"b6_lock": "on", "b6_setting": "lock"}
}
},
"b6_smoke_stove_linkage_gear": {
"options": {
"1档": {"b6_smoke_stove_linkage_gear": 1, "b6_setting": "smoke_stove_linkage"},
"2档": {"b6_smoke_stove_linkage_gear": 2, "b6_setting": "smoke_stove_linkage"},
"3档": {"b6_smoke_stove_linkage_gear": 3, "b6_setting": "smoke_stove_linkage"},
}
},
"b6_delay_gear_linkage_gear": {
"options": {
"1档": {"b6_delay_gear_linkage_gear": 1, "b6_setting": "delay_gear_linkage"},
"2档": {"b6_delay_gear_linkage_gear": 2, "b6_setting": "delay_gear_linkage"},
"3档": {"b6_delay_gear_linkage_gear": 3, "b6_setting": "delay_gear_linkage"},
}
},
"b6_delay_time_value": {
"options": {
"": {"b6_delay_time": "off", "b6_setting": "delay_time"},
"1分钟": {"b6_delay_time": "on", "b6_delay_time_value": 1, "b6_setting": "delay_time"},
"2分钟": {"b6_delay_time": "on", "b6_delay_time_value": 2, "b6_setting": "delay_time"},
"3分钟": {"b6_delay_time": "on", "b6_delay_time_value": 3, "b6_setting": "delay_time"},
"4分钟": {"b6_delay_time": "on", "b6_delay_time_value": 4, "b6_setting": "delay_time"},
"5分钟": {"b6_delay_time": "on", "b6_delay_time_value": 5, "b6_setting": "delay_time"},
"6分钟": {"b6_delay_time": "on", "b6_delay_time_value": 6, "b6_setting": "delay_time"},
"7分钟": {"b6_delay_time": "on", "b6_delay_time_value": 7, "b6_setting": "delay_time"},
"8分钟": {"b6_delay_time": "on", "b6_delay_time_value": 8, "b6_setting": "delay_time"},
"9分钟": {"b6_delay_time": "on", "b6_delay_time_value": 9, "b6_setting": "delay_time"},
"10分钟": {"b6_delay_time": "on", "b6_delay_time_value": 10, "b6_setting": "delay_time"},
}
}
},
Platform.SENSOR: {
"b3_upstair_status": {
"device_class": SensorDeviceClass.ENUM,
},
"b6_wind_pressure": {
"device_class": SensorDeviceClass.PRESSURE,
"unit_of_measurement": UnitOfPressure.PA,
"state_class": SensorStateClass.MEASUREMENT
},
"b7_left_status": {
"device_class": SensorDeviceClass.ENUM,
},
"b7_left_gear": {
"device_class": SensorDeviceClass.ENUM,
},
"b7_right_status": {
"device_class": SensorDeviceClass.ENUM,
},
"b7_right_gear": {
"device_class": SensorDeviceClass.ENUM,
}
}
}
}
}

View File

@@ -1,4 +1,5 @@
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES, PRECISION_WHOLE
from homeassistant.const import Platform, UnitOfTemperature, PRECISION_HALVES, PRECISION_WHOLE, \
CONCENTRATION_PARTS_PER_MILLION, 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
@@ -6,19 +7,13 @@ from homeassistant.components.switch import SwitchDeviceClass
DEVICE_MAPPING = {
"default": {
"rationale": ["off", "on"],
"queries": [{}, {"query_type":"run_status"}],
"queries": [{}, {"query_type":"run_status"}, {"query_type":"indoor_humidity"}, {"query_type":"indoor_temperature"}],
"centralized": [],
"entities": {
Platform.FAN: {
"fan": {
"power": "new_wind_machine",
"speeds": [
{"fresh_air_fan_speed": 20},
{"fresh_air_fan_speed": 40},
{"fresh_air_fan_speed": 60},
{"fresh_air_fan_speed": 80},
{"fresh_air_fan_speed": 100},
],
"speeds": list({"fresh_air_fan_speed": value + 1} for value in range(0, 100)),
"preset_modes": {
"heat_exchange": {
"fresh_air_mode": 1,
@@ -113,6 +108,128 @@ DEVICE_MAPPING = {
},
"aux_heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"follow_body_sense_enable": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [0, 1]
},
},
Platform.SENSOR: {
"mode": {
"device_class": SensorDeviceClass.ENUM,
},
"indoor_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"indoor_humidity": {
"device_class": SensorDeviceClass.HUMIDITY,
"unit_of_measurement": "%",
"state_class": SensorStateClass.MEASUREMENT
}
}
}
},
"22259015": {
"rationale": ["off", "on"],
"queries": [{}, {"query_type": "run_status"}, {"query_type": "module_30"}, {"query_type": "module_31"},
{"query_type": "module_32"}],
"centralized": [],
"calculate": {
"get": [
{
"lvalue": "[indoor_humidity]",
"rvalue": "[humidity_value]"
},
{
"lvalue": "[indoor_temperature]",
"rvalue": "[t1_temp]"
},
{
"lvalue": "[co2_value]",
"rvalue": "[co2_concentration]"
},
{
"lvalue": "[pm25_value]",
"rvalue": "[dust_co2]"
}
],
"set": []
},
"entities": {
Platform.CLIMATE: {
"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",
"cool_power_saving": 0,
"strong_wind": "off"
},
"eco": {"eco": "on", "cool_power_saving": 1},
"comfort": {"comfort_power_save": "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",
"pre_mode": "mode",
"aux_heat": "ptc",
"min_temp": 17,
"max_temp": 30,
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_HALVES,
}
},
Platform.SWITCH: {
"dry": {
"device_class": SwitchDeviceClass.SWITCH,
},
"prevent_straight_wind": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [0, 1]
},
"aux_heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"follow_body_sense_enable": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [0, 1]
},
"manul_fresh_air": {
"device_class": SwitchDeviceClass.SWITCH,
},
"auto_comfort_fresh_air": {
"device_class": SwitchDeviceClass.SWITCH,
},
"auto_fresh_off_co2": {
"device_class": SwitchDeviceClass.SWITCH,
},
"comfort_fresh_air": {
"device_class": SwitchDeviceClass.SWITCH,
}
},
Platform.SENSOR: {
@@ -128,6 +245,190 @@ DEVICE_MAPPING = {
"device_class": SensorDeviceClass.HUMIDITY,
"unit_of_measurement": "%",
"state_class": SensorStateClass.MEASUREMENT
},
"co2_value": {
"device_class": SensorDeviceClass.CO2,
"unit_of_measurement": CONCENTRATION_PARTS_PER_MILLION,
"state_class": SensorStateClass.MEASUREMENT,
},
"pm25_value": {
"device_class": SensorDeviceClass.PM25,
"unit_of_measurement": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"state_class": SensorStateClass.MEASUREMENT,
},
}
}
},
("22019031", "22019035", "22019045"): {
"rationale": ["off", "on"],
"queries": [{}, {"query_type":"fresh_air"}, {"query_type":"indoor_humidity"}, {"query_type":"indoor_temperature"}, {"query_type":"outdoor_temperature"}],
"centralized": [],
"entities": {
Platform.FAN: {
"fan": {
"power": "fresh_air",
"speeds": [
{"fresh_air": "on", "fresh_air_fan_speed": 40},
{"fresh_air": "on", "fresh_air_fan_speed": 60},
{"fresh_air": "on", "fresh_air_fan_speed": 80},
{"fresh_air": "on", "fresh_air_fan_speed": 100}
],
}
},
Platform.CLIMATE: {
"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",
"cool_power_saving": 0,
# "comfort_sleep": "off",
"strong_wind": "off"
},
"eco": {"eco": "on", "cool_power_saving": 1},
"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",
"pre_mode": "mode",
"aux_heat": "ptc",
"min_temp": 17,
"max_temp": 30,
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_HALVES,
}
},
Platform.SWITCH: {
"fresh_air_remove_odor": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [0, 1],
},
"dry": {
"device_class": SwitchDeviceClass.SWITCH,
},
"prevent_straight_wind": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [0, 1]
},
"aux_heat": {
"device_class": SwitchDeviceClass.SWITCH,
},
"follow_body_sense_enable": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [0, 1]
},
},
Platform.SENSOR: {
"mode": {
"device_class": SensorDeviceClass.ENUM,
},
"indoor_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"outdoor_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"fresh_air_temp": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"indoor_humidity": {
"device_class": SensorDeviceClass.HUMIDITY,
"unit_of_measurement": "%",
"state_class": SensorStateClass.MEASUREMENT
}
}
}
},
"17497071": {
"rationale": ["off", "on"],
"queries": [{}, {"query_type": "water_model_run_status"}],
"centralized": [],
"entities": {
Platform.CLIMATE: {
"thermostat": {
"power": "water_model_power",
"hvac_modes": {
"off": {"water_model_power": "off"},
"heat": {"water_model_power": "on"},
},
"preset_modes": {
"auto": {"water_model_temperature_auto": "on", "water_temp_linkage_switch": 0},
"link": {"water_model_temperature_auto": "off", "water_temp_linkage_switch": 1},
"manual": {"water_model_temperature_auto": "off", "water_temp_linkage_switch": 0}
},
"target_temperature": "water_model_temperature_set",
"current_temperature": ["temperature", "small_temperature"],
"pre_mode": "mode",
"aux_heat": "water_model_ptc",
"min_temp": 25,
"max_temp": 60,
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_HALVES,
}
},
Platform.SWITCH: {
"water_model_power_save": {
"device_class": SwitchDeviceClass.SWITCH,
},
"water_model_go_out": {
"device_class": SwitchDeviceClass.SWITCH,
},
"water_model_ptc": {
"device_class": SwitchDeviceClass.SWITCH,
"translation_key": "aux_heat",
},
},
Platform.SENSOR: {
"tw1_in_water_temp": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"tw1_out_water_temp": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"humidity": {
"device_class": SensorDeviceClass.HUMIDITY,
"unit_of_measurement": "%",
"state_class": SensorStateClass.MEASUREMENT
}
}
}
@@ -166,13 +467,7 @@ DEVICE_MAPPING = {
Platform.FAN: {
"fan": {
"power": "fresh_air",
"speeds": [
{"fresh_air": 3, "fresh_air_fan_speed": 20},
{"fresh_air": 3, "fresh_air_fan_speed": 40},
{"fresh_air": 3, "fresh_air_fan_speed": 60},
{"fresh_air": 3, "fresh_air_fan_speed": 80},
{"fresh_air": 3, "fresh_air_fan_speed": 100},
],
"speeds": list({"fresh_air": 3, "fresh_air_fan_speed": value + 1} for value in range(0, 100)),
"preset_modes": {
"heat_exchange": {
"fresh_air_mode": 1,
@@ -264,16 +559,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,
}
},
@@ -369,7 +661,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": "float([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

@@ -0,0 +1,77 @@
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
DEVICE_MAPPING = {
"default": {
"rationale": ["off", "on"],
"queries": [{}],
"centralized": [],
"entities": {
Platform.CLIMATE: {
"Zone1": {
"translation_key": "zone1",
"power": "zone1_power_state",
"hvac_modes": {
"off": {"zone1_power_state": "off"},
"heat": {"zone1_power_state": "on"},
},
"target_temperature": "room_temp_set",
"current_temperature": "T4",
"min_temp": "room_min_set_temp",
"max_temp": "room_max_set_temp",
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_HALVES,
},
"DHW": {
"translation_key": "dhw",
"power": "dhw_power_state",
"hvac_modes": {
"off": {"dhw_power_state": "off"},
"heat": {"dhw_power_state": "on"},
},
"target_temperature": "dhw_temp_set",
"current_temperature": "T4",
"min_temp": "dhw_min_set_temp",
"max_temp": "dhw_max_set_temp",
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_HALVES,
}
},
Platform.SWITCH: {
"fastdhw_state": {
"device_class": SwitchDeviceClass.SWITCH,
"translation_key": "fastdhw_state",
},
"forcetbh_state": {
"device_class": SwitchDeviceClass.SWITCH,
"translation_key": "forcetbh_state",
},
},
Platform.SENSOR: {
"run_mode_set": {
"device_class": SensorDeviceClass.ENUM,
"translation_key": "mode",
},
"room_temp_set": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT,
"translation_key": "room_temperature",
},
"t4": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT,
"translation_key": "outside_temperature",
},
"tank_actual_temp":{
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"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

@@ -0,0 +1,69 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PERCENTAGE, PRECISION_HALVES, PRECISION_WHOLE
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
DEVICE_MAPPING = {
"default": {
"manufacturer": "美的",
"rationale": ["off", "on"],
"queries": [{}],
"calculate": {
"get": [
{
"lvalue": "[temperature]",
"rvalue": "float(([set_temperature] - 106) / 74 * 37 + 38)"
},
{
"lvalue": "[cur_temperature]",
"rvalue": "float(([water_box_temperature] - 106) / 74 * 37 + 38)"
}
],
"set": [
{
"lvalue": "[set_temperature]",
"rvalue": "float(([temperature] - 38) / 37 * 74 + 106)"
},
]
},
"centralized": [],
"entities": {
Platform.CLIMATE: {
"water_heater": {
"power": "power",
"hvac_modes": {
"off": {"power": "off"},
"heat": {"power": "on"},
},
"preset_modes": {
"标准": {"mode": "standard"},
"节能": {"mode": "energy"},
"速热": {"mode": "compatibilizing"},
},
"target_temperature": "temperature",
"current_temperature": "cur_temperature",
"min_temp": 38,
"max_temp": 75,
"temperature_unit": UnitOfTemperature.CELSIUS,
"precision": PRECISION_WHOLE,
}
},
Platform.SWITCH: {
"power": {
"device_class": SwitchDeviceClass.SWITCH,
},
"mute": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.SENSOR: {
"cur_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
},
}
}
}

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

@@ -0,0 +1,150 @@
from homeassistant.const import Platform, UnitOfElectricPotential, UnitOfTemperature, UnitOfTime
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": [{}],
"calculate": {
"get": [
{
"lvalue": "[remain_time]",
"rvalue": "[remain_time]"
}
],
"set": {
}
},
"entities": {
Platform.NUMBER: {
"temperature": {
"min": 0,
"max": 100,
"step": 1
},
"detergent": {
"min": 0,
"max": 5,
"step": 1
},
"softener": {
"min": 0,
"max": 5,
"step": 1
},
"dehydration_speed": {
"min": 0,
"max": 1600,
"step": 100
},
"soak_time": {
"min": 0,
"max": 40,
"step": 10
},
"wash_time": {
"min": 0,
"max": 20,
"step": 1
},
"rinse_count": {
"min": 0,
"max": 3,
"step": 1
},
"dehydration_time": {
"min": 0,
"max": 9,
"step": 1
},
"wash_level": {
"min": 0,
"max": 8,
"step": 1
},
"rinse_level": {
"min": 0,
"max": 8,
"step": 1
},
"wash_strength": {
"min": 1,
"max": 4,
"step": 1
},
},
Platform.BINARY_SENSOR: {
"softener_lack": {
"device_class": BinarySensorDeviceClass.PROBLEM,
},
"detergent_lack": {
"device_class": BinarySensorDeviceClass.PROBLEM,
},
"door_opened": {
"device_class": BinarySensorDeviceClass.PROBLEM,
},
"bucket_water_overheating": {
"device_class": BinarySensorDeviceClass.PROBLEM,
},
},
Platform.SWITCH: {
"power": {
"device_class": SwitchDeviceClass.SWITCH,
},
"control_status": {
"rationale": ["pause", "start"],
},
"lock": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.SELECT: {
"mode": {
"options": {
"normal": {"mode": "normal"},
"dry": {"mode": "dry"},
"continus": {"mode": "continus"},
}
},
"program": {
"options": {
"标准": {"program": "standard"},
"速洗": {"program": "fast"},
"家纺": {"program": "blanket"},
"羊毛": {"program": "wool"},
"浸洗": {"program": "embathe"},
"记忆": {"program": "memory"},
"童装": {"program": "child"},
"强洗": {"program": "strong_wash"},
"桶自洁": {"program": "bucket_self_clean"},
}
},
},
Platform.SENSOR: {
"running_status": {
"device_class": SensorDeviceClass.ENUM
},
"appointment_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
"state_class": SensorStateClass.MEASUREMENT
},
"remain_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
"state_class": SensorStateClass.MEASUREMENT
},
"progress": {
"device_class": SensorDeviceClass.BATTERY,
"unit_of_measurement": "%",
"state_class": SensorStateClass.MEASUREMENT
},
"error_code": {
"device_class": SensorDeviceClass.ENUM
},
}
}
}
}

View File

@@ -18,6 +18,48 @@ DEVICE_MAPPING = {
}
},
"entities": {
Platform.NUMBER: {
"temperature": {
"min": 0,
"max": 100,
"step": 1
},
"detergent": {
"min": 0,
"max": 5,
"step": 1
},
"softener": {
"min": 0,
"max": 5,
"step": 1
},
"dehydration_time": {
"min": 0,
"max": 9,
"step": 1
},
"dehydration_speed": {
"min": 0,
"max": 1600,
"step": 100
},
"dirty_degree": {
"min": 0,
"max": 4,
"step": 1
},
"soak_count": {
"min": 0,
"max": 5,
"step": 1
},
"wash_time": {
"min": 0,
"max": 20,
"step": 1
},
},
Platform.BINARY_SENSOR: {
"softener_lack": {
"device_class": BinarySensorDeviceClass.PROBLEM,
@@ -66,19 +108,6 @@ DEVICE_MAPPING = {
},
},
Platform.SELECT: {
"dehydration_speed": {
"options": {
"0": {"dehydration_speed": "0"},
"400": {"dehydration_speed": "400"},
"600": {"dehydration_speed": "600"},
"800": {"dehydration_speed": "800"},
"1000": {"dehydration_speed": "1000"},
"1200": {"dehydration_speed": "1200"},
"1400": {"dehydration_speed": "1400"},
"1600": {"dehydration_speed": "1600"},
"1300": {"dehydration_speed": "1300"}
}
},
"mode": {
"options": {
"normal": {"mode": "normal"},
@@ -190,68 +219,11 @@ DEVICE_MAPPING = {
"hanfu_wash": {"program": "hanfu_wash"}
}
},
"temperature": {
"options": {
"0": {"temperature": "0"},
"20": {"temperature": "20"},
"30": {"temperature": "30"},
"40": {"temperature": "40"},
"50": {"temperature": "50"},
"60": {"temperature": "60"},
"70": {"temperature": "70"},
"90": {"temperature": "90"},
"95": {"temperature": "95"}
}
},
"detergent": {
"options": {
"0": {"detergent": "0"},
"1": {"detergent": "1"},
"2": {"detergent": "2"},
"3": {"detergent": "3"},
"4": {"detergent": "4"},
"5": {"detergent": "5"}
}
},
"softener": {
"options": {
"0": {"softener": "0"},
"1": {"softener": "1"},
"2": {"softener": "2"},
"3": {"softener": "3"},
"4": {"softener": "4"},
"5": {"softener": "5"}
}
},
"dirty_degree": {
"options": {
"0": {"dirty_degree": "0"},
"1": {"dirty_degree": "1"},
"2": {"dirty_degree": "2"},
"3": {"dirty_degree": "3"},
"4": {"dirty_degree": "4"}
}
},
"soak_count": {
"options": {
"0": {"soak_count": "0"},
"1": {"soak_count": "1"},
"2": {"soak_count": "2"},
"3": {"soak_count": "3"},
"4": {"soak_count": "4"},
"5": {"soak_count": "5"}
}
}
},
Platform.SENSOR: {
"running_status": {
"device_class": SensorDeviceClass.ENUM
},
"wash_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
"state_class": SensorStateClass.MEASUREMENT
},
"appointment_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
@@ -273,11 +245,6 @@ DEVICE_MAPPING = {
"unit_of_measurement": "%",
"state_class": SensorStateClass.MEASUREMENT
},
"dehydration_time_value": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
"state_class": SensorStateClass.MEASUREMENT
},
"customize_machine_cycle": {
"device_class": SensorDeviceClass.ENUM
},
@@ -315,11 +282,6 @@ DEVICE_MAPPING = {
"active_oxygen": {
"device_class": SensorDeviceClass.ENUM
},
"dehydration_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
"state_class": SensorStateClass.MEASUREMENT
},
"cloud_cycle_jiepai_time2": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,

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
@@ -10,22 +10,32 @@ DEVICE_MAPPING = {
"queries": [{}],
"centralized": [],
"entities": {
Platform.WATER_HEATER: {
Platform.NUMBER: {
"water_quality": {
"min": 0,
"max": 3,
"step": 1
}
},
Platform.CLIMATE: {
"water_heater": {
"power": "power",
"operation_list": {
"hvac_modes": {
"off": {"power": "off"},
"on": {"power": "on"},
"heat": {"power": "on"},
},
"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: {
"power": {
"device_class": SwitchDeviceClass.SWITCH,
},
"ti_protect": {
"device_class": SwitchDeviceClass.SWITCH,
},
@@ -47,33 +57,8 @@ DEVICE_MAPPING = {
"water_flow": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.SELECT: {
"water_quality": {
"options": {
"0": {"water_quality": 0},
"1": {"water_quality": 1},
"2": {"water_quality": 2},
"3": {"water_quality": 3}
}
},
"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: {
@@ -105,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,
@@ -148,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,
@@ -195,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

@@ -10,9 +10,14 @@ DEVICE_MAPPING = {
"centralized": [],
"entities": {
Platform.SWITCH: {
"power": {
"device_class": SwitchDeviceClass.SWITCH,
},
"bubble": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": [0, 1]
},
"cold_water": {
"device_class": SwitchDeviceClass.SWITCH,
},
"cold_water_master": {
"device_class": SwitchDeviceClass.SWITCH,

View File

@@ -0,0 +1,70 @@
from homeassistant.components.switch import SwitchDeviceClass
from homeassistant.const import Platform, UnitOfElectricPotential, UnitOfTemperature, UnitOfTime, UnitOfPressure
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
DEVICE_MAPPING = {
"default": {
"rationale": ["off", "on"],
"queries": [{}],
"entities": {
Platform.SWITCH: {
"winter_mode": {
"device_class": SwitchDeviceClass.SWITCH,
},
"summer_mode": {
"device_class": SwitchDeviceClass.SWITCH,
},
"power": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.SENSOR: {
"heat_exchanger": {
"device_class": SensorDeviceClass.ENUM,
},
"fan_type": {
"device_class": SensorDeviceClass.ENUM,
},
"ignitor_output": {
"device_class": SensorDeviceClass.ENUM,
},
"fan_output": {
"device_class": SensorDeviceClass.ENUM,
},
"current_heat_set_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"current_bath_set_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"heat_out_water_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"bath_out_water_temperature": {
"device_class": SensorDeviceClass.TEMPERATURE,
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"water_gage": {
"unit_of_measurement": UnitOfPressure.PA,
"state_class": SensorStateClass.MEASUREMENT
},
},
Platform.BINARY_SENSOR: {
"heating_work": {
"device_class": BinarySensorDeviceClass.RUNNING
},
"bathing_work": {
"device_class": BinarySensorDeviceClass.RUNNING
},
},
}
}
}

View File

@@ -66,30 +66,30 @@ DEVICE_MAPPING = {
Platform.SELECT: {
"mode": {
"options": {
"Rice": {"mode": "essence_rice", "work_status": "cooking"},
"Porridge": {"mode": "gruel", "work_status": "cooking"},
"精华饭": {"mode": "essence_rice", "work_status": "cooking"},
"稀饭": {"mode": "gruel", "work_status": "cooking"},
"热饭": {"mode": "heat_rice", "work_status": "cooking"},
"Congee": {"mode": "boil_congee", "work_status": "cooking"},
"Soup": {"mode": "cook_soup", "work_status": "cooking"},
"Steam": {"mode": "stewing", "work_status": "cooking"},
"煮粥": {"mode": "boil_congee", "work_status": "cooking"},
"煲汤": {"mode": "cook_soup", "work_status": "cooking"},
"蒸煮": {"mode": "stewing", "work_status": "cooking"},
}
},
"rice_type": {
"options": {
"None": {"rice_type": "none"},
"Northeast rice": {"rice_type": "northeast"},
"Long-grain rice": {"rice_type": "longrain"},
"Fragrant rice": {"rice_type": "fragrant"},
"Wuchang rice": {"rice_type": "five"},
"": {"rice_type": "none"},
"东北大米": {"rice_type": "northeast"},
"长粒米": {"rice_type": "longrain"},
"香米": {"rice_type": "fragrant"},
"五常大米": {"rice_type": "five"},
}
},
"work_status": {
"options": {
"Stop": {"work_status": "cancel"},
"Cooking": {"work_status": "cooking"},
"Warming": {"work_status": "keep_warm"},
"Soaking": {"work_status": "awakening_rice"},
"Delay": {"work_status": "schedule"}
"停止": {"work_status": "cancel"},
"烹饪": {"work_status": "cooking"},
"保温": {"work_status": "keep_warm"},
"醒米": {"work_status": "awakening_rice"},
"预约": {"work_status": "schedule"}
}
}
}

View File

@@ -1,5 +1,5 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, UnitOfElectricPotential, \
UnitOfVolume, UnitOfMass
UnitOfVolume, UnitOfMass, PERCENTAGE
from homeassistant.components.sensor import SensorStateClass, SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
@@ -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,
},
@@ -129,11 +117,16 @@ DEVICE_MAPPING = {
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"state_class": SensorStateClass.MEASUREMENT
},
"water_consumption_ml": {
"water_consumption": {
"device_class": SensorDeviceClass.VOLUME,
"unit_of_measurement": UnitOfVolume.LITERS,
"state_class": SensorStateClass.TOTAL_INCREASING
},
"water_consumption_ml": {
"device_class": SensorDeviceClass.VOLUME,
"unit_of_measurement": UnitOfVolume.MILLILITERS,
"state_class": SensorStateClass.TOTAL_INCREASING
},
"keep_warm_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
@@ -258,11 +251,23 @@ DEVICE_MAPPING = {
"velocity": {
"device_class": SensorDeviceClass.ENUM,
},
"error": {
"device_class": SensorDeviceClass.ENUM,
"life_1": {
"device_class": SensorDeviceClass.BATTERY,
"unit_of_measurement": PERCENTAGE,
},
"category": {
"device_class": SensorDeviceClass.ENUM,
"life_2": {
"device_class": SensorDeviceClass.BATTERY,
"unit_of_measurement": PERCENTAGE,
},
"in_tds": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "mg/L",
"state_class": SensorStateClass.MEASUREMENT
},
"out_tds": {
"device_class": SensorDeviceClass.WATER,
"unit_of_measurement": "mg/L",
"state_class": SensorStateClass.MEASUREMENT
},
}
}

View File

@@ -1,4 +1,5 @@
from homeassistant.const import Platform, UnitOfTemperature, UnitOfTime, PERCENTAGE
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
@@ -23,17 +24,7 @@ DEVICE_MAPPING = {
Platform.FAN: {
"fan": {
"power": "power",
"speeds": [
{"gear": "1"},
{"gear": "2"},
{"gear": "3"},
{"gear": "4"},
{"gear": "5"},
{"gear": "6"},
{"gear": "7"},
{"gear": "8"},
{"gear": "9"},
],
"speeds": list({"gear": value + 1} for value in range(0, 9)),
"oscillate": "swing",
"preset_modes": {
"normal": {"mode": "normal"},
@@ -102,29 +93,9 @@ DEVICE_MAPPING = {
"device_class": SensorDeviceClass.ENUM,
"state_class": SensorStateClass.MEASUREMENT
},
"timer_off_hour": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.HOURS,
"state_class": SensorStateClass.MEASUREMENT
},
"timer_off_minute": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
"state_class": SensorStateClass.MEASUREMENT
},
"timer_on_hour": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.HOURS,
"state_class": SensorStateClass.MEASUREMENT
},
"timer_on_minute": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.MINUTES,
"state_class": SensorStateClass.MEASUREMENT
},
"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": {
@@ -153,5 +124,86 @@ DEVICE_MAPPING = {
}
}
}
},
"xxxxxx": {
"rationale": ["off", "on"],
"queries": [{}],
"centralized": [
"power", "gear"
],
"entities": {
Platform.SWITCH: {
"display_on_off": {
"device_class": SwitchDeviceClass.SWITCH,
"rationale": ["on", "off"]
},
"anion": {
"device_class": SwitchDeviceClass.SWITCH,
},
"temp_wind_switch": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.FAN: {
"fan": {
"power": "power",
"speeds": list({"gear": value + 1} for value in range(0, 100)),
"preset_modes": {
"self_selection": {"mode": "self_selection"},
"sleeping_wind": {"mode": "sleeping_wind"},
"purified_wind": {"mode": "purified_wind"}
}
}
},
Platform.SELECT: {
"voice": {
"options": {
"open_buzzer": {"voice": "open_buzzer"},
"close_buzzer": {"voice": "close_buzzer"},
"mute": {"voice": "mute"}
}
},
"lr_shake_switch": {
"options": {
"off": {"lr_shake_switch": "off"},
"default": {"lr_shake_switch": "default"},
"normal": {"lr_shake_switch": "normal"},
}
},
"ud_shake_switch": {
"options": {
"off": {"ud_shake_switch": "off"},
"default": {"ud_shake_switch": "default"},
"normal": {"ud_shake_switch": "normal"},
}
},
},
Platform.SENSOR: {
"real_gear": {
"device_class": SensorDeviceClass.ENUM,
"state_class": SensorStateClass.MEASUREMENT
},
"dust_life_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.HOURS,
"state_class": SensorStateClass.MEASUREMENT
},
"filter_life_time": {
"device_class": SensorDeviceClass.DURATION,
"unit_of_measurement": UnitOfTime.HOURS,
"state_class": SensorStateClass.MEASUREMENT
},
"current_angle": {
"device_class": SensorDeviceClass.WIND_DIRECTION,
"unit_of_measurement": DEGREE,
"state_class": SensorStateClass.MEASUREMENT
},
"target_angle": {
"device_class": SensorDeviceClass.WIND_DIRECTION,
"unit_of_measurement": DEGREE,
"state_class": SensorStateClass.MEASUREMENT
}
}
}
}
}

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

@@ -54,7 +54,18 @@ class MideaFanEntity(MideaEntity, FanEntity):
)
self._key_power = self._config.get("power")
self._key_preset_modes = self._config.get("preset_modes")
self._key_speeds = self._config.get("speeds")
speeds_config = self._config.get("speeds")
# 处理范围形式的 speeds 配置: {"key": "gear", "value": [1, 9]}
if isinstance(speeds_config, dict) and "key" in speeds_config and "value" in speeds_config:
key_name = speeds_config["key"]
value_range = speeds_config["value"]
if isinstance(value_range, list) and len(value_range) == 2:
start, end = value_range[0], value_range[1]
self._key_speeds = [{key_name: str(i)} for i in range(start, end + 1)]
else:
self._key_speeds = speeds_config
else:
self._key_speeds = speeds_config
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
@@ -80,10 +91,14 @@ class MideaFanEntity(MideaEntity, FanEntity):
@property
def preset_modes(self):
if self._key_preset_modes is None:
return None
return list(self._key_preset_modes.keys())
@property
def preset_mode(self):
if self._key_preset_modes is None:
return None
return self._dict_get_selected(self._key_preset_modes)
@property
@@ -97,6 +112,10 @@ class MideaFanEntity(MideaEntity, FanEntity):
def oscillating(self):
return self._get_status_on_off(self._key_oscillate)
@property
def current_direction(self):
return self._dict_get_selected(self._key_directions)
async def async_turn_on(
self,
percentage: int | None = None,
@@ -136,3 +155,9 @@ class MideaFanEntity(MideaEntity, FanEntity):
if self.oscillating != oscillating:
await self._async_set_status_on_off(self._key_oscillate, oscillating)
async def async_set_direction(self, direction: str):
if not self._key_directions:
return
new_status = self._key_directions.get(direction)
if new_status:
await self.async_set_attributes(new_status)

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.14"
"version": "v0.1.26"
}

View File

@@ -125,7 +125,7 @@ class MideaEntity(CoordinatorEntity[MideaDataUpdateCoordinator], Entity):
@property
def device_attributes(self) -> dict:
"""Return device attributes."""
return self.coordinator.data.attributes
return self.coordinator.data.attributes if self.coordinator.data else {}
@property
def available(self) -> bool:

View File

@@ -0,0 +1,117 @@
from homeassistant.components.number import NumberEntity
from homeassistant.const import Platform
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .core.logger import MideaLogger
from .midea_entity import MideaEntity
from . import load_device_config
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up number entities for Midea devices."""
account_bucket = hass.data.get(DOMAIN, {}).get("accounts", {}).get(config_entry.entry_id)
if not account_bucket:
async_add_entities([])
return
device_list = account_bucket.get("device_list", {})
coordinator_map = account_bucket.get("coordinator_map", {})
devs = []
for device_id, info in device_list.items():
device_type = info.get("type")
sn8 = info.get("sn8")
config = await load_device_config(hass, device_type, sn8) or {}
entities_cfg = (config.get("entities") or {}).get(Platform.NUMBER, {})
manufacturer = config.get("manufacturer")
rationale = config.get("rationale")
coordinator = coordinator_map.get(device_id)
device = coordinator.device if coordinator else None
for entity_key, ecfg in entities_cfg.items():
devs.append(MideaNumberEntity(
coordinator, device, manufacturer, rationale, entity_key, ecfg
))
async_add_entities(devs)
class MideaNumberEntity(MideaEntity, NumberEntity):
"""Midea number entity."""
def __init__(self, coordinator, device, manufacturer, rationale, entity_key, config):
super().__init__(
coordinator,
device.device_id,
device.device_name,
f"T0x{device.device_type:02X}",
device.sn,
device.sn8,
device.model,
entity_key,
device=device,
manufacturer=manufacturer,
rationale=rationale,
config=config,
)
# 从配置中读取数值范围,如果没有则使用默认值
self._min_value = self._config.get("min", 0.0)
self._max_value = self._config.get("max", 100.0)
self._step = self._config.get("step", 1.0)
self._mode = self._config.get("mode", "auto") # auto, box, slider
@property
def native_value(self) -> float | None:
"""Return the current value."""
# Use attribute from config if available, otherwise fall back to entity_key
attribute = self._config.get("attribute", self._entity_key)
value = self._get_nested_value(attribute)
if value is None:
return None
# 确保返回的是数值类型
try:
return float(value)
except (ValueError, TypeError):
MideaLogger.warning(
f"Failed to convert value '{value}' to float for number entity {self._entity_key}"
)
return None
@property
def native_min_value(self) -> float:
"""Return the minimum value."""
return float(self._min_value)
@property
def native_max_value(self) -> float:
"""Return the maximum value."""
return float(self._max_value)
@property
def native_step(self) -> float:
"""Return the step value."""
return float(self._step)
@property
def mode(self) -> str:
"""Return the mode of the number entity."""
return self._mode
async def async_set_native_value(self, value: float) -> None:
"""Set the value of the number entity."""
# 确保值在有效范围内
value = max(self._min_value, min(self._max_value, value))
# Use attribute from config if available, otherwise fall back to entity_key
attribute = self._config.get("attribute", self._entity_key)
# 如果配置中指定了转换函数或映射,可以在这里处理
# 否则直接设置属性值
await self.async_set_attribute(attribute, str(int(value)))

View File

@@ -123,6 +123,12 @@
},
"entity": {
"binary_sensor": {
"heating_work": {
"name": "Heating Work"
},
"bathing_work": {
"name": "Bathing Work"
},
"doorswitch": {
"name": "Door Switch"
},
@@ -314,6 +320,13 @@
"thermostat": {
"name": "Thermostat",
"state_attributes": {
"preset_modes": {
"state": {
"auto": "auto",
"link": "link",
"manual": "manual"
}
},
"fan_mode": {
"state": {
"silent": "Silent",
@@ -322,6 +335,12 @@
}
}
},
"zone1": {
"name": "区域1"
},
"dhw": {
"name": "DHW"
},
"colmo_turing_central_ac_climate": {
"name": "Thermostat",
"state_attributes": {
@@ -349,6 +368,39 @@
}
},
"select": {
"bright": {
"name": "Brightness"
},
"bias_gear": {
"name": "Bias Gear"
},
"humidity": {
"name": "Humidity"
},
"b3_function_control": {
"name": "Disinfection Cabinet Control"
},
"b6_power": {
"name": "Smoke Machine Power"
},
"b6_power_on_light": {
"name": "Smoke Machine Power On Light"
},
"b6_lock": {
"name": "Smoke Machine Lock"
},
"b6_gear": {
"name": "Smoke Machine Gear"
},
"b6_smoke_stove_linkage_gear": {
"name": "Smoke Stove Linkage Gear"
},
"b6_delay_gear_linkage_gear": {
"name": "Smoke Machine Delay Gear Linkage Gear"
},
"b6_delay_time_value": {
"name": "Smoke Machine Delay Time (Minute)"
},
"warm_target_temp": {
"name": "Warm Target Temperature"
},
@@ -367,12 +419,6 @@
"control_type": {
"name": "Control Type"
},
"dehydration_speed": {
"name": "Dehydration Speed"
},
"db_dehydration_speed": {
"name": "DB Dehydration Speed"
},
"db_detergent": {
"name": "DB Detergent"
},
@@ -403,9 +449,6 @@
"detergent_density": {
"name": "Detergent Density"
},
"dirty_degree": {
"name": "Dirty Degree"
},
"disinfectant": {
"name": "Disinfectant"
},
@@ -490,9 +533,6 @@
"season": {
"name": "Season"
},
"soak_count": {
"name": "Soak Count"
},
"softener_density": {
"name": "Softener Density"
},
@@ -544,9 +584,6 @@
"type_select": {
"name": "Type Select"
},
"water_quality": {
"name": "Water Quality"
},
"work_status": {
"name": "Work Status"
},
@@ -571,15 +608,6 @@
"gear": {
"name": "Gear"
},
"detergent": {
"name": "Detergent"
},
"softener": {
"name": "Softener"
},
"temperature": {
"name": "Temperature"
},
"ptc": {
"name": "PTC"
},
@@ -630,6 +658,9 @@
}
},
"sensor": {
"fresh_air_temp": {
"name": "Fresh Air Temp"
},
"running_status": {
"name": "Running Status"
},
@@ -645,6 +676,9 @@
"move_direction": {
"name": "Move Direction"
},
"work_status": {
"name": "Work Status"
},
"sub_work_status": {
"name": "Sub Work Status"
},
@@ -672,6 +706,21 @@
"auto_min_set_temp": {
"name": "Auto Min Set Temp"
},
"b3_upstair_status": {
"name": "Disinfection Cabinet Status"
},
"b7_left_status": {
"name": "Left Stove Status"
},
"b7_left_gear": {
"name": "Left Stove Gear"
},
"b7_right_status": {
"name": "Right Stove Status"
},
"b7_right_gear": {
"name": "Left Stove Gear"
},
"bath_out_volume": {
"name": "Bath Out Volume"
},
@@ -1200,6 +1249,9 @@
"work_time": {
"name": "Work Time"
},
"set_time": {
"name": "Set Time"
},
"zero_cold_tem": {
"name": "Zero Cold Temperature"
},
@@ -1383,6 +1435,48 @@
"ud_diy_up_percent": {
"name": "UD DIY Up Percent"
},
"heat_exchanger": {
"name": "Heat Exchanger Mode"
},
"fan_type": {
"name": "Fan Type"
},
"ignitor_output": {
"name": "Ignitor Output"
},
"fan_output": {
"name": "Fan Output"
},
"current_heat_set_temperature": {
"name": "Current Heat Set Temperature"
},
"current_bath_set_temperature": {
"name": "Current Bath Set Temperature"
},
"heat_out_water_temperature": {
"name": "Heat Out Water Temperature"
},
"bath_out_water_temperature": {
"name": "Bath Out Water Temperature"
},
"water_gage": {
"name": "Water Gage"
},
"in_tds": {
"name": "In TDS"
},
"out_tds": {
"name": "Out TDS"
},
"life_1": {
"name": "RO Filter Cartridge Lifespan"
},
"life_2": {
"name": "MPC Filter Cartridge Lifespan"
},
"water_consumption": {
"name": "Water Consumption"
},
"water_consumption_ml": {
"name": "Water Consumption (ml)"
},
@@ -1437,6 +1531,9 @@
"left_status": {
"name": "Left Status"
},
"right_status": {
"name": "Right Status"
},
"eq_recipe_action": {
"name": "Eq Recipe Action"
},
@@ -1502,6 +1599,12 @@
},
"heat_temp_set": {
"name": "Heat Temperature Set"
},
"tw1_in_water_temp": {
"name": "Inlet Water Temperature"
},
"tw1_out_water_temp": {
"name": "Outlet Water Temperature"
}
},
"light": {
@@ -1509,12 +1612,80 @@
"name": "Light"
}
},
"number": {
"water_quality": {
"name": "Water Quality"
},
"b6_lightness": {
"name": "Smoke Machine Lightness"
},
"dehydration_speed": {
"name": "Dehydration Speed"
},
"db_dehydration_speed": {
"name": "DB Dehydration Speed"
},
"detergent": {
"name": "Detergent"
},
"softener": {
"name": "Softener"
},
"temperature": {
"name": "Temperature"
},
"soak_count": {
"name": "Soak Count"
},
"dirty_degree": {
"name": "Dirty Degree"
},
"soak_time": {
"name": "Soak Time"
},
"wash_time": {
"name": "Wash Time"
},
"rinse_count": {
"name": "Rinse Count"
},
"dehydration_time": {
"name": "Dehydration Time"
},
"wash_level": {
"name": "Wash Level"
},
"rinse_level": {
"name": "Rinse Level"
},
"wash_strength": {
"name": "Water Strength"
}
},
"fan": {
"fan": {
"name": "Fan"
}
},
"switch": {
"winter_mode": {
"name": "Winter Mode"
},
"summer_mode": {
"name": "Summer Mode"
},
"follow_body_sense_enable": {
"name": "Follow Body Sense"
},
"waterions": {
"name": "Disinfection"
},
"mute": {
"name": "Mute"
},
"b6_light": {
"name": "Smoke Machine Light"
},
"temp_wind_switch": {
"name": "Wind Change with Temperature"
},
@@ -1762,7 +1933,7 @@
"name": "CO2 Check Enable"
},
"cold_water": {
"name": "Cold Water"
"name": "Cold Water Single Loop"
},
"cold_water_ai": {
"name": "Cold Water AI"
@@ -2399,6 +2570,18 @@
},
"work_switch": {
"name": "Work Switch"
},
"water_model_power_save": {
"name": "Power Save Mode"
},
"water_model_go_out": {
"name": "Away Mode"
},
"fastdhw_state": {
"name": "Fast Hot Water"
},
"forcetbh_state": {
"name": "Force Standby"
}
}
}

View File

@@ -123,6 +123,12 @@
},
"entity": {
"binary_sensor": {
"heating_work": {
"name": "采暖功能状态"
},
"bathing_work": {
"name": "生活热水工作状态"
},
"doorswitch": {
"name": "门锁开关"
},
@@ -314,6 +320,13 @@
"thermostat": {
"name": "温控器",
"state_attributes": {
"preset_modes": {
"state": {
"auto": "自动",
"link": "联动",
"manual": "手动"
}
},
"fan_mode": {
"state": {
"silent": "静音",
@@ -322,6 +335,12 @@
}
}
},
"zone1": {
"name": "区域1"
},
"dhw": {
"name": "DHW"
},
"colmo_turing_central_ac_climate": {
"name": "温控器",
"state_attributes": {
@@ -353,6 +372,39 @@
}
},
"select": {
"bright": {
"name": "亮度"
},
"bias_gear": {
"name": "降噪设置"
},
"humidity": {
"name": "设定湿度"
},
"b3_function_control": {
"name": "消毒柜控制"
},
"b6_power": {
"name": "烟机开关"
},
"b6_power_on_light": {
"name": "烟机开机开灯"
},
"b6_lock": {
"name": "烟机锁屏"
},
"b6_gear": {
"name": "烟机档位"
},
"b6_smoke_stove_linkage_gear": {
"name": "烟灶联动档位"
},
"b6_delay_gear_linkage_gear": {
"name": "烟机延时关机风速档位"
},
"b6_delay_time_value": {
"name": "烟机延时关机时间(分钟)"
},
"warm_target_temp": {
"name": "保温目标温度"
},
@@ -371,12 +423,6 @@
"control_type": {
"name": "控制类型"
},
"dehydration_speed": {
"name": "脱水转速"
},
"db_dehydration_speed": {
"name": "脱水转速"
},
"db_detergent": {
"name": "洗涤剂"
},
@@ -407,9 +453,6 @@
"detergent_density": {
"name": "洗涤剂浓度"
},
"dirty_degree": {
"name": "脏污程度"
},
"disinfectant": {
"name": "消毒剂"
},
@@ -494,9 +537,6 @@
"season": {
"name": "季节"
},
"soak_count": {
"name": "浸泡次数"
},
"softener_density": {
"name": "柔顺剂浓度"
},
@@ -551,9 +591,6 @@
"type_select": {
"name": "类型选择"
},
"water_quality": {
"name": "水质"
},
"work_status": {
"name": "工作状态"
},
@@ -575,15 +612,6 @@
"gear": {
"name": "档位"
},
"detergent": {
"name": "洗涤剂"
},
"softener": {
"name": "柔顺剂"
},
"temperature": {
"name": "温度"
},
"ptc": {
"name": "电辅热"
},
@@ -634,6 +662,9 @@
}
},
"sensor": {
"fresh_air_temp": {
"name": "新风温度"
},
"running_status": {
"name": "运行状态"
},
@@ -649,6 +680,9 @@
"move_direction": {
"name": "移动方向"
},
"work_status": {
"name": "工作状态"
},
"sub_work_status": {
"name": "子工作状态"
},
@@ -676,6 +710,21 @@
"auto_min_set_temp": {
"name": "自动最小设定温度"
},
"b3_upstair_status": {
"name": "消毒柜状态"
},
"b7_left_status": {
"name": "左灶状态"
},
"b7_left_gear": {
"name": "左灶档位"
},
"b7_right_status": {
"name": "右灶状态"
},
"b7_right_gear": {
"name": "右灶档位"
},
"bath_out_volume": {
"name": "浴缸出水量"
},
@@ -1204,6 +1253,9 @@
"work_time": {
"name": "工作时间"
},
"set_time": {
"name": "设置工作时间"
},
"zero_cold_tem": {
"name": "零冷水温度"
},
@@ -1387,6 +1439,48 @@
"ud_diy_up_percent": {
"name": "上下自定义上百分比"
},
"heat_exchanger": {
"name": "采暖模式"
},
"fan_type": {
"name": "风机模式"
},
"ignitor_output": {
"name": "点火状态"
},
"fan_output": {
"name": "风机状态"
},
"current_heat_set_temperature": {
"name": "当前采暖设定温度"
},
"current_bath_set_temperature": {
"name": "当前生活热水设定温度"
},
"heat_out_water_temperature": {
"name": "采暖出水实际温度"
},
"bath_out_water_temperature": {
"name": "生活用水出水实际温度"
},
"water_gage": {
"name": "水压"
},
"in_tds": {
"name": "进水TDS"
},
"out_tds": {
"name": "出水TDS"
},
"life_1": {
"name": "RO滤芯寿命"
},
"life_2": {
"name": "MPC滤芯寿命"
},
"water_consumption": {
"name": "用水量"
},
"water_consumption_ml": {
"name": "用水量(毫升)"
},
@@ -1441,6 +1535,9 @@
"left_status": {
"name": "左侧状态"
},
"right_status": {
"name": "右侧状态"
},
"eq_recipe_action": {
"name": "等量食谱动作"
},
@@ -1506,6 +1603,12 @@
},
"heat_temp_set": {
"name": "制热设定温度"
},
"tw1_in_water_temp": {
"name": "进水温度"
},
"tw1_out_water_temp": {
"name": "出水温度"
}
},
"light": {
@@ -1513,12 +1616,80 @@
"name": "电灯"
}
},
"number": {
"water_quality": {
"name": "水质"
},
"b6_lightness": {
"name": "烟机照明"
},
"dehydration_speed": {
"name": "脱水转速"
},
"db_dehydration_speed": {
"name": "脱水转速"
},
"detergent": {
"name": "洗涤剂"
},
"softener": {
"name": "柔顺剂"
},
"temperature": {
"name": "温度"
},
"soak_count": {
"name": "浸泡次数"
},
"dirty_degree": {
"name": "脏污程度"
},
"soak_time": {
"name": "浸泡时间"
},
"wash_time": {
"name": "洗涤时间"
},
"rinse_count": {
"name": "漂洗次数"
},
"dehydration_time": {
"name": "脱水时间"
},
"wash_level": {
"name": "洗涤水位"
},
"rinse_level": {
"name": "漂洗水位"
},
"wash_strength": {
"name": "水流强度"
}
},
"fan": {
"fan": {
"name": "风扇"
}
},
"switch": {
"winter_mode": {
"name": "冬季模式"
},
"summer_mode": {
"name": "夏季模式"
},
"follow_body_sense_enable": {
"name": "随身感"
},
"waterions": {
"name": "消杀"
},
"mute": {
"name": "静音"
},
"b6_light": {
"name": "烟机灯"
},
"temp_wind_switch": {
"name": "风随温变"
},
@@ -1766,7 +1937,7 @@
"name": "CO2检测启用"
},
"cold_water": {
"name": "零冷水"
"name": "零冷水单次循环"
},
"cold_water_ai": {
"name": "AI零冷水"
@@ -2403,6 +2574,18 @@
},
"work_switch": {
"name": "工作开关"
},
"water_model_power_save": {
"name": "节能模式"
},
"water_model_go_out": {
"name": "外出模式"
},
"fastdhw_state": {
"name": "快速生活热水"
},
"forcetbh_state": {
"name": "强制待机"
}
}
}