feat: auto-adaptation for T0xE1 devices via cloud config - #226
Conversation
sususweet
left a comment
There was a problem hiding this comment.
感谢提交,这给设备适配提供了一个新思路。但是对于插件和集成的改动应该做到不影响其他类型的设备,这是一条基本原则。
请更新这个pr,做到最小化改动,不影响其他设备的功能。
| value = self._config.get("value") | ||
|
|
||
| # 判断是否为中央空调设备(T0x21) | ||
| is_central_ac = self._device.device_type == 0x21 if self._device else False |
There was a problem hiding this comment.
影响了其他类型设备的行为,对于插件和集成的改动应该做到不影响其他类型的设备。
|
请在这个过程中慎用vibe coding,代码应经过充分测试和人工审查,遵循编码规范 |
e958cc2 to
a0b1ef7
Compare
# Conflicts: # custom_components/midea_auto_cloud/core/device.py
|
修改好了,之前确实考虑不周不好意思。由于没有验证MSmartHome的API是否可以支持 |
| import hmac | ||
|
|
||
| iot_key = clouds["美的美居"]["iot_key"] |
There was a problem hiding this comment.
这一段请用公共的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": |
There was a problem hiding this comment.
这里有没有通用一点的处理方法, 不建议在实体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"), |
There was a problem hiding this comment.
这里有没有通用一点的处理方法, 不建议在实体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 |
There was a problem hiding this comment.
这里有没有通用一点的处理方法, 不建议在实体sensor里对状态和指令进行硬编码
| """Turn the switch off.""" | ||
| await self._run_validators() | ||
| # E1: devOffKeep — close keep before power off | ||
| if self._entity_key == "power": |
There was a problem hiding this comment.
这里有没有通用一点的处理方法, 不建议在实体switch里对状态和指令进行硬编码
| try: | ||
| airswitch = int(attrs.get("airswitch", 0)) | ||
| except (ValueError, TypeError): | ||
| airswitch = 0 |
There was a problem hiding this comment.
这里有没有通用一点的处理方法, 不建议在实体number里对状态和指令进行硬编码
|
目前对于洗碗机这部分的改动还是太大,且牵扯到每个具体实体的修改,将很多洗碗机独有的属性硬编码进了实体,这不利于后期的代码维护和更新。建议在本PR基础上,再研究一下你所说的根据配置信息获取设备的具体实体,能否迁移到所有设备上,而不仅仅是洗碗机E1,做一个通用一些的解决方案。 另外请看一下 #229 ,你们的提交有冲突,都修改了洗碗机E1,建议你们讨论一下。 此外自动化测试没有通过,请检查。 |
|
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 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. |
|
@sususweet 审查过后可以先合并另一个PR,我这个可能还是一个长期工程 |
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,欢迎友好讨论
与小程序对齐清单
curMode.jsgetModeLabelpower.js/status.jsstatusNum > 0keep.jscloseKeep / setKeepOn / startKeepdry.jscancelDry / setDryOn / startDrypower.jswithDiff("devOffKeep")operator.jswithDiff("autoThrowWithMode")operator.json ? 1 : 2operator.jse.work_status = "work"diff.jscatch → localDiffadditionFunc.jsc[t].min || 0MODE_FUNCS_BTNdeviceSettingWechat.jsmergeSettingkeep.js/dry.js架构变更
_local_data统一到 device 对象_LOCAL_DATA全局字典(select.py)device._local_data(device.py)device_attributesproperty(midea_entity.py),所有实体平台自动merge
_local_data,无需各自 importentity popping → runtime 条件过滤
apply_device_config()直接 pop 不支持的实体coordinator._device_mapping_entitiesplatform_setup.py从 coordinator 读取,只创建真正支持的实体云端优先 + 本地兜底
_DEFAULT_DIFF+get_default_diff()(匹配小程序diff.js的cloudRequest → .then/.catch模式)_compute_diff_flags恢复fallback参数get_device_mapping构建默认_mode_features,云端到后apply_device_config覆盖为精确版本命令构建修复
启动/预约命令完整对齐小程序
operator.jsbuild_start_command/build_order_command补回"work_status": "work"/"work_status": "order"(重构时误删)door_auto_open值转换1 if val else 2(匹配operator.js的on ? 1 : 2)autoThrowWithModediff 检查(匹配withDiff("autoThrowWithMode"))"work_status": "work"污染取消预约/取消保管返回值 int 化
airswitch: "0"→airswitch: 0、dryswitch: "0"→dryswitch: 0airswitch: "2"→airswitch: 2、dryswitch: "2"→dryswitch: 2keep.js/dry.js)预约指令属性白名单移除
set_attributes不再过滤未知键order_set_hour/order_set_min等延迟参数能正确发送按钮与状态管理
开机逻辑:固定 3s → 轮询 status_num
async_set_attributes→async_set_control(避免乐观更新污染设备状态)power.js:statusNum > 0即算开机_clear_local_data移到async_set_control之前,消除 race condition_auto_seed_e1_mode()恢复 mode空挡状态过滤
_clear_local_data尾部 auto-seed 恢复 modecurrent_option无匹配时回退_last_option(避免闪空白)neutral_gear翻译(已从选项中移除)实体注册与显示
功能开关:SELECT → SWITCH
door_auto_open / auto_throw / more_dry / more_dry_wash从 SELECT 改回 SWITCH_FEATURE_MAP对应更新"select"→"switch"local_only+mode_dependent,存_local_data随 start 发送devOffKeep 关机前关保管
switch.py电源关闭前检查 diff flag,发送airswitch: 0power.js:withDiff("devOffKeep") → closeKeep → powerOff云端名称显示
apply_device_configStep 1 用云端modeList[].name作为选项 keylast_user_mode存 mode 标识符("glass_wash")而非显示名("轻柔洗")additional/water_level等 SELECT 命名方式一致添加缺失实体与选项
strong_wash_func(additional 12)选项dry_set_minNUMBER(0-180min, step 10)wash_stageENUM sensor(从错误 BINARY_SENSOR 迁移)keep/dry双路径移除虚假传感器
diy_flag / diy_main_wash / diy_piao_wash / diy_timesdiy_flag小程序代码零匹配;其余为数值字段非 boolean数据流优化
optimistic UI 更新
async_select_option后立即async_write_ha_state()+async_update_listeners()JSON 序列化修复
_mode_features/_missing_features从 Pythonset→listapply_device_config不再抛Object of type set is not JSON serializablework_time min 从 0 改为云端取值
w_min = None→ 首个有效min初始化additionFunc.js:c[t].min || 0文件句柄泄漏修复
open().read()→with open(...) as f:(__init__.py)