guillaut5/bryton460-webapp

GitHub: guillaut5/bryton460-webapp

一款通过逆向工程将 GPX 文件转换为 Bryton 460 GPS 码表原生二进制格式的非官方 Web 工具。

Stars: 0 | Forks: 0

# bryton460-webapp 一款 HTML 工具,用于从 GPX 文件生成 **Bryton 460** GPS 的原生文件。 专为无法使用 Bryton 官方应用的 PC 用户设计(如 Nokia 用户、仅有 PC 的用户等)。 **非官方工具 — 与 Bryton 无关。** 文件格式均通过逆向工程获取,仅用于互操作目的。 基于 [MIT License](LICENSE) 授权。 **→ Demo : [dev.agriscope.fr/bryton.html](https://dev.agriscope.fr/bryton.html)** ![截图](https://static.pigsec.cn/wp-content/uploads/repos/cas/23/23de0c88a00929a6ab90585e6225dc1fc3727906be606a355b9dc6f07dba2a7e.png) ## 开发说明 ### 前置条件 - Node.js ≥ 18 ### 安装环境 ``` npm install ``` ### 运行测试 ``` npm test ``` 40 个单元测试,覆盖二进制编码器和地理相关函数。 ### 构建发行版 ``` npm run build ``` 生成 `dist/index.html` — 这是一个完全独立的单文件(CSS + JS 由 `vite-plugin-singlefile` 内联处理)。 部署到静态 Web 服务器上的**只需这一个文件**。 ### 带自动刷新的本地开发 ``` npm run dev ``` 打开 `http://localhost:5173` 即可享受 hot reload。 ## 使用说明 1. 在 Chrome 或 Edge 中打开 `dist/index.html`(或在线 demo) 2. 拖拽一个 `.gpx` 文件 3. 下载 `.zip` 文件 → 解压到 Bryton(USB 大容量存储)的 `Tracks\` 目录中 ## 生成格式 | 文件 | 内容 | |---|---| | `.smy` | 摘要:bbox、点数、距离、D+ | | `.tinfo` | 每次爬坡的起点/终点标记 | | `.track` | 轨迹:每个点的纬度、经度、海拔、坡度 | | `/dupli.track` | 轨迹的副本(firmware 要求) | | `/list.junc` + `list2.junc` | 交叉路口 / 转弯 | | `/.climb` | 爬坡数据(距离、长度、D+、%) | | `/sort1.path` | 地理段索引 | ## 发布说明 ### v0.4 — 2026-07-02 - **通过 OSRM 实现 turn-by-turn 导航** — UI 中新增选项:将 GPX 轨迹提交至 [OSRM](https://project-osrm.org/) 服务(map matching),以便从 OSM 道路网络获取真实的转弯指令。 生成包含 UTF-8 街道名称、方向代码和到下一转弯处距离的 B 格式 `.tinfo` 文件 — firmware 会将其读取为手动规划的路线。 支持的指令:出发、到达、直行、轻微转弯、右转、左转、 急转弯、掉头、岔路口、环岛(1/2/3+ 出口)。 道路网络之外的部分(自行车道、海岸小径)将被静默忽略 — 其他转弯指令依然有效。 UI 会显示统计细分:`✓ 17 个转弯 — ↻ 右转 6 ↺ 左转 2 → 直行 1`。 - **`.tinfo` B 格式逆向工程** — 通过分析两个参考文件 (`voiceTrip`, `test_route`)得出:在相同的 44 字节格式中采用了 nav + 爬坡混合结构。 生成的文件严格遵循以下顺序:DÉPART (0x01), INFO rue départ (0xFA), 转弯, INFO distance totale (0xFA), ARRIVÉE (0x21), 爬坡标记 (0xBE/0xBF)。 - **v0.3 重构** — 采用 ES 模块架构 (src/),Vite 构建,Vitest (40 个测试), `vite-plugin-singlefile` → 完全独立的 `dist/index.html` (~134 KB)。 - **`sort1.path` 已验证** — 基于 OSM zoom 13 瓦片进行分段,保留 1 个点的重叠。 在 100K 数据上验证:41 个段,tx ∈ [4182,4185],ty ∈ [2982,2990]。 - **通过 Overpass API 生成 `list.junc`** — 真实的 OSM 交叉路口(半径 25m), 如果 Overpass 不可用,则回退至通过角度变化 ≥ 25° 进行检测。 ### v0.3 — 2026-06-29 开发架构重构为结合 Vite + Vitest 的 ES 模块 (src/)。 (与此版本一同发布的关联功能请参见 v0.4。) ### v0.2 — 2026-06-25 - **`.track` byte 10 : 局部坡度** — 每个点现在都会编码以 % 为单位的坡度(带符号的 int8), 计算窗口为 200m 的滑动平均值。已通过 Bryton 官方文件验证:偏差 ≤ 1%。 - **`.smy` : D- = 0** — Bryton 官方应用不填充 D− 字段。已修复以保持一致性。 - **爬坡检测** — 将中位数平滑(按点数)替换为基于距离的 滑动平均值 (200m)。中位数会产生人为的阶梯效应,导致漏判爬坡起点。 新阈值:长度 ≥ 500m,D+ ≥ 25m,坡度 ≥ 2.2%,评分 = grade × D+。 ### v0.1 — 2026-06-25 - 初始原型版本:独立 HTML 页面,实现 GPX → Bryton 460 zip 转换 - 生成 8 种原生格式文件:`.smy`, `.tinfo`, `.track`, `dupli.track`, `list.junc`, `list2.junc`, `.climb`, `sort1.path` - 可选的 RDP / 均匀简化 - 如果 GPX 没有海拔数据,通过 Open-Elevation API 获取 SRTM 海拔 - 通过 File System Access API (Chrome/Edge) 进行直接 USB 传输 ## 二进制格式详情 所有整数均为 **Little Endian**(低位字节在前)。 ### `.smy` — 固定 68 字节 路线摘要,Bryton 会优先读取此文件以显示路线列表。 ``` Offset Taille Type Contenu 00 2 uint16 version = 1 02 2 uint16 nb_points 04 4 int32 lat_max × 1 000 000 08 4 int32 lat_min × 1 000 000 12 4 int32 lon_max × 1 000 000 16 4 int32 lon_min × 1 000 000 20 4 int32 distance totale (m) 24 4 int32 inconnu (= 1 638 732 dans tous les fichiers officiels) 28 32 — zéros 60 4 int32 D+ (m) 64 4 int32 D− = toujours 0 (l'appli officielle ne remplit pas ce champ) ``` 真实示例(官方 100K): ``` 01 00 54 3C 30 D2 9D 02 54 AD 99 02 11 C9 3B 00 A1 0E 3A 00 ↑ ↑ ↑ ↑ ↑ 15444 43.775°N 43.626°N 3.885°E 3.806°E 5E 82 01 00 4C 01 19 00 00…(32 zéros)…00 87 04 00 00 00 00 00 00 ↑ ↑ ↑ ↑ 98910m inconnu D+=1159m D-=0 ``` ### `.track` — N × 16 字节 每个 GPS 点对应一条记录。作为主文件 — 整个轨迹都存储于此。 ``` Offset Taille Type Contenu 00 4 int32 lat × 1 000 000 04 4 int32 lon × 1 000 000 08 2 uint16 altitude (m) 10 1 int8 pente locale (%) — moyenne glissante sur 200m 11 5 — zéros ``` 真实示例(100K 的点 0 和点 4): ``` pt0 54 AD 99 02 9D 95 3B 00 1F 00 FF 00 00 00 00 00 ↑ ↑ ↑ ↑ 43.626°N 3.906°E 31m -1% pt4 96 AD 99 02 98 95 3B 00 1F 00 FF 00 00 00 00 00 ↑ 43.626°N (+4m Nord vs pt0) ``` `FF` 作为带符号 int8 = -1。坡度:`00`=0%,`0A`=+10%,`F6`=-10%,`7F`=最大 +127%。 ### `.tinfo` — N × 88 字节(每次爬坡 N 对) 爬坡索引。按对存储:每次爬坡包含一条“起点”记录和一条“终点”记录。 每条记录长 44 字节 — 只有前 4 个字节有效,其余均为零。 ``` Offset Taille Type Contenu 00 4 uint32 (0x00BE << 16) | ptIdx → début de montée (0x00BF << 16) | ptIdx → fin de montée 04 40 — zéros ``` 真实示例(100K 的 5 次爬坡): ``` rec0 DE 04 BE 00 → 0x00BE04DE → flag=0xBE (début), ptIdx=0x04DE=1246 rec1 93 05 BF 00 → 0x00BF0593 → flag=0xBF (fin), ptIdx=0x0593=1427 rec2 26 0A BE 00 → ptIdx=2598 rec3 BE 10 BF 00 → ptIdx=4286 ... (5 paires = 10 records = 440 octets) ``` ### `list.junc` / `list2.junc` — N × 12 字节 + sentinel 路线经过的 OSM 交叉路口。这两个文件完全相同。 最后一条记录是 sentinel `FF×12`。 ``` Offset Taille Type Contenu 00 4 int32 lat × 1 000 000 04 4 int32 lon × 1 000 000 08 2 uint16 ptIdx (point de trace le plus proche) 10 1 uint8 flag : 0x01 = virage, 0x00 = tout droit 11 1 uint8 bearing × (256/360) — 0=Nord, 64=Est, 128=Sud, 192=Ouest ``` 真实示例(100K 开头部分): ``` junc0 D0 AD 99 02 45 95 3B 00 00 00 01 34 ↑ ↑ ↑ ↑ ↑ 43.626°N 3.906°E pt=0 virage 0x34=52 → 73° (NE) junc4 F8 AE 99 02 79 8A 3B 00 26 00 01 30 junc5 F8 AE 99 02 79 8A 3B 00 26 00 01 5A ↑ même carrefour, deux routes qui partent → deux records ``` Sentinel:`FF FF FF FF FF FF FF FF FF FF FF FF` ### `sort1.path` — N × 16 字节 空间索引:按 OSM zoom 13 瓦片切割轨迹。 使 Bryton 仅加载当前瓦片约 500 个点,而不是整个轨迹的 15,000 个点。 ``` Offset Taille Type Contenu 00 4 uint32 start_ptIdx 04 4 uint32 end_ptIdx 08 4 uint32 tile_id = (tile_y_z13 << 16) | tile_x_z13 12 4 uint32 0 ``` 各段之间存在** 1 个点的重叠**:段 N 的 `end` = 段 N+1 的 `start` − 1。 OSM zoom 13 瓦片公式: ``` tx = floor((lon + 180) / 360 × 8192) ty = floor((1 − ln(tan(lat_rad) + 1/cos(lat_rad)) / π) / 2 × 8192) ``` 真实示例(100K — 41 个段,tx ∈ [4182,4185],ty ∈ [2982,2990]): ``` seg0 00 00 00 00 F9 01 00 00 58 10 AE 0B 00 00 00 00 ↑ ↑ ↑ start=0 end=505 tile=(tx=4184, ty=2990) seg1 F8 01 00 00 0F 04 00 00 58 10 AD 0B 00 00 00 00 ↑ start=504 ← overlap d'1 point avec seg0 ``` ### `dupli.track` 根目录 `.track` 的逐字节副本。确切作用未知 — 但没有它 firmware 就会崩溃。 ## 格式设计理念 — 一切均预计算 Bryton 460 搭载了一颗低端 ARM 处理器(几十 MHz,~1 MB RAM)。 没有三角函数,没有解析,运行时也没有动态内存分配。 所有繁重的工作均**在转换时的 PC 端完成** — 设备只负责读取和显示。 | 数值 | 计算位置 | 存储方式 | |---|---|---| | 局部坡度 | PC — 200m 滑动窗口 | 带符号 `int8` 存于 `.track` 的 byte 10 | | 交叉路口方位角 | PC — haversine 公式 | `uint8` 0–255 存于 `list.junc` (非度数或弧度) | | 边界框 / 距离 / D+ | PC | `.smy` — 固定 68 字节 | | 爬坡索引 | PC — 根据剖面图检测 | `.tinfo` — 起始/结束 ptIdx 对 | | 每个瓦片的空间索引 | PC — Mercator z=13 计算 | `sort1.path` | ### 两步式“轨迹上”检测 Bryton 不会每秒将 GPS 位置与轨迹的 15,000 个点进行对比。 它将 `sort1.path` 用作粗略过滤器: ``` Étape 1 — filtre rapide (sort1.path) GPS → tuile OSM z=13 courante → charge uniquement les ~500 points du segment correspondant → élimine les 14 500 autres points Étape 2 — comparaison fine (.track) Pour chaque point du segment : distance(GPS, point) < seuil → sur le trajet → off route ``` 如果 `sort1.path` 不正确(tuile_id = 0 而非实际瓦片),Bryton 会立即提示 “off route”,根本不去查看 `.track` 中的坐标。 ### 设备端不使用余弦 方位角以 **0–255** 存储(而非度数): ``` 0 → Nord 64 → Est 128 → Sud 192 → Ouest ``` 设备只需对一个字节进行简单的箭头查找。在生成文件时, 由我们负责计算 `atan2` 及 `× 256/360` 转换。 ## 参考数据 ``` data_references/ 100k/ 100K.gpx ← sortie Strava, ~100km, région Montpellier output_brytonofficial/ ← fichiers générés par l'appli Bryton officielle (ground truth) output_mytool/ ← fichiers générés par cet outil (pour comparaison) bales/ bales.gpx ← sortie Strava, Pyrénées, départ ~1200m ``` ## 已确认与估算的内容 通过与 Bryton 官方应用在 100 km / 15,444 个点的真实轨迹上生成的文件进行逐字节对比来验证。 | 文件 | 状态 | 详情 | |---|---|---| | `.track` lat/lon/ele | ✅ 正确 | int32/uint16 LE 编码已验证 | | `.track` byte 10 坡度 | ✅ 正确 | 对比官方文件偏差 ≤ 1% (200m 窗口) | | `.smy` | ✅ 正确 | bbox、距离、D+ 正确 — D- = 0 与官方一致 | | `.tinfo` | ✅ 正确 | Flags 0xBE/0xBF + 16 位 ptIdx | | `.climb` 结构 | ✅ 正确 | 4 × float32 : start_m, longueur_m, D+_m, grade | | `sort1.path` | ✅ 正确 | 按 OSM z=13 瓦片分段 — 格式已验证 | | `.climb` 检测 | 🔶 估算 | 第 1 次爬坡准确,其余对比官方 ≈ ±2 km | | `list.junc` | 🔶 估算 | 根据转弯角度检测 — 并非真实的 OSM 交叉路口 |
标签:GPS轨迹, HTML, MITM代理, 前端工具, 后端开发, 多模态安全, 数据可视化, 数据转换, 文件解析