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, 智能家居, 物联网, 自定义脚本, 蓝牙网关, 辐射检测