Skip to content

feat: auto-adaptation for T0xE1 devices via cloud config  - #226

Draft
NOisi-x wants to merge 6 commits into
sususweet:masterfrom
NOisi-x:master
Draft

feat: auto-adaptation for T0xE1 devices via cloud config #226
NOisi-x wants to merge 6 commits into
sususweet:masterfrom
NOisi-x:master

Conversation

@NOisi-x

@NOisi-x NOisi-x commented Jul 19, 2026

Copy link
Copy Markdown

feat: auto-adaptation for T0xE1 devices via cloud config

概述

以meiju微信小程序源代码为权威参考,重构集成中 T0xE1(洗碗机)的设备映射、实体注册和命令构建逻辑,实现类型下全设备的自动适配。

核心原则:DEVICE_MAPPING 中default配置为所有E1类别洗碗机功能的超集,初始化时通过云端下发的设备配置(modeList / more / setting)和 diff 白名单(diffType)动态过滤,只在HA中注册受支持的功能实体,在select实体下按条件只显示受支持的功能。
最终效果——无论 SN8 是哪款,一个 default映射覆盖所有 E1 设备,无需手写 per-model 子配置。

default配置的功能超集均为个人手工调整的最佳实现,比较符合小程序前端的逻辑。

但由于小程序存在很多前端规则,如前端可以通过用户选择的模式来渲染附加功能的开关和列表,这在HA中很难完美实现,我部分实现了类似的限制,例如additional会根据本地用户选择的mode来决定是否可用和具体的列表,但实现依然不完美,已经是我认为的最佳实现方式。

已知一个问题:首次配置集成和HA重启后,设备处于关机状态,由于设备上报的mode状态是neutral_gear,且本地mode值也未出现,所以所有mode_dependent的实体均不可用,需要用户先power_on设备或手动切换一次mode才能恢复可用。这个状态下有一个保底,直接启动时会自动采用云配置列表中的第一个mode作为本地mode,可以正常启动。

本次提交主要是为设备适配提供一种思路,现在的手写配置方式依然可以继续使用,但我认为这种方式会导致设备适配的代码量和维护成本过高,所以我希望通过这种方式来减少设备适配的代码量和维护成本。
对于T0xE1设备是破坏性更新,需要重新配置集成来应用自动适配逻辑。

非T0xE1的所有设备均为无变化,保持了最大兼容性。

本人非专业coder,主要靠Vibe coding,欢迎友好讨论

与小程序对齐清单

改动 小程序依据(文件:行)
云端 name 显示 curMode.js getModeLabel
轮询开机 status_num != 0 power.js / status.js statusNum > 0
airswitch 0/1/2 三态 keep.js closeKeep / setKeepOn / startKeep
dryswitch 0/1 二态 dry.js cancelDry / setDryOn / startDry
devOffKeep 关保管 power.js withDiff("devOffKeep")
autoThrowWithMode operator.js withDiff("autoThrowWithMode")
door_auto_open 1/2 operator.js on ? 1 : 2
work_status: "work"/"order" operator.js e.work_status = "work"
mode 选项不含 neutral_gear 小程序 modeList 无此 key
diy_flag 不存在 全量搜索零匹配
_DEFAULT_DIFF 兜底 diff.js catch → localDiff
work_time min additionFunc.js c[t].min || 0
SWITCH 一键切换 MODE_FUNCS_BTN
实体注册过滤 deviceSettingWechat.js mergeSetting
keep/dry 独立按钮 keep.js / dry.js

架构变更

  1. _local_data 统一到 device 对象

    • 删除模块级 _LOCAL_DATA 全局字典(select.py
    • 改存 device._local_datadevice.py
    • 新增 device_attributes property(midea_entity.py),所有实体平台自动
      merge _local_data,无需各自 import
  2. entity popping → runtime 条件过滤

    • apply_device_config() 直接 pop 不支持的实体
    • 过滤后的 entities 存到 coordinator._device_mapping_entities
    • platform_setup.py 从 coordinator 读取,只创建真正支持的实体
    • 未支持的实体不会出现在 HA entity registry 中
  3. 云端优先 + 本地兜底

    • 恢复 _DEFAULT_DIFF + get_default_diff()(匹配小程序 diff.js
      cloudRequest → .then/.catch 模式)
    • _compute_diff_flags 恢复 fallback 参数
    • get_device_mapping 构建默认 _mode_features,云端到后
      apply_device_config 覆盖为精确版本

命令构建修复

  1. 启动/预约命令完整对齐小程序 operator.js

    • build_start_command / build_order_command 补回 "work_status": "work" /
      "work_status": "order"(重构时误删)
    • door_auto_open 值转换 1 if val else 2(匹配 operator.js
      on ? 1 : 2
    • autoThrowWithMode diff 检查(匹配 withDiff("autoThrowWithMode")
    • 删除 wash_mode 选项中的 "work_status": "work" 污染
  2. 取消预约/取消保管返回值 int 化

    • airswitch: "0"airswitch: 0dryswitch: "0"dryswitch: 0
    • airswitch: "2"airswitch: 2dryswitch: "2"dryswitch: 2
    • 匹配小程序 JavaScript 数字字面量(keep.js / dry.js
  3. 预约指令属性白名单移除

    • set_attributes 不再过滤未知键
    • order_set_hour / order_set_min 等延迟参数能正确发送

按钮与状态管理

  1. 开机逻辑:固定 3s → 轮询 status_num

    • async_set_attributesasync_set_control(避免乐观更新污染设备状态)
    • 匹配小程序 power.jsstatusNum > 0 即算开机
    • _clear_local_data 移到 async_set_control 之前,消除 race condition
    • 清空后立即调用 _auto_seed_e1_mode() 恢复 mode
  2. 空挡状态过滤

    • _clear_local_data 尾部 auto-seed 恢复 mode
    • current_option 无匹配时回退 _last_option(避免闪空白)
    • 删除 neutral_gear 翻译(已从选项中移除)

实体注册与显示

  1. 功能开关:SELECT → SWITCH

    • door_auto_open / auto_throw / more_dry / more_dry_wash 从 SELECT 改回 SWITCH
    • _FEATURE_MAP 对应更新 "select""switch"
    • SWITCH 支持 local_only + mode_dependent,存 _local_data 随 start 发送
  2. devOffKeep 关机前关保管

    • switch.py 电源关闭前检查 diff flag,发送 airswitch: 0
    • 匹配小程序 power.jswithDiff("devOffKeep") → closeKeep → powerOff
  3. 云端名称显示

    • apply_device_config Step 1 用云端 modeList[].name 作为选项 key
    • last_user_mode 存 mode 标识符("glass_wash")而非显示名("轻柔洗"
    • additional / water_level 等 SELECT 命名方式一致
  4. 添加缺失实体与选项

    • strong_wash_func(additional 12)选项
    • dry_set_min NUMBER(0-180min, step 10)
    • wash_stage ENUM sensor(从错误 BINARY_SENSOR 迁移)
    • wash_mode 添加 keep / dry 双路径
  5. 移除虚假传感器

    • 删除 diy_flag / diy_main_wash / diy_piao_wash / diy_times
    • diy_flag 小程序代码零匹配;其余为数值字段非 boolean
    • 删除所有手写子映射(全自动适配)

数据流优化

  1. optimistic UI 更新

    • async_select_option 后立即 async_write_ha_state() +
      async_update_listeners()
    • mode_dependent 实体在选模式后立即变可用,不等下次轮询
  2. JSON 序列化修复

    • _mode_features / _missing_features 从 Python setlist
    • apply_device_config 不再抛 Object of type set is not JSON serializable
  3. work_time min 从 0 改为云端取值

    • w_min = None → 首个有效 min 初始化
    • 匹配小程序 additionFunc.jsc[t].min || 0
  4. 文件句柄泄漏修复

    • open().read()with open(...) as f:__init__.py

@sususweet sususweet left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢提交,这给设备适配提供了一个新思路。但是对于插件和集成的改动应该做到不影响其他类型的设备,这是一条基本原则。
请更新这个pr,做到最小化改动,不影响其他设备的功能。

value = self._config.get("value")

# 判断是否为中央空调设备(T0x21)
is_central_ac = self._device.device_type == 0x21 if self._device else False

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

影响了其他类型设备的行为,对于插件和集成的改动应该做到不影响其他类型的设备。

@sususweet

Copy link
Copy Markdown
Owner

请在这个过程中慎用vibe coding,代码应经过充分测试和人工审查,遵循编码规范

@NOisi-x
NOisi-x marked this pull request as draft July 20, 2026 14:50
@NOisi-x
NOisi-x force-pushed the master branch 2 times, most recently from e958cc2 to a0b1ef7 Compare July 21, 2026 06:31
# Conflicts:
#	custom_components/midea_auto_cloud/core/device.py
@NOisi-x
NOisi-x marked this pull request as ready for review July 21, 2026 06:40
@NOisi-x

NOisi-x commented Jul 21, 2026

Copy link
Copy Markdown
Author

修改好了,之前确实考虑不周不好意思。由于没有验证MSmartHome的API是否可以支持 getAppModelConfig 相同或类似的方法所以先只应用于美的美居路径的设备,靠增量代码专属逻辑实现,保证最大兼容性。

Comment on lines +999 to +1001
import hmac

iot_key = clouds["美的美居"]["iot_key"]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这一段请用公共的api_request函数包装,便于状态的统一管理


# For start_wash: auto power-on if the device is off before running
# validators (which would otherwise reject the operation).
if command_builder == "start_wash":

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里有没有通用一点的处理方法, 不建议在实体Button里对状态和指令进行硬编码

diff_flags = getattr(self.coordinator, "_diff_flags", {}) or {}
keep_start_now = getattr(self.coordinator, "_keep_start_now", False)
status_num = get_status_num(
data.get("work_status"),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里有没有通用一点的处理方法, 不建议在实体Button里对状态和指令进行硬编码

"""Return the native value of the sensor."""
if self._computed_status:
from .device_mapping.T0xE1 import get_status_num, get_status_text
attrs = self.device_attributes

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里有没有通用一点的处理方法, 不建议在实体sensor里对状态和指令进行硬编码

"""Turn the switch off."""
await self._run_validators()
# E1: devOffKeep — close keep before power off
if self._entity_key == "power":

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里有没有通用一点的处理方法, 不建议在实体switch里对状态和指令进行硬编码

try:
airswitch = int(attrs.get("airswitch", 0))
except (ValueError, TypeError):
airswitch = 0

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里有没有通用一点的处理方法, 不建议在实体number里对状态和指令进行硬编码

@sususweet

Copy link
Copy Markdown
Owner

目前对于洗碗机这部分的改动还是太大,且牵扯到每个具体实体的修改,将很多洗碗机独有的属性硬编码进了实体,这不利于后期的代码维护和更新。建议在本PR基础上,再研究一下你所说的根据配置信息获取设备的具体实体,能否迁移到所有设备上,而不仅仅是洗碗机E1,做一个通用一些的解决方案。

另外请看一下 #229 ,你们的提交有冲突,都修改了洗碗机E1,建议你们讨论一下。

此外自动化测试没有通过,请检查。

@phntom

phntom commented Jul 23, 2026

Copy link
Copy Markdown

Hi @NOisi-x@sususweet asked us to coordinate the overlap with #229. I have now split #229 down to a minimal, one-file program-selection bugfix on current master; it does not touch cloud adaptation or any shared entity classes. HACS and hassfest pass.

I removed the delayed-start/raw-command proposal from #229 and preserved it separately. I will not put that forward for merge until we can align it with the more general approach being discussed here. I can share the hardware captures and E1 codec behavior if they help the design or testing.

@NOisi-x
NOisi-x marked this pull request as draft July 27, 2026 12:47
@NOisi-x

NOisi-x commented Jul 28, 2026

Copy link
Copy Markdown
Author

@sususweet 审查过后可以先合并另一个PR,我这个可能还是一个长期工程

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants