前两篇分别介绍了 GD6 的产品全貌和硬件设计。这篇进入开发者最关心的部分:代码怎么用。

GD6 的固件和 Web 配置系统已在 GitHub 开源(github.com/ManThink/gd6-open)。这篇文章从仓库结构开始,逐步讲清楚开发环境搭建、固件编译、多区域构建、刷写、Web 界面定制、REST API 调用和远程 MQTT 配置通道的使用。


仓库概览

基本信息

项目 内容
仓库地址 https://github.com/ManThink/gd6-open
描述 LoRaWAN gateway based on ESP32
主语言 C(ESP-IDF 框架)
构建系统 PlatformIO + ESP-IDF
开源许可证 见仓库 LICENSE 文件
当前版本 1.0.2(见 version.txt)
首次发布 2026 年 7 月 23 日

目录结构

gd6-open/
├── src/                    # 主应用代码
│   ├── main.c              # 入口,分 6 阶段启动
│   ├── ethernet_w5500.c    # W5500 以太网驱动
│   ├── cellular_4g.c       # 4G 模组 PPP 拨号
│   ├── net_route_mgr.c     # 三链路主备切换
│   ├── wifi_ap.c           # Wi-Fi AP + STA
│   ├── web_server.c        # HTTP REST API + 静态文件
│   ├── config_rpc.c        # 配置核心(解析→校验→落盘→应用)
│   ├── auth.c              # 登录密码 + Token 鉴权
│   ├── mqtt_bridge.c       # UDP↔MQTT 桥
│   ├── remote_config.c     # 远程 MQTT 配置通道
│   ├── ota_update.c        # A/B 双分区 OTA
│   ├── sys_log.c           # 内存环形日志
│   ├── uplink_driver.c     # pktfwd / station 上行驱动选择
│   ├── periph_power.c      # 外设电源管理
│   ├── status_led.c        # 状态灯 + 看门狗
│   └── factory_reset.c     # 恢复出厂
├── components/             # 第三方组件
│   ├── packet_forwarder/   # Semtech lora_pkt_fwd
│   ├── sx1302_hal/         # SX1302 HAL(Semtech v2.1.0 移植)
│   ├── basicstation/       # Semtech BasicStation 2.0.6
│   └── esp_modem/          # 4G PPP 模组
├── frontend/               # Web UI 源码(Vite 构建)
├── data/                   # SPIFFS 文件系统镜像
│   ├── web/                # Web UI 静态文件(构建产物)
│   ├── global_conf.json    # 当前激活的射频 + 网关配置
│   ├── regions/            # 各 LoRaWAN 区域配置模板
│   ├── eth_config.json     # 以太网配置
│   ├── wifi_ap.json        # Wi-Fi AP/STA 配置
│   ├── cellular_config.json # 4G 配置
│   ├── mqtt_bridge_config.json # MQTT 桥配置
│   └── remote_config.json  # 远程配置通道设置
├── scripts/                # 构建脚本
│   ├── build_regions.py    # 按区域一键构建
│   └── embed_defaults_copy.py # 默认配置同步
├── tools/                  # 烧录器 + 加解密脚本
├── hardware/               # 硬件工程文件
├── partitions.csv          # 双 OTA 分区表
├── platformio.ini          # PlatformIO 配置
├── sdkconfig.defaults      # SDK 配置
└── CMakeLists.txt          # 顶层 CMake

关键目录的作用:

与 ManThink 其他开源仓库的关系

ManThink 在 GitHub 上还有其他仓库:

仓库 用途
ThinkLink ThinkLink 物联网平台
TKL-EB-SDK EdgeBus SDK,边缘设备开发
ThinkLink-docker ThinkLink 平台 Docker 部署

GD6 是网关硬件的开源仓库,ThinkLink 是平台的开源仓库。两者可以配合使用,但 GD6 也可以独立连接 ChirpStack、TTN 等第三方网络服务器。


开发环境搭建

前置要求

GD6 的构建栈是 PlatformIO + ESP-IDF,不需要单独安装 ESP-IDF——PlatformIO 会自动管理框架版本。

需要安装:

  1. PlatformIO IDE:VS Code 扩展或 PlatformIO Core CLI。安装方式参考 platformio.org/install。
  2. Python 3.x:PlatformIO 的运行依赖。
  3. Node.js 18+:如果需要修改 Web 前端,用于构建 frontend/。
  4. USB 驱动:ESP32-S3 的 USB 串口驱动(通常是 CP2102 或原生 USB CDC)。

PlatformIO 版本锁定

platformio.ini 中明确锁定了平台版本:

platform = espressif32 @ 6.13.0

这个版本对应 ESP-IDF 5.5.3。注释中特别说明:不要用裸 espressif32,否则会浮动到最新版本(7.0.x / IDF 6.0),而 IDF 6.0 移除了内置 mqtt 组件,会导致编译失败。

这是第一个容易踩的坑:如果你 PlatformIO 更新后编译报错,第一步检查 espressif32 版本是否被改动了。

克隆代码

git clone https://github.com/ManThink/gd6-open.git
cd gd6-open

验证环境

pio run                # 编译固件

如果编译成功并生成 firmware.bin,说明环境就绪。首次编译会下载 ESP-IDF 框架和依赖,需要一些时间。


编译固件

基本编译

pio run                          # 构建固件
pio run --target upload          # 烧录固件(通过 USB)
pio run --target uploadfs        # 烧录 SPIFFS 镜像
pio device monitor               # 串口监视器(115200 波特率)
pio run --target menuconfig      # SX1302 引脚 / SPI 配置
pio run --target clean           # 清理 build/

串口波特率锁定 115200。platformio.ini 中注释说明:ESP-IDF 控制台默认 115200,不设的话 pio run -t monitor 会用 9600 导致乱码。

多区域构建

GD6 的应用代码与频段无关——区域差异全在 global_conf.json 里。各区域模板在 data/regions/ 目录下(AS923 / EU868 / US915 / AU915 / CN470 / KR920 / IN865)。

scripts/build_regions.py 提供了一键构建工具:

# 列出可用区域和中心频率
python scripts/build_regions.py --list

# 把某区域设为开发默认(覆盖 data/global_conf.json)
python scripts/build_regions.py --activate EU868
pio run && pio run -t uploadfs

# 一键构建全部区域的固件 + SPIFFS(产物在 dist/<REGION>/)
python scripts/build_regions.py

# 只构建指定区域
python scripts/build_regions.py EU868 US915 AS923

# 仅重打 SPIFFS(不重编固件,改配置时更快)
python scripts/build_regions.py --fs-only AS923

构建产物命名(版本号取自 version.txt):

文件 说明 烧录偏移
GD6-<ver>-<REGION>-full.bin 完整 8 MiB Flash 镜像 0x0
GD6-<ver>-<REGION>-firmware.bin App 固件 0x20000
GD6-<ver>-<REGION>-spiffs.bin SPIFFS 镜像 0x5A0000
GD6-<ver>-<REGION>-global_conf.json 构建时使用的配置副本 —
build_info.json 构建元数据与偏移表 —

注意:射频前端硬件必须匹配目标频段。868 / 915 / 470 等模块不可混用——这是物理限制,不是软件限制(第二篇有详细说明)。


刷写固件

通过 USB 串口刷写

适用于首次烧录或开发调试:

# 常规烧录(固件 + SPIFFS)
pio run --target upload
pio run --target uploadfs

# 整片擦除后烧录完整镜像(首次烧录或恢复)
python -m esptool --chip esp32s3 erase_flash
python -m esptool --chip esp32s3 write_flash 0x0 GD6-1.0.2-EU868-full.bin

整片擦除会覆盖全部分区和设备数据(包括 Gateway EUI),需要保留的数据请先备份。

通过 Web 控制台 OTA 刷写

适用于已部署的网关在线升级:

  1. 打开 Web 控制台 → "固件升级"页面
  2. 核对当前版本和活动分区
  3. 选择与网关型号、硬件版本和目标区域匹配的 .bin 固件
  4. 等待上传、校验和写入完成
  5. 按提示重启,重新登录后核对版本

OTA 使用 A/B 双分区机制:新固件写入备用分区,校验通过后切换启动分区。升级过程中不要刷新页面、断网或断电。

通过浏览器烧录器刷写

仓库的 tools/flasher/ 目录包含一个浏览器烧录器(基于 ESP Web Serial API),支持在 Chrome/Edge 中直接通过 USB 刷写固件。这个工具还集成了 ESP32-S3 外部看门狗(TPL5010)的喂狗控制,以及中止和异常复位保护。

不要混淆固件类型

说明书明确提醒:不要把整片 Flash 制造镜像(-full.bin)当作 Web OTA 固件上传。Web OTA 只接受 App 固件(-firmware.bin),因为 OTA 写入的是备用分区,不是整片 Flash。


分区表与 Flash 布局

GD6 使用自定义分区表(partitions.csv),8 MB Flash 划分如下:

分区 类型 偏移 大小 说明
nvs data/nvs 0x9000 24 KB 非易失存储(密码等)
otadata data/ota 0xF000 8 KB OTA 启动槽记录
phy_init data/phy 0x11000 4 KB RF PHY 初始化
ota_0 app 0x20000 2.75 MB 固件槽 A
ota_1 app 0x2E0000 2.75 MB 固件槽 B
storage data/spiffs 0x5A0000 2.375 MB Web UI + 配置文件

整表正好填满 8 MB。两个 OTA 分区各 2.75 MB,SPIFFS 分区 2.375 MB。如果固件超过 2.75 MB,需要重新调整分区表。


Web 配置系统定制

前端源码结构

Web 界面源码在 frontend/ 目录,使用 Vite 构建:

cd frontend && npm install       # 首次安装依赖
cd frontend && npm run build      # 构建到 data/web/
pio run --target uploadfs         # 烧录到 SPIFFS
cd frontend && npm run dev        # 本地开发服务器

构建产物输出到 data/web/(index.html、app.js、style.css),然后随 SPIFFS 镜像一起烧录。

不要直接改 data/web/

README 中有明确警告:不要直接修改 data/web/*——它们是构建产物。正确的做法是修改 frontend/ 中的源码,然后 npm run build。

如果需要修改 Web 界面的文本、样式或布局,流程是:

  1. 修改 frontend/ 中的源码
  2. npm run build 生成新的 data/web/ 文件
  3. pio run --target uploadfs 烧录到网关

本地开发调试

npm run dev 会启动 Vite 本地开发服务器,/api/* 请求通过 Vite proxy 转发到目标网关。这样可以在本地修改前端代码,实时预览效果,而不需要每次都烧录。

web/ 目录的保护

在线覆盖 web/ 文件可能导致控制台无法打开,且恢复出厂不会还原被损坏的 Web 文件。如果确实需要在线更新 Web 界面,确保有串口访问能力作为后备恢复手段。


REST API 使用

认证方式

GD6 的 REST API 使用 Token 鉴权:

  1. 调用 POST /api/login,提交密码,获取 Token
  2. 后续请求在 Header 中携带 Token
  3. Token 与 Web 登录密码共用——修改密码后旧 Token 失效

默认登录密码:wifi!0804(首次登录后请立即修改)。

API 端点列表

方法 路径 说明
GET /api/status 系统/网络/LoRa/SPIFFS 状态
POST /api/login · /api/logout 登录/登出
PUT /api/password 修改密码
GET /api/auth_state 登录态查询
GET/PUT /api/ethernet 以太网配置
POST /api/ethernet/power 以太网电源控制
GET/PUT /api/wifi Wi-Fi 配置
GET /api/wifi/scan Wi-Fi 扫描
GET/PUT /api/lora LoRa 网关配置
GET/PUT /api/cellular 4G 配置
GET /api/cellular/status 4G 实时状态
GET/PUT /api/mqtt_bridge MQTT 桥配置
GET /api/mqtt_bridge/status MQTT 桥状态
POST/DELETE /api/mqtt_bridge/cert MQTT 桥证书
GET/PUT /api/remote_config 远程配置通道设置
GET /api/remote_config/status 远程配置状态
POST/DELETE /api/remote_config/cert 远程配置证书
GET /api/station BasicStation 状态
GET /api/logs 日志(增量)
DELETE /api/logs 清空日志
GET /api/logs/download 下载日志
GET /api/fs 文件列表
POST /api/fs/upload 上传文件
GET /api/firmware 固件信息
POST /api/firmware/upload OTA 上传
POST /api/restart 重启 LoRa 服务或系统

调用示例

获取系统状态:

# 登录获取 Token
TOKEN=$(curl -s -X POST http://192.168.4.1/api/login \
  -H "Content-Type: application/json" \
  -d '{"password":"wifi!0804"}' | jq -r '.token')

# 查询系统状态
curl -s http://192.168.4.1/api/status \
  -H "Authorization: Bearer $TOKEN" | jq

修改以太网为静态 IP:

curl -s -X PUT http://192.168.4.1/api/ethernet \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "static",
    "ip": "192.168.1.200",
    "netmask": "255.255.255.0",
    "gateway": "192.168.1.1",
    "dns1": "8.8.8.8"
  }'

批量管理多台网关的脚本示例:

#!/bin/bash
# 批量查询网关状态
GATEWAYS=("192.168.1.101" "192.168.1.102" "192.168.1.103")
PASSWORD="your_password"

for GW in "${GATEWAYS[@]}"; do
  TOKEN=$(curl -s -X POST "http://$GW/api/login" \
    -H "Content-Type: application/json" \
    -d "{\"password\":\"$PASSWORD\"}" | jq -r '.token')

  echo "=== $GW ==="
  curl -s "http://$GW/api/status" \
    -H "Authorization: Bearer $TOKEN" | jq '{firmware: .firmware_ver, uptime: .uptime, lora: .lora_status}'
done

远程 MQTT 配置通道

与 MQTT Bridge 的区别

GD6 有两条独立的 MQTT 连接,不要混淆:

MQTT Bridge 远程 MQTT 配置通道
用途 LoRaWAN 数据转发 远程读写设备配置
对应文件 mqtt_bridge_config.json remote_config.json
代码模块 src/mqtt_bridge.c src/remote_config.c
连接 独立 TLS 连接 独立 TLS 连接
鉴权 Broker 账号 设备登录密码(RPC 鉴权)

两条连接完全隔离,不能混用 Topic 或凭据。

安全限制

远程通道有一个关键安全设计:远程通道不能修改其自身 Broker 设置。

这是为了防止远程误配置导致设备失联。如果远程通道能修改自己的 Broker 地址或凭据,一次错误的配置就可能让设备永久失去远程管理能力。

配置方式

在 Web 控制台 → "远程配置"页面配置:

  1. 填写 Broker URI、Client ID、账号密码
  2. 配置 Topic 和 QoS
  3. 上传 TLS 证书(如需)
  4. 观察连接状态

配置完成后,可以通过 MQTT 消息远程读写允许的设备配置项,并触发重启。


SPIFFS 配置文件

配置文件一览

文件 说明
/spiffs/global_conf.json SX1302 射频参数 + 网关完整配置(含 uplink_driver 选择)
/spiffs/eth_config.json 以太网 DHCP / 静态 IP
/spiffs/wifi_ap.json Wi-Fi AP/STA 配置
/spiffs/cellular_config.json 4G APN / 认证 / 优先级
/spiffs/mqtt_bridge_config.json MQTT 桥 broker / 账号 / TLS / topic
/spiffs/remote_config.json 远程配置通道 broker / 账号 / TLS / topic

这些 JSON 文件同时也是恢复出厂的还原目标——它们被内嵌进固件,恢复出厂时从固件中提取默认值覆盖回 SPIFFS。

配置生效流程

配置 JSON 修改后需要重启网关才能生效。SPIFFS 写入 Flash 后即持久化,不依赖 RAM 维持——写入后即使立即断电也不会丢失,但需要重启才能加载新配置。

通过 Web 控制台管理文件

Web 控制台的"文件系统"页面可以直接管理 /spiffs 下的文件,支持查看和编辑配置 JSON。这为深度定制提供了入口——可以直接修改配置文件的字段,而不仅限于 Web 界面暴露的选项。


上行驱动:Packet Forwarder vs Basic Station

global_conf.json 中的 uplink_driver 字段选择占用 SX1302 的上行驱动,两者互斥:

驱动 值 说明
Packet Forwarder pktfwd Semtech UDP packet forwarder(默认)
Basic Station station Semtech BasicStation 2.0.6,wss/TLS 连 LNS

切换驱动需要重启网关。Basic Station 模式下,信道平面由 LNS 下发,凭据和配置走 SPIFFS 中的独立文件(tc.* / cups.*)。


启动流程

了解启动流程有助于排查问题。app_main()(src/main.c)分 6 个阶段启动:

阶段 动作
Phase 0 NVS 初始化 + 事件循环(状态灯和恢复出厂按键尽早就绪)
Phase 1 挂载 SPIFFS——挂载失败会禁掉 Web 服务器和 packet forwarder
Phase 2 启动 Wi-Fi Soft-AP(异步、非阻塞)
Phase 3 路由管理器启动 W5500 以太网 → 4G PPP,主备切换
Phase 4 启动 HTTPD(REST API + 静态文件)+ 远程 MQTT 配置通道
Phase 5 SX1302 自检 → 按 uplink_driver 启动 packet forwarder 或 BasicStation

设计原则:后续阶段容忍前面的非致命失败,确保设备始终能通过 Wi-Fi AP 进入恢复模式。即使 SPIFFS 挂载失败或 SX1302 自检失败,Web 控制台仍然可访问。


二次开发场景示例

场景一:自定义信道配置

如果需要非标准的信道配置(比如自定义中心频率),可以直接修改 data/global_conf.json:

python scripts/build_regions.py --activate EU868
# 然后编辑 data/global_conf.json 中的 radio_0 / radio_1 中心频率
pio run --target uploadfs

场景二:集成到自有运维系统

通过 REST API 可以将 GD6 网关集成到自有运维系统:

场景三:定制 Web 界面

修改 frontend/ 中的 Vue 源码,添加自定义页面或修改现有界面,然后 npm run build + pio run --target uploadfs 烧录。


常见问题

Q: 编译报错,提示 mqtt 组件找不到?

A: 检查 platformio.ini 中的 platform = espressif32 @ 6.13.0 是否被改动。IDF 6.0 移除了内置 mqtt 组件,必须锁定在 6.13.0(IDF 5.5.3)。

Q: 烧录后串口乱码?

A: 确认串口波特率是 115200。platformio.ini 中已设置 monitor_speed = 115200。

Q: Web OTA 上传固件后无法启动?

A: 检查上传的是 App 固件(-firmware.bin)还是完整镜像(-full.bin)。Web OTA 只接受 App 固件。如果上传了完整镜像,等待自动重启,若仍不可访问,通过 Wi-Fi AP 或串口执行维修恢复,不要反复断电。

Q: 恢复出厂后 Web 界面打不开?

A: 恢复出厂不会还原 web/ 目录。如果之前覆盖过 web/ 文件导致损坏,需要通过串口重新烧录 SPIFFS 镜像(pio run --target uploadfs)。

Q: 修改了 data/ 下的配置但没生效?

A: 修改 data/ 下的文件后需要执行 pio run --target uploadfs 烧录到 SPIFFS。在线修改 SPIFFS 文件后需要重启网关才能生效。


小结

GD6 的开源仓库不是一个空壳——它包含了完整的固件源码、Web 界面源码、构建工具链和硬件工程文件。从克隆代码到编译出可用的固件,整个流程可以在半小时内完成。

几个关键要点:

  1. PlatformIO 版本锁定 6.13.0,不要浮动到最新版本
  2. 多区域构建用 build_regions.py 脚本,不要手动改 global_conf.json 的区域字段
  3. Web 界面改 frontend/,不改 data/web/
  4. REST API 和远程 MQTT 通道提供了完整的编程化管理能力
  5. 两条 MQTT 连接完全隔离,数据转发和远程配置不能混用

下一篇我们会进入部署实战——从开箱到连接 ThinkLink / ChirpStack / TTN,一步步走完整个部署流程。


相关链接


ManThink — 让物联网开发更高效。