『瀚思彼岸』» 智能家居技术论坛

 找回密码
 立即注册
查看: 1409|回复: 9

[技术探讨] 批量自动重载不可用实体的集成条目

[复制链接]

1

主题

15

帖子

241

积分

中级会员

Rank: 3Rank: 3

积分
241
金钱
226
HASS币
0
发表于 2024-4-9 21:18:15 | 显示全部楼层 |阅读模式
本帖最后由 gz234748157 于 2024-4-10 12:31 编辑

平常有各种各样的原因,有些实体会变成不可用状态,还需要通过手动重载集成条目去恢复。重载集成条目的调用如下:
{
        domain: "homeassistant",
        service: "reload_config_entry",
        data: {
                entry_id: Entry ID
        }
}
但我不想手动去重载,也不想硬编码几个实体ID和Entry ID去做自动化,那太不智能了,所以试着去做一个自动重载的功能。

这里同步一下我所理解的HA实体概念:
我们正常添加设备,是会先通过集成(Platform)去创建一个条目(Entry),创建好条目就会添加设备(Device),而设备因为有各种各样的功能和传感器,就会生成N个实体(Entity)。
所以整个关系就是:HA->Platform->Entry->Device->Entity。


知道这个关系就要开始研究怎么做了,整个逻辑的思路很简单:当发现有实体的状态为不可用,自动去通过它所在的Entry ID重载。

实体状态查询很简单,官方API文档就给到/api/states这个接口去查询所有的实体状态。
Pasted image 20240409195908.png

当然,还能通过Subscribe的方式去订阅实体的状态。

而Entry ID,就是平常在HA地址栏里看到的这一串
Pasted image 20240409194303.png
对于Entry ID的查询,已知Entry、Device、Entity的注册信息都在.storage/的文件里,但我翻了一遍官网的API文档,发现并没有公开任何接口查询方法,那就只能抓包研究了。

通过抓包网页版HA的WebSocket包发现,当发送{"type:config/entity_registry/list"}指令时,会返回所有实体的详细注册信息,其中就包括它所在的Entry ID。看数据结构,应该就是从.storage/core.entity_registry调出的数据。
{
        "id": 32,
        "type": "result",
        "success": true,
        "result": [{
                "area_id": null,
                "config_entry_id": "0703xxxxxxxxxxxxxx",
                "device_id": "f9efxxxxxxxxxxx",
                "disabled_by": null,
                "entity_category": null,
                "entity_id": "fan.211106xxxxxx_fan",
                "has_entity_name": false,
                "hidden_by": null,
                "icon": null,
                "id": "dddf00dcxxxxxxxxx",
                "labels": [],
                "name": null,
                "options": {
                        "conversation": {
                                "should_expose": true
                        }
                },
                "original_name": "电风扇",
                "platform": "midea_ac_lan",
                "translation_key": null,
                "unique_id": "midea_ac_lan.211106xxxxxx_fan"
        }]
}
两个接口各自都有对方没有的关键信息:状态、Entry ID,但他们有个共同点:都有实体ID。

那现在要做的逻辑就很明了:先找到不可用的实体,找出他们的实体ID,再通过实体ID去找到他的Entry ID,然后批量调用homeassistant.reload_config_entry去重载。

整个重载逻辑流程大致就是这样:
Pasted image 20240409200817.png
为了灵活配置,避免因为各种各样的特殊情况,我加入了一些例外配置,避免本不需要重载Entry的情况下被错误拉去重载了。
Pasted image 20240409201326.png

附上JSON配置:
[{"id":"325906dc479f1c16","type":"subflow","name":"重载Entry","info":"","category":"","in":[{"x":60,"y":40,"wires":[{"id":"636bbc6753eb840d"}]}],"out":[{"x":500,"y":40,"wires":[{"id":"3b68d20b81452b82","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"636bbc6753eb840d","type":"function","z":"325906dc479f1c16","name":"重载Entry","func":"var entry=msg.payload;\nmsg.payload = {\n  domain: "homeassistant",\n  service: "reload_config_entry",\n  data: {\n    entry_id: entry\n  }\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":180,"y":40,"wires":[["3b68d20b81452b82"]]},{"id":"3b68d20b81452b82","type":"api-call-service","z":"325906dc479f1c16","name":"","server":"3b9976cf.8cca9a","version":5,"debugenabled":false,"domain":"","service":"","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":350,"y":40,"wires":[[]]},{"id":"9d45952ba1a6611d","type":"inject","z":"6277ce2382ca0ea3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"600","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":2120,"wires":[["b7bec502fc9c5fe3"]]},{"id":"b7bec502fc9c5fe3","type":"ha-api","z":"6277ce2382ca0ea3","name":"获取实体状态","server":"3b9976cf.8cca9a","version":1,"debugenabled":false,"protocol":"http","method":"get","path":"http://localhost:8123/api/states","data":"","dataType":"jsonata","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":300,"y":2120,"wires":[["a0de84691f6a3e26","1ae6c5e7fe6f3e4b"]]},{"id":"a0de84691f6a3e26","type":"function","z":"6277ce2382ca0ea3","name":"整理需要重载的实体id","func":"//配置需要重载的特定集成\nmsg.config_platform = ["xiaomi_gateway3", "aqara_gateway"];\n//配置不需要走重载逻辑的例外实体\nmsg.exclude_entity_ids = ["这里填写你想要配置的例外entity_id"];\n//配置需要重载的状态\nconst need_reload_states = ["unavailable"];\n\n\nlet unavailableEntities = []; // 创建一个空数组来存储不可用的实体信息\nmsg.payload.forEach(function (entity) {\n    if (need_reload_states.includes(entity.state) && !msg.exclude_entity_ids.includes(entity.entity_id)) {\n        // 当实体的状态为需要重载的状态,并且不是例外的实体时,记录实体ID和状态\n        unavailableEntities.push({\n            entity_id: entity.entity_id,\n            state: entity.state\n        });\n    }\n});\n\n//给向下传递筛选出来的数据\nmsg.unavailableEntities = unavailableEntities;\nmsg.payload = unavailableEntities.length;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":2120,"wires":[["e2970ea50b21a96e","7cf864be78774232"]]},{"id":"1ae6c5e7fe6f3e4b","type":"debug","z":"6277ce2382ca0ea3","name":"debug 36","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":450,"y":2060,"wires":[]},{"id":"e2970ea50b21a96e","type":"switch","z":"6277ce2382ca0ea3","name":"判断是否有需要重载的实体id","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":780,"y":2120,"wires":[["aad207e889a11289"]]},{"id":"aad207e889a11289","type":"ha-api","z":"6277ce2382ca0ea3","name":"查询实体注册表","server":"3b9976cf.8cca9a","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{"type":"config/entity_registry/list"}","dataType":"json","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":370,"y":2200,"wires":[["8abb1dd78f9a27fb"]]},{"id":"8abb1dd78f9a27fb","type":"function","z":"6277ce2382ca0ea3","name":"查找符合的entry id","func":"var unavailableEntities = msg.unavailableEntities;\n\n// 创建一个空对象来存储config_entry_id\nvar configEntryIds = [];\n\nmsg.payload.forEach(function (entity) {\n    // 遍历unavailableEntities数组查找匹配的entity_id\n    unavailableEntities.forEach(function (unavailableEntity) {\n        //判断platform是否配置内\n        if (entity.entity_id === unavailableEntity.entity_id\n            && msg.config_platform.includes(entity.platform)\n            && !configEntryIds.includes(entity.config_entry_id)) {\n            // 如果找到了匹配的entity_id,记录entity的config_entry_id\n            configEntryIds.push(entity.config_entry_id);\n        }\n    });\n});\n\nmsg.configEntryIds = configEntryIds;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":570,"y":2200,"wires":[["67728f63105adfc5","a8c4e444956e7692"]]},{"id":"a6e24ef09fb5322c","type":"debug","z":"6277ce2382ca0ea3","name":"debug 37","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1340,"y":2280,"wires":[]},{"id":"67728f63105adfc5","type":"function","z":"6277ce2382ca0ea3","name":"遍历每个Entry id","func":"var current_reload_entry_id = ""\nif (msg.configEntryIds.length > 0) {\n    current_reload_entry_id = msg.configEntryIds[0];\n    msg.configEntryIds.shift(); // 移除第一个元素\n    msg.reload_entry_id = current_reload_entry_id;\n} else {\n    msg.reload_entry_id = "";\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":2280,"wires":[["56dfac2db63f5595"]]},{"id":"358657d2eab7a41f","type":"subflow:325906dc479f1c16","z":"6277ce2382ca0ea3","name":"","x":1160,"y":2280,"wires":[["67728f63105adfc5","a6e24ef09fb5322c"]]},{"id":"56dfac2db63f5595","type":"switch","z":"6277ce2382ca0ea3","name":"判断是否还有需要重载的Entry id","property":"reload_entry_id","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":650,"y":2280,"wires":[["32d5508ca350a8f7"]]},{"id":"32d5508ca350a8f7","type":"function","z":"6277ce2382ca0ea3","name":"把entry_id赋值给payload","func":"msg.payload = msg.reload_entry_id;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":2280,"wires":[["358657d2eab7a41f","3f6f16216f9b679d"]]},{"id":"7cf864be78774232","type":"debug","z":"6277ce2382ca0ea3","name":"debug 39","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"unavailableEntities","targetType":"msg","statusVal":"","statusType":"auto","x":700,"y":2060,"wires":[]},{"id":"a8c4e444956e7692","type":"debug","z":"6277ce2382ca0ea3","name":"debug 40","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":2200,"wires":[]},{"id":"3f6f16216f9b679d","type":"debug","z":"6277ce2382ca0ea3","name":"debug 41","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1110,"y":2240,"wires":[]},{"id":"3b9976cf.8cca9a","type":"server","name":"Home Assistant","addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","statusSeparator":"","enableGlobalContextStore":false}]



flows (1).json.zip (2.91 KB, 下载次数: 17)





评分

参与人数 3金钱 +22 收起 理由
jeblove + 5 感谢楼主分享!
隔壁的王叔叔 + 12 高手,这是高手!
wuyouning + 5

查看全部评分

回复

使用道具 举报

4

主题

104

帖子

393

积分

中级会员

Rank: 3Rank: 3

积分
393
金钱
289
HASS币
0
发表于 2024-4-9 21:39:12 | 显示全部楼层
楼主666,刚好解决我现在的需求
回复

使用道具 举报

0

主题

220

帖子

1735

积分

金牌会员

Rank: 6Rank: 6

积分
1735
金钱
1515
HASS币
0
QQ
发表于 2024-4-10 09:56:42 | 显示全部楼层
集成不再提供此实体。如果实体不再使用,可以在“设置”中删除它。
是否有方法批量删除这类实体?
回复

使用道具 举报

10

主题

1332

帖子

4250

积分

论坛元老

Rank: 8Rank: 8

积分
4250
金钱
2918
HASS币
0
发表于 2024-4-10 11:01:25 | 显示全部楼层
楼主请上传下json文件吧,代码被吃了,倒入不进去,谢谢。
回复

使用道具 举报

146

主题

2267

帖子

7004

积分

元老级技术达人

积分
7004
金钱
4732
HASS币
30
发表于 2024-4-10 11:48:39 | 显示全部楼层
这个确实很实用,不知道ha官方有没有这种类似思路的,ha蓝图自动化,确实经常有设备受网络波动,变成不可用
回复

使用道具 举报

1

主题

15

帖子

241

积分

中级会员

Rank: 3Rank: 3

积分
241
金钱
226
HASS币
0
 楼主| 发表于 2024-4-10 12:19:09 | 显示全部楼层
隔壁的王叔叔 发表于 2024-4-10 11:01
楼主请上传下json文件吧,代码被吃了,倒入不进去,谢谢。

json文件已上传
回复

使用道具 举报

1

主题

15

帖子

241

积分

中级会员

Rank: 3Rank: 3

积分
241
金钱
226
HASS币
0
 楼主| 发表于 2024-4-10 13:08:19 | 显示全部楼层
sunshine 发表于 2024-4-10 09:56
集成不再提供此实体。如果实体不再使用,可以在“设置”中删除它。
是否有方法批量删除这类实体? ...

好问题,『不再提供此实体』这个情况的state也是显示unavailable。但我留意到这种类型在查询实体状态接口返回的attributes里会多出一个叫restored:true的字段。
比如:
{
        "entity_id": "weather.charming_s_hass",
        "state": "unavailable",
        "attributes": {
                "restored": true,
                "friendly_name": "Charming's HASS",
                "supported_features": 3
        },
        "last_changed": "2024-04-08T14:49:42.382497+00:00",
        "last_updated": "2024-04-08T14:49:42.382497+00:00",
        "context": {
                "id": "01HTZ2ZE7E9TXXXXXXXXX",
                "parent_id": null,
                "user_id": null
        }
}

但我的暂时样本数据不足,我目前HA里只有两个实体是『不再提供此实体』的状态,且都是多出了restored这个字段。有兴趣研究的话可以在你的HA上确认一下是不是也是这样。

至于删除的实体话,我抓包看到删除的调用是需要调用Websocket接口,传入的数据如下
{"type":"config/entity_registry/remove","entity_id":"xxxxxxxxxx"}


最重要的两个逻辑都确定了,那也就可以实现自动化批量删除了
回复

使用道具 举报

10

主题

1332

帖子

4250

积分

论坛元老

Rank: 8Rank: 8

积分
4250
金钱
2918
HASS币
0
发表于 2024-4-10 14:37:50 | 显示全部楼层

感谢大佬分享。
回复

使用道具 举报

0

主题

117

帖子

1336

积分

金牌会员

Rank: 6Rank: 6

积分
1336
金钱
1219
HASS币
0
发表于 2024-7-10 12:38:55 | 显示全部楼层
收藏了,感谢!
回复

使用道具 举报

0

主题

32

帖子

285

积分

中级会员

Rank: 3Rank: 3

积分
285
金钱
253
HASS币
0
发表于 2024-9-2 16:43:43 | 显示全部楼层
感谢分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian

GMT+8, 2024-11-24 02:45 , Processed in 0.120600 second(s), 36 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表