前两篇分别介绍了 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
关键目录的作用:
src/是主应用代码,每个.c文件对应一个功能模块。代码严格开启-Wall编译警告。components/是第三方组件,SX1302 HAL 和 Packet Forwarder 基于 Semtech 官方源码移植,原始参考代码保存在.upstream/目录,修改前可以先 diff。frontend/是 Web 界面的 Vue/Vite 源码,构建产物输出到data/web/。data/是 SPIFFS 文件系统镜像,烧录后即为网关的配置存储区。
与 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 会自动管理框架版本。
需要安装:
- PlatformIO IDE:VS Code 扩展或 PlatformIO Core CLI。安装方式参考 platformio.org/install。
- Python 3.x:PlatformIO 的运行依赖。
- Node.js 18+:如果需要修改 Web 前端,用于构建
frontend/。 - 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 刷写
适用于已部署的网关在线升级:
- 打开 Web 控制台 → "固件升级"页面
- 核对当前版本和活动分区
- 选择与网关型号、硬件版本和目标区域匹配的
.bin固件 - 等待上传、校验和写入完成
- 按提示重启,重新登录后核对版本
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 界面的文本、样式或布局,流程是:
- 修改
frontend/中的源码 npm run build生成新的data/web/文件pio run --target uploadfs烧录到网关
本地开发调试
npm run dev 会启动 Vite 本地开发服务器,/api/* 请求通过 Vite proxy 转发到目标网关。这样可以在本地修改前端代码,实时预览效果,而不需要每次都烧录。
web/ 目录的保护
在线覆盖 web/ 文件可能导致控制台无法打开,且恢复出厂不会还原被损坏的 Web 文件。如果确实需要在线更新 Web 界面,确保有串口访问能力作为后备恢复手段。
REST API 使用
认证方式
GD6 的 REST API 使用 Token 鉴权:
- 调用
POST /api/login,提交密码,获取 Token - 后续请求在 Header 中携带 Token
- 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 控制台 → "远程配置"页面配置:
- 填写 Broker URI、Client ID、账号密码
- 配置 Topic 和 QoS
- 上传 TLS 证书(如需)
- 观察连接状态
配置完成后,可以通过 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 网关集成到自有运维系统:
- 定时调用
/api/status采集网关运行状态 - 调用
/api/logs拉取日志 - 通过
/api/remote_config批量下发配置变更 - 通过
/api/restart远程重启
场景三:定制 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 界面源码、构建工具链和硬件工程文件。从克隆代码到编译出可用的固件,整个流程可以在半小时内完成。
几个关键要点:
- PlatformIO 版本锁定 6.13.0,不要浮动到最新版本
- 多区域构建用
build_regions.py脚本,不要手动改global_conf.json的区域字段 - Web 界面改
frontend/,不改data/web/ - REST API 和远程 MQTT 通道提供了完整的编程化管理能力
- 两条 MQTT 连接完全隔离,数据转发和远程配置不能混用
下一篇我们会进入部署实战——从开箱到连接 ThinkLink / ChirpStack / TTN,一步步走完整个部署流程。
相关链接
- GD6 开源仓库:github.com/ManThink/gd6-open
- GD61x 规格与使用说明书:think-link.net/docs/zh/Device/LoRaWANGateway/gd61x-user-guide
- GD6 配置说明书:think-link.net/docs/zh/Device/LoRaWANGateway/gd6-configuration-guide
- 第一篇:ManThink 推出 GD6 开源 LoRaWAN 网关:ESP32-S3 + SX1302 室内方案
- 第二篇:GD6 硬件设计解析:ESP32-S3 + SX1302 的选型逻辑与工程取舍
- ThinkLink 平台官网:www.think-link.net
- ManThink 官网:www.manthink.cn
ManThink — 让物联网开发更高效。