15 Commits

Author SHA1 Message Date
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
14 changed files with 542 additions and 38 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

View File

@@ -19,9 +19,12 @@
## 目前支持的设备类型
- T0x13 电灯
- T0x15 养生壶
- T0x21 中央空调网关
- T0x26 浴霸
- T0x3D 电热水瓶
- T0x9B 蒸烤箱
- T0x9C 集成灶
- T0xA1 除湿机
- T0xAC 空调
- T0xB2 电蒸箱

View File

@@ -57,7 +57,9 @@ PLATFORMS: list[Platform] = [
Platform.WATER_HEATER,
Platform.FAN,
Platform.LIGHT,
Platform.HUMIDIFIER
Platform.HUMIDIFIER,
Platform.NUMBER,
Platform.BUTTON
]
@@ -138,20 +140,51 @@ 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):
# 使用Home Assistant配置目录而不是当前工作目录
config_dir = hass.config.path(DOMAIN)
os.makedirs(config_dir, exist_ok=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)
# if not os.path.exists(bit):
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)
return True
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)

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

@@ -361,11 +361,8 @@ class MeijuCloud(MideaCloud):
command_data = {
"nodeid": nodeid,
"acattri_ctrl": {
"aclist": [{
"nodeid": nodeid,
"modelid": modelid,
"type": idtype
}],
"modelid": modelid, "type": idtype, "aclist_data": nodeid[-2:],
"event": control
}
}

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

@@ -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,86 @@
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": ["b6_light"],
"entities": {
Platform.NUMBER: {
"b6_lightness": {
"min": 0,
"max": 100,
"step": 1
}
},
Platform.SWITCH: {
"b6_light": {
"device_class": SwitchDeviceClass.SWITCH,
},
},
Platform.SELECT: {
"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: {
"b6_wind_pressure": {
"device_class": SensorDeviceClass.PRESSURE,
"unit_of_measurement": UnitOfPressure.PA,
"state_class": SensorStateClass.MEASUREMENT
}
}
}
}
}

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

@@ -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.16"
}

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

@@ -349,6 +349,27 @@
}
},
"select": {
"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"
},
@@ -1509,12 +1530,20 @@
"name": "Light"
}
},
"number": {
"b6_lightness": {
"name": "Smoke Machine Lightness"
}
},
"fan": {
"fan": {
"name": "Fan"
}
},
"switch": {
"b6_light": {
"name": "Smoke Machine Light"
},
"temp_wind_switch": {
"name": "Wind Change with Temperature"
},

View File

@@ -353,6 +353,27 @@
}
},
"select": {
"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": "保温目标温度"
},
@@ -1513,12 +1534,20 @@
"name": "电灯"
}
},
"number": {
"b6_lightness": {
"name": "烟机照明"
}
},
"fan": {
"fan": {
"name": "风扇"
}
},
"switch": {
"b6_light": {
"name": "烟机灯"
},
"temp_wind_switch": {
"name": "风随温变"
},