本帖最后由 雪晴科技HTH 于 2026-8-20 08:45 编辑
B站视频地址错误,请重试。
B站视频地址错误,请重试。
【项目介绍】
一款 2.9 元的蓝牙防丢器(iTag 类小标签),虽然便宜,但它是一台标准的 BLE GATT 设备。通过纯软件逆向,我把它改造成了一个智能家居无线按键,配合 Home Assistant 实现各种联动,整个过程不用动硬件、不刷固件。
【实现的功能】
1. 单击 / 双击识别,触发任意 HA 自动化(设置里可选目标实体)
2. 寻物报警:一键让标签持续响铃,按下按键即停止
3. 静音模式:按下标签按键无声(针对该型号实测的指令)
4. 电量显示、实时按键记录
5. 封装成 Electron 桌面应用,手机浏览器也能直接访问控制
【演示】
双击标签 → 切换卧室灯 / 触发自动化,延迟极低。
【技术要点(逆向心得)】
- 服务 0xFFE0,特征 0xFFE1:按键通知,值恒为 0x01,单击/双击靠软件计时判定(300~500ms 窗口)
- 特征 0x2A06(Immediate Alert):写入 0x01 = 响铃,写入 0x02 = 静音 —— 注意这个型号和标准协议是相反的,0x02 并不会响铃,这是实测踩坑得来的
- 断开报警走特征 0xFFE2(Set LinkLost Alert),写入 0x00 可关闭
- HA 联动通过 REST API 调用服务,无需 MQTT
【结尾】
几块钱的成本,就能得到一个好用的智能家居无线按键。代码整理好后会开源,欢迎交流讨论~
软件下载地址:
『来自123云盘用户雪晴科技HTH的分享』ITAG智能按键 Setup 1.0.0.zip
链接:https://1812732169.share.123pan.cn/123pan/rGdKVv-JPydd?pwd=vj7M#
提取码:vj7M
测试软件下载(用于扫描测试自己的标签):
大家可以先下载测试,测试完记住蓝牙标签的mac地址,后续更新esp32作为中继的版本,需要使用到蓝牙的mac地址。
https://xueqing.lanzout.com/iT7Hr42ul3qh
HA联动的源代码下载(知道自己标签的mac地址之后,可以搜索替换):
通过网盘分享的文件:ITAG智能按键-程序源码.zip
链接: https://pan.baidu.com/s/1Vr3shzTyMKImpCs7EPsOkA?pwd=gjct 提取码: gjct 复制这段内容后打开百度网盘手机App,操作更方便哦
如果自己不知道修改哪里,可以试一下agent工具。
https://xueqing.lanzout.com/i6khE42umz3c(里面是源代码和AI提示词)
我需要把项目中的 ITAG 智能按键主程序 itag_visualizer.py 适配成使用我自己的 iTag 蓝牙标签
(替换掉作者原来的标签地址)。
背景:
- 这是单文件程序(Python + 内嵌 HTML/JS 前端,http.server 实现,端口 8899),用 bleak 连接 BLE 标签。
- 它启动时会先读同目录的 config.json(存在时优先);读不到才用代码里的 DEFAULT_CONFIG 兜底。
- 请先阅读 itag_visualizer.py 和 config.json 这两个文件,再按清单执行。
我的设备信息:
- 我的 iTag MAC 地址:【填你的MAC,如 12:34:56:AB:CD:EF】
- 我的 HA 地址(可选):【如 http://192.168.1.100:8123】
- 我的 HA 长期访问令牌(可选):【填token】
- 双击判定窗口毫秒数(默认 500):500
需要修改的位置(必须全部检查):
1. config.json(推荐,最高优先级):改 itag_mac 为我的 MAC;改 ha_url/ha_token;
timing.multi_click_window_ms 按需调整;文件不存在则按 DEFAULT_CONFIG 生成。
2. itag_visualizer.py 代码默认值(config.json 缺失时才生效,也建议同步改):
- DEFAULT_CONFIG 的 itag_mac(约第 39 行)
- DEFAULT_CONFIG 的 ha_url/ha_token(约第 40-41 行,敏感信息,必须换成我自己的)
- DEFAULT_CONFIG 的 timing.multi_click_window_ms / cooldown_ms(约第 60-61 行)
- 前端兜底 itag_mac: currentConfig?.itag_mac || "..."(约第 1802 行,必须与后端一致)
- 文件头 docstring 里的 MAC 是注释,可顺手更新。
3. 只改配置,不动业务逻辑:不要改 BLE 连接、手势识别、响铃指令(0x2A06)、按键通知(0xFFE1)等
任何功能代码;不要改端口(默认 8899)。
输出要求:
- 先报告所有需要改的位置(文件+行号+原值→新值)让我确认;
- 然后执行修改,并保留 config.json.bak 备份;
- 最后给出 3 行以内的验证方法(如:运行 python itag_visualizer.py,网页右上角显示我的 MAC)。
关闭标签蓝牙断开后的自动报警代码
"""Disable ITAG link-lost alarm: write ONLY 0x00 to 0xFFE2, then disconnect"""
import asyncio
import sys
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace", line_buffering=True)
sys.stderr.reconfigure(encoding="utf-8", errors="replace", line_buffering=True)
except Exception:
pass
from bleak import BleakClient
ADDR = "FF:FF:12:B6:1F:4C"
FFE2_LINKLOST = "0000ffe2-0000-1000-8000-00805f9b34fb" # Set LinkLost Alert
def p(*a):
print(*a, flush=True)
async def main():
p(f"Connecting to {ADDR} ...")
client = BleakClient(ADDR, timeout=15)
try:
await client.connect()
p("Connected.")
# ONLY write 0x00 = disable link lost alarm
await client.write_gatt_char(FFE2_LINKLOST, b"\x00", response=True)
p("[OK] LinkLost Alert <- 0x00 (OFF). 设置完成!")
# Verify current state by writing 0x01 and reading back? No - just confirm write.
p("保持连接 5 秒后断开...")
await asyncio.sleep(5)
except Exception as e:
p(f"ERROR: {e}")
finally:
try:
await client.disconnect()
p("已断开连接。")
p()
p(">>> 现在请听 15 秒:如果不再响 = 关闭成功!")
except Exception:
pass
if __name__ == "__main__":
asyncio.run(main())
关闭标签蓝牙断开后的自动报警代码
"""Enable ITAG link-lost alarm: write ONLY 0x01 to 0xFFE2, then disconnect"""
import asyncio
import sys
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace", line_buffering=True)
sys.stderr.reconfigure(encoding="utf-8", errors="replace", line_buffering=True)
except Exception:
pass
from bleak import BleakClient
ADDR = "FF:FF:12:B6:1F:4C"
FFE2_LINKLOST = "0000ffe2-0000-1000-8000-00805f9b34fb" # Set LinkLost Alert
def p(*a):
print(*a, flush=True)
async def main():
p(f"Connecting to {ADDR} ...")
client = BleakClient(ADDR, timeout=15)
try:
await client.connect()
p("Connected.")
# ONLY write 0x01 = enable link lost alarm
await client.write_gatt_char(FFE2_LINKLOST, b"\x01", response=True)
p("[OK] LinkLost Alert <- 0x01 (ON). 设置完成!")
p("保持连接 5 秒后断开...")
await asyncio.sleep(5)
except Exception as e:
p(f"ERROR: {e}")
finally:
try:
await client.disconnect()
p("已断开连接。")
p()
p(">>> 现在请听 15 秒:如果断开后开始响 = 打开成功!")
except Exception:
pass
if __name__ == "__main__":
asyncio.run(main())
|