VibeEngineering-LLC/radex-esp32

GitHub: VibeEngineering-LLC/radex-esp32

一款开源 ESPHome BLE 网关固件,使 ESP32 能够 7×24 小时持续采集 Radex MR107ion 氡气检测器数据并接入 Home Assistant,摆脱手机 App 和厂商云服务的依赖。

Stars: 0 | Forks: 0

# Radex MR107ion → ESP32 → Home Assistant / Web UI 现成的 ESPHome 固件,可将 ESP32 开发板变成**永久的 BLE 网关** 连接氡气检测器 **Radex MR107ion**(Quarta-Rad, [quartarad.com](https://quartarad.com)) 和你的智能家居。设备将不再依赖手机上的 **RadexM** 应用—— 只要 ESP32 插在插座上,数据就会全天候 24/7 流式传输,无需制造商的云服务。 ``` Radex MR107ion ─BLE READ-poll─► ESP32 ─Web UI v3─► браузер │ ├─► ESPHome API ─► Home Assistant │ └─► Народмон (инфраструктура есть, ВЫКЛ по умолчанию) ``` 完整的知识库位于 [`SKILL.md`](SKILL.md)。完整的 BLE 协议解析位于 [`references/mr107ion.md`](references/mr107ion.md)。 ## 哪个固件对应哪个开发板 该技能包提供基于同一代码库的 **四个 YAML** 配置。区别在于:目标 ESP32 模块、 应用场景和 Web UI 布局。 | 固件 | 目标开发板 | 特点 | 适用场景 | |---|---|---|---| | **`firmware/radex_gateway_s3.yaml`** *(当前推荐, 2026-06-17)* | **ESP32-S3-DevKitC-1** (`board: esp32-s3-devkitc-1`, framework=arduino) | 1个 MR107ion 的 BLE 客户端,Web Server v3 + Basic Auth + sorting_groups (数据 / 服务),ESPHome API 加密,看门狗 WiFi,Narodmon 基础设施 (switch ALWAYS_OFF, 4 种协议),USB-C,4 个并行 BLE 插槽 | 适用于基于全新 S3 开发板的**单个** MR107ion 的生产级网关 | | `firmware/radex_gateway_s3_baseline.yaml` | ESP32-S3-DevKitC-1 | 不含传感器的纯 baseline (包含 BLE/API/Web UI 但无轮询逻辑) — 用于硬件检测和 OTA | S3 新开发板的首个固件 (刷入完整固件前的冒烟测试) | | `firmware/radex_gateway.yaml` | ESP32-DevKitC (WROOM-32 / WROVER), framework=esp-idf | v0.3.0-step8 (最后一步 — `bluetooth_proxy` 已注释),BLE READ-poll 4 handle 轮询,两个 Web UI v3 表格 (“数据” / “服务”),使用 `web_server.log: false` 防止 json:111 | 旧版 ESP32-DevKitC 开发板,ESP-IDF | | `firmware/radex_gateway_v2.yaml` | ESP32-DevKitC | Web Server v2 替代方案 (带有主题前缀 `1.x` / `2.x` / `3.x` / `4.x` 的单一扁平表格) | 需要“一次性显示全部”而不必切换分组时 | 所有固件均监听相同的 MR107ion 协议 (自定义服务 `FE651Y00-…`,通过主干 `FE651700-…` 进行 ATT-handle READ-poll),仅在 UI / 协议栈 / HARD 规则上有所不同。 ## 测量内容 固件循环轮询设备的 **4 个 BLE handle** (round-robin,每次读取间隔 4.3 秒, 完整周期 ≈ 17.2 秒),并在 Web UI 中发布以下传感器数据: | 指标 | 单位 | 设备数据源 | ESPHome sensor | |---|---|---|---| | **最新氡气** | Bq/m³ | float32 LE @ handle `0x0049` (`OAR_last`) | `radon_bqm3` | | **氡气平均值(设备)** | Bq/m³ | float32 LE @ handle `0x0040` (`OAR_sred`) | `radon_avg_bqm3` | | **每小时氡气平均** | Bq/m³ | sliding_window 60 × 60 c, `filter_out: nan` | `radon_avg_hour` | | **每日氡气平均** | Bq/m³ | sliding_window 1440 × 60 c, `filter_out: nan` | `radon_avg_day` | | **温度** | °C | int16 LE @ handle `0x0058` (`temper_x10`),解码 `÷10` | `temperature_c` | | **湿度** | % | uint8 @ handle `0x005E` (`humidity`) | `humidity_pct` | | **RSSI BLE** | dBm | 来自广播包 (`on_ble_advertise`) | `rssi_radex` | 服务 `FE651700-…` 的 15 个特性的完整映射表 (包括 `Sko_OAR_sred`、`OAR_min/max`、 `OAR_mov_avr`、`t_izm_last`) — 见下文的“完整 BLE 协议解析”部分及 [`references/mr107ion.md`](references/mr107ion.md)。 ## 支持的设备 | 设备 | 测量内容 | BLE | 现成固件 | |---|---|---|---| | **Radex MR107ion** | 空气氡气 (Bq/m³,电离室) + 温度 + 湿度 | ✅ | `firmware/radex_gateway_s3.yaml` (当前推荐) | | Radex RD1212-BT | 辐射背景 (剂量率,盖革计数器) | ✅ | 计划在协议逆向工程后支持 | | Radex One | 辐射背景 | ❌ 仅限 USB | 不包含在此技能中 (不适用 BLE 网关) | 设备产品线通过制造商统一的 **RadexM** 应用进行识别 ([Android](https://play.google.com/store/apps/details?id=ru.quartarad.radexm), [iOS](https://apps.apple.com/us/app/radexm/id1524841479))。所有支持 BLE 的设备 均使用相同的 GATT 服务模板 (`FE651Y00-…`)。 ## 运行不需要什么 - ❌ Quarta-Rad 账户 / 绑定云服务 - ❌ 需要智能手机一直放在设备旁边 - ❌ Pairing / bonding (Radex 不需要) - ❌ 付费软件 ## 需要什么 | 物品 | 用途 | |---|---| | ESP32-S3-DevKitC-1 *(当前推荐板)* | 网关大脑,$10,支持 USB-C | | USB Type-C 数据线 | 刷写固件和供电 | | Radex MR107ion 及其 BLE | 数据源 | | Wi-Fi 2.4 GHz + 互联网 | 用于 Home Assistant / Web UI | | 电脑上装有 `esphome` 2026.5+ | `pip install esphome` | | BLE 设备地址 (MAC) | 可通过 RadexM 查看,或在刷写后通过 Web UI 查看 | ## 部署指南 (5 分钟) ### 1. 准备 secrets ``` git clone https://github.com/Verter73/claude-skills.git cd claude-skills\radex-esp32\firmware\ Copy-Item secrets.example.yaml secrets.yaml ``` 打开 `secrets.yaml` 并填写: - `wifi_ssid` / `wifi_password` — 家庭 Wi-Fi 2.4 GHz。 - `ap_password` — captive-portal AP “radex-gw-s3 Fallback” 的密码 (≥8 个字符)。 - `api_encryption_key` — `openssl rand -base64 32`。 - `ota_password` — 用于 OTA 的长密码。 - `web_server_auth_user` / `web_server_auth_pass` — 用于 `/` 的 Basic Auth。 - `radex_mac` — **可保留为 placeholder**,MAC 会在首次启动后通过 Web UI 设置。 ### 2. 编译并刷写 ``` $esp = "$env:LOCALAPPDATA\Programs\Python\Python312\Scripts\esphome.exe" $env:PYTHONIOENCODING = "utf-8"; $env:PYTHONUTF8 = "1" & $esp compile radex_gateway_s3.yaml & $esp upload radex_gateway_s3.yaml --device COM ``` 查找 COM 端口: ``` Get-CimInstance Win32_PnPEntity | Where-Object { $_.Name -match 'CH340|CP210|FTDI|USB Serial' } ``` ⚠️ 在某些机器上 COM5 是 **SoundBlaster,而不是 ESP32**。请选择名称中 包含 `USB-SERIAL CH340` / `Silicon Labs CP210x` / `USB Serial Device` 的端口。 ### 3. 首次启动 → captive portal 如果未配置 WiFi — ESP 将开启 AP **“radex-gw-s3 Fallback”**,使用 `ap_password` 中的密码。用手机连接 → `http://192.168.4.1/` → 选择家庭 网络 → 输入密码 → **Save**。ESP 将重启。 ### 4. Web UI 和 MAC 绑定 `http://radex-gw-s3.local/` (用户名/密码 = `web_server_auth_user/pass`)。 - **“Service”** 组 → 运行 BLE 扫描 → 找到 `MR107ion-12AB` 行,复制 MAC。 - 粘贴到“MAC Radex”输入框 → “应用 MAC 并重启”。 - 重启后,**“Data”** 组中将会显示氡气/温度/湿度数据。 ### 5. Home Assistant 设置 → 设备与服务 → **添加集成** → ESPHome → 主机 `radex-gw-s3.local` (或 IP),加密密钥 — 就是 `secrets.yaml` 中的那个。 ## 在 Web UI 中会看到什么 `radex_gateway_s3.yaml` 中的 ESPHome Web UI v3 会在每个卡片上绘制 **迷你走势图**, 点击时会显示 **全尺寸图表**。两个主题分组 (`sorting_groups`): ### “数据”组 (`sg_data`) | 卡片 | 显示内容 | |---|---| | **最新氡气** | 来自设备的最新 `OAR_last`,Bq/m³,约每 17 秒更新一次 (round-robin 周期) | | **设备氡气平均** | 来自设备的 `OAR_sred` (MR107ion 内部统计数据),Bq/m³ | | **每小时氡气平均** | 基于 `OAR_last` 的滑动窗口 60 × 60 c,在 ESP 上计算,Bq/m³ | | **每日氡气平均** | 基于 `OAR_last` 的滑动窗口 1440 × 60 c,Bq/m³ | | **温度** | int16 LE / 10,°C | | **湿度** | uint8,% | ### “服务”组 (`sg_service`) | 卡片 | 作用 | |---|---| | **BLE 扫描器** | “Start BLE scan” 按钮 + 找到的设备列表;在首次绑定 MAC 时使用 | | **MAC Radex** | 用于手动输入 MAC 的字段 + “应用 MAC 并重启” 按钮 (MAC 保存在 NVS) | | **BLE 已连接** | ON/OFF,到设备的 GATT 会话状态 | | **BLE: 连接 / 重连** | 计数器,用于诊断稳定性 | | **RSSI BLE** | 设备的信号强度,dBm | | **重新连接 Radex** | 强制 BLE 断开 + 新建连接 | | **重置 BLE 计数器** | 清零连接/重连计数器 | | **WiFi 信号 / SSID / BSSID / IP / 网关 MAC** | 网络诊断 | | **API 已连接** | ESPHome API 状态 (另一端是否有 HA) | | **Uptime** | 自上次重启以来的时间 | | **Switch “上传到 Narodmon”** | 主开关 (默认 OFF,`restore_mode: ALWAYS_OFF`) | | **Select “发送方式”** | 4 种 Narodmon 传输方式:`HTTP GET` / `HTTP POST` / `HTTPS POST` / `JSON POST` | | **指标名称** RR1 / T1 / H1 | Narodmon 端的指标名称 | | **按钮“立即发送到 Narodmon”** | 手动触发;仅在开启 (ON) 时有效 | | **Reboot / Safe Mode / Factory reset** | 开发板的标准控制按钮 | 在替代固件 `radex_gateway_v2.yaml` (Web Server v2) 中 — 是一个带有 主题前缀 `1.x` (设备) / `2.x` (BLE+网络) / `3.x` (Narodmon) / `4.x` (系统) 的扁平表格;内容相同。 ## 架构 ``` ┌────────────┐ BLE READ-poll ┌────────────────┐ HTTP/WiFi ┌──────────────┐ │ Radex │ ───────────────► │ ESP32-S3 │ ──────────► │ Браузер, │ │ MR107ion │ 4 handle │ -DevKitC-1 │ JSON + │ HA, │ │ (Quarta- │ каждые 4.3 с │ N16R8 │ Web UI v3 │ Python │ │ Rad) │ (~17.2 с цикл) │ (ESPHome, │ │ │ │ │ │ arduino) │ │ │ └────────────┘ └────────────────┘ └──────────────┘ │ I/O ▼ ┌──────────────┐ │ Round-robin │ │ read pump │ │ 4 handle → │ │ 6 sensors + │ │ sliding-win │ │ hour/day │ └──────────────┘ │ │ опционально, ВЫКЛ по умолчанию ▼ ┌──────────────┐ │ Народмон │ │ HTTP/HTTPS/ │ │ JSON, 600 с │ │ ALWAYS_OFF │ └──────────────┘ ``` 没有云服务,没有账号。一切都在你的本地网络中运行,源代码完全开放, 固件属于你自己。 ## MR107ion 协议 完整的 BLE GATT 映射以及对服务 `FE651700-…` 全部 15 个特性的分析 — [`references/mr107ion.md`](references/mr107ion.md)。 简而言之:2 个自定义服务,基础 UUID 为 `FE651Y00-00B0-4240-BA50-05CA45BF8AAA`,其中 `Y=6` 用于 配置,`Y=7` 用于测量。采用 **ATT Read** 轮询 (而不是 Notify),RadexM 应用在 27 分钟内 发送了 5,184 次 read-request (≈ 3.2 req/s,每个 handle 约 324 次读取)。 自解析协议:每个特性占用 **连续的 3 个 handle** (声明 + 值 + 用户描述 `0x2901`,其中包含 ASCII 音译的人类可读字段名)。 ## 平台稳定性 — 为什么 S3 上用 `arduino`,为什么经典版上用 `log false` **简短结论**:ESP32-S3-DevKitC-1 N16R8 (16 MB Flash + 8 MB 嵌入式八进制 PSRAM) + `framework: arduino` — 是稳定且推荐的配置。经典版 ESP32-DevKitC v4 (WROOM-32 / 4 MB flash / 无 PSRAM) + `esp-idf` 只能在 `web_server.log: false` 且固定使用 `bluetooth_proxy: # active: true` (已注释) 时正常工作 — 任何 开启调试日志或恢复 btproxy 的尝试都会在浏览器中按第一次 F5 时破坏堆内存。 这是在 2026 年 6 月本项目的一系列事件中验证过的: ### 无 PSRAM 的 DevKitC 上的 json:111 (v0.3.0-broken-no-log-false) 在从 bt-proxy baseline 重建后的第一个 v0.3.0 版本中使用了 `web_server.log: true` (在 注释中有提及,但在 YAML 中实际缺失 = 默认为 `true`)。DevKitC 在浏览器 + SSE Debug Log + 并行向 Narodmon 发送 `http_request` 时会出现连锁崩溃: ``` [E][json:111]: JSON document overflow [W][component:522] web_server took a long time (73 ms) ``` `min_free_heap` 跌破约 12 KB,Web UI 加载为空。**一行代码修复:** `web_server: log: false` (v0.3.0-step5b, 2026-06-14)。已作为 DevKitC 的 HARD 默认值硬编码在 `radex_gateway.yaml` 中。 ### v3 sorting_groups 上的 lit-element 修复 (v0.3.0-step6b) `web_server: version: 3` 使用了 web-component lit-element;空的 `sorting_group` (例如,内部没有传感器的 `sg_re`) 会破坏渲染 — 卡片不显示。修复方法: 不要创建空组;如果暂时没有实体分配给该组 — 将该块完全删除。 ### esp-idf 上的 Auth Expired 及手动设置的 600/180 (来自 atomfast/radoneye 的 INC-12) 在 esp-idf 上,BLE 协议栈遵循其自身的共存平衡器;手动设置的扫描参数 `interval: 600ms, window: 180ms` (在 arduino 上很稳定) 会破坏这种模型 — WiFi 射频 得不到足够的空口时间,导致每隔几秒就会出现 `STA_DISCONNECTED reason=15 (AUTH_EXPIRE)`。 最终得出的 HARD 规则是:在 esp-idf 上 — **使用 `640ms / 32ms` (~5 % 占空比)** 或者完全移除 `scan_parameters`。在 S3 + arduino 上 — 无法复现,开发者保留了默认的扫描参数。 ### S3 上的 `log: true` — 有意为之的特例 ESP32-S3-DevKitC-1 N16R8 拥有 **8 MB 嵌入式八进制 PSRAM**。堆内存余量比 经典版 DevKitC 大一个数量级;按 F5 时不会发生 json:111。在 `radex_gateway_s3.yaml` 中保留了 `web_server: log: true` — Debug Log 小部件可以正常工作。**回退条件** (已硬编码在 YAML 注释中):在 S3 上出现任何重启 / json:111 / OOM → 恢复为 `log: false` 且不要重新启用, 无需询问。 ### 结论:如何选择 - ⭐ **ESP32-S3-DevKitC-1 N16R8** → `radex_gateway_s3.yaml` (arduino, log: true)。推荐使用的 当前配置。 - **经典版 ESP32-DevKitC v4** (如果手头已有) → `radex_gateway.yaml` (esp-idf, v0.3.0-step8, log: false, `bluetooth_proxy: # active: true` 已注释)。 在此配置下很稳定;在 esp-idf 上使用其他配置则不行。 - **经典版上的替代 UX** → `radex_gateway_v2.yaml` (Web Server v2,带前缀的 单一扁平表格)。 - **全新刷入 S3 开发板前的硬件冒烟测试** → `radex_gateway_s3_baseline.yaml` (最小配置:包含 BLE/API/Web UI 但无轮询逻辑)。 ## Radex MR107ion BLE 协议完整解析 ### 设备在空中的识别 | 参数 | 值 | |---|---| | 型号 | **MR107ion** (带 BLE 的新版 MR107) | | 广播 local_name | `MR107ion 0214` (最后 4 位数字是修订版本) | | PDU 类型 | ADV_IND | | Pairing / bonding | 不需要 | ### GATT 表 (实时 HCI 捕获) | Handle 范围 | Service UUID | 用途 | |---|---|---| | `0x0001–0x0009` | `0x1800` | GAP (标准) | | `0x000a–0x000d` | `0x1801` | GATT (标准) | | `0x000e–0x0013` | `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` | Nordic UART (NUS) — **占位符,不工作** | | `0x0014–0x0020` | `0x180a` | Device Information | | `0x0021–0x003d` | `FE651600-00B0-4240-BA50-05CA45BF8AAA` | Custom Config (R/W, 14 项特性) | | `0x003e–0x006b` | `FE651700-00B0-4240-BA50-05CA45BF8AAA` | Custom Measurement (R, 15 项特性) | 主要工作是通过两个自定义服务进行的,基础 UUID 为 `FE651Y00-00B0-4240-BA50-05CA45BF8AAA`,其中 `Y=6` 用于配置,`Y=7` 用于 测量。 ### 交互模式 — READ-poll,非 Notify **逆向工程的主要发现。** RadexM 应用 **是以循环 ATT Read 方式轮询设备的, 而不是订阅 Notify。** 在 27 分钟的 HCI 会话中: - ATT Read 请求总数:**5,184** (平均 ≈ 3.2 req/sec)。 - 每个关键 handle 被查询了 **324 次** (每个 handle ≈ 每 5 秒 1 次)。 - 会话期间的 Notify 数据包:**0**。设备直接忽略了针对 NUS 的 CCCD 写入。 这与 RadonEye / AtomFast (仅限 push-only) 截然不同,但这简化了 向 ESP32 的移植:不需要等待异步的 Notify,可以按照任何合适的 周期进行轮询。 ### 自解析协议 (`User Description 0x2901`) 服务 `FE651700-…` 的每个特性占用 **3 个连续的 handle**: 1. **Decl** (奇数,从 `0x003F` 开始) — 属性 + value_handle + 基础 UUID 为 `FE6517NN-00B0-4240-BA50-05CA45BF8AAA` 的 128 位 UUID,其中 `NN` = 01, 02, 03 … 15 (十六进制)。 2. **Value** (decl+1) — 具体的数据 (float32 LE / uint32 LE / uint16 LE / uint8)。 3. **User Description** (decl+2, UUID `0x2901`) — **ASCII 人类可读的字段名** (俄语音译,QuartaRad 的笔迹)。 首先读取 desc-handle → 获得字段名;然后读取偏移量 -1 处的 value。 ### 服务 FE651700 完整映射表 (来自 MR107ion 0214 的实时值) | # | Decl | Value | Desc | 名称 | 类型 | 原始值 | 解码 | 含义 | |---|---|---|---|---|---|---|---|---| | 1 | 0x003F | **0x0040** | 0x0041 | `OAR_sred` | float32 LE | `55 89 93 42` | **73.79** | 氡气平均值, Bq/m³ | | 2 | 0x0042 | **0x0043** | 0x0044 | `Sko_OAR_sred` | float32 LE | `A1 2A 39 41` | **11.572** | 平均值的标准差, Bq/m³ | | 3 | 0x0045 | **0x0046** | 0x0047 | `t_izm_last` | uint32 LE | `8B 58 00 00` | **22667** | Uptime / 最后测量时间,秒 (+1/秒) | | 4 | 0x0048 | **0x0049** | 0x004A | **`OAR_last`** ⭐ | float32 LE | `00 00 8A 42` | **69.0** | **最新氡气, Bq/m³** | | 5 | 0x004B | **0x004C** | 0x004D | `OAR_mov_avr` | float32 LE | `55 55 92 42` | **73.17** | 滑动平均值, Bq/m³ | | 6 | 0x004E | **0x004F** | 0x0050 | `Qakk` (截断) | uint16 LE | `61 00` | **97** | 名称被截断为 4 字节;可能是 `Qakkum_OAR` | | 7 | 0x0051 | **0x0052** | 0x0053 | `OAR_min` | float32 LE | `7C 3B 47 42` | **49.81** | 氡气最小值, Bq/m³ | | 8 | 0x0054 | **0x0055** | 0x0056 | `OAR_max` | float32 LE | `23 F7 99 42` | **76.98** | 氡气最大值, Bq/m³ | | 9 | 0x0057 | **0x0058** | 0x0059 | `temper_x10` | int16 LE *) | `0A 01` | **266** | 温度 × 10 → **26.6 °C** | | 10 | 0x005A | **0x005B** | 0x005C | `preassure` *(原文如此)* | uint16 LE | `00 00` | 0 | 压力 (无传感器?) | | 11 | 0x005D | **0x005E** | 0x005F | `humidity` | uint8 | `31` | **0x31 = 49** | 湿度, % | | 12 | 0x0060 | **0x0061** | 0x0062 | `quantity_izm` | uint16 LE | `04 00` | 4 | 测量单位数量 | | 13 | 0x0063 | **0x0064** | 0x0065 | `num_izmer` | uint16 LE | `03 00` | 3 | 测量次数 | | 14 | 0x0066 | **0x0067** | 0x0068 | `t_prepare` | uint16 LE | `00 00` | 0 | 准备时间,秒 | | 15 | 0x0069 | **0x006A** | 0x006B | `mis_psw` | uint16 LE | `80 00` | 128 | misc / 状态标志 | ### 合理性检查 (逆向工程的内部一致性) - `OAR_last` (69.0) 处于最小值/最大值 (49.81 / 76.98) 范围内,并且接近 平均值/滑动平均值 (73.79 / 73.17) — 所有数值都是一致的。 - `temper_x10` = 266 → 26.6 °C,`humidity` = 49 % — 典型的室内环境条件。 - `t_izm_last` 每秒增加 +1 (在 25 秒的观察中增加了 +25) — 即 uptime。 ### 用于 ESPHome 移植的最小轮询集合 每个循环只需进行 4 次读取: | Handle | 字段 | 格式 | 传感器 | |---|---|---|---| | **0x0049** | OAR_last | float LE | `radon_bqm3` — 主要数值 | | 0x0040 | OAR_sred | float32 LE | `radon_avg_bqm3` | | 0x0058 | temper_x10 | int16 LE / 10 *) | `temperature_c` | | 0x005E | humidity | uint8 | `humidity_pct` | 技能固件中的轮询周期为:针对 4 个 handle 轮询,每 4.3 秒一次 (一个完整周期 ≈17.2 秒)。氡气是基于分钟窗口的积分统计,因此对于 独立客户端来说,30-60 秒是合理的实用默认值。RadexM 应用之所以以 ~5 Hz 的频率进行轮询,纯粹是为了界面的流畅度。 *) `temper_x10` 被解码为 `int16 LE` — 这是一个超集,同时兼容 `uint16` 和 `int16` 二进制补码。详细理由请参见 [`references/mr107ion.md`](references/mr107ion.md)。 ### 用于 ESPHome `ble_client.characteristic` 的 UUID **特性** 的基础 UUID — `FE6517NN-00B0-4240-BA50-05CA45BF8AAB` (最后一个 字节为 `AAB`);特性编号 `NN` 位于 `17NN` 的位置。不要与 **服务** Custom Measurement 的 UUID 混淆 — `FE651700-00B0-4240-BA50-05CA45BF8AAA` (最后一个字节为 `AAA`)。Quarta-Rad 的服务及其特性具有不同的最后一个 字节 — 这在全部 15 个 char-UUID 中都可以看到。 | NN | 特性 UUID | 字段 | |---|---|---| | `01` | `FE651701-…AAB` | OAR_sred (handle 0x0040) | | `04` | `FE651704-…AAB` | **OAR_last (0x0049)** ⭐ | | `09` | `FE651709-…AAB` | temper_x10 (0x0058) | | `11` | `FE651711-…AAB` | humidity (0x005E) | ### 确认 NUS 只是占位符 应用在连接时会向 `0x0013` (NUS TX 描述符) 进行两次 CCCD-write (`value=0100` — 订阅 Notify),设备静默忽略了这两次请求。MR107ion 上的 NUS 服务 声明仅仅是为了兼容应用商店的搜索, 实际上并不起作用。 ## 图表存放在哪里 在 **ESP 的 RAM 中**。ESPHome Web UI v3 会存储自上次重启以来传感器的历史记录 (每个典型卡片约 1 小时)。如果需要永久历史记录 — 请通过 ESPHome API 连接到 Home Assistant (`api_encryption_key` 已在 `secrets.yaml` 中生成)。 HA 会自动获取所有实体。 ## Narodmon 基础设施 — 默认关闭 (HARD) 固件中创建了 Switch “上传到 Narodmon”,但**必须**设置为 `restore_mode: ALWAYS_OFF`: ``` - platform: template name: "Выгружать на Народмон" id: narodmon_enabled restore_mode: ALWAYS_OFF # ← HARD: после reboot/safe-mode/OTA всегда OFF optimistic: true ``` 在重启、短暂断电、进入安全模式、恢复出厂设置、OTA 升级后 — switch **始终**会恢复为 OFF。操作员不会遇到“意外开启上传到云服务”的惊吓。 Select “发送方式” 提供了 4 种传输方式 (见技能包 [`narodmon-iot`](https://github.com/Verter73/claude-skills/tree/master/narodmon-iot)): - `HTTP GET` — `narodmon.ru/get?ID=&RR1=&T1=&H1=` - `HTTP POST` — `narodmon.ru/post`, form-urlencoded - `HTTPS POST` — 同上,但通过 mbedTLS - `JSON POST` — `narodmon.ru/json`, application/json 服务器端的最小时间间隔为 5 分钟;短于此时间 = IP 将被封禁 1 小时。 固件中被硬编码为 600 秒。 ## HARD 反模式 (违反 → 泄露机密 / 损坏设备) - ❌ **不要发布 `firmware.bin` / `firmware.factory.bin`** — 二进制文件中包含 明文的 WiFi-SSID、WiFi 密码、设备 MAC、OTA 密码、API 加密密钥。 仅限 YAML + 带有 placeholder 的 `secrets.example.yaml`。 - ❌ **不要移除 Narodmon switch 上的 `restore_mode: ALWAYS_OFF`** (HARD 规则 2026-06-17)。 - ❌ **不要运行 `esphome logs --device COM`** — DTR/RTS 会导致开发板重启并 切断 BLE 会话。请使用 OTA-logger (`--device .local`) 或者执行 `mode COM DTR=OFF RTS=OFF` + Python-serial。 - ❌ **不要尝试订阅 NUS Notify (`6E400003-…`)** — 它只是个占位符, 永远不会发送 Notify。仅支持对 `FE651700-…` 进行 READ-poll。 - ❌ **不要混淆 MR107 (仅限 USB 的旧款) 和 MR107ion (带 BLE 的新款)** — 旧的 USB 逆向工具并不适用。 - ❌ **在未经操作员明确同意的情况下,不要在 C3/S3 上使用 `web_server.log: true`** — 遇到频繁 F5 刷新时的 SSE Debug Log 会导致 json:111 溢出及近 OOM (修复 v0.3.0-step5b/6g 的根源)。在 S3 上,作为例外,开发者将其保留为 `true` — 如果 出现重启,无需询问,直接恢复为 `false`。 ## 3× ble_client (旧板) / 4× (S3) 限制的兼容性 | 开发板 | BLE central 插槽 | 该固件占用数量 | |---|---|---| | ESP32-DevKitC (Bluedroid) | 3 | 1 | | ESP32-S3 | 4 (`esp32_ble.max_connections: 4`) | 1 | 空闲的插槽可用于同一开发板上的 AtomFast 和/或 RadonEye。这符合 统一 BLE 网关的理念 (1 个 ESP32-S3 → 通过 `ble_client` 连接多个设备)。 并行公共网关 AtomFast — 见 [VibeEngineering-LLC/atomfast-esp32](https://github.com/VibeEngineering-LLC/atomfast-esp32)。 ## 已知问题与限制 - **基于 Handle 的读取与 MR107ion 的 0214 修订版绑定**。在其他修订版上 — 需替换为基于 UUID 的读取 (速度较慢,但能抵御 handle 映射表变更)。 - **MTU 为 23** (无法增加) — 影响不大,有效载荷可以塞进去。 - **Pseudo-NUS (`6E400001-…`)** 已被设备声明,但不发送 Notify — 不要尝试 通过 CCCD 订阅。 - **Narodmon-`select`** 包含 4 个选项。服务器的最小时间间隔为 5 分钟; 短于此时间 = IP 将被封禁 1 小时。固件中被硬编码为 600 秒。 - **仅修改了 `secrets.yaml` 时的 ESPHome 缓存**:`esphome upload` 可能会刷入 **旧的缓存 `firmware.bin`** 而不重新编译。解决方法是 — 在修改机密信息后,始终在 `compile/upload` 之前执行 `esphome clean `。 ## 技能包结构 ``` radex-esp32/ ├── README.md ← эта инструкция ├── SKILL.md ← обзор семейства + методология RE нового прибора ├── firmware/ │ ├── radex_gateway_s3.yaml ← АКТУАЛЬНАЯ (ESP32-S3-DevKitC-1) │ ├── radex_gateway_s3_baseline.yaml ← baseline для S3 (smoke-тест) │ ├── radex_gateway.yaml ← v0.3.0-step8 (ESP32-DevKitC, ESP-IDF, Web UI v3) │ ├── radex_gateway_v2.yaml ← альтернатива (Web Server v2, плоский UI) │ ├── secrets.example.yaml ← шаблон секретов │ ├── CHANGELOG.md ← история версий │ ├── include/ ← C-хедеры (BLE hooks) │ ├── www/ ← кастом-CSS/JS для v2 (log_limit.css, reorder_v2.js) │ └── archive/ ← старые YAML └── references/ └── mr107ion.md ← полная GATT-таблица MR107ion (live HCI + ble-explorer sweep) ``` ## 如何添加新的 Radex 设备 详细的方法论请见 [`SKILL.md`](SKILL.md),其中的“新 Radex 设备的逆向策略”部分。简而言之: 1. 获取来自 Android RadexM 的 **btsnoop HCI 捕获** (开发者选项 → 启用 Bluetooth HCI snoop log → `adb bugreport` → `FS/data/misc/bluetooth/logs/btsnoop_hci.log`)。 2. 通过 `scripts/parse_btsnoop_v2.py` 或 Wireshark 进行分析。 3. 将 GATT 映射表记录在 `references/.md` 中。 4. 通过技能包 [`ble-explorer`](https://github.com/Verter73/claude-skills/tree/master/ble-explorer) 在硬件上进行验证 (带有 GATT-enum + Notify 捕获的 Web UI)。 5. 复制一份 `firmware/radex_gateway_s3.yaml`,将 UUID/handles 和 lambda 解析器替换为新格式。 ## 相关技能包 - [`esp32-dev`](https://github.com/Verter73/claude-skills/tree/master/esp32-dev) — ESP32 + ESPHome 的通用开发实践。 - [`ble-explorer`](https://github.com/Verter73/claude-skills/tree/master/ble-explorer) — 通用 BLE-RE 网关 (GATT-enum, 0x2901, Notify 捕获)。 - [`atomfast-esp32`](https://github.com/Verter73/claude-skills/tree/master/atomfast-esp32) — 针对于 AtomFast 的相邻技能包 (基于 Notify 的 γ-剂量计),作为上传至 Narodmon 的模板。 - [`radoneye-esp32`](https://github.com/Verter73/claude-skills/tree/master/radoneye-esp32) — RadonEye Plus2 (同样是氡气检测,但为 Notify 模式,而非 READ-poll)。 - [`narodmon-iot`](https://github.com/Verter73/claude-skills/tree/master/narodmon-iot) — 上传到 narodmon.ru 的协议。 ## 制造商 [Quarta-Rad / QuartaRad](https://quartarad.com), 莫斯科, RU. 制造商应用: [RadexM Android](https://play.google.com/store/apps/details?id=ru.quartarad.radexm) · [RadexM iOS](https://apps.apple.com/us/app/radexm/id1524841479). ## 许可证 MIT.
标签:ESP32, ESPHome, Home Assistant, 智能家居, 物联网, 自定义脚本, 蓝牙网关, 辐射检测