在嵌入式项目中,将设备接入 Home Assistant(HA)实现可视化与自动化控制,是非常常见的需求。
本文基于 NetX Duo + MQTT 协议,实现一个风扇控制器的数据上报,并通过 HA 自动发现(Discovery)机制,自动生成传感器实体。
设备通过 MQTT 向 HA 上报:
- 环境温度1
- 环境温度2
- 风扇转速(rpm)
实验硬件:
- STM32F746G-DISCO 开发板
1. 核心原理: MQTT自动发现
HA 自动发现的核心在于配置主题 (Config Topic)。设备启动时,需向特定格式的主题发送 JSON 配置信息,告知 HA:
- 设备的名称和唯一 ID
- 数据上报的 状态主题 (State Topic)
- 如何从状态 JSON 中提取具体数值(JSON Template)
Topic 格式规范:
homeassistant/<component>/<node_id>/<object_id>/config
2. 宏定义与主题设计
首先定义 MQTT 相关的 Topic。我们将传感器归类在 fan_node 节点下。
/* --- MQTT Topic 宏定义 --- */
/* 状态上报主题:HA通过这个主题获取实时数据 */
#define FAN_STATE_TOPIC "device/ap_fan/status"
/* HA 自动发现配置主题:HA通过这些主题自动创建实体 */
#define HA_DISC_BASE "homeassistant/sensor/fan_node"
#define HA_DISC_TEMP1_TOPIC HA_DISC_BASE "/temp_1/config"
#define HA_DISC_TEMP2_TOPIC HA_DISC_BASE "/temp_2/config"
#define HA_DISC_RPM_TOPIC HA_DISC_BASE "/fan_rpm/config"
3. 实现自动发现:HA_Discovery_Init
此函数在 MQTT 连接成功后调用一次。它定义了设备的基本信息(型号、厂家),并将多个传感器绑定到同一个“设备”下。
void HA_Discovery_Init(void) {
char config_payload[1024]; // 增加缓冲区大小以容纳设备信息
// 定义共同的设备信息 JSON 字符串
// identifiers 是唯一的 ID(建议用 MAC 地址或固定字符串)
const char* device_info = ",\"dev\":{\"ids\":[\"ap_fan_01\"],\"name\":\"AP风扇控制器\",\"mdl\":\"STM32F746-G0\",\"mf\":\"DIY\"}";
// 1. 注册温度计1
snprintf(config_payload, sizeof(config_payload),
"{\"name\":\"温度1\",\"stat_t\":\"" FAN_STATE_TOPIC "\",\"val_tpl\":\"{{value_json.t_fan}}\",\"unit_of_meas\":\"°C\",\"dev_cla\":\"temperature\",\"unique_id\":\"fan_t1\"%s}", device_info);
nxd_mqtt_client_publish(&mqtt_client, HA_DISC_TEMP1_TOPIC, strlen(HA_DISC_TEMP1_TOPIC), config_payload, strlen(config_payload), NX_TRUE, 1, NX_WAIT_FOREVER);
// 2. 注册温度计2
snprintf(config_payload, sizeof(config_payload),
"{\"name\":\"温度2\",\"stat_t\":\"" FAN_STATE_TOPIC "\",\"val_tpl\":\"{{value_json.t_env}}\",\"unit_of_meas\":\"°C\",\"dev_cla\":\"temperature\",\"unique_id\":\"fan_t2\"%s}", device_info);
nxd_mqtt_client_publish(&mqtt_client, HA_DISC_TEMP2_TOPIC, strlen(HA_DISC_TEMP2_TOPIC), config_payload, strlen(config_payload), NX_TRUE, 1, NX_WAIT_FOREVER);
`
// 3. 注册转速表
snprintf(config_payload, sizeof(config_payload),
"{\"name\":\"转速\",\"stat_t\":\"" FAN_STATE_TOPIC "\",\"val_tpl\":\"{{value_json.rpm}}\",\"unit_of_meas\":\"RPM\",\"unique_id\":\"fan_rpm\"%s}", device_info);
nxd_mqtt_client_publish(&mqtt_client, HA_DISC_RPM_TOPIC, strlen(HA_DISC_RPM_TOPIC), config_payload, strlen(config_payload), NX_TRUE, 1, NX_WAIT_FOREVER);
}
- stat_t:指定状态主题,HA 将从这个主题获取实时数据。
- val_tpl:指定如何从状态 JSON 中提取数值。这里我们使用了 value_json 变量来解析 JSON 字符串。
- device_info:将多个传感器绑定到同一个设备,方便 HA 进行管理和展示。
3. 添加NetxDuo与MQTT组件
- 添加 NetX Duo 协议栈
可通过 STM32CubeMX 直接集成 NetX Duo Firmware。具体操作步骤可参考如下文档:
添加 NetXDuo支持 - STM32F779I-EVAL
- 启用 MQTT 功能组件
在 Software Packs → Components 中,勾选 MQTT Add-on,即可启用 MQTT 协议支持。

4. MQTT 线程入口:连接并上报数据
在主循环中,我们构造紧凑的 JSON 负载进行上报。通过信号量驱动,确保数据更新时才触发发布。
/* --- MQTT 线程入口:连接并上报数据 --- */
void mqtt_app_thread_entry(ULONG thread_input) {
UINT status;
NXD_ADDRESS server_ip;
char mqtt_payload[128];
server_ip.nxd_ip_version = NX_IP_VERSION_V4;
server_ip.nxd_ip_address.v4 = MQTT_SERVER_ADDRESS;
/* 1. 创建 MQTT 客户端 */
status = nxd_mqtt_client_create(&mqtt_client, "F746_Gateway",
MQTT_CLIENT_ID_STRING, strlen(MQTT_CLIENT_ID_STRING),
&client_ip, &client_pool,
(VOID*)mqtt_client_stack, sizeof(mqtt_client_stack),
MQTT_THREAD_PRIORITY, NULL, 0);
status = nxd_mqtt_client_login_set(&mqtt_client,
MQTT_USERNAME, strlen(MQTT_USERNAME),
MQTT_PASSPORT, strlen(MQTT_PASSPORT));
int32_t t1, t2;
while(1) {
/* 2. 连接 MQTT Broker */
status = nxd_mqtt_client_connect(&mqtt_client, &server_ip, MQTT_PORT, 60, NX_TRUE, NX_WAIT_FOREVER);
if (status == NX_SUCCESS) {
HA_Discovery_Init(); // 连接成功后立即发送发现包
while(1) {
/* 3. 等待新数据通知 */
if (tx_semaphore_get(&sem_new_fan_data, TX_WAIT_FOREVER) == TX_SUCCESS) {
// 构造 JSON 负载
t1 = g_fan_data.temp_fan;
t2 = g_fan_data.temp_env;
snprintf(mqtt_payload, sizeof(mqtt_payload),
"{\"t_fan\":%ld.%01u,\"t_env\":%ld.%01u,\"rpm\":%u}",
t1 / 10, (unsigned int)(t1 < 0 ? -t1 % 10 : t1 % 10),
t2 / 10, (unsigned int)(t2 < 0 ? -t2 % 10 : t2 % 10),
(unsigned int)g_fan_data.rpm);
// 发布实时数据到状态主题
status = nxd_mqtt_client_publish(&mqtt_client, FAN_STATE_TOPIC, strlen(FAN_STATE_TOPIC),
mqtt_payload, strlen(mqtt_payload),
NX_FALSE, 1, 100);
if (status != NX_SUCCESS) break; // 发布失败(断线),退出内循环重连
}
}
}
tx_thread_sleep(500); // 断线重连等待
}
}

883

被折叠的 条评论
为什么被折叠?



