自制ESP32设备通过MQTT集成至HomeAssistant
前言:
本文档记录将基于MQTT的ESP32自定义设备接入HomeAssistant,实现属性上报和远程控制。
一、硬件介绍
如下图,这是基于ESP32,MQ-2,BH1750,BME280,断路器和四路继电器的物联网模拟装置,用于模拟物联网设备。
可以采集温度、湿度、气压、烟感和控制四路继电器和一路断路器。
它上传数据的格式是这样的,顾名思义,这里就不详细介绍:
json
{
"temperature": 18.59,
"humidity": 47.14,
"pressure": 1038.11,
"lightIntensity": 66.67,
"smokeDensity": 0.15,
"relayStatus_1": 1,
"relayStatus_2": 0,
"relayStatus_3": 0,
"relayStatus_4": 1,
"circuitBreakerStatus": 1
}
并且这个设备还可以接收MQTT消息,用来控制四路继电器和断路器,其接收数据的格式为:
json
{
"dalay_1": 1,
"dalay_2": 0,
"dalay_3": 0,
"dalay_4": 1,
"circuit_breaker":1
}
其中,控制项可缺省,比如这样
json
{
"dalay_1": 1,
"dalay_2": 0,
}
也是可以的。
硬件模拟程序
考虑到大家并没有具体的硬件设备,这里我提供一个用于模拟上述装置的ESP32程序,不依靠其他硬件,仅使用程序模拟采集数据(使用虚拟随机值),和被控制的过程(通过命令行打印控制信息)不需要连接硬件,只需要任意的开发板即可。
模拟程序: https://github.com/XAUT-WisdomLab/ESP32_MQTT_HA_Demo
程序主文件代码如下:
c
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include <sys/socket.h>
#include "esp_eth.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "mqtt_client.h"
#include "cJSON.h"
// Wi-Fi配置
#define ESP_WIFI_STA_SSID CONFIG_ESP_WIFI_STA_SSID
#define ESP_WIFI_STA_PASSWD CONFIG_ESP_WIFI_STA_PASSWD
// MQTT配置
#define MQTT_CLIENT_ID "ESP32-%s"
#define MQTT_USERNAME "ESP32-%s"
#define MQTT_PASSWD "ESP32-%s"
#define MQTT_HOST_URL CONFIG_MQTT_HOST_URL
// MQTT主题
#define MQTT_STATE_TOPIC_FMT "home/device/%s/state"
#define MQTT_COMMAND_TOPIC_FMT "home/device/%s/set"
// 设备ID
char DEVICE_ID[24] = "";
static const char *TAG = "app";
// 定义一个结构体来存储MQTT主题
typedef struct
{
char client_id[64];
char username[64];
char passwd[64];
char state_topic[64];
char command_topic[64];
} MqttConfig;
// 全局变量
MqttConfig mqtt_configs;
// MQTT客户端句柄
esp_mqtt_client_handle_t mqtt_client;
// 定义传感器数据结构体
struct Data
{
float temperature; // 当前环境温度(摄氏度)
float humidity; // 当前环境湿度(百分比)
float pressure; // 当前环境气压(帕斯卡)
float lightIntensity; // 当前光照强度(勒克斯)
float smokeDensity; // 当前烟雾浓度(PPM)
char relayStatus[4]; // 当前继电器状态(开或关)
char circuitBreakerStatus; // 当前断路器状态(开或关)
};
// 初始化数据结构实例
struct Data systemData = {
.temperature = 25.0,
.humidity = 50.0,
.pressure = 101325.0,
.lightIntensity = 300,
.smokeDensity = 0,
.relayStatus = {0, 0, 0, 0},
.circuitBreakerStatus = 0};
// 继电器状态(从继电器模块获取的全局变量)
bool relay_state[4] = {0, 0, 0, 0};
// 断路器状态(全局变量)
bool circuit_breaker_state = 0; // 初始状态为关闭
// 生成随机浮点数
float random_float(float min, float max)
{
return min + (esp_random() % 1000) / 1000.0f * (max - min);
}
// 设置MQTT配置
void setup_mqtt_configs(MqttConfig *configs, const char *id)
{
assert(configs != NULL);
assert(id != NULL);
snprintf(configs->client_id, sizeof(configs->client_id), MQTT_CLIENT_ID, id);
snprintf(configs->username, sizeof(configs->username), MQTT_USERNAME, id);
snprintf(configs->passwd, sizeof(configs->passwd), MQTT_PASSWD, id);
snprintf(configs->state_topic, sizeof(configs->state_topic), MQTT_STATE_TOPIC_FMT, id);
snprintf(configs->command_topic, sizeof(configs->command_topic), MQTT_COMMAND_TOPIC_FMT, id);
}
// 设备注册
static void mqtt_app_register()
{
// 设备标识符
char device_identifier[64];
snprintf(device_identifier, sizeof(device_identifier), "device_%s", DEVICE_ID);
// 设备信息
cJSON *device = cJSON_CreateObject();
cJSON_AddStringToObject(device, "name", "综合环境监测控制设备");
cJSON_AddStringToObject(device, "model", "6S8Y91");
cJSON_AddStringToObject(device, "manufacturer", "若甫科技有限公司");
cJSON *identifiers = cJSON_CreateArray();
cJSON_AddItemToArray(identifiers, cJSON_CreateString(device_identifier));
cJSON_AddItemToObject(device, "identifiers", identifiers);
// 传感器注册
const char *sensor_names[] = {"温度", "湿度", "气压", "光照", "烟感"};
const char *sensor_key[] = {"temperature", "humidity", "pressure", "lightIntensity", "smokeDensity"};
const char *sensor_units[] = {"°C", "%", "hPa", "lx", "ppm"};
for (int i = 0; i < 5; i++)
{
cJSON *config = cJSON_CreateObject();
char unique_id[64], name[64], state_topic[128], value_template[128];
snprintf(unique_id, sizeof(unique_id), "device_%s_sensor%d", DEVICE_ID, i);
snprintf(name, sizeof(name), "%s", sensor_names[i]);
snprintf(state_topic, sizeof(state_topic), "home/device/%s/state", DEVICE_ID);
snprintf(value_template, sizeof(value_template), "{{ value_json.%s }}", sensor_key[i]);
cJSON_AddStringToObject(config, "name", name);
cJSON_AddStringToObject(config, "unique_id", unique_id);
cJSON_AddStringToObject(config, "state_topic", state_topic);
cJSON_AddStringToObject(config, "value_template", value_template);
cJSON_AddStringToObject(config, "unit_of_measurement", sensor_units[i]);
cJSON_AddItemToObject(config, "device", cJSON_Duplicate(device, 1));
// 发布注册消息
char register_topic[128];
snprintf(register_topic, sizeof(register_topic), "homeassistant/sensor/%s/config", unique_id);
char *config_str = cJSON_PrintUnformatted(config);
esp_mqtt_client_publish(mqtt_client, register_topic, config_str, 0, 1, 0);
ESP_LOGI(TAG, "Register command: %s", config_str);
cJSON_free(config_str);
cJSON_Delete(config);
}
// 继电器注册
for (int i = 1; i <= 4; i++)
{
cJSON *config = cJSON_CreateObject();
char unique_id[64], name[64], state_topic[128], command_topic[128], value_template[128], command_template[128];
snprintf(unique_id, sizeof(unique_id), "device_%s_relay_%d", DEVICE_ID, i);
snprintf(name, sizeof(name), "继电器 %d", i);
snprintf(state_topic, sizeof(state_topic), "home/device/%s/state", DEVICE_ID);
snprintf(command_topic, sizeof(command_topic), "home/device/%s/set", DEVICE_ID);
snprintf(value_template, sizeof(value_template), "{{ value_json.relayStatus_%d }}", i);
snprintf(command_template, sizeof(command_template), "{ \"dalay_%d\": {{ value }} }", i);
cJSON_AddStringToObject(config, "name", name);
cJSON_AddStringToObject(config, "unique_id", unique_id);
cJSON_AddStringToObject(config, "state_topic", state_topic);
cJSON_AddStringToObject(config, "command_topic", command_topic);
cJSON_AddStringToObject(config, "value_template", value_template);
cJSON_AddStringToObject(config, "command_template", command_template);
cJSON_AddNumberToObject(config, "payload_on", 1);
cJSON_AddNumberToObject(config, "payload_off", 0);
cJSON_AddItemToObject(config, "device", cJSON_Duplicate(device, 1));
// 发布注册消息
char register_topic[128];
snprintf(register_topic, sizeof(register_topic), "homeassistant/switch/%s/config", unique_id);
char *config_str = cJSON_PrintUnformatted(config);
esp_mqtt_client_publish(mqtt_client, register_topic, config_str, 0, 1, 0);
ESP_LOGI(TAG, "Register command: %s", config_str);
cJSON_free(config_str);
cJSON_Delete(config);
}
// 断路器注册
cJSON *cb_config = cJSON_CreateObject();
char cb_unique_id[64], cb_state_topic[128], cb_command_topic[128];
snprintf(cb_unique_id, sizeof(cb_unique_id), "home/device_%s_circuit_breaker", DEVICE_ID);
snprintf(cb_state_topic, sizeof(cb_state_topic), "home/device/%s/state", DEVICE_ID);
snprintf(cb_command_topic, sizeof(cb_command_topic), "home/device/%s/set", DEVICE_ID);
cJSON_AddStringToObject(cb_config, "name", "断路器");
cJSON_AddStringToObject(cb_config, "unique_id", cb_unique_id);
cJSON_AddStringToObject(cb_config, "state_topic", cb_state_topic);
cJSON_AddStringToObject(cb_config, "command_topic", cb_command_topic);
cJSON_AddStringToObject(cb_config, "value_template", "{{ value_json.circuitBreakerStatus }}");
cJSON_AddStringToObject(cb_config, "command_template", "{ \"circuit_breaker\": {{ value }} }");
cJSON_AddNumberToObject(cb_config, "payload_on", 1);
cJSON_AddNumberToObject(cb_config, "payload_off", 0);
cJSON_AddItemToObject(cb_config, "device", cJSON_Duplicate(device, 1));
char cb_register_topic[128];
snprintf(cb_register_topic, sizeof(cb_register_topic), "homeassistant/switch/%s/config", cb_unique_id);
char *cb_config_str = cJSON_PrintUnformatted(cb_config);
esp_mqtt_client_publish(mqtt_client, cb_register_topic, cb_config_str, 0, 1, 0);
ESP_LOGI(TAG, "Register command: %s", cb_config_str);
cJSON_free(cb_config_str);
cJSON_Delete(cb_config);
// 释放设备对象
cJSON_Delete(device);
}
// 发送JSON数据
void send_json(struct Data *data)
{
// 创建cJSON根对象
cJSON *root = cJSON_CreateObject();
if (root == NULL)
{
ESP_LOGE(TAG, "Failed to create JSON object");
return;
}
char buffer[16]; // 缓冲区用于格式化字符串
// 处理并格式化值为两位小数
snprintf(buffer, sizeof(buffer), "%.2f", data->temperature);
cJSON_AddStringToObject(root, "temperature", buffer);
snprintf(buffer, sizeof(buffer), "%.2f", data->humidity);
cJSON_AddStringToObject(root, "humidity", buffer);
snprintf(buffer, sizeof(buffer), "%.2f", data->pressure);
cJSON_AddStringToObject(root, "pressure", buffer);
snprintf(buffer, sizeof(buffer), "%.2f", data->lightIntensity);
cJSON_AddStringToObject(root, "lightIntensity", buffer);
snprintf(buffer, sizeof(buffer), "%.2f", data->smokeDensity);
cJSON_AddStringToObject(root, "smokeDensity", buffer);
// 继电器状态
for (int i = 0; i < 4; ++i)
{
char relayKey[16]; // 生成"relayStatus_1", "relayStatus_2"等
sprintf(relayKey, "relayStatus_%d", i + 1);
cJSON_AddNumberToObject(root, relayKey, relay_state[i]);
}
// 断路器状态
cJSON_AddNumberToObject(root, "circuitBreakerStatus", data->circuitBreakerStatus ? 1 : 0);
// 将JSON对象转换为字符串
char *json_str = cJSON_Print(root);
if (json_str == NULL)
{
ESP_LOGE(TAG, "Failed to print JSON object");
cJSON_Delete(root);
return;
}
// 打印JSON字符串
ESP_LOGI(TAG, "JSON: %s", json_str);
// 发送JSON字符串
esp_mqtt_client_publish(mqtt_client, mqtt_configs.state_topic, json_str, 0, 1, 0);
// 释放资源
cJSON_Delete(root);
free(json_str);
}
// 记录非零错误
static void log_error_if_nonzero(const char *message, int error_code)
{
if (error_code != 0)
{
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
}
}
// 解析并打印JSON数据
void parse_and_print_json(const char *json_str)
{
// 解析JSON字符串
cJSON *json = cJSON_Parse(json_str);
if (json == NULL)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL)
{
fprintf(stderr, "Error before: %s\n", error_ptr);
}
return;
}
// 处理继电器状态
for (int i = 0; i < 4; i++)
{
char key[10];
snprintf(key, sizeof(key), "dalay_%d", i + 1); // 构造键名"dalay_1"至"dalay_4"
cJSON *dalay = cJSON_GetObjectItemCaseSensitive(json, key);
if (dalay && cJSON_IsNumber(dalay))
{
relay_state[i] = dalay->valueint;
ESP_LOGI(TAG, "Relay %d 被控制,当前状态: %s", i + 1, relay_state[i] ? "开" : "关");
}
}
// 处理断路器状态
cJSON *circuit_breaker = cJSON_GetObjectItemCaseSensitive(json, "circuit_breaker");
if (circuit_breaker && cJSON_IsNumber(circuit_breaker))
{
circuit_breaker_state = circuit_breaker->valueint;
ESP_LOGI(TAG, "断路器被控制,当前状态: %s", circuit_breaker_state ? "开" : "关");
}
// 释放JSON解析对象
cJSON_Delete(json);
}
// MQTT客户端事件处理函数
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
esp_mqtt_event_handle_t event = event_data;
esp_mqtt_client_handle_t client = event->client;
int msg_id;
switch ((esp_mqtt_event_id_t)event_id)
{
// MQTT连接
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
// 发送注册消息
mqtt_app_register();
// 订阅控制主题
msg_id = esp_mqtt_client_subscribe(client, mqtt_configs.command_topic, 0);
ESP_LOGI(TAG, "Sent subscribe successful, msg_id=%d", msg_id);
break;
// MQTT断开连接
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
// MQTT已订阅
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
break;
// MQTT取消订阅
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
// MQTT已发布
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
// MQTT数据接收
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
// 确保数据是以null结尾的字符串
char *json_data = (char *)malloc(event->data_len + 1);
if (json_data == NULL)
{
ESP_LOGE(TAG, "Failed to allocate memory for JSON data");
break;
}
memcpy(json_data, event->data, event->data_len);
json_data[event->data_len] = '\0'; // 添加字符串终止符
parse_and_print_json(json_data);
// 读取断路器状态
systemData.circuitBreakerStatus = circuit_breaker_state;
// 打包并发送数据
send_json(&systemData);
free(json_data);
break;
// MQTT错误
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
{
log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
}
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
}
// 启动MQTT客户端
static void mqtt_app_start(void)
{
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = MQTT_HOST_URL,
.credentials.client_id = mqtt_configs.client_id,
.credentials.username = mqtt_configs.username,
.credentials.authentication.password = mqtt_configs.passwd,
};
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
// 注册事件处理函数
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
// 启动MQTT客户端
esp_mqtt_client_start(mqtt_client);
}
// Wi-Fi事件回调函数
void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
static uint8_t connect_count = 0;
// Wi-Fi启动事件
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
ESP_ERROR_CHECK(esp_wifi_connect());
}
// Wi-Fi断开事件
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
connect_count++;
if (connect_count < 6)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_ERROR_CHECK(esp_wifi_connect());
}
else
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
}
}
// Wi-Fi获取IP事件
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
// 创建MQTT客户端
mqtt_app_start();
}
}
// Wi-Fi初始化
static void wifi_sta_init(void)
{
ESP_ERROR_CHECK(esp_netif_init());
// 注册Wi-Fi启动事件
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
// 注册Wi-Fi断开事件
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
// 注册IP获取事件
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
// 初始化STA设备
esp_netif_create_default_wifi_sta();
// 初始化Wi-Fi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 配置STA设备
wifi_config_t sta_config = {
.sta = {
.ssid = ESP_WIFI_STA_SSID,
.password = ESP_WIFI_STA_PASSWD,
.bssid_set = false,
},
};
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
// 启动Wi-Fi
ESP_ERROR_CHECK(esp_wifi_start());
// 配置省电模式
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
}
void app_main(void)
{
ESP_LOGI(TAG, "启动...");
// 初始化NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 创建默认事件循环
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 配置并启动Wi-Fi
wifi_sta_init();
// 生成设备ID
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
snprintf(DEVICE_ID, sizeof(DEVICE_ID), "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
ESP_LOGI(TAG, "设备ID: %s", DEVICE_ID);
// 设置MQTT主题
setup_mqtt_configs(&mqtt_configs, DEVICE_ID);
// 打印初始化后的结构体属性
ESP_LOGI(TAG, "温度: %.2f", systemData.temperature);
ESP_LOGI(TAG, "湿度: %.2f", systemData.humidity);
ESP_LOGI(TAG, "气压: %.2f", systemData.pressure);
ESP_LOGI(TAG, "光照强度: %.2f", systemData.lightIntensity);
ESP_LOGI(TAG, "烟雾浓度: %.2f", systemData.smokeDensity);
ESP_LOGI(TAG, "继电器状态: %s", systemData.relayStatus);
ESP_LOGI(TAG, "断路器状态: %c", systemData.circuitBreakerStatus);
// 模拟传感器初始化
ESP_LOGI(TAG, "MQ2 传感器初始化");
ESP_LOGI(TAG, "BH1750 传感器初始化");
ESP_LOGI(TAG, "BME280 传感器初始化");
ESP_LOGI(TAG, "继电器初始化");
ESP_LOGI(TAG, "断路器初始化");
for (;;)
{
// 数据采集延迟
vTaskDelay(pdMS_TO_TICKS(10000));
// 生成随机传感器数据
systemData.temperature = random_float(10.0f, 35.0f); // 温度范围 10°C ~ 35°C
systemData.humidity = random_float(30.0f, 90.0f); // 湿度范围 30% ~ 90%
systemData.pressure = random_float(950.0f, 1050.0f); // 气压范围 950 hPa ~ 1050 hPa
systemData.lightIntensity = random_float(0.0f, 1000.0f); // 光照强度范围 0 ~ 1000 lux
systemData.smokeDensity = random_float(0.0f, 100.0f); // 烟雾浓度范围 0 ~ 100 PPM
// 更新断路器状态
systemData.circuitBreakerStatus = circuit_breaker_state;
// 打包并发送数据
send_json(&systemData);
}
}
程序说明
该程序实现了一个综合环境监测控制设备。主要功能包括通过随机数模拟传感器数据采集,使用MQTT协议与服务器进行通信,并处理相关控制指令。以下是程序主要部分的解释。
- Wi-Fi配置:程序通过宏定义
ESP_WIFI_STA_SSID
和ESP_WIFI_STA_PASSWD
进行Wi-Fi配置。设备启动时会自动连接到指定的Wi-Fi网络。 - MQTT配置:MQTT配置包括客户端ID、用户名、密码以及服务器URL等信息。所有MQTT主题均由设备ID生成,确保唯一性。
- 设备ID生成:设备ID由设备的MAC地址生成,以确保每个设备都有唯一的标识符。
- 数据结构:定义了一个
Data
结构体来存储传感器数据,包括温度、湿度、气压、光照强度和烟雾浓度等。还包括继电器状态和断路器状态。 - 传感器数据生成:通过
random_float
函数生成随机浮点数来模拟传感器数据。数据范围设定在合理的范围内,比如温度范围10°C到35°C等。 - MQTT主题设置:通过
setup_mqtt_configs
函数设置MQTT主题,包括状态主题和控制主题。 - 设备注册:在
mqtt_app_register
函数中,通过MQTT协议将设备和传感器信息注册到Home Assistant。每个传感器和继电器都有唯一的主题和配置。 - 数据发送:通过
send_json
函数将传感器数据打包成JSON字符串并通过MQTT协议发送到服务器。 - 控制指令处理:在
parse_and_print_json
函数中解析接收到的JSON指令,更新继电器和断路器的状态,并在命令行打印控制信息。 - MQTT事件处理:在
mqtt_event_handler
函数中处理各种MQTT事件,包括连接、断开、订阅、发布和接收数据等。 - Wi-Fi事件处理:在
WIFI_CallBack
函数中处理各种Wi-Fi事件,包括连接、断开和获取IP等。 - 主程序:在
app_main
函数中,初始化NVS、创建默认事件循环、配置并启动Wi-Fi,生成设备ID,设置MQTT主题,并循环生成随机传感器数据并发送。
程序使用:
- 下载代码到本地
- 运行
idf.py menuconfig
命令,添加自己的WiFi连接信息,和HomeAssistant
的MQTT代理服务地址(需要在HomeAssistant上配置MQTT集成),如下:
- 此时
HomeAssistant
上就有了我们的模拟设备:
在平台控制设备,也能在设备端看到对应的控制命令:
与HA平台的通信与控制(MQTT)
这部分比较简单,我下面只放一下通信主题和消息的设置,其他的请参考代码进行理解:
设备注册
主题:
homeassistant/sensor/{device_id}_temperature/config
homeassistant/sensor/{device_id}_humidity/config
homeassistant/sensor/{device_id}_pressure/config
homeassistant/sensor/{device_id}_lightIntensity/config
homeassistant/sensor/{device_id}_smokeDensity/config
homeassistant/switch/{device_id}_relay_1/config
homeassistant/switch/{device_id}_relay_2/config
homeassistant/switch/{device_id}_relay_3/config
homeassistant/switch/{device_id}_relay_4/config
homeassistant/switch/{device_id}_circuit_breaker/config
注意: 每个实体的 config 主题必须唯一,否则 Home Assistant 无法正确区分它们。因此这里把设备分为了多个实体。
内容:
json
[
{
"name": "{device_name}-温度",
"unique_id": "{device_id}_temperature",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"value_template": "{{ value_json.temperature }}",
"unit_of_measurement": "°C"
},
{
"name": "{device_name}-湿度",
"unique_id": "{device_id}_humidity",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"value_template": "{{ value_json.humidity }}",
"unit_of_measurement": "%"
},
{
"name": "{device_name}-气压",
"unique_id": "{device_id}_pressure",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"value_template": "{{ value_json.pressure }}",
"unit_of_measurement": "hPa"
},
{
"name": "{device_name}-光照强度",
"unique_id": "{device_id}_lightIntensity",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"value_template": "{{ value_json.lightIntensity }}",
"unit_of_measurement": "lx"
},
{
"name": "{device_name}-烟雾浓度",
"unique_id": "{device_id}_smokeDensity",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"value_template": "{{ value_json.smokeDensity }}",
"unit_of_measurement": "ppm"
},
{
"name": "{device_name}-继电器-1",
"unique_id": "{device_id}_relay_1",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"command_topic": "home/device/{device_id}/set",
"payload_on": 1,
"payload_off": 0,
"value_template": "{{ value_json.relayStatus_1 }}",
"command_template": "{ \"relay_1\": {{ value }} }"
},
{
"name": "{device_name}-继电器-2",
"unique_id": "{device_id}_relay_2",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"command_topic": "home/device/{device_id}/set",
"payload_on": 1,
"payload_off": 0,
"value_template": "{{ value_json.relayStatus_2 }}",
"command_template": "{ \"relay_2\": {{ value }} }"
},
{
"name": "{device_name}-继电器-3",
"unique_id": "{device_id}_relay_3",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"command_topic": "home/device/{device_id}/set",
"payload_on": 1,
"payload_off": 0,
"value_template": "{{ value_json.relayStatus_3 }}",
"command_template": "{ \"relay_3\": {{ value }} }"
},
{
"name": "{device_name}-继电器-4",
"unique_id": "{device_id}_relay_4",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"command_topic": "home/device/{device_id}/set",
"payload_on": 1,
"payload_off": 0,
"value_template": "{{ value_json.relayStatus_4 }}",
"command_template": "{ \"relay_4\": {{ value }} }"
},
{
"name": "{device_name}-断路器",
"unique_id": "{device_id}_circuit_breaker",
"device": {
"name": "{device_name}",
"model": "{device_model}",
"manufacturer": "{device_manufacturer}",
"identifiers": ["{device_id}"]
},
"state_topic": "home/device/{device_id}/state",
"command_topic": "home/device/{device_id}/set",
"payload_on": 1,
"payload_off": 0,
"value_template": "{{ value_json.circuitBreakerStatus }}",
"command_template": "{ \"circuit_breaker\": {{ value }} }"
}
]
上报状态
主题: "home/device/{device_id}/state"
其中 device_id
为设备ID 唯一标识
负载:
json
{
"temperature": "25.00",
"humidity": "50.00",
"pressure": "101325.00",
"lightIntensity": "300.00",
"smokeDensity": "0.00",
"relayStatus_1": 0,
"relayStatus_2": 0,
"relayStatus_3": 0,
"relayStatus_4": 0,
"circuitBreakerStatus": 0
}
控制设备
主题: home/device/{device_id}/set
其中 device_id
为设备ID 唯一标识
负载:
json
{
"relay_1": 1,
"relay_2": 0,
"relay_3": 1,
"relay_4": 0,
"circuit_breaker": 1
}