我把家里的格兰斯柯新风"偷"进了 Home Assistant——一篇人人可复刻的小程序逆向实录

家里装的格兰斯柯新风一体机,控制全靠微信:小程序叫"格兰斯柯智能控制",服务号里叫"格兰斯柯新风系统"。
设备不错,但我想要的从来不只是"打开微信→找到小程序→点开关"——
我想要它在 Home Assistant 里:跟传感器联动、进自动化、上仪表盘、接入小爱同学一句话开关。于是花了一晚上,把它的通信协议逆向了出来。整个过程不碰设备一个字节,全程只分析"微信和云端自己发的数据"。
这篇文章把每一步都摊开讲——照着做,你也能把自己的任何"微信小程序家电"接进 Home Assistant。
语音联调翻了三次车;接入当晚连接就断了、第二天实体全灰——这种"不到 24 小时就熄火"的隐患,也一并拆解。
先看成果
集成完成后,Home Assistant 里多出了 21 个实体:
| 类型 | 实体 |
|---|---|
| 风机 | 开关、1~3 档风速(低/中/高一一对应)、智能/手动模式 |
| 传感器 ×9 | PM2.5、CO₂、TVOC、室内外温湿度、室外 PM2.5、滤芯寿命 96% |
| 能耗 ×3 | 累计功耗 103.42 度 / 今日 16.52 度 / 昨日 40.54 度(与微信"功耗查询"同源) |
| 开关 ×4 | 辅热、童锁、新风(外循环)、屏幕显示 |
| 定时 ×2 | 定时开机 / 定时关机(微信"更多"页同款功能,可设每天/单次/指定星期) |
| 空调模块 | 制热/制冷/除湿/送风 + 温度设定 |
| 锁 | 童锁 |
也就是说,服务号"更多"页里的东西,一个不落全部进了 HA:

▲ 微信服务号里的"更多"页——功耗查询、定时开/关机、电辅热、童锁,这些在 HA 里都有对应实体

▲ HA 里的风机控制:拖动滑条直接控制真机,低/中/高与 1/2/3 档一一对应

▲ CO₂ 传感器 + 历史曲线,和微信小程序看到的是同一份数据
而且——微信小程序照样能用。两条控制通路互不干扰:家里人在微信里开,我在 HA 里关,谁也不耽误谁。
零、先讲原理(看懂了这个,后面全是体力活)
先别急着敲命令。我们要搞清楚一件事:微信小程序到底是怎么控制你的空调的?
它当然不是通过魔法。你的手机链路是这样的:
微信小程序 ──HTTPS──> 厂商服务器(iot.szglancetech.com)
│
▼
机智云 Gizwits 物联网平台(设备真正挂的地方)
▲
│ WebSocket 长连接
你家的新风机
关键洞察有三个:
洞察 1:绝大部分白电的"智能",都不是厂商自研的。
它们接的是第三方物联网云平台——机智云(Gizwits)、涂鸦(Tuya)、苏宁小 Biu、海尔优家……本案的格兰斯柯接的是机智云。而机智云是有公开 OpenAPI 文档的,协议是标准化的。这意味着:只要拿到四个参数(appid、uid、token、设备 did),我们就能用官方接口直接控制设备——根本不需要破解任何东西。
洞察 2:小程序包在电脑上是加密落盘的,但运行时数据在内存里是明文的。
Linux/Windows 版微信会把小程序包(.wxapkg)缓存在本地,但它是加密的(V1MMWX 头)。新手到这里往往就卡死了——别硬刚加密,让小程序自己运行起来,然后去进程内存里捞它正在使用的登录凭证和报文样例。这是整个流程里最省力的一步妙棋。
洞察 3:服务号里的"H5 控制页"就是一张普通网页,它的源码公开可下载。
"格兰斯柯新风系统"服务号里的控制页面,本质是 http://iot.szglancetech.com/gizwits-wxjw/ 这张 Vue 网页。虽然它要求在微信里打开,但 JS 源码谁都能下载——所有控制指令、加密算法、数据点定义,全明晃晃写在里面。

▲ 离开微信打开是"请在微信客户端打开链接"——但源码是公开的
一句话总结我们的策略:
内存拿凭证 → 源码学协议 → 官方 API 做集成
一、准备工作
你需要:
- ✅ 一台装了电脑版微信的机器(Windows / macOS / Linux 都行,本文以 Linux 为例,其他系统只是路径不同)
- ✅ 在电脑微信里打开目标小程序(重要!后面会讲为什么)
- ✅ 一台跑着 Home Assistant 的机器(Docker 安装的最常见)
- ✅ 会复制粘贴,会重启容器,仅此而已
第 1 步:打开小程序,找到它的"身份证号"
先在电脑微信里打开"格兰斯柯智能控制"小程序,随便点点设备页。然后开个终端,看看微信进程的启动参数:
ps aux | grep WeChatAppEx | grep -oE "wmpf-appid=wx[0-9a-f]+"

输出里的 wxb84aee9a584036c7 就是这个小程序的 AppID——相当于它的身份证号,后面到处会用到。
顺手还能找到它的本地缓存包:
# Linux 版微信的小程序包路径
find ~/.xwechat/radium -path "*packages*" -name "*.wxapkg"

▲ 这个 140×140 的小图标,就是从微信缓存里翻出来的小程序本体之一
打开这个包看看?文件头是 V1MMWX——微信对小程序包做了加密,前 1024 字节 AES-256-CBC,密钥从你的微信账号派生。
新手别在这里死磕解密。 我们直接跳到更聪明的路:内存取证。
二、内存取证:让小程序"自己交出"登录凭证
小程序正在运行,意味着它刚通过 HTTPS 拿到了登录 token、刚连上了机智云的 WebSocket、刚收到设备状态——这些数据此刻就躺在微信进程的内存里,明文。
我们写个小脚本,扫描微信进程的可读内存段,找三类东西:
login_req开头的 WebSocket 登录报文(金矿:里面有现成的 appid + uid + token)"did"设备编号- 所有非腾讯系的 URL(找到厂商服务器地址)
核心代码其实非常短(完整版在文末的 gizwits_hunter.py,一键运行):
import re
WS_LOGIN_RE = re.compile(
rb'\{"cmd":\s*"login_req",\s*"data":\s*\{[^{}]*?"appid":\s*"([0-9a-f]{32})"'
rb'[^{}]*?"uid":\s*"([0-9a-f]{32})"[^{}]*?"token":\s*"([A-Za-z0-9._\-]{16,64})"'
)
for pid in 找到的微信进程:
with open(f"/proc/{pid}/mem", "rb") as mem:
for 起始, 结束 in 可读内存段:
buf = mem.read(...)
for m in WS_LOGIN_RE.finditer(buf):
print("抓到了!", m.groups())
⚠️ 需要管理员权限(读别人进程的内存当然要 root),并且小程序必须开着(token 才没被回收):
sudo python3 gizwits_hunter.py --phone-id my-ha-001
一跑,惊喜来了:

我们直接从内存里挖出了一整条可用的 WebSocket 登录报文:
{ "cmd": "login_req", "data": {
"appid": "8615f64ed9f249b69628da35136f5271",
"uid": "f228c2be692547bea83fc5674deb2a0d",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"p0_type": "attrs_v4", "heartbeat_interval": 180 } }
同时还捞到了一堆关键情报:
- 机智云官方通道:
wss://m2m.gizwits.com:8880/ws/app/v1 - 厂商服务器:
http://iot.szglancetech.com/gizwits-wxjw/ - 设备编号:
did = DT0LhnOwjSC7om2Yyb23vr
gizwits_hunter.py 的完整输出长这样(注册自管账号、绑定设备、生成 HA 配置,一气呵成):

到这里,这场逆向已经基本结束了——剩下的就是用官方协议做集成。
三、读源码:把控制指令"翻译"出来
有凭证还不够,我们得知道发什么字段、什么值。这就要请出服务号里那张 H5 网页的源码了。
把 app.js(951KB)下载下来,用 grep 顺藤摸瓜。先看它怎么登录厂商服务器:

// H5 源码里的密码函数 d(t):
function d(t) {
var e = md5(t);
if (32 === e.length) {
var i = e.toLowerCase().substr(8, 16); // ← 就这?
return console.log("jwt user pw", t, i), i
}
}
密码 = md5(你的openid) 的第 8~24 位。 一个 md5 截断,就拿到了厂商服务器的通行证。用 curl 验证一下,果然拿到了正式的 JWT token。
接着挖控制指令。H5 里每个按钮的点击处理都明明白白:
| 界面按钮 | 实际下发的字段 |
|---|---|
| 开/关机 | {"power": true/false} |
| 风速 1~3 档 | {"speed": 1~3}(注意:只发这一个字段,别捎带别的——原因见第九节踩坑实录) |
| 智能/手动 | {"auto": true/false} |
| 新风(外循环) | {"fresh": true/false}(false = 内循环) |
| 辅热 | {"heat": true/false} |
| 童锁 | {"childlock": true/false} |
| 空调模块 制热/制冷/除湿/送风 | {"acmode": 1/2/3/4} |
| 温度调节 | {"actempcool": 16~31} |
功耗和定时开关机藏得深一点,但也在源码里现出原形:
- 功耗数据点是
energyA(累计)/energyT(今日)/energyY(昨日),换算就一句(v/100).toFixed(2)度; - 定时开关机是一个 32 位位域整数:
启用<<24 | 时<<16 | 分<<8 | 星期重复,重复位 0~5 是周一到周六、位 6 是周日——H5 里transformAlarmdata和calcAlarm两个函数互为镜像,一个编码一个解码。
传感器的数据点名字也一并拿到了:pm25、co2、tvoc、tempin、humidity、surplus1(滤芯剩余寿命)……
四、走正门:注册自己的机智云账号,"合法地"接管设备
现在到了整篇文章最漂亮的转折。
内存里抓到的 token 是微信账号的,63 天后过期,而且"借"别人的账号总归不优雅。但机智云是正规物联网平台,它有匿名注册接口——任何人都可以创建一个自己的账号:
curl -X POST https://api.gizwits.com/app/users \
-H 'X-Gizwits-Application-Id: 8615f64ed9f249b69628da35136f5271' \
-d '{"phone_id":"my-home-assistant","lang":"zh-cn"}'
{ "token": "xxx...", "uid": "1854ffdcb16649ebb5b6892f8c6671c3",
"expire_at": 1809243696 }
再用从 /app/bindings 接口查到的设备 did + passcode,把设备绑定到我们自己的账号:
curl -X POST https://api.gizwits.com/app/bindings \
-H 'X-Gizwits-User-Token: <刚拿到的token>' \
-d '{"devices":[{"did":"DT0LhnOwjSC7om2Yyb23vr","passcode":"<设备码>"}]}'
{ "failed": [], "success": ["DT0LhnOwjSC7om2Yyb23vr"] }

绑定成功。同一个设备可以同时被多个账号绑定——所以微信小程序完全不受影响,而我们的 HA 从此有了自己的合法通道。这就是"正门":不破解、不绕过,用平台自己的规则把设备接管过来。
💡 为什么这套方法能推广?
因为机智云、涂鸦这些平台的"一机多绑"是产品设计如此(为了家人共享)。只要你的家电小程序接的是这类平台,appid一换,整套流程原样能跑。
五、验证:用 30 行 Python 直连设备
写个最小可用脚本验证全链路(WebSocket → 登录 → 订阅 → 读状态):

# 伪代码版(完整版见文末)
ws = connect("wss://m2m.gizwits.com:8880/ws/app/v1")
ws.send({"cmd": "login_req", "data": {appid, uid, token, ...}})
ws.send({"cmd": "subscribe_req", "data": [{"did": did}]})
ws.send({"cmd": "c2s_read", "data": {"did": did}})
# 设备立刻回推全量状态:
# power=true, speed=3, pm25=0, co2=427, tempin=25, 滤芯剩余=96% ...
实时数据全部到手——和微信小程序看到的是同一个数据流。
写控制也只是换个命令:
ws.send({"cmd": "c2s_write", "data": {"did": did, "attrs": {"speed": 2}}})
# 设备风速从 3 档变 2 档,1 秒内回显确认 ✓
六、落地:打包成 Home Assistant 集成
协议通了,剩下就是体力活:把这些能力包装成 HA 自定义组件 glancetek_air。
文件结构(共 11 个文件,约 600 行,文末获取):
custom_components/glancetek_air/
├── manifest.json # 集成元数据
├── const.py # 数据点定义
├── __init__.py # 入口:注册账号→绑定→连WS→加载平台
├── config_flow.py # YAML 导入流程
├── gizwits_api.py # 机智云 REST(匿名注册/续期/绑定)
├── gizwits_ws.py # WebSocket 客户端(心跳/重连/收发)
├── fan.py # 风机实体(开关/风速/预设)
├── sensor.py # 9 个传感器
├── switch.py # 4 个开关
├── climate.py # 空调模块
└── lock.py # 童锁
第 1 步:把组件复制进 HA
Docker 安装的 HA(目录换成你自己的):
docker cp custom_components/glancetek_air homeassistant:/config/custom_components/
第 2 步:在 configuration.yaml 里加 5 行
glancetek_air:
phone_id: my-ha-001 # 你注册时用的 phone_id,随便起,别改来改去
devices:
- did: DT0LhnOwjSC7om2Yyb23vr
passcode: "你的设备码"
name: 格兰斯柯新风
第 3 步:重启 Home Assistant
docker restart homeassistant
集成启动时会自动完成三件事:匿名注册(幂等,同 phone_id 永远拿回同一个账号)→ 绑定设备 → 建立 WebSocket 长连接。
而这条连接由一个常驻自愈 supervisor 看护:WebSocket 深夜悄悄断了?它自己重连;token 失效被服务器拒登录?它当场匿名注册换新再连;断网/路由器重启?它按指数退避爬起来;甚至 HA 启动那一刻 DNS 还没就绪也没关系,它一直在后台重试到成功。为什么必须有它——见第八节末尾,那是一次"第二天早上实体全灰"的真实事故。
七、验收:21 个实体全部上线
重启完成后,打开 HA。看到那 21 个实体了吗?


▲ 我的 HA 概览页(演示账号视角)
打开开发者工具 → 状态,输入 ge_lan 过滤:


▲ PM2.5 实时为 0——新风干得不错

▲ 滤芯剩余 96%,这个数据点微信里都要点三层菜单才能看到,现在进自动化随取随用
最后,激动人心的控制实测——从 HA 里把风速从 3 档调到 2 档:

curl -X POST http://localhost:8123/api/services/fan/set_preset_mode \
-H "Authorization: Bearer <你的HA令牌>" \
-d '{"entity_id":"fan.ge_lan_si_ke_xin_feng_feng_ji","preset_mode":"2档"}'
设备真的动了,面板 1 秒内回显 2档 67%。再调回 3 档、切智能模式、拖滑条低中高……全部通过(完整回归矩阵见第九节)。
定时开关机也测一发——微信"更多"页里那个功能,现在是一条服务调用:
# 每天 08:00 定时开机(repeat 位掩码:127 = 每天)
curl -X POST http://localhost:8123/api/services/glancetek_air/set_timer \
-H "Authorization: Bearer <你的HA令牌>" \
-d '{"entity_id":"switch.ge_lan_si_ke_xin_feng_ding_shi_kai_ji","time":"08:00","repeat":127}'
定时开关立刻变为 on,属性显示 08:00 每天——和微信里手动设置的效果一模一样,设备回显确认。能耗传感器同样实测:累计 103.42 kWh / 今日 16.52 kWh / 昨日 40.54 kWh,还带 total_increasing 状态类,可以直接拖进 HA 能源面板看电费曲线。
从微信独占到 HA 原生实体,只花了一个晚上。
八、进阶玩法(有了实体之后)
有了这 16 个实体,HA 的全部能力瞬间对这台新风机敞开:
# 自动化 1:CO₂ 超标自动开新风
automation:
- alias: "CO2超标开新风"
trigger:
- platform: numeric_state
entity_id: sensor.ge_lan_si_ke_xin_feng_co2
above: 800
action:
- service: fan.turn_on
target: {entity_id: fan.ge_lan_si_ke_xin_feng_feng_ji}
- service: fan.set_percentage
target: {entity_id: fan.ge_lan_si_ke_xin_feng_feng_ji}
data: {percentage: 80}
# 自动化 2:滤芯寿命低于 10% 推送到手机
- alias: "滤芯该换了"
trigger:
- platform: numeric_state
entity_id: sensor.ge_lan_si_ke_xin_feng_lu_xin_shou_ming
below: 10
action:
- service: notify.mobile_app
data: {message: "新风滤芯只剩 10% 了,记得买!"}
再配上小爱同学网关、Apple Watch、AI 语音助手——"关一下新风"从此不需要解锁手机打开微信找到小程序再点一下。
九的前传:第四次翻车,就发生在接入后的第二天早上
能控制只是及格线,能长期无人值守地跑才是集成的毕业考。而这第四个坑,
不是理论推演——是我在自己身上踩的:
接入当晚一切完美。第二天早上打开 HA,21 个实体全部 unavailable。
没有任何报错弹窗。CO₂、能耗、温度的记录曲线在昨夜某刻集体停摆——可能是家里网络
抖了一下,可能是机智云服务器踢掉了长时间空闲的连接(它的心跳间隔参数是 180 秒,
但服务器端的断连阈值明显更苛刻)。而我最初的实现是"启动时连一次,断线后就静静
躺在那里"——WebSocket 一断,整个集成就变成了植物人。
只能手动点"重新加载实体"救活。这暴露出两个设计缺陷:
- 断线后无重连——一次网络抖动就是一次宕机,恢复全靠人肉;
- HA 有个特性火上浇油:entry 初始化失败后不会自动重试,日志里静悄悄的,
唯一的提示是实体变灰。你的自动化、语音、历史曲线全部陪葬。
于是给集成装上了自愈 supervisor(常驻看护循环):
| 故障场景 | supervisor 的自愈动作 |
|---|---|
| WS 深夜悄悄断开(本事故元凶) | 指数退避重连(10s→20s→40s…上限 10 分钟),爬起来后补拉一次全量状态 |
| token 失效,登录被拒 | 当场匿名注册(幂等,同一 phone_id 永远拿回同一账号)→ 换新 token → 立即重连 |
| token 还剩 7 天到期 | 心跳循环主动刷新,不给过期留机会(63 天有效期的长尾风险也顺手覆盖) |
| HA 启动时 DNS 未就绪 | setup 不再 fail-fast,后台持续重试(不再需要手动重启 HA) |
验证一(主动出击):故意用一个失效 token 启动连接——日志原文:
登录失败(token 可能过期): Gizwits login failed: {'cmd': 'login_res', 'data': {'success': False}} → 尝试刷新 token
>>> reauth 触发:真实匿名注册刷新 token
Gizwits WS 已连接并订阅
收到实时数据: {'co2': 465, 'tvoc': 221, 'energyA': 13576, ...}
失效 token → 自动刷新 → 重连 → CO₂/能耗数据实时恢复,全程 40 秒,零人工介入。
验证二(自愈现场直播):装上 supervisor 的当天早上,家里的网络真的抖了——
正好全程记录了它的自愈过程:
09:21:55 登录失败(token 可能过期)→ 尝试刷新 token
09:22:06 token 刷新失败: [Errno -3](DNS 也在抖),10s 后重试
09:22:23 WS 断开,20s 后重连
09:25:56 token 刷新失败: [Errno -3],10s 后重试
09:27:29 WS 断开,160s 后重连
(DNS 恢复)→ 登录成功 → 数据恢复 → 控制指令正常下发回显
同样是一场昨夜级别的网络抖动——上次结局是"第二天早上实体全灰",这次结局是
supervisor 自己爬起来,期间没人碰过 HA。从今往后这台新风的记录曲线,理论上可以
一直画到设备退休。
九、踩坑实录:四次翻车(语音联调三次 + 一夜之间实体全灰)
到这里实体都齐了,按说该收工了。但真正的"最后一公里"才刚开始——接入语音助手(GLM)后,我连续翻了三次车。这部分过程太真实了,专门记下来,因为每个坑都是通用经验。
第一次翻车:fan.set_speed not found
对着语音助手说"新风风量调到2档",它准确地识别了设备、确认了目标,然后——

▲ 识别全对,执行 404:Action fan.set_speed not found
fan.set_speed 这个服务,HA 在 2022 年 10 月就弃用、随后彻底移除了,现在只有 fan.set_percentage 和 fan.set_preset_mode。但大模型的训练语料里还满是旧文档,它"背旧书"生成了一个不存在的调用。
当时我的临时解法:给实体加"标签"硬教 LLM——

▲ 在实体标签里手写档位映射:1档=20%、2档=40%……
你从截图里还能看出当时埋的雷:标签里写了 5 档映射(4档=80%、5档=100%),而这台机器物理上只有 3 档。百分比控制确实生效了,但"2档"这种说法始终时灵时不灵。
根治(两个动作):
- 把档位做成实体原生能力——
preset_modes: ['手动', '智能', '1档', '2档', '3档'],LLM 从实体属性里就能"看见"档位,直接命中fan.set_preset_mode; - 注册兼容服务
glancetek_air.set_speed,就算 LLM 再生成旧风格调用也有兜底。
第二次翻车:切档位,新风模式被"顺手"改了
用预设模式切 2 档,风量确实变了——但打开微信小程序一看,模式从"内循环"变成了"新风":

▲ 调完风量后微信里的实际状态:模式被切到了"新风",而我只让它改风量
而用滑条的"高/中/低"调就没这个问题。同一台设备,两条路径行为不一致——这个现象本身就是最好的线索。
排查发现:我上一版在切档指令里捎带了 auto: false(想着"切档同时退出智能模式",看似合理)。但这台设备的固件有个联动陷阱:写指令里只要出现 auto 字段,就会连带修改 fresh(内/外循环)。滑条路径我只发 speed,所以干净。
翻回微信 H5 源码求证——原版切风速时只发 {"speed": n} 一个字段,从不碰 auto。照抄原版语义,问题消失。
💡 给所有逆向者的一条铁律:能抄原版行为就别自己发挥。固件的联动怪癖永远比你想的多。
第三次翻车:滑条的"中"档,落在了 3 档上
修复副作用后做回归测试,发现 HA 风机面板的"低/中/高"三档错位:"中"档实际是 2 档和 3 档,永远摸不到 1 档:

▲ HA 风机面板:振荡档"高/中/低/关闭"。当时"中"落 3 档、"低"落 2 档
根因是两套映射体系打架:我按协议 5 档做百分比(1档=20%),但 speed_count=3 让 HA 面板按 3 档均分(低=33% 中=67% 高=100%)。67% 进我的 5 档换算 ceil 出 3 档,跟"高"重叠;20% 这个档位在 HA 3 档体系里压根不存在。
修复:百分比改为最近邻对齐 HA 振荡档——33%→1档、67%→2 档、100%→3 档,滑条三档与设备一一对应。回归实测全绿:
| 操作 | 结果 | 新风模式被误动? |
|---|---|---|
| 预设切 2 档 | 2 档(67%) | 否 ✅ |
| 面板"低" | 1 档 | 否 ✅ |
| 面板"中" | 2 档 | 否 ✅ |
| 面板"高" | 3 档 | 否 ✅ |
| 预设"智能" | 智能模式(唯一动 auto 的入口) | 预期内 |

▲ 三次翻车修完:语音"调到2档"“调到40%"全部一次命中
顺带说明:面板上的"关闭"按钮是直接关机(HA fan 域的标准语义,0%→turn_off→电源关闭),这台设备也不存在"风速归零但开机"的状态——和微信里的电源键行为一致,属正常。
静态排坑清单(另外几条雷)
- 小程序包解不开是正常的——
V1MMWX加密跟账号绑定,别死磕。走内存路线。 - 内存扫描必须用 sudo——普通用户连
/proc/pid/mem都打不开。 - 小程序要处于打开状态——关掉小程序 token 就可能被回收,扫不到。
- 机智云的服务器 TLS 很老——只支持 TLS1.2 + AES128-SHA,Python 连接时必须放宽安全级别:
ctx.set_ciphers("DEFAULT:@SECLEVEL=1") # 否则握手失败 - WebSocket 帧超过 126 字节要用扩展长度——标准要求,很多简易实现会漏,导致服务器报
parameters format error。 - HA 新版 API 变了——
async_forward_platforms已改名async_forward_entry_setups;实体必须设unique_id否则每次重启重复注册;自定义服务必须在services.yaml声明;cv.percentage校验器已不存在。 - 常驻任务别用
hass.async_create_task——会被 HA bootstrap 当作 setup 前台任务等待,拖慢启动收尾。用loop.create_task挂后台,并在EVENT_HOMEASSISTANT_STOP时清理,两处日志警告("Setup timed out" 和 "still running after final writes")才能归零。 - 连接建立后要主动补一次
c2s_read——setup 时发的全量读取可能落在连接建立之前,直接发进了断开的 socket(静默丢弃),实体会一直 unknown。把它挪到"订阅成功"之后发,问题消失。 - 本机型风速实际只有 3 档——协议支持 1~5,但
capability数据点会告诉你真实上限(我的机器全是 0,即 3 档封顶)。发 4 档设备会静默忽略,和微信里行为一致,属正常。
十、写在最后:这套方法论的边界
复盘一下,这次逆向没有破解任何加密、没有伪造任何身份、没有碰设备固件一个字节。我们做的三件事——读自己电脑上自己进程的内存、下载公开网页的源码、调用物联网平台文档里的官方接口——全部在"分析自家设备如何工作"的正当范围内,本质上是把厂商藏在微信后面的能力,用平台自己提供的方式重新暴露出来。
这个方法论的适用边界也很清晰:
- ✅ 适用:小程序接的是机智云/涂鸦等公开物联网平台的家电(这样的白电非常多:某新风、某净水器、某取暖器、某晾衣架……)
- ❌ 不适用:厂商完全自建云、协议私有加密、或者蓝牙直连不走云的设备
最后一句话总结这次的经历:
当一个家电厂商把"智能"做成微信里的孤岛时,逆向不是黑客行为,而是用户拿回自己设备控制权的正当手段。
资源获取
本文提到的所有代码,关注后回复「新风」获取:

或者微信搜索 开元俱乐部
glancetek_air完整 HA 集成组件(开箱即用,含 token 自愈/断线重连 supervisor)gizwits_hunter.py一键抓取脚本(本文第二节那个)- 逆向过程笔记(含机智云 WS 协议完整格式)
如果你家的设备不是格兰斯柯,同样可以试试 gizwits_hunter.py——只要它接的是机智云平台,90% 的步骤完全一样。跑一遍脚本,看能不能抓到 login_req,能抓到就说明这条路走得通。
定时自动化的想象空间:
# 工作日早上 7 点半自动开新风(比设备定时更强:还能加条件)
automation:
- alias: "工作日晨间新风"
trigger: { platform: time, at: "07:30:00" }
condition:
- condition: time, weekday: [mon, tue, wed, thu, fri]
action:
- service: glancetek_air.set_timer
data: { entity_id: switch...定时关机, time: "22:00", repeat: 127 }
觉得有用的话,转发给那个总抱怨"又要开微信关空调"的家人吧 😄
原创不易,转载请注明出处。设备型号:格兰斯柯 JWDCAC001 新风一体机;HA 版本 2026.10;实测日期 2026-11-05~07(含功耗/定时/语音联调三轮排错与 token 自愈验证)。、
Q.E.D.


