概述
本文以 03_AT_SmartStreetlight 工程为例,按硬件接线、云平台建产品、MCU 工程集成、设备调试、SaaS 与 App 接入的顺序,完成智慧路灯的 AT 方案上云。STM32 采集光敏模块 AO,控制 WS2811F RGB 灯模拟路灯;蜂窝模组承担联网与云端数据通信,两者通过 USART1 交换 AT 指令和 TTLV 数据。
设备实现5项属性:灯状态、强控开关、光照映射值、光控阈值和控制策略。自动策略由采样值与阈值比较控制白灯,强控策略由云端开关控制白灯;设备支持全量首报、灯状态变化上报、5分钟采样值上报及云端读取属性。
智慧路灯 AT 方案全链路架构图
图1:智慧路灯 AT 方案全链路架构图
实施顺序:核对供电与接线 → 创建产品及5项物模型 → 配置 PK/PS → 编译烧录 → 平台上线及首报 → 自动/强控验证 → SaaS/App 配置。
本文以 EG800Z 作为通信硬件示例。更换模组时,先核对 QuecThing AT 指令、事件格式及接口电平。源码片段来自03工程,在原工程上下文中使用。
1. 前置条件
1.1 硬件与开发环境
| 项目 | 型号/要求 | 数量 |
|---|---|---|
| MCU 开发板 | STM32F103VET6,烧录前核对实物 | 1 |
| 蜂窝模组及底板 | EG800Z 示例;支持工程使用的 QuecThing AT 指令 | 1 |
| 光敏模块 | 带 AO 模拟量接口;本文以 ATK-MB001 实物为例 | 1 |
| RGB 灯模块 | WS2811F,单线 DIN 控制,用白灯模拟路灯 | 1 |
| SIM 卡与天线 | SIM 已激活且有流量;天线匹配模组频段 | 各1 |
| USB 转 TTL | 查看 USART2 日志,电平与 MCU 匹配 | 1 |
| 调试器 | ST-Link 或 J-Link,使用 SWD | 1 |
| 供电与连接线 | 按底板、光敏模块和灯模块规格准备 | 按需 |
| IDE | Keil MDK-ARM v5,安装匹配器件包与编译器 | — |
| 串口工具 | QCOM、SSCOM 等 | — |
| 工程源码 | 03_AT_SmartStreetlight 完整目录 | — |
工程文件名为 STM32F103ZET6.ioc,内部配置为 STM32F103VET6、LQFP100,Keil Device 为 STM32F103VE。烧录前核对实物丝印、Flash 容量与调试配置。
硬件实物按尺寸分两排展示:
第一排:蜂窝模组 / 光敏传感器模块
图2:主要硬件实物(第二排:STM32 开发板 / WS2811F RGB 灯)
光敏模块以 ATK-MB001 为例,选型时核对 AO 接口、供电及输出电压范围。
1.2 硬件接线
断电接线。下表 TX/RX 均以 MCU 端为基准,串口交叉连接。
| MCU 引脚 | MCU 功能 | 外设引脚 | 方向 | 用途 |
|---|---|---|---|---|
| PA9 | USART1_TX | 模组 RXD | MCU → 模组 | AT 指令与业务数据发送 |
| PA10 | USART1_RX | 模组 TXD | 模组 → MCU | 响应、URC 与下发数据接收 |
| PA2 | USART2_TX | USB-TTL RXD | MCU → PC | 调试日志输出 |
| PA3 | USART2_RX | USB-TTL TXD | PC → MCU | 已配置接收引脚,当前无需通过此线发送业务命令 |
| PA4 | ADC1_IN4 | 光敏模块 AO | 传感器 → MCU | 实际业务光照采样 |
| PA8 | GPIO 推挽输出 | WS2811F DIN/DI | MCU → RGB | 单线灯光控制 |
| GND | 公共参考地 | 模组、传感器、RGB、USB-TTL GND | 共地 | 各接口信号参考 |
本工程使用 PA4 采集 AO,DO 可不接。硬件连接.md 中标注的 PA5 当前配置为模拟输入;如需使用 DO,须另行配置 GPIO 输入并增加读取逻辑。
板载及烧录接口:
| MCU 引脚 | 硬件端 | 配置/作用 |
|---|---|---|
| PE5 | 板载 R_LED | 按键高电平时在循环中翻转 |
| PE6 | 板载 G_LED | 每隔500ms翻转,运行指示 |
| PA15 | 板载 KEY | 下拉输入,高电平按下 |
| PA13 | 调试器 SWDIO | 双向数据 |
| PA14 | 调试器 SWCLK | 调试器向 MCU 输出时钟 |
| GND | 调试器 GND | 调试共地 |
供电检查:模组使用满足峰值电流要求的专用供电或底板,不从 MCU GPIO 取电。光敏 AO 不得超过 ADC 允许输入范围;UART 和 RGB DIN 电平不匹配时使用适当转换。灯模块按规格供电,所有模块共地。USB-TTL 不得使用 RS-232 电平。
智慧路灯 MCU 与蜂窝模组、光敏模块、RGB 灯及调试接口引脚连接图
图3:智慧路灯硬件引脚连接图
1.3 串口与采样参数
| 项目 | 工程配置 |
|---|---|
| USART1 | 115200,8数据位,1停止位,无校验,无硬件流控;AT 通信 |
| USART2 | 同上;printf 重定向输出日志 |
| ADC1 | PA4/PA5配置为模拟输入;业务明确选择通道4 |
| 光敏任务周期 | 1000ms |
| 光照映射值周期上报 | 5分钟 |
| RGB 输出 | PA8 软件时序;自动/强控开灯均为白色 |
接线完成检查:确认 PA9→模组 RXD、PA10←模组 TXD、PA4←AO、PA8→DIN,随后检查各模块电源与公共 GND。先完成这些连接再上电。
2. 云平台配置
2.1 创建产品
登录 移远物联网开发者中心。
进入 产品开发 → 产品管理 → 创建产品。
选择路灯/照明相关品类;没有匹配品类时选择自定义品类。
联网方式选择实际蜂窝网络类型,数据格式选择 物模型,填写产品名称并创建。
创建产品入口示例
图4:创建产品入口
品类选择示例,实际选择路灯或照明品类
图5:品类查找与选择
保存当前产品的 ProductKey(PK)、ProductSecret(PS),在3.7节填写到固件,并检查产品设备激活额度/License。
平台截图为操作示例,产品名称和属性以自己创建的产品及本文物模型为准;激活额度规则以当前账号页面为准。
2.2 定义5项物模型
进入产品 功能定义,按下面的 ID、code 和类型创建属性。大小写、下划线及枚举值必须一致。
功能定义操作示例,属性按本文创建
图6:功能定义入口
| ID | code | 类型 | 设备表权限 | 含义/默认值 |
|---|---|---|---|---|
| 1 | Light_state | BOOL | 读写 | 本地灯态反馈,0灭、1亮;默认0 |
| 2 | Light_control | BOOL | 读写 | 强控开关,0关、1开;默认0 |
| 3 | Light_level | INT | 读写 | ADC反转映射的0~100整数值;初始化0,随后采样更新 |
| 4 | Light_limit | INT | 读写 | 自动比较阈值,0~100,默认50 |
| 5 | ControlStrategy | ENUM | 读写 | 1自动、2强控;默认1 |
权限依据 thing_model_tabel.c 的实际表项。面板将 Light_state、Light_level 作为显示值,控制使用策略和强控开关。Light_level 是0~100映射值,不标注 lux 单位。
平台配置检查:属性数为5,枚举值为1/2;将 Light_limit 输入限制为0~100;确认 PK/PS 属于当前产品。
3. 设备端开发
本章按“加入工程 → 打通串口 → 绑定属性 → 装配通道 → 配置联网 → 接入采样与灯控 → 验证上下行”的顺序操作。直接使用03完整工程时,在对应文件中核对现有实现,只需替换产品凭证和适配实际硬件;移植到自己的工程时,按相同顺序加入文件和调用。
下列源码用于定位和核对关键实现,不要将同名函数重复追加到原文件。每完成一组配置先编译,再继续下一步;硬件与云端验证统一在3.11节烧录后执行。
3.1 打开工程并确认文件位置
源码目录:knowledge-base/quectel-iot/辅助文档(自定义)/03_AT_SmartStreetlight/。
复制一份完整工程作为开发目录,避免只复制 Keil 工程文件。
用 Keil 打开 MDK-ARM/STM32F103ZET6.uvprojx,核对 Device、Flash 算法与实物。
保留 Core、APP、Middleware、Drivers 等相对目录结构。
| 层级 | 文件 | 集成内容 |
|---|---|---|
| 外设层 | Core/Src/gpio.c、adc.c、usart.c | GPIO、ADC、双串口 |
| AT移植层 | APP/at_uart.c/.h | 发送、单字节接收与 FIFO 入队 |
| 应用层 | APP/app.c | 通道装配、初始化与循环任务 |
| AT业务层 | APP/at_dev.c、at_cmd_table.c、at_urc_table.c | 联网、上报、下发及读取回复 |
| 物模型层 | APP/thing_model_tabel.c/.h | 5项属性定义和变量绑定 |
| 驱动层 | Middleware/light_sensor.c/.h、rgb_led.c/.h | 采样、策略执行、灯光时序 |
| 通用中间件 | Middleware/at_core.c、spsc_fifo.c、ttlv.c、thing_model.c及头文件 | AT队列与TTLV编解码 |
thing_model_tabel 是原工程文件名,保留源文件、头文件及引用中的一致拼写。
3.2 核对外设和编译包含路径
操作文件:Keil 工程配置、Core/Src/main.c、gpio.c、adc.c、usart.c。
在 Keil 的 APP、Middleware 分组中核对3.1节所列 .c 文件;移植时加入完整源文件及配套头文件。
打开 Options for Target → C/C++ → Include Paths,保留 ../Core/Inc、HAL/CMSIS 目录,加入 ../APP、../Middleware。
按下表核对外设。使用 CubeMX 重新生成代码时,保留原工程用户代码区和 HAL 时基配置。
| 外设/引脚 | 核对配置 | 对应文件 |
|---|---|---|
| USART1 | PA9 TX、PA10 RX,115200/8N1,无流控,开启全局中断 | usart.c |
| USART2 | PA2 TX、PA3 RX,115200/8N1,日志输出 | usart.c |
| ADC1 | 软件触发、右对齐;原工程扫描2次转换,通道4为Rank1、通道5为Rank2,采样55.5周期 | adc.c |
| PA4/PA5 | 模拟输入;业务采样函数选择通道4,接线只使用PA4 AO | adc.c |
| PA8 | 高速推挽输出,初始低电平 | gpio.c |
| PE5/PE6、PA15 | 板载LED输出、按键下拉输入 | gpio.c |
在外设初始化之后调用 mcu_init(),在主循环中持续调用 mcu_task()。
Core/Src/main.c 的启动顺序为:
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_ADC1_Init();
extern void mcu_init(void);
mcu_init();
主循环对应调用如下:
while (1)
{
extern void mcu_task(void);
mcu_task();
}
执行 Build,先排除找不到头文件、源文件未加入编译和重复定义,再继续核对接收链路。
工程使用 TIM8 作为 HAL 时基,迁移时一并保留对应时基文件、IRQ 和 HAL_TIM_PeriodElapsedCallback() 中的 HAL_IncTick() 调用,避免长时间阻塞主循环。
检查结果:PA8为高速推挽输出,PA4为模拟输入;USART1中断开启;HAL_MspInit() 保留 __HAL_AFIO_REMAP_SWJ_NOJTAG(),释放 JTAG 引脚同时保留 SWD,以便使用 PA15 按键。
3.3 接入 USART1 AT 收发
操作文件:APP/at_uart.c/.h、Core/Src/stm32f1xx_it.c。
在 at_uart.c 核对 USART1 发送、结束符、启动接收和字节入队函数;保留 at_uart.h、usart.h 的引用。
确认发送使用 huart1,每次接收完成后将字节交给 AT FIFO,再启动下一次接收。
uint8_t huart1_rx_data;
void usart1_send(const char *data, uint16_t len)
{
for (uint16_t i = 0; i < len; i++)
{
while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE) == RESET)
;
huart1.Instance->DR = (uint8_t)data[i];
}
}
void usart1_send_line_ending(void)
{
usart1_send(“\r\n”, 2);
}
void usart1_start_recv_data(void)
{
HAL_UART_Receive_IT(&huart1, &huart1_rx_data, 1);
}
void usart1_recv_irq(at_channel_t *channel)
{
at_recv_push(channel, (const uint8_t *)&huart1_rx_data, 1);
usart1_start_recv_data();
}
在 Core/Src/stm32f1xx_it.c 串起 USART1 IRQ、HAL 完成回调和应用入口:
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
extern void uart_irq(void);
uart_irq();
}
}
uart_irq() 位于 APP/app.c,内部调用 usart1_recv_irq(&g_at_channel)。
迁移时在已有回调中合并 USART1 分支,不要重复定义同名 HAL 回调。
检查结果:接收完成后重新启动单字节接收,模组返回数据可持续进入 FIFO;USART2仍独立输出日志。
烧录后可在 usart1_recv_irq() 设置断点,确认模组响应到达;确认后移除断点,再进行连续收发测试,避免断点停顿影响串口时序。
3.4 定义物模型并绑定变量
操作文件:APP/thing_model_tabel.c/.h、Middleware/rgb_led.h。
将第2章平台属性与下列ID逐项核对,保留原枚举值。
typedef enum
{
PROP_LIGHT_STATE = 1,
PROP_LIGHT_CONTROL = 2,
PROP_LIGHT_LEVEL = 3,
PROP_LIGHT_LIMIT = 4,
PROP_CONTROL_STRATEGY = 5,
} tm_property_item_id_e;
#define PROPERTY_ITEM_NUM 5
在 rgb_led.h 核对共享状态结构及策略枚举,确保绑定地址的字段类型正确:
typedef enum
{
STRATEGY_AUTO = 1,
STRATEGY_FORCED = 2,
} control_strategy_e;
typedef struct
{
uint8_t light_state;
uint8_t light_control;
int32_t light_limit;
int32_t strategy;
int32_t light_level;
double r;
double g;
double b;
} rgb_led_t;
extern rgb_led_t rgb_led;
在 thing_model_tabel.c 保留5项属性表和绑定函数;Light_control 绑定3.10节的控制回调,其他属性绑定空回调:
static void prop_changed_noop(void)
{
}
extern void on_light_control_change(void);
tm_t tm_property_table[PROPERTY_ITEM_NUM] = {
{ .func_id = PROP_LIGHT_STATE, .func_type = FTYPE_PROPERTY,
.func_name = “Light_state”, .data_type = DTYPE_BOOL,
.rw_type = RWTYPE_READ_WRITE },
{ .func_id = PROP_LIGHT_CONTROL, .func_type = FTYPE_PROPERTY,
.func_name = “Light_control”, .data_type = DTYPE_BOOL,
.rw_type = RWTYPE_READ_WRITE },
{ .func_id = PROP_LIGHT_LEVEL, .func_type = FTYPE_PROPERTY,
.func_name = “Light_level”, .data_type = DTYPE_INT,
.rw_type = RWTYPE_READ_WRITE },
{ .func_id = PROP_LIGHT_LIMIT, .func_type = FTYPE_PROPERTY,
.func_name = “Light_limit”, .data_type = DTYPE_INT,
.rw_type = RWTYPE_READ_WRITE },
{ .func_id = PROP_CONTROL_STRATEGY, .func_type = FTYPE_PROPERTY,
.func_name = “ControlStrategy”, .data_type = DTYPE_ENUM,
.rw_type = RWTYPE_READ_WRITE },
};
void thing_model_init(void)
{
tm_table_init(FTYPE_PROPERTY, tm_property_table, PROPERTY_ITEM_NUM);
tm_property_bind(PROP_LIGHT_STATE, &rgb_led.light_state, prop_changed_noop);
tm_property_bind(PROP_LIGHT_CONTROL, &rgb_led.light_control,
on_light_control_change);
tm_property_bind(PROP_LIGHT_LEVEL, &rgb_led.light_level, prop_changed_noop);
tm_property_bind(PROP_LIGHT_LIMIT, &rgb_led.light_limit, prop_changed_noop);
tm_property_bind(PROP_CONTROL_STRATEGY, &rgb_led.strategy, prop_changed_noop);
}
绑定对象定义在 Middleware/rgb_led.h:BOOL 字段使用 uint8_t,INT/ENUM 使用 int32_t。
绑定字段宽度须与物模型类型一致,不要将 double 字段按 INT 绑定。r/g/b 仅用于内部颜色控制。
检查结果:在 AT 通道初始化之前调用 thing_model_init(),上报和下发共用 rgb_led 的同一组字段。
3.5 装配 AT 通道并接入任务
操作文件:APP/app.c、Middleware/rgb_led.c。
在 app.c 核对端口对象,将发送、接收初始化映射到3.3节的USART1函数。
保留全局通道及两块256字节缓冲;按“物模型绑定 → 通道装配 → 灯和传感器初始化”执行。
将联网、AT引擎、采样、灯控和周期上报接入同一个循环任务:
static at_port_t g_at_port = {
.init = usart1_start_recv_data,
.get_tick_ms = HAL_GetTick,
.lock = NULL,
.unlock = NULL,
.send = usart1_send,
.send_end_line = usart1_send_line_ending,
};
static at_callback_t g_at_cb = {
.timeout_cb = NULL,
.resp_done_cb = NULL,
};
uint8_t channel_rx_buf[256];
uint8_t channel_resp_buf[256];
at_channel_t g_at_channel;
void mcu_init(void)
{
thing_model_init();
at_set_port(&g_at_channel, &g_at_port);
at_set_cb(&g_at_channel, &g_at_cb);
at_cmd_set_table(&g_at_channel, g_cmd_table, APP_CMD_MAX);
at_urc_set_table(&g_at_channel, g_urc_table, g_urc_table_count);
at_set_rx_buff(&g_at_channel, channel_rx_buf, sizeof(channel_rx_buf));
at_set_resp_buff(&g_at_channel, channel_resp_buf, sizeof(channel_resp_buf));
at_init(&g_at_channel);
rgb_led_init();
light_sensor_init();
}
void mcu_task(void)
{
module_task(&g_at_channel);
at_state_machine(&g_at_channel);
led_task();
light_sensor_task();
rgb_led_task();
light_sensor_periodic_report();
}
void uart_irq(void)
{
usart1_recv_irq(&g_at_channel);
}
led_task() 是原文件的板载指示灯任务,保留原实现。接收调用链为 HAL回调 → uart_irq() → usart1_recv_irq() → FIFO → at_state_machine()。
在 rgb_led.c 核对共享状态的唯一实例和默认初始化。保留现有驱动辅助函数,在初始化中设置默认值并关灯:
rgb_led_t rgb_led;
void rgb_led_init(void)
{
rgb_led.light_state = 0;
rgb_led.light_control = 0;
rgb_led.light_limit = 50;
rgb_led.strategy = STRATEGY_AUTO;
rgb_led.light_level = 0;
rgb_led.r = 0;
rgb_led.g = 0;
rgb_led.b = 0;
WS_SetColor(0, 0, 0);
}
检查结果:主循环持续调用以上任务,不用长延时阻塞 AT 状态机;复位后绿色板载 LED 持续翻转。
3.6 注册命令表和 URC 表
操作文件:APP/at_cmd_table.c/.h、APP/at_urc_table.c/.h。
核对 at_cmd_table.h 中的命令枚举与 .c 中的索引一致。
在 g_cmd_table 初始化器中核对以下核心表项,将解析函数定义保留在命令表之前。
以下是表内条目,不是独立的全局声明。保留原表中的其他命令,不用此片段覆盖整个表。
[APP_CMD_QIOTCFG] = { “+QIOTCFG”, 5000, parse_advance_on_ok },
[APP_CMD_QIOTREG] = { “+QIOTREG”, 5000, parse_advance_on_ok },
[APP_CMD_QIOTSTATE] = { “+QIOTSTATE”, 5000, parse_qiotstate_noop },
[APP_CMD_QIOTMODELTD] = { “+QIOTMODELTD”, 5000, parse_advance_on_ok },
[APP_CMD_QURCCFG] = { “+QURCCFG”, 5000, parse_advance_on_ok },
解析器实现如下。公共回调推进步骤;平台状态查询则必须匹配状态8。
static int parse_advance_on_ok(at_ret_t status, const char *resp, uint16_t len)
{
(void)status;
(void)resp;
(void)len;
module_init_next_step();
return 0;
}
static int parse_qiotstate_noop(at_ret_t status, const char *resp, uint16_t len)
{
if (status != AT_OK || resp == NULL || len == 0)
return 0;
static const char needle[] = “+QIOTSTATE: 8”;
const uint16_t nlen = (uint16_t)(sizeof(needle) - 1);
if (nlen > len)
return 0;
for (uint16_t i = 0; i + nlen <= len; i++)
{
if (memcmp(resp + i, needle, nlen) == 0)
{
module_init_next_step();
return 0;
}
}
return 0;
}
在 APP/at_urc_table.c 保留3.10节的事件处理及辅助函数,在文件末尾注册URC表。表项的 user 指向同一个 AT 通道,供回复数据使用:
extern at_channel_t g_at_channel;
at_urc_item_t g_urc_table[] = {
{ “+QIOTEVT:”, urc_cb_qiotevt, &g_at_channel },
};
uint8_t g_urc_table_count = sizeof(g_urc_table) / sizeof(g_urc_table[0]);
检查结果:同时检查模组实际响应、平台在线状态及属性到达情况;不要只凭 step 推进或初始化日志判断每条指令成功。
3.7 配置凭证、联网并进行首报
操作文件:APP/at_dev.c。
打开第2章创建的产品,复制PK/PS,替换原文件对应宏值,保留 #ifndef 结构:
下例尖括号内容为占位符,必须替换为自己的凭证。不要复制源码默认凭证,也不要公开实际PS。
#ifndef PRODUCT_PK
#define PRODUCT_PK “<你的ProductKey>”
#endif
#ifndef PRODUCT_PS
#define PRODUCT_PS “<你的ProductSecret>”
#endif
核对联网状态机与 module_task()、module_init_next_step()。命令由主循环提交,响应解析器推进步骤,不在主循环中重复手工发送同一组指令。
| step | 执行动作 | 下一步检查 |
|---|---|---|
| 0 | 设置URC输出到uart1 | 核对实际命令响应 |
| 1 | 查询连接模式 | 核对实际查询响应 |
| 2 | 设置本产品PK/PS | 核对配置响应 |
| 3 | 启用平台连接 | 继续查询平台状态 |
| 4 | 约5秒轮询平台状态 | 匹配 +QIOTSTATE: 8 |
| 5 | 编码并上报5项属性 | 平台核对首报数据 |
| 6 | 设置初始化完成 | 转入持续采样和业务任务 |
对应源码如下,保留原文件中的头文件引用与函数声明:
uint8_t is_inited = 0;
uint8_t step = 0;
uint8_t send_flag = 0;
static void modele_init(at_channel_t *channel)
{
switch (step)
{
case 0:
if (0 == send_flag)
{
send_flag = 1;
at_send(channel, APP_CMD_QURCCFG, AT_OP_WRITE,
“”%s",“%s”“, “urcport”, “uart1”);
}
break;
case 1:
if (0 == send_flag)
{
send_flag = 1;
at_send(channel, APP_CMD_QIOTREG, AT_OP_READ, NULL);
}
break;
case 2:
if (0 == send_flag)
{
send_flag = 1;
at_send(channel, APP_CMD_QIOTCFG, AT_OP_WRITE,
“”%s”,“%s”,“%s”", “productinfo”,
PRODUCT_PK, PRODUCT_PS);
}
break;
case 3:
if (0 == send_flag)
{
send_flag = 1;
at_send(channel, APP_CMD_QIOTREG, AT_OP_WRITE, “1”);
}
break;
case 4:
if (0 == send_flag)
{
send_flag = 1;
at_send(channel, APP_CMD_QIOTSTATE, AT_OP_READ, NULL);
}
else
{
static uint32_t last_poll_ms = 0;
uint32_t now_ms = channel->port->get_tick_ms();
if (now_ms - last_poll_ms >= 5000)
{
last_poll_ms = now_ms;
send_flag = 0;
}
}
break;
case 5:
if (0 == send_flag)
{
send_flag = 1;
if (module_report_props(channel) < 0)
{
LOG(“ttlv encode all failed\r\n”);
send_flag = 0;
}
}
break;
case 6:
LOG(“Module init Complete\r\n”);
is_inited = 1;
break;
}
}
void module_task(at_channel_t *channel)
{
if (is_inited == 0)
modele_init(channel);
}
void module_init_next_step(void)
{
send_flag = 0;
if (0xff > step)
step++;
}
module_report_props() 同时用于上线首报和下发写入后的全量回传:
int module_report_props(at_channel_t *channel)
{
static uint8_t buf[64];
int len;
len = tm_property_encode_many(0, buf, sizeof(buf));
if (len <= 0)
return -1;
return at_send_data(channel, APP_CMD_QIOTMODELTD, AT_OP_WRITE,
buf, (uint16_t)len, “%d,%u”, 0, (uint16_t)len);
}
mask=0 表示编码全部已绑定属性。
at_send_data() 为“发命令头 → 等待模组返回 > → 发 payload”的异步过程,发送缓冲使用 static,确保数据在发送结束前持续有效。
检查结果:状态查询出现 +QIOTSTATE: 8,平台显示设备在线,通信日志出现5项属性首报。首报是发送时的当前快照,采样值以随后任务更新和读取结果为准。
3.8 接入光敏采样与自动开关灯
操作文件:Middleware/light_sensor.c/.h。
在文件开头核对采样参数及本地动作记录;保留原文件的头文件引用、通道声明和旧常量定义。
#define VREF (3.3f)
#define ADC_MAX (4095.0f)
#define LIGHT_LEVEL_MIN (0)
#define LIGHT_LEVEL_MAX (100)
#define LIGHT_SENSOR_PERIOD_MS (1000U)
#define LIGHT_LEVEL_REPORT_PERIOD_MS (5U * 60U * 1000U)
typedef enum
{
LIGHT_ACT_NONE = 0,
LIGHT_ACT_ON,
LIGHT_ACT_OFF,
} light_action_e;
static light_action_e s_last_action = LIGHT_ACT_NONE;
void light_sensor_init(void)
{
s_last_action = LIGHT_ACT_NONE;
rgb_led_close();
}
static int32_t clamp_limit(int32_t v)
{
if (v < (int32_t)LIGHT_LEVEL_MIN)
return (int32_t)LIGHT_LEVEL_MIN;
if (v > (int32_t)LIGHT_LEVEL_MAX)
return (int32_t)LIGHT_LEVEL_MAX;
return v;
}
核对 light_sensor_read_raw(),业务采样明确选择通道4,保留 HAL 通道配置与转换顺序;再用映射函数将ADC值转为0~100:
uint32_t light_sensor_read_raw(void)
{
ADC_ChannelConfTypeDef sCh = { 0 };
sCh.Rank = ADC_REGULAR_RANK_1;
sCh.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
sCh.Channel = ADC_CHANNEL_4;
if (HAL_ADC_ConfigChannel(&hadc1, &sCh) != HAL_OK)
return 0U;
if (HAL_ADC_Start(&hadc1) != HAL_OK)
return 0U;
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
return (uint32_t)HAL_ADC_GetValue(&hadc1);
return 0U;
}
static double light_sensor_calc_level(uint32_t raw)
{
double level = (1.0 - (double)raw / ADC_MAX) * 100.0;
if (level < (double)LIGHT_LEVEL_MIN)
level = (double)LIGHT_LEVEL_MIN;
if (level > (double)LIGHT_LEVEL_MAX)
level = (double)LIGHT_LEVEL_MAX;
return level;
}
light_sensor_read_voltage() 通过 raw*3.3/4095 换算电压。Light_level 经反转映射后转为 int32_t,小数被截断。
核对自动控制任务:每秒更新采样值,仅策略1执行阈值判断;动作改变时更新灯态并调用3.9节的联合上报函数。
void light_sensor_task(void)
{
static uint32_t last_tick = 0;
uint32_t now = HAL_GetTick();
if ((now - last_tick) < LIGHT_SENSOR_PERIOD_MS)
return;
last_tick = now;
uint32_t raw = light_sensor_read_raw();
float voltage = (float)raw * VREF / ADC_MAX;
rgb_led.light_level = (int32_t)light_sensor_calc_level(raw);
if ((uint8_t)rgb_led.strategy != STRATEGY_AUTO)
return;
double limit = (double)clamp_limit(rgb_led.light_limit);
uint8_t old_state = rgb_led.light_state;
if (rgb_led.light_level <= limit)
{
if (s_last_action != LIGHT_ACT_ON)
{
WS_SetColor(255, 255, 255);
s_last_action = LIGHT_ACT_ON;
rgb_led.light_state = 1;
}
}
else
{
if (s_last_action != LIGHT_ACT_OFF)
{
WS_SetColor(0, 0, 0);
s_last_action = LIGHT_ACT_OFF;
rgb_led.light_state = 0;
}
}
if (rgb_led.light_state != old_state)
report_light_state_change();
(void)voltage;
(void)LEGACY_LIGHT_ON_VOLTAGE;
(void)LEGACY_LIGHT_OFF_VOLTAGE;
}
clamp_limit() 将比较时的阈值限制到0~100,自动判断使用整数映射值与单个阈值比较。
平台将输入限制为0~100;计算时的限幅不自动改写原字段。旧的2.5V/2.3V宏不参与当前自动判断。
自动策略验证:
上电使用默认 ControlStrategy=1、Light_limit=50。
改变传感器受光条件,通过平台主动读取 Light_level 观察数值变化;需要核对原始值时,在调试器中观察采样函数返回值或任务内的raw。
让映射值分别位于阈值两侧,按下表检查白灯和 Light_state。
| ADC原始值示例 | Light_level整数值 | 阈值50时的自动输出 |
|---|---|---|
| 0 | 100 | 关灯,状态0 |
| 1024 | 74 | 关灯,状态0 |
| 3072 | 24 | 开白灯,状态1 |
| 4095 | 0 | 开白灯,状态1 |
遮光/照光时 AO 的变化方向需按所用模块实测确认。按 Light_level <= Light_limit 开灯,否则关灯的条件验收,映射值不使用 lux 单位。
采样函数返回0时,先检查HAL返回状态、供电和AO电压,再判断是否为有效低端读数;当前映射会将返回0换算为100。
3.9 实现灯态变化上报与5分钟周期上报
操作文件:Middleware/light_sensor.c、APP/app.c。
保留 extern at_channel_t g_at_channel;,确认调用的是3.5节装配的通道。
保留原文件中位于 light_sensor_task() 之前的 report_light_state_change();自动灯态改变后联合编码ID1和ID3。
保留 light_sensor_periodic_report(),并确认已在 mcu_task() 中调用。周期到达且初始化完成后,单独上报ID3。
static void report_light_state_change(void)
{
static uint8_t buf[16];
int len;
len = tm_property_encode_many(
(1UL << PROP_LIGHT_STATE) | (1UL << PROP_LIGHT_LEVEL),
buf, sizeof(buf));
if (len <= 0)
return;
at_send_data(&g_at_channel, APP_CMD_QIOTMODELTD, AT_OP_WRITE,
buf, (uint16_t)len, “%d,%u”, 0, (uint16_t)len);
}
void light_sensor_periodic_report(void)
{
static uint32_t last_tick = 0;
uint32_t now = HAL_GetTick();
if ((now - last_tick) < LIGHT_LEVEL_REPORT_PERIOD_MS)
return;
last_tick = now;
extern uint8_t is_inited;
if (is_inited == 0)
return;
static uint8_t buf[8];
int len;
len = tm_property_encode_one(PROP_LIGHT_LEVEL, buf, sizeof(buf));
if (len <= 0)
return;
at_send_data(&g_at_channel, APP_CMD_QIOTMODELTD, AT_OP_WRITE,
buf, (uint16_t)len, "%d,%u", 0, (uint16_t)len);
}
原文件周期宏为 LIGHT_LEVEL_REPORT_PERIOD_MS=(5U60U1000U)。属性掩码按 1UL << 属性ID 设置,不按数组下标设置。
检查结果:自动灯态改变时看到 ID1+ID3联合上报;仅采样值改变但灯态不变时,等待周期上报或主动读取。周期检查跟随设备运行计时,不应假定总在上线后恰好5分钟发生。
3.10 接入云端强控与属性读取回复
3.10.1 执行强控开关
操作文件:Middleware/rgb_led.c、APP/at_urc_table.c。
将原 rgb_led.c/.h 作为完整驱动加入工程,保留PA8时序函数;自动和强控共同使用同一个灯模块。
在 rgb_led.c 核对 rgb_led_task(),循环任务仅在策略2时按强控值刷新白灯:
void rgb_led_task(void)
{
if (rgb_led.strategy == STRATEGY_FORCED)
{
if (rgb_led.light_control == 1)
{
rgb_led_apply_color(255, 255, 255);
rgb_led_light();
rgb_led.light_state = 1;
}
else
{
rgb_led_apply_color(0, 0, 0);
rgb_led_light();
rgb_led.light_state = 0;
}
}
}
rgb_led_apply_color() 保存颜色,rgb_led_light() 调用 WS_SetColor();后者直接对 PA8 输出时序,原驱动及其时序辅助函数应整体保留。
在 APP/at_urc_table.c 核对 on_light_control_change(),与3.4节绑定名称一致。该函数保留外部可见性,不加 static;强控策略下字段改变立即执行开关动作:
void on_light_control_change(void)
{
if (rgb_led.strategy == STRATEGY_FORCED)
{
if (rgb_led.light_control == 1)
{
rgb_led_apply_color(255, 255, 255);
rgb_led_light();
rgb_led.light_state = 1;
}
else
{
rgb_led_apply_color(0, 0, 0);
rgb_led_light();
rgb_led.light_state = 0;
}
}
}
平台先写 ControlStrategy=2,再写 Light_control=true/false,逐条观察物理输出。只改策略时由循环任务应用已有强控值,写入后的即时回传与下一轮任务可能有先后差异,必要时重新读取灯态确认。
3.10.2 分发云端写入和读取事件
在 APP/at_urc_table.c 保留以下辅助函数,和事件回调一并加入编译:
| 函数 | 作用 | 集成位置 |
|---|---|---|
| match_event() | 识别10210写入或10211读取事件 | URC回调前 |
| fetch_follow_line() | 从FIFO取得写入TTLV,等待后续串口数据约50ms | URC回调前 |
| parse_dl_read() | 解析PkgID及请求属性列表 | URC回调前 |
| reply_qiotmodel_read() | 编码请求属性并带PkgID回复 | URC回调前 |
核对事件回调,并通过3.6节URC表注册,不在主循环直接调用它:
static void urc_cb_qiotevt(const char *line, uint16_t len, void *user)
{
if (user == NULL || len < 7)
return;
at_channel_t *ch = (at_channel_t *)user;
switch (match_event(line, len))
{
case 10210:
{
uint8_t buf[AT_LINE_MAX];
uint16_t got = 0;
if (fetch_follow_line(ch, buf, sizeof(buf), &got) != AT_OK)
return;
(void)tm_downlink_dispatch(buf, got, NULL);
(void)module_report_props(ch);
break;
}
case 10211:
{
uint16_t wanted_ids[8] = { 0 };
uint8_t wanted_cnt = 0;
uint32_t pkg_id = parse_dl_read(line, len, wanted_ids,
(uint8_t)(sizeof(wanted_ids) / sizeof(wanted_ids[0])), &wanted_cnt);
if (pkg_id == 0)
return;
reply_qiotmodel_read(ch, (uint16_t)pkg_id, wanted_ids, wanted_cnt);
break;
}
default:
break;
}
}
10210写入事件后从接收 FIFO 取TTLV,按属性ID更新已绑定字段;值确实改变时触发变化回调,随后全量回传。10211读取事件携带 PkgID 和属性ID列表,例如 +QIOTEVT: 5,10211,62,3,4 表示读取ID3、4,PkgID为62。
核对回复函数,按请求列表编码并原样回带 PkgID;编码缓冲保留静态存储:
static uint8_t g_read_reply_buf[128];
static void reply_qiotmodel_read(at_channel_t *ch, uint16_t pkg_id,
const uint16_t *wanted_ids, uint8_t wanted_cnt)
{
uint16_t len = 0;
int n;
if (wanted_cnt == 0)
{
n = tm_property_encode_many(0, g_read_reply_buf, sizeof(g_read_reply_buf));
if (n <= 0)
return;
len = (uint16_t)n;
}
else
{
for (uint8_t i = 0; i < wanted_cnt; i++)
{
n = tm_property_encode_one(wanted_ids[i], g_read_reply_buf + len,
(uint16_t)(sizeof(g_read_reply_buf) - len));
if (n <= 0)
continue;
len = (uint16_t)(len + n);
}
}
if (len == 0)
return;
(void)at_send_data(ch, APP_CMD_QIOTMODELTD, AT_OP_WRITE,
g_read_reply_buf, len, “%d,%u,%u”, 1, len, pkg_id);
}
检查结果:写强控开关后白灯改变;主动读取ID1、3、4时返回相应属性且PkgID匹配。完整执行顺序按下表操作:
| 顺序 | 平台操作/硬件操作 | 实物与属性检查 |
|---|---|---|
| 1 | 等待在线,读取ID1~5 | 核对当前值及默认策略 |
| 2 | 写策略2,等待回传,再写强控true | 白灯亮,再读ID1为1 |
| 3 | 改变受光条件,再读ID3 | 映射值更新,白灯保持强控 |
| 4 | 写强控false | 白灯灭,再读ID1为0 |
| 5 | 写策略1,让映射值先跨到阈值一侧,再跨到另一侧 | 每次检查比较条件、实物及灯态变化上报 |
| 6 | 读取ID3、4 | 返回当前映射值和阈值,读应答PkgID对应本次请求 |
本地自动动作记录只在传感器初始化时清除。在线切回策略1后,通过跨阈值操作检查两侧动作;若要单独验证自动模式的初始行为,复位后使用默认策略1测试。
3.11 编译、烧录与本章验收
核对 PK/PS、5项属性ID和类型;避免把真实PS提交到公开仓库。
用 Keil Build(F7),检查编译器兼容性、头文件路径及链接结果。
连接 SWD,核对 Device、调试器和 Flash 下载算法,Download(F8)。
打开 USART2 日志后复位,观察运行指示与 Module init Complete。
在平台确认在线、5项首报,再执行第4章测试。
通过条件:编译烧录成功 → 平台在线 → 首报完整 → 自动灯态变化及联合上报 → 强控开关闭环 → 指定属性读取成功 → 至少一次5分钟周期上报。
4. 设备调试与验证
4.1 先确认通信与网络
手动检查模组前,断开 MCU 对模组 RX 的驱动,再连接匹配电平的 USB-TTL,避免两路TX同时驱动同一输入。
| 指令 | 检查内容 | 预期/判断依据 |
|---|---|---|
| AT | 基础通信 | OK |
| AT+CPIN? | SIM | READY |
| AT+CSQ | 信号 | 结合网络状态判断,99为未知/不可用 |
| AT+CEREG? | LTE注册 | 通常1为已注册、5为漫游;以模组手册为准 |
| AT+QIOTSTATE? | 平台连接 | 工程以状态8继续首报 |
确认后恢复 MCU 接线。USART2只输出原工程已有日志;如需观察每条AT及采样raw,使用串口抓取、调试器或另加受控日志,不假定已有逐步成功日志。
4.2 检查首报、变化上报与周期上报
设备在线后,打开产品的 设备调试 / 通信日志:
设备调试和通信日志操作示例
图7:设备调试与通信日志
| 测试项 | 操作 | 检查结果 |
|---|---|---|
| 全量首报 | 复位并等待在线 | 收到ID1~5当前值 |
| 自动开灯 | 默认自动策略,使映射值跨到阈值以下或相等 | 白灯亮,状态1;灯态改变时ID1+ID3上报 |
| 自动关灯 | 使映射值跨到阈值以上 | 白灯灭,状态0;灯态改变时ID1+ID3上报 |
| 修改阈值 | 保持受光条件,修改0~100范围内阈值使比较分支改变 | 下一采样周期执行相应动作 |
| 周期上报 | 在线持续运行至少覆盖一个5分钟周期 | 收到ID3,无需每秒都有消息 |
4.3 强控和属性读取
写 ControlStrategy=2,确认策略回传为2。
写 Light_control=true,确认白灯亮;读取 Light_state 应为1。
改变光敏受光条件,读取 Light_level,确认采样值仍更新,灯保持强控状态。
写 Light_control=false,确认白灯灭;读取 Light_state 应为0。
读取ID3、4,检查返回映射值和阈值;抓取AT时核对读取回复带原PkgID。
复位恢复默认自动策略,再按4.2节跨阈值测试。
Light_state 是MCU维护的软件灯态,验证时同时观察实物,确认平台数值与实际亮灭一致。
5. SaaS 数据接入
5.1 使用平台资产管理 SaaS
在设备已在线且读写验证通过后,进入平台提供的资产管理 SaaS。
在产品管理中启用当前智慧路灯产品,核对设备同步与账号权限。
配置列表/看板,展示灯态、映射值、阈值与策略;将映射值标注为“光照映射值(0~100)”,不标为lux。
提供控制入口时,使用 ControlStrategy 和 Light_control,按先策略后开关的顺序操作。
进入资产管理SaaS示例
图8:进入资产管理 SaaS
SaaS启用产品示例
图9:在 SaaS 启用产品
具体菜单和可用能力以当前平台与账号为准。完成后让灯态变化一次,检查SaaS收到的数据与开发者中心一致。
5.2 自建 SaaS(可选)
按平台文档创建服务端凭证,配置目标产品的消息订阅。
使用 AMQP 客户端接收设备上报,处理确认、重连及重复消息。
按产品、设备和属性ID保存数据,区分周期上报与灯态变化上报。
远程控制使用平台相应设备控制接口;AMQP消费连接不等于设备控制接口。
参考 AMQP 消息订阅概述。这是自建服务端的接入路径,与启用现成SaaS二选一或按需组合;03工程本身只包含设备端。
6. App 绑定与控制
按当前产品的绑定能力配置设备绑定方式和访问权限,准备平台要求的二维码或绑定码。
在移联万物 App 中按支持的方式添加设备,确认绑定对象是当前产品下的目标设备。
配置面板:灯态显示、0~100映射值显示、0~100阈值输入、自动/强控枚举、强控开关。
先在设备调试页验证同样的属性写入,再从App重复强控开关流程,核对实物、App和平台数据。
App仅显示/控制本文5项属性,不添加固件没有实现的RGB颜色、定时开关、位置或功率属性。自动与强控的执行顺序、上报节奏和读取语义与设备调试页一致。若绑定或面板尚未配置,应先完成平台设置,烧录固件不会自动创建这些配置。
7. 常见问题排查
7.1 设备与业务问题
| 现象 | 按顺序检查 |
|---|---|
| 找不到匹配MCU或无法烧录 | 实物丝印、Keil Device、器件包、SWD接线、Flash算法;不按工程文件名判断芯片 |
| 模组无响应 | 电源、共地、UART电平、PA9/PA10交叉连接、波特率、USART1接收中断 |
| 一直等平台状态8 | SIM、天线、网络注册、AT支持情况、PK/PS和产品激活额度 |
| 只见初始化日志,平台没有数据 | 检查实际AT返回、平台在线与通信日志,分别确认联网和业务消息到达 |
| PA5接DO没有作用 | 当前业务走PA4 AO;现有PA5配置不是数字输入 |
| 采样变化方向与预期相反 | 实测AO随遮光/照光的变化,按3.8节反转映射与比较条件核对 |
| 采样值没有每秒更新到平台 | 本地每秒更新;普通变化等待5分钟上报或主动读取 |
| 自动策略下写强控开关不亮 | 先设置策略2,再写强控开关,并检查PA8、DIN、电源和共地 |
| 修改策略后回传灯态与瞬间实物不同 | 等待循环任务执行后读取灯态;把策略切换和光照跨阈值分开验证 |
| 下发成功但读回复超时 | 核对10211事件、PkgID、请求ID与回复参数,检查TTLV缓冲生命周期 |
| INT/ENUM值异常 | 检查ID、int32_t宽度及绑定地址;BOOL使用uint8_t |
| 掉电后阈值或策略恢复默认 | 当前参数初始化来自固件;需要持久化时另行增加存储和恢复流程 |
| 面板字段与截图不同 | 截图为入口示例,实际面板以本文5项物模型为准 |
7.2 模组联网 AT 自检
设备不上线、一直等待状态8时,按下表从基础通信逐项检查,前一项异常先处理,再继续检查后续项目。
暂停 MCU 的 AT 通信,断开 MCU TX 对模组 RX 的驱动,再用匹配电平的 USB-TTL 连接模组 AT 端口并共地。
按模组串口配置设置波特率和行结束符,先发送 AT 确认通信,再依次执行查询命令,记录完整响应。
检查完成后断开调试工具,恢复 MCU 接线并复位设备,在平台重新确认在线、首报和控制闭环。
USART2 是 MCU 日志口,以下命令应发送到模组 AT 端口。不要让 USB-TTL 与 MCU 的 TX 同时驱动模组 RX。不同模组和固件的指令支持、返回格式以对应 AT 手册为准;这些查询不属于当前工程自动联网流程。
| AT 命令 | 检查项 | 返回值判断 | 异常时处理 |
|---|---|---|---|
| AT | 串口通信 | 返回 OK | 检查上电状态、供电、共地、TX/RX交叉连接、接口电平、波特率及回车换行 |
| ATI、AT+CGMR | 模组型号与固件 | 记录实际返回的型号/版本信息 | 对照该版本手册核对 QuecThing 指令支持;不能仅凭某条命令返回ERROR判断固件过旧 |
| AT+CPIN? | SIM 就绪状态 | +CPIN: READY 表示卡接口已就绪 | 未检测到卡时断电检查卡槽、触点和插卡方向;返回 SIM PIN/SIM PUK 时按运营商流程解锁,勿反复猜测密码 |
| AT+CFUN? | 模组功能状态 | 常见 +CFUN: 1 为完整功能,具体以手册为准 | 若处于最小功能或关闭射频状态,确认用途后按手册恢复正常工作模式 |
| AT+CSQ | 信号质量 | 返回 +CSQ: ,;RSSI通常为0~31,99表示未知或不可测 | 检查天线、频段匹配和覆盖环境;信号数值正常不代表已经注网 |
| AT+CEREG? | LTE/EPS 注册 | 常见 +CEREG: ,,检查 :1已注册、5漫游注册 | 0未注册且未搜索、2正在搜索、3注册被拒、4状态未知;结合SIM业务状态、网络覆盖和频段排查,被拒时联系运营商核实卡权限 |
| AT+CREG? | 支持GSM/2G时的注册状态 | 常见返回中的 为1或5表示注册成功 | 仅对支持相应制式的模组使用;LTE接入不能只凭CREG判断,优先核对CEREG |
| AT+COPS? | 运营商选择情况 | 已选网时通常返回运营商标识和接入制式,标识可能为名称或数字编码 | 未返回运营商信息时结合CEREG和CSQ判断;不单独用此项认定联网成功 |
| AT+CGATT? | 分组域附着 | 支持该指令时,+CGATT: 1 表示已附着 | 为0时先查注册状态、SIM业务权限及数据服务;已附着仍需继续检查数据连接 |
| AT+CGDCONT?、AT+CGPADDR | PDP/APN配置及地址 | 核对实际使用的CID与运营商APN;有效地址可作为数据上下文建立的检查依据 | 没有有效地址时核对APN、套餐和流量状态;通过运营商及模组手册确认后再修改,不照搬其他卡的APN |
| AT+QIOTREG?、AT+QIOTSTATE? | QuecThing连接模式与平台状态 | 本工程设置连接模式1,并等待 +QIOTSTATE: 8 | 网络正常但不到状态8时,检查产品PK/PS、激活额度、固件支持情况及实际错误/事件响应 |
CPIN: READY 只说明卡接口就绪,不表示卡已激活、有流量或具备当前网络的接入权限;套餐、停机及流量状态需通过运营商渠道查询。附着、PDP上下文和平台连接是不同层次,已注网不等于已上云。
7.3 按结果定位故障
| 检查结果 | 下一步操作 |
|---|---|
| AT无响应 | 先解决串口、电平和供电问题,再查SIM和网络 |
| SIM未就绪 | 处理插卡、卡接口或PIN/PUK状态,不反复重发平台连接命令 |
| SIM就绪但未注册 | 检查天线、覆盖、制式/频段及运营商卡权限;搜索状态先等待,被拒状态记录响应后联系运营商 |
| 已注册但数据连接未建立 | 核对附着、实际CID/APN、卡套餐与流量,按模组手册检查数据上下文 |
| 数据网络正常但平台未连接 | 核对PK/PS、激活额度和QuecThing事件,记录QIOTSTATE及错误响应 |
| 状态8但无属性或下发 | 回到USART1接收链路、URC输出端口、物模型ID/类型及TTLV发送检查 |
排查顺序:基础通信 → SIM就绪 → 功能状态与信号 → 网络注册 → 分组数据 → 平台状态8 → 属性上下行。手动查询期间不要与 MCU 自动状态机同时发命令,恢复运行后再执行设备端验收。