- 物联网
- 嵌入式
- 通信
- 智能硬件
【免费下载链接】firmware
The official firmware for Meshtastic, an open-source, off-grid mesh communication system.
本指南围绕 Meshtastic 开源固件仓库中的 test/fixtures/nodedb/README.md 展开,系统讲解其 "Fake NodeDB Fixtures" 测试数据流水线:如何用确定性 JSONL 种子文件生成 v25meshtastic_NodeDatabase二进制原型文件,并加载到 Portduino 模拟器或 USB 硬件设备上,用于测试固件启动时NodeDB::loadFromDisk的节点数据库读取、裁剪与迁移逻辑。读完本文,你将掌握种子生成、时间戳语义、设备白名单与角色白名单约束、proto 编译、手工场景编辑以及 XModem 推送硬件等一整套可复现的测试数据操作流程。
一、什么是 Fake NodeDB Fixtures
test/fixtures/nodedb/目录下存放的是确定性的 JSONL 种子文件:每一行代表一个"看起来真实"的 Meshtastic 对端节点(NodeInfoLite 头部 + 可选的 PositionLite / DeviceMetrics / EnvironmentMetrics / StatusMessage 卫星数据),配套工具链负责把它们编译为二进制.proto文件并推到设备上测试。
这套 fixture 面向固件 v25 的meshtastic_NodeDatabase磁盘格式(对应设备端文件/prefs/nodes.proto,常量定义见 src/mesh/NodeDB.h),用途是让测试环境拥有一份"最近刚听到过邻居"的节点数据库快照,从而验证:
- 冷启动时
loadFromDisk()(见 src/mesh/NodeDB.cpp)对 v25 文件的解析与版本检查; - 超过平台节点上限时
nodeDBSelfCare()(见 src/mesh/NodeDB.cpp)的截断 / 降级 / 重写逻辑; - 旧版本
nodes.proto文件的迁移路径(DEVICESTATE_MIN_VER = 24、DEVICESTATE_CUR_VER = 25,见 src/mesh/NodeDB.h)。
默认地理位置以美国新墨西哥州 Truth or Consequences, NM(33.1284°N, 107.2528°W)为中心,节点按 60 km 的高斯分布铺开;如需要不同的地理分布,可在生成种子时用--centroid与--spread-km调整。
目录内容
| 文件 | 大小 | 用途 |
|---|---|---|
seed_v25_0250.jsonl | ~200 KB | 匹配 ESP32-S3 高闪存档的MAX_NUM_NODES上限 |
seed_v25_0500.jsonl | ~400 KB | 介于各平台上限之间的压力场景 |
seed_v25_1000.jsonl | ~800 KB | 大型网状网络压力场景 |
seed_v25_2000.jsonl | ~1.6 MB | 截断/驱逐压力(超出所有平台上限) |
其中 250 节点档位正好对应固件侧的迁移解码上限NODEDB_MIGRATION_LOAD_CEILING = 250(src/mesh/NodeDB.h)——即"其它固件写出的 nodes.proto 在解码流中的最大节点数",它不是本构建的节点上限;本构建上限是MAX_NUM_NODES,在 Portduino 上是运行时值portduino_config.MaxNodes(默认 200,见 variants/native/portduino/variant.h),在真实硬件上是编译期常量。
二、流水线总览
从种子到设备,数据经过如下三级流水线:
bin/gen-fake-nodedb-seed.py ↓ (single Random(seed); no wall-clock dependence) test/fixtures/nodedb/seed_v25_<N>.jsonl ← committed, hand-editable ↓ bin/seed-json-to-proto.py ↓ (resolves *_offset_sec → now-relative epochs at compile time) build/fixtures/nodedb/nodes_v25_<N>.proto ← .gitignored, fresh timestamps ↓ - Portduino: cp to ~/.portduino/<config>/prefs/nodes.proto - Hardware: XModem upload via the meshtastic-mcp push_fake_nodedb tool三个环节各自职责清晰:
- 种子生成(
bin/gen-fake-nodedb-seed.py):用固定随机种子生成结构字段,产出可提交、可手工编辑的 JSONL; - proto 编译(
bin/seed-json-to-proto.py):把 JSONL 解析为NodeDatabaseprotobuf 并序列化为二进制,输出被.gitignore忽略的构建产物; - 加载:Portduino 直接拷贝到 prefs 目录,真实硬件则通过 XModem 协议推送到
/prefs/nodes.proto。
编译侧的核心入口是loadFromDisk()中通过loadProto(nodeDatabaseFileName, ...)对/prefs/nodes.proto的pb_decode解码(src/mesh/NodeDB.cpp),随后按版本号决定"丢弃重装默认值 / 迁移 / 直接使用"。
三、确定性契约:结构确定、时间戳新鲜
这套 fixture 最核心的设计约束是"结构字段确定 + 时间戳非确定",两者分别服务于可复现测试与"看起来像刚听到过"的真实感。
3.1 结构字段:固定 seed 即逐字节一致
给定固定--seed,以下字段完全确定:
NodeInfoLite头部:num、long_name、short_name、hw_model、role、public_key、snr、channel、hops_away、next_hop、全部bitfield标志位;PositionLite:纬度/经度/海拔与位置来源;DeviceMetrics:电池电量、电压、信道占用、发送占用、运行时长;EnvironmentMetrics:温度、湿度、气压、IAQ;StatusMessage:状态文本(通常为健康状态)。
种子生成器只使用单个random.Random(args.seed)实例,不依赖墙钟时间;输出 JSONL 时启用sort_keys=True与ensure_ascii=False,保证同一 seed 在任何 Python 版本下都产出逐字节一致的 JSONL(详见 bin/gen-fake-nodedb-seed.py)。文件首行的_meta中generated_at_iso由 seed 推导而来而非墙钟,因此也不影响确定性。
3.2 时间戳:offset 相对时间,编译时解析
JSONL 中所有时间都以*_offset_sec("距 now 多少秒")存储,例如last_heard_offset_sec、position.time_offset_sec。编译步骤把它们从当前墙钟减去,得到绝对 Unix epoch:
def _resolve_time(node, field_absolute, field_offset, now_epoch): if field_absolute in node and node[field_absolute] is not None: return int(node[field_absolute]) # 绝对时间优先 offset = node.get(field_offset, 0) return max(0, int(now_epoch) - int(offset)) # 否则 now - offset(实现见 bin/seed-json-to-proto.py)这样无论 fixture 何时生成,加载到设备上的 NodeDB 都呈现"最近刚刚听到"的对端。需要为 CI 产出逐字节一致的构建产物时,可给编译步骤传入--now-epoch T钉死"当前时刻"。
四、设备与角色白名单:只生成真实活跃的配置
为了让 fixture 贴近现实部署、避免污染测试语义,种子生成对hw_model和role做了两层白名单约束。
4.1 Active-board 设备白名单
hw_model被限制为以下两个集合的交集:
- 在
variants/*/*/platformio.ini中声明custom_meshtastic_support_level = 1的板卡变体(即官方一等公民支持板,例如variants/esp32c3/heltec_esp32c3/platformio.ini、variants/esp32c6/m5stack_unitc6l/platformio.ini等); HardwareModel枚举中存在的值。
这样会自然排除:已废弃/旧版板卡(Heltec V1-V2、TLORA V1-V2、经典 TBEAM(4) 与 TBEAM_V0P7(6)、Nano G1、Station G1/G2 等),以及仅供 fuzzer 使用的哨兵值(PORTDUINO、ANDROID_SIM、DIY_V1、LORA_RELAY_V1 等)。
实际抽样的权重表HW_MODEL_WEIGHTS定义在 bin/gen-fake-nodedb-seed.py:Heltec V3 权重最高(14.0),T_DECK(9.0)、HELTEC_V4 / RAK4631(8.0)次之,其余板卡按 0.3 的均匀低权重构成"长尾",模拟真实网格中主流设备占比更高的分布。当新板卡晋升为 tier-1(或旧板卡退役)时,需要同步刷新该权重表。README 提供了一行命令打印当前交集:
for f in $(find variants -name 'platformio.ini' | xargs grep -lE 'custom_meshtastic_support_level = 1'); do grep custom_meshtastic_hw_model_slug "$f" | awk -F= '{print $2}' | tr -d ' ' done | sort -u | comm -12 - <( bin/_generated/meshtastic_v25/__init__.py >/dev/null 2>&1 || ./bin/regen-py-protos.sh >&2 python3 -c "import sys; sys.path.insert(0,'bin/_generated'); \ from meshtastic_v25.mesh_pb2 import HardwareModel; \ print('\n'.join(HardwareModel.keys()))" | sort )注意该命令会先确保仓库内生成的 Python protobuf 绑定存在(不存在则调用bin/regen-py-protos.sh)。
4.2 Role 角色白名单
role从非废弃的Config.DeviceConfig.Role值中抽取,权重表见 bin/gen-fake-nodedb-seed.py:
- 已排除:
ROUTER_CLIENT(v2.3.15 起废弃)、REPEATER(v2.7.11 起废弃); - 在册:
CLIENT(权重 75.0,占绝大多数)、CLIENT_MUTE、ROUTER、TRACKER、SENSOR、TAK、CLIENT_HIDDEN、LOST_AND_FOUND、TAK_TRACKER、ROUTER_LATE、CLIENT_BASE。
4.3 其他现实化细节
种子生成器还模拟了大量真实细节(bin/gen-fake-nodedb-seed.py):
- 名称池:60 个首词 × 60 个尾词共 3600 种组合,5% 概率附加类似呼号的后缀(如
KX7AB),long_name硬限制 24 字符(对应 nanopbmax_size:25减去 NUL); short_name:10% 概率为纯 emoji,其余为首字母 + 3 位字母数字;emoji 池只选取 4 字节以内的(如 🦊、🌵),显式排除带变音选择符的 ❄️/☀️(6 字节会撑爆 nanopbmax_size:5);hops_away按近似几何分布:0 跳 55%、1 跳 25%、2 跳 12%、3 跳 5%、4 跳 2%、5-7 跳 1%;- 电池:5% 概率为插电状态(
battery_level = 101、voltage = 4.20),其余均匀分布在 10-100 并线性映射到 3.3-4.2V; - 92% 节点携带 32 字节公钥,8% 无密钥(空字符串);
- 状态文本 92% 为健康词(OK / online / running...),8% 为告警词(low-batt / no-gps / weak-signal...)。
五、快速上手:四条常用操作路径
5.1 用新鲜时间戳重新编译(日常最常用)
./bin/regen-fake-nodedbs.sh该脚本(bin/regen-fake-nodedbs.sh)按固定尺寸/种子对SIZES=(250 500 1000 2000)、SEEDS=(20260511 20260512 20260513 20260514)循环:只从已提交的 JSONL 重新编译四个.proto到build/fixtures/nodedb/,时间戳使用当前墙钟。适合在设备刚刷机后想要"看起来最近活跃"的缓存状态时重跑。脚本同时负责:
- 首次运行自动调用
./bin/regen-py-protos.sh生成仓库内 Python protobuf 绑定; - 优先使用
.venv/bin/python3,否则回退系统python3(需装有meshtastic依赖,或通过uv run --with meshtastic执行)。
5.2 有意刷新种子(重生成 JSONL 结构)
REGEN_SEEDS=yes ./bin/regen-fake-nodedbs.sh设置环境变量REGEN_SEEDS=yes后,脚本会先用bin/gen-fake-nodedb-seed.py覆盖提交的 JSONL 文件,再编译 proto。生成的结构性数据发生改变,需将 JSONL 改动提交进仓库。
5.3 手工编辑特定场景
# 找到要调整的节点,原地编辑该行。 $EDITOR test/fixtures/nodedb/seed_v25_0250.jsonl # 重新编译并推送。 ./bin/regen-fake-nodedbs.shJSONL 每行是一个节点,首行是元数据。字段 schema 内联记录在 bin/gen-fake-nodedb-seed.py 中。若想覆盖某个时间戳,直接把last_heard_offset_sec替换为last_heard(绝对 epoch),编译步骤会优先采用绝对时间。
真实节点行示例(取自seed_v25_0250.jsonl):
{"bitfield": {"has_is_unmessagable": true, "has_user": true, "is_favorite": false, "is_ignored": false, "is_key_manually_verified": false, "is_licensed": false, "is_muted": false, "is_unmessagable": false, "via_mqtt": false}, "channel": 0, "hops_away": 2, "hw_model": "HELTEC_WIRELESS_TRACKER_V2", "last_heard_offset_sec": 4809, "long_name": "Drifting Phoenix", "next_hop": 253, "num": "0x0005e869", "position": {"altitude": 1338, "latitude": 33.690292, "location_source": "LOC_INTERNAL", "longitude": -106.436201, "time_offset_sec": 4996}, "public_key_hex": "056060d6ceae374c7ee39ffb5fb6c2503d238610f2277c47e7cc008a9a096dc5", "role": "CLIENT", "short_name": "DB5I", "snr": 6.64, "status": {"status": "ready"}, "telemetry": null}5.4 加载到 Portduino(macOS / Linux 原生模拟器)
cp build/fixtures/nodedb/nodes_v25_1000.proto ~/.portduino/default/prefs/nodes.proto # 运行原生二进制;loadFromDisk 会在启动时读取该文件。Portduino 运行时节点上限来自配置文件portduino_config.MaxNodes(默认 200,见 variants/native/portduino/variant.h),因此 250/500/1000/2000 档位的 fixture 正好可以分别验证"等于上限、超过上限"时的自护理行为:加载后nodeDBSelfCare()会把超出的节点截断到MAX_NUM_NODES(src/mesh/NodeDB.cpp),并确保自身节点(self)被固定到索引 0。
5.5 推送到 USB 硬件(meshtastic-mcp)
在 meshtastic-mcp 工具面内调用push_fake_nodedb:
push_fake_nodedb( size=500, target="hardware", port="/dev/cu.usbmodem21301", # 通过 list_devices 发现 confirm=True, # 门禁:拦截破坏性写入 + 重启 )该工具把 proto 以XModem 协议流式传输到/prefs/nodes.proto,然后触发 1 秒重启,让loadFromDisk在下一次启动时读取。传输细节:CRC16-CCITT 校验每个块;每个块收到 NAK 后最多重试 5 次,超过则发送CAN中止。
六、JSONL Schema 参考
num是十六进制字符串(如"0xa1b2c3d4");public_key_hex为 64 个十六进制字符(32 字节),无密钥节点为空字符串;hw_model与role使用枚举名称,编译步骤通过HardwareModel.Value(name)/Config.DeviceConfig.Role.Value(name)解析(未知名称会抛ValueError,这正是指定的校验方式,见 bin/seed-json-to-proto.py)。
6.1 bitfield:命名布尔位打包
bitfield是一组命名布尔值,编译时按位位置打包成整数。位布局与固件侧 src/mesh/NodeDB.h 的NODEINFO_BITFIELD_*_SHIFT一一对应:
| JSON 键 | 位位置 | 固件宏(shift) |
|---|---|---|
is_key_manually_verified | 0 | NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED |
is_muted | 1 | NODEINFO_BITFIELD_IS_MUTED |
via_mqtt | 2 | NODEINFO_BITFIELD_VIA_MQTT |
is_favorite | 3 | NODEINFO_BITFIELD_IS_FAVORITE |
is_ignored | 4 | NODEINFO_BITFIELD_IS_IGNORED |
has_user | 5 | NODEINFO_BITFIELD_HAS_USER |
is_licensed | 6 | NODEINFO_BITFIELD_IS_LICENSED |
is_unmessagable | 7 | NODEINFO_BITFIELD_IS_UNMESSAGABLE |
has_is_unmessagable | 8 | NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE |
打包实现见 bin/seed-json-to-proto.py。固件侧对位标志的使用(收藏、忽略、密钥手动验证等受保护标志)可在 src/mesh/NodeDB.cpp 中看到读取示例。
6.2 可空卫星数据与覆盖比率
position/telemetry/environment/status均可为null;种子生成时由覆盖比率参数决定哪些节点携带哪些卫星数据,默认值(也写死在regen-fake-nodedbs.sh调用中)为:
--position-coverage:0.85(85% 节点有位置)--telemetry-coverage:0.70--environment-coverage:0.25--status-coverage:0.40
编译后分别落入NodeDatabase的positions/telemetry/environment/status卫星数组(v25 新增结构),这正是seed-json-to-proto.py启动时断言NodeDatabase.DESCRIPTOR.fields_by_name中存在positions的原因——若加载到旧于 v25 的 protobuf 绑定会直接报错并提示运行bin/regen-py-protos.sh(bin/seed-json-to-proto.py)。
6.3 经纬度:浮点度数 → 1e-7 整数
latitude/longitude在 JSONL 中为浮点度数,编译时按固件PositionLite的存储方式转换为int32微度:
pl.latitude_i = int(round(float(pos["latitude"]) * 1e7)) pl.longitude_i = int(round(float(pos["longitude"]) * 1e7))(见 bin/seed-json-to-proto.py)。海拔在种子生成时以 Truth or Consequences 谷底地形为基准,按均值 1376 m、标准差 250 m 的高斯分布取样。
6.4 其他种子生成参数
bin/gen-fake-nodedb-seed.py支持完整的命令行参数(bin/gen-fake-nodedb-seed.py):
| 参数 | 默认值 | 说明 |
|---|---|---|
--count | 必填 | 生成节点数 |
--seed | 必填 | 确定性随机种子 |
--out | 必填 | JSONL 输出路径 |
--centroid | 33.1284,-107.2528 | 经纬度中心 |
--spread-km | 60.0 | 高斯分布标准差(km) |
--position/--telemetry/--environment/--status-coverage | 0.85 / 0.70 / 0.25 / 0.40 | 各卫星数据覆盖比率 |
--my-node-num | 无 | 从生成集合中排除的本机 NodeNum(十六进制或十进制) |
--last-heard-mean-sec | 3600 | last_heard_offset_sec的指数分布均值 |
--last-heard-max-sec | 7 * 86400 | 最近听到偏移的最大上限 |
节点num生成范围为[4, 0x80000000)(固件保留 0-3,见 bin/gen-fake-nodedb-seed.py),输出按num升序排列,保证顺序不依赖集合哈希。
七、适用前提与边界
- 本文所有命令与参数均以当前仓库内容为准:Python 侧依赖仓库内生成的
meshtastic_v25绑定(由bin/regen-py-protos.sh生成),PyPI 上的meshtastic包仅作为回退,且可能滞后于固件分支的 v25 卫星数据库 schema(bin/seed-json-to-proto.py); - 编译产物
build/fixtures/nodedb/*.proto被.gitignore忽略,属于构建期生成物;只有test/fixtures/nodedb/*.jsonl是提交进仓库的种子源文件; - 设备端加载行为与固件实现强绑定:v24 及以下版本文件会触发迁移,低于
DEVICESTATE_MIN_VER会被整体丢弃重建默认数据库(src/mesh/NodeDB.cpp);超过平台上限的节点会在启动自护理阶段被截断或降级到 warm tier; - 手工编辑 JSONL 时请遵守
_validate_node的硬约束(bin/seed-json-to-proto.py):long_name不超过 24 字符、short_name不超过 4 个 UTF-8 字节、public_key_hex为空或恰好 64 个十六进制字符,否则编译会直接报错而非静默通过。
通过这套流水线,开发者可以用 250/500/1000/2000 四个档位的确定性 fixture,系统性地覆盖节点数据库的容量边界、迁移、自护理与卫星数据裁剪等固件路径,既保证 CI 可复现,又让设备端测试数据始终保持"新鲜"。
- 物联网
- 嵌入式
- 通信
- 智能硬件
【免费下载链接】firmware
The official firmware for Meshtastic, an open-source, off-grid mesh communication system.
相关推荐
Doctrine Data Fixtures 数据库测试数据加载工具详解
Doctrine Data Fixtures 数据库测试数据加载工具详解 概述 Doctrine Data Fixtures 是一个专为 Doctrine OR
social-auto-upload Bilibili上传详细教程:使用biliup集成自动化投稿
social auto upload Bilibili上传详细教程:使用biliup集成自动化投稿 social auto upload是一款强大的视频自动化上
后端RPA工作流自动化AI 技能超级指南:Superagent测试数据管理的终极方案 — 从Fixtures到动态生成
超级指南:Superagent测试数据管理的终极方案 — 从Fixtures到动态生成 在现代JavaScript开发中,可靠的API测试离不开高质量的测试数据
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考