当前位置: 首页 > news >正文

智能灯集成自动控制

智能灯集成自动控制

准备材料

  1. 一个esp32芯片
  2. 一个LED灯
  3. 一个三键开关
  4. 一个亮度传感器
  5. 一个红外热释电传感器
  6. 杜邦线
  7. 一个面包板

使用软件

  1. Visual Studio Code
  2. MQTTX
  3. MQTT服务器
  4. VSCode安装ESP-idf插件,最新的2.0以上版本存在BUG,最好使用2.0以下版本(占用30-40G)
  5. Everything(搜索文件位置)

接线

  1. VIN接面包板的+那一排
  2. GND接面包板的-那一排
  3. 3V3接面包板的+那一排(不能与VIN同一排)
  4. LED灯GND接面包板的GND,R接ESP32的D5
  5. 开关的KOOM接面板板的GND,PLAY接ESP32的D4
  6. 光敏传感器AO接D32,VCC接面包板的3V3(接VIN可能烧坏),GND接面包板的GND
  7. 红外热释电传感器OUT接D12,VCC接面包板的3V3,GND接面包板的GND

VSCode添加文件夹

  1. 使用Everything,搜索\tcp\,使用VScode打开
    image
  2. 使用Everything,搜索\BLINK\,使用VScode新窗口打开文件夹
    image
  3. 使用Everything,搜索\adc\,使用VScode新窗口打开oneshot_read文件夹
    image

1.编写开关控制LED灯的代码

  • 在Blank的main的blink_example_main.c的app_main内编写如下代码
void app_main()
{// 配置 LED 引脚 GPIO 5gpio_reset_pin(5);gpio_set_direction(5, GPIO_MODE_OUTPUT);// 配置 开关 引脚 GPIO 4gpio_reset_pin(4);gpio_set_direction(4, GPIO_MODE_INPUT);// 添加布尔值进行判断本次是否完成状态切换bool enable = true;// LED初始状态int i = 0;while (1){// 读取开关状态int switch_state = gpio_get_level(4);// 开关按下时成立,切换高低电平if (switch_state == 0 && enable) {enable = false;i = !i;gpio_set_level(5, i);  // 设置 LED 状态printf("开关按下\n");printf("开关状态: %d\n", switch_state);printf("LED状态: %d\n", i);printf("enable状态: %d\n", enable);}// 开关释放时,让开关按下的条件成立if (switch_state == 1 && !enable) {enable = true;printf("开关释放\n");printf("enable状态: %d\n", enable);}// 延时100msvTaskDelay(100 / portTICK_PERIOD_MS);}
}
  • 构建,烧录,监视设备
    image
    image

2. 编程MQTT控制LED灯的代码

  • 配置Broker URL、WiFi SSID、WiFi Password(网络需要2.4GHz)
    image
  • 在Blank的main的app_main内编写如下代码
static const char *TAG = "mqtt_example";
bool first_link = true;
esp_mqtt_client_handle_t public_client = NULL;
// LED初始状态
int i = 0;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;public_client = client;int msg_id;switch ((esp_mqtt_event_id_t)event_id) {case MQTT_EVENT_CONNECTED:// MQTT服务器连接成功后触发ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");// 连接成功后订阅主题 /topic/qos0,订阅成功后可以接受到该主题的消息msg_id = esp_mqtt_client_subscribe(client, "/topic/qos100", 0);ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);first_link = false;break;case MQTT_EVENT_DISCONNECTED:ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");first_link = true;break;case MQTT_EVENT_SUBSCRIBED:// 订阅主题成功后触发ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);msg_id = esp_mqtt_client_publish(client, "/topic/qos100", "订阅成功,开始接收消息", 0, 0, 0);ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);break;case MQTT_EVENT_UNSUBSCRIBED:ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);break;case MQTT_EVENT_PUBLISHED:ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);break;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);// 将接收到的消息转换为字符串进行处理char* json = event->data;cJSON* root = cJSON_Parse(json);if (root == NULL) {printf("JSON解析失败\n");return;}cJSON* command = cJSON_GetObjectItem(root, "command");if (command == NULL) {printf("JSON解析失败\n");return;}cJSON* params = cJSON_GetObjectItem(root, "params");if (params == NULL) {printf("JSON解析失败\n");return;}cJSON* lightId = cJSON_GetObjectItem(params, "lightId");if (lightId == NULL) {printf("JSON解析失败\n");return;}cJSON* status = cJSON_GetObjectItem(params, "status");if (status == NULL) {printf("JSON解析失败\n");return;}cJSON* data = cJSON_GetObjectItem(params, "data");if (data == NULL) {printf("JSON解析失败\n");return;}if (strstr(command->valuestring, "control") && strstr(lightId->valuestring, "001")&& strstr(status->valuestring, "on") && first_link == false) {i = 1;printf("接收到 'on' 命令,打开LED灯。\n");gpio_set_level(5, i); // 设置 LED 引脚为高电平,打开 LED 灯cJSON_ReplaceItemInObject(params, "data", cJSON_CreateString("LED set success, status is ON"));char *json_to_send = cJSON_PrintUnformatted(root); msg_id = esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);}if (strstr(command->valuestring, "control") && strstr(lightId->valuestring, "001") && strstr(status->valuestring, "off") && first_link == false) {i = 0;printf("接收到 'off' 命令,关闭LED灯。\n");gpio_set_level(5, i); // 设置 LED 引脚为低电平,关闭 LED 灯cJSON_ReplaceItemInObject(params, "data", cJSON_CreateString("LED set success, status is OFF"));char *json_to_send = cJSON_PrintUnformatted(root); msg_id = esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);}cJSON_Delete(root);break;case MQTT_EVENT_ERROR:ESP_LOGI(TAG, "MQTT_EVENT_ERROR");if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {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;}
}static void mqtt_app_start(void)
{esp_mqtt_client_config_t mqtt_cfg = {.broker.address.uri = CONFIG_BROKER_URL,};
#if CONFIG_BROKER_URL_FROM_STDINchar line[128];if (strcmp(mqtt_cfg.broker.address.uri, "FROM_STDIN") == 0) {int count = 0;printf("Please enter url of mqtt broker\n");while (count < 128) {int c = fgetc(stdin);if (c == '\n') {line[count] = '\0';break;} else if (c > 0 && c < 127) {line[count] = c;++count;}vTaskDelay(10 / portTICK_PERIOD_MS);}mqtt_cfg.broker.address.uri = line;printf("Broker url: %s\n", line);} else {ESP_LOGE(TAG, "Configuration mismatch: wrong broker url");abort();}
#endif /* CONFIG_BROKER_URL_FROM_STDIN */esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);/* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);esp_mqtt_client_start(client);
}void app_main(void)
{ESP_LOGI(TAG, "[APP] Startup..");ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());esp_log_level_set("*", ESP_LOG_INFO);esp_log_level_set("mqtt_client", ESP_LOG_VERBOSE);esp_log_level_set("mqtt_example", ESP_LOG_VERBOSE);esp_log_level_set("transport_base", ESP_LOG_VERBOSE);esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);esp_log_level_set("transport", ESP_LOG_VERBOSE);esp_log_level_set("outbox", ESP_LOG_VERBOSE);ESP_ERROR_CHECK(nvs_flash_init());ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.* Read "Establishing Wi-Fi or Ethernet Connection" section in* examples/protocols/README.md for more information about this function.*/ESP_ERROR_CHECK(example_connect());mqtt_app_start();// 配置 LED 引脚 GPIO 5gpio_reset_pin(5);gpio_set_direction(5, GPIO_MODE_OUTPUT);// 配置 开关 引脚 GPIO 4gpio_reset_pin(4);gpio_set_direction(4, GPIO_MODE_INPUT);// 添加布尔值进行判断本次是否完成状态切换bool enable = true;while (1){// 读取开关状态int switch_state = gpio_get_level(4);// 开关按下时成立,切换高低电平if (switch_state == 0 && enable) {enable = false;i = !i;gpio_set_level(5, i);  // 设置 LED 状态printf("开关按下\n");printf("开关状态: %d\n", switch_state);printf("LED状态: %d\n", i);printf("enable状态: %d\n", enable);int msg_id;if (i == 1){cJSON* root = cJSON_CreateObject();cJSON_AddStringToObject(root, "command", "control");cJSON* params = cJSON_CreateObject();cJSON_AddItemToObject(root, "params", params);cJSON_AddStringToObject(params, "lightId", "001");cJSON_AddStringToObject(params, "status", "on");cJSON_AddStringToObject(params, "data", "LED is ON");char *json_to_send = cJSON_PrintUnformatted(root); msg_id = esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);cJSON_Delete(root);} else if (i == 0){cJSON* root = cJSON_CreateObject();cJSON_AddStringToObject(root, "command", "control");cJSON* params = cJSON_CreateObject();cJSON_AddItemToObject(root, "params", params);cJSON_AddStringToObject(params, "lightId", "001");cJSON_AddStringToObject(params, "status", "off");cJSON_AddStringToObject(params, "data", "LED is OFF");char *json_to_send = cJSON_PrintUnformatted(root);msg_id = esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);cJSON_Delete(root);}}// 开关释放时,让开关按下的条件成立if (switch_state == 1 && !enable) {enable = true;printf("开关释放\n");printf("enable状态: %d\n", enable);}// 延时100msvTaskDelay(100 / portTICK_PERIOD_MS);}
}
  • 构建、编译。烧录
  • mqttx订阅2个主题
    image
  • mqttx向主题qos100发送JSON格式数据
    image
  • LED灯完成开关,并返回JSON格式数据
    image

3. 编写光敏传感器调用代码

  • 在oneshot_read文件下的main内复制oneshot_read_main到main下,并重命名为oneshot_read_simple
    image
  • 修改main下的CmakeLists,让srcs后面仅有oneshot_read_simple
    image
  • 对oneshot_read_simple进行修改
// 修改标签
const static char *TAG = "LIGHT_SENSOR";// ADC第4通道对应ESP32的GPIO32引脚(D4)
#define ADC1_CHAN0 ADC_CHANNEL_4
// ADC衰减12dB,表示输入电压范围为0-3.3V
#define ADC_ATTEN ADC_ATTEN_DB_12// 定义句柄,之后使用句柄去自动选择操作对象
adc_oneshot_unit_handle_t adc1_handle;
// 设置校准算法的上下文环境初始为空
adc_cali_handle_t adc_cali_handle = NULL;
// 定义一个标志位,表示是否成功启用校准
static bool do_calibration = false;// 定义取平均值,减少误差
int get_light_average(void)
{int sum = 0;int raw_val = 0;for (int i = 0; i < 100; i++) {// 使用adc_oneshot_read函数读取ADC值,传入句柄、通道和存储原始值的变量地址ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, ADC1_CHAN0, &raw_val));sum += raw_val;}return sum / 100;
}// 校准初始化函数(原本就有,仅修改名称)
static bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{adc_cali_handle_t handle = NULL;esp_err_t ret = ESP_FAIL;bool calibrated = false;#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTEDif (!calibrated) {ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");adc_cali_curve_fitting_config_t cali_config = {.unit_id = unit,.chan = channel,.atten = atten,.bitwidth = ADC_BITWIDTH_DEFAULT,};ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);if (ret == ESP_OK) {calibrated = true;}}
#endif#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTEDif (!calibrated) {ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");adc_cali_line_fitting_config_t cali_config = {.unit_id = unit,.atten = atten,.bitwidth = ADC_BITWIDTH_DEFAULT,};ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);if (ret == ESP_OK) {calibrated = true;}}
#endif*out_handle = handle;if (ret == ESP_OK) {ESP_LOGI(TAG, "Calibration Success");} else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");} else {ESP_LOGE(TAG, "Invalid arg or no memory");}return calibrated;
}void app_main(void)
{//-------------ADC1 初始化---------------//// 创建并初始化 ADC 控制器的句柄adc_oneshot_unit_init_cfg_t config = {// 配置1号模块(ADC1).unit_id = ADC_UNIT_1,};// 根据config对adc1_handle进行初始化ESP_ERROR_CHECK(adc_oneshot_new_unit(&config, &adc1_handle));//-------------ADC1 通道配置---------------//// 配置引脚通道信息adc_oneshot_chan_cfg_t adc_config = {// 设置ADC的位宽为默认值(12位).bitwidth = ADC_BITWIDTH_DEFAULT,// 配置引脚为衰减12dB,表示输入电压范围为0-3.3V.atten = ADC_ATTEN,};// 使用adc1_handle获取权限,根据adc_config配置ADC1的第4通道(GPIO32)ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, ADC1_CHAN0, &adc_config));//-------------ADC1 校准初始化---------------//// 传入参数:模块、引脚通道、衰减和校准句柄地址,返回是否成功启用校准do_calibration = adc_calibration_init(ADC_UNIT_1, ADC1_CHAN0, ADC_ATTEN, &adc_cali_handle);// 定义平均值和校准后的值int avg_val = 0;int voltage = 0;while (1) {// 调用函数,获取100次采样的平均值avg_val = get_light_average();// 打印平均值ESP_LOGI(TAG, "100次采样 ————》avg_val: %d", avg_val);// 如果成功启用校准,则将平均值转换为电压值并打印if (do_calibration) {// 使用adc_cali_handle将平均值转换为校准后的值ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle, avg_val, &voltage));// 打印校准后的值ESP_LOGI(TAG, "校准后的电压 ————》voltage: %d mV", voltage);}// 每隔5秒钟输出一次vTaskDelay(5000 / portTICK_PERIOD_MS);}}

4. 编写光敏传感器结合红外热释电传感器代理

  • 复制oneshot_read_simple重命名为auto_light_task
    image
  • 修改CmakeLists
    image
  • 编写代码
/** SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD** SPDX-License-Identifier: Apache-2.0*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_caps.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "driver/gpio.h"/*  光敏传感器   */
const static char *TAG = "AUTO_LIGHT";// ADC第4通道对应ESP32的GPIO32引脚(D4)
#define ADC1_CHAN0 ADC_CHANNEL_4
// ADC衰减12dB,表示输入电压范围为0-3.3V
#define ADC_ATTEN ADC_ATTEN_DB_12
// 定义暗的阈值
#define DARK_THRESHOLD   2700// 定义句柄,之后使用句柄去自动选择操作对象
adc_oneshot_unit_handle_t adc1_handle;
// 设置校准算法的上下文环境初始为空
adc_cali_handle_t adc_cali_handle = NULL;
// 定义一个标志位,表示是否成功启用校准
static bool do_calibration = false;int get_light_average(void)
{int sum = 0;int raw_val = 0;for (int i = 0; i < 100; i++) {ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, ADC1_CHAN0, &raw_val));sum += raw_val;}return sum / 100;
}// 校准初始化函数
static bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{adc_cali_handle_t handle = NULL;esp_err_t ret = ESP_FAIL;bool calibrated = false;#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTEDif (!calibrated) {ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");adc_cali_curve_fitting_config_t cali_config = {.unit_id = unit,.chan = channel,.atten = atten,.bitwidth = ADC_BITWIDTH_DEFAULT,};ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);if (ret == ESP_OK) {calibrated = true;}}
#endif#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTEDif (!calibrated) {ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");adc_cali_line_fitting_config_t cali_config = {.unit_id = unit,.atten = atten,.bitwidth = ADC_BITWIDTH_DEFAULT,};ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);if (ret == ESP_OK) {calibrated = true;}}
#endif*out_handle = handle;if (ret == ESP_OK) {ESP_LOGI(TAG, "Calibration Success");} else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");} else {ESP_LOGE(TAG, "Invalid arg or no memory");}return calibrated;
}/*  红外热释电传感器   */
#define PIR_PIN 12static void pir_gpio_init(void)
{gpio_config_t io_conf = {.intr_type = GPIO_INTR_DISABLE, // 禁止中断.mode = GPIO_MODE_INPUT,        // 设置为输入模式.pin_bit_mask = (1ULL << PIR_PIN), // 配置PIR_PIN引脚.pull_down_en = 0,              // 禁止下拉.pull_up_en = 0,                // 禁止上拉};gpio_config(&io_conf); // 初始化GPIO配置ESP_LOGI(TAG, "红外热释电传感器(GPIO12)初始化完成");
}/*  传感器驱动初始化   */
void sensor_driver_init(void) {ESP_LOGI(TAG, "开始初始化传感器驱动...");// 1. ADC1 初始化adc_oneshot_unit_init_cfg_t config = { .unit_id = ADC_UNIT_1 };ESP_ERROR_CHECK(adc_oneshot_new_unit(&config, &adc1_handle));// 2. ADC1 通道配置adc_oneshot_chan_cfg_t adc_config = { .bitwidth = ADC_BITWIDTH_DEFAULT, .atten = ADC_ATTEN };ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, ADC1_CHAN0, &adc_config));// 3. ADC1 校准初始化do_calibration = adc_calibration_init(ADC_UNIT_1, ADC1_CHAN0, ADC_ATTEN, &adc_cali_handle);// 4. PIR 初始化pir_gpio_init();ESP_LOGI(TAG, "所有传感器驱动初始化完成!");
}/*  获取传感器数据   */
void get_sensor_data(int *out_pir_state, int *out_light_adc, int *out_light_mv) {// 1. 获取红外热释电传感器状态*out_pir_state = gpio_get_level(PIR_PIN); // 2. 获取光照ADC平均值*out_light_adc = get_light_average();// 3. 获取校准后的电压(mV)if (do_calibration) {if (adc_cali_raw_to_voltage(adc_cali_handle, *out_light_adc, out_light_mv) != ESP_OK) {*out_light_mv = 0; // 如果转换失败,给个默认值0}} else {*out_light_mv = 0;}
}void auto_logic_task(void *pvParameters) {int pir_val, light_adc, light_mv;while (1) {// 获取数据get_sensor_data(&pir_val, &light_adc, &light_mv);// 逻辑判断if (pir_val == 1 && light_mv > DARK_THRESHOLD) {ESP_LOGI(TAG, ">>> 触发开灯! 有人(PIR:%d) 且 暗(ADC:%d / %dmV) <<<", pir_val, light_adc, light_mv);} else {ESP_LOGI(TAG, "未触发。PIR:%d, ADC:%d, mV:%d", pir_val, light_adc, light_mv);}vTaskDelay(5000 / portTICK_PERIOD_MS);}
}void app_main(void)
{sensor_driver_init();xTaskCreate(auto_logic_task, "auto_logic_task", 4096, NULL, 5, NULL);ESP_LOGI(TAG, "系统启动...");
}

5. 集成到TCP代码内

  • 复制auto_light_task到tcp/main文件夹下(app_main需要修改为其他名称)
    image
  • 编写auto_light_sensor.h头文件导出auto_light_task内的方法
#ifndef AUTO_LIGHT_SENSOR_H
#define AUTO_LIGHT_SENSOR_Hvoid sensor_driver_init(void);void get_sensor_data(int *out_pir_state, int *out_light_adc, int *out_light_mv);#define DARK_THRESHOLD   2700#endif
  • 在tcp/main/app_main内引入头文件
    #include "auto_light_task.h"

  • 集成后的代码

static const char *TAG = "mqtt_example";
bool first_link = true;
esp_mqtt_client_handle_t public_client = NULL;
// LED初始状态
int led_state = 0;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;public_client = client;int msg_id;switch ((esp_mqtt_event_id_t)event_id) {case MQTT_EVENT_CONNECTED:// MQTT服务器连接成功后触发ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");// 连接成功后订阅主题 /topic/qos0,订阅成功后可以接受到该主题的消息msg_id = esp_mqtt_client_subscribe(client, "/topic/qos100", 0);ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);first_link = false;break;case MQTT_EVENT_DISCONNECTED:ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");first_link = true;break;case MQTT_EVENT_SUBSCRIBED:// 订阅主题成功后触发ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);msg_id = esp_mqtt_client_publish(client, "/topic/qos100", "订阅成功,开始接收消息", 0, 0, 0);ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);break;case MQTT_EVENT_UNSUBSCRIBED:ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);break;case MQTT_EVENT_PUBLISHED:ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);break;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);// 将接收到的消息转换为字符串进行处理char* json = event->data;cJSON* root = cJSON_Parse(json);if (root == NULL) {printf("JSON解析失败\n");return;}cJSON* command = cJSON_GetObjectItem(root, "command");if (command == NULL) {printf("JSON解析失败\n");return;}cJSON* params = cJSON_GetObjectItem(root, "params");if (params == NULL) {printf("JSON解析失败\n");return;}cJSON* lightId = cJSON_GetObjectItem(params, "lightId");if (lightId == NULL) {printf("JSON解析失败\n");return;}cJSON* status = cJSON_GetObjectItem(params, "status");if (status == NULL) {printf("JSON解析失败\n");return;}cJSON* data = cJSON_GetObjectItem(params, "data");if (data == NULL) {printf("JSON解析失败\n");return;}if (strstr(command->valuestring, "control") && strstr(lightId->valuestring, "001")&& strstr(status->valuestring, "on") && first_link == false) {led_state = 1;printf("接收到 'on' 命令,打开LED灯。\n");gpio_set_level(5, led_state); // 设置 LED 引脚为高电平,打开 LED 灯cJSON_ReplaceItemInObject(params, "data", cJSON_CreateString("LED set success, status is ON"));char *json_to_send = cJSON_PrintUnformatted(root); msg_id = esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);} if (strstr(command->valuestring, "control") && strstr(lightId->valuestring, "001") && strstr(status->valuestring, "off") && first_link == false) {led_state = 0;printf("接收到 'off' 命令,关闭LED灯。\n");gpio_set_level(5, led_state); // 设置 LED 引脚为低电平,关闭 LED 灯cJSON_ReplaceItemInObject(params, "data", cJSON_CreateString("LED set success, status is OFF"));char *json_to_send = cJSON_PrintUnformatted(root); msg_id = esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);}cJSON_Delete(root);break;case MQTT_EVENT_ERROR:ESP_LOGI(TAG, "MQTT_EVENT_ERROR");if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {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;}
}static void mqtt_app_start(void)
{esp_mqtt_client_config_t mqtt_cfg = {.broker.address.uri = CONFIG_BROKER_URL,};
#if CONFIG_BROKER_URL_FROM_STDINchar line[128];if (strcmp(mqtt_cfg.broker.address.uri, "FROM_STDIN") == 0) {int count = 0;printf("Please enter url of mqtt broker\n");while (count < 128) {int c = fgetc(stdin);if (c == '\n') {line[count] = '\0';break;} else if (c > 0 && c < 127) {line[count] = c;++count;}vTaskDelay(10 / portTICK_PERIOD_MS);}mqtt_cfg.broker.address.uri = line;printf("Broker url: %s\n", line);} else {ESP_LOGE(TAG, "Configuration mismatch: wrong broker url");abort();}
#endif /* CONFIG_BROKER_URL_FROM_STDIN */esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);/* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);esp_mqtt_client_start(client);
}
void app_main(void)
{ESP_LOGI(TAG, "[APP] Startup..");ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());esp_log_level_set("*", ESP_LOG_INFO);esp_log_level_set("mqtt_client", ESP_LOG_VERBOSE);esp_log_level_set("mqtt_example", ESP_LOG_VERBOSE);esp_log_level_set("transport_base", ESP_LOG_VERBOSE);esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);esp_log_level_set("transport", ESP_LOG_VERBOSE);esp_log_level_set("outbox", ESP_LOG_VERBOSE);ESP_ERROR_CHECK(nvs_flash_init());ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());ESP_ERROR_CHECK(example_connect());mqtt_app_start();// 初始化传感器驱动sensor_driver_init();// 配置 LED 引脚 GPIO 5gpio_reset_pin(5);gpio_set_direction(5, GPIO_MODE_OUTPUT);// 配置 开关 引脚 GPIO 4gpio_reset_pin(4);gpio_set_direction(4, GPIO_MODE_INPUT);// 添加布尔值进行判断本次是否完成状态切换bool enable = true;int report_tick = 0;while (1){// ================= 自动控制逻辑 =================int pir_val, light_adc, light_mv;get_sensor_data(&pir_val, &light_adc, &light_mv);// 定义一个静态变量作为倒计时器,记录连续不满足条件的次数static int no_trigger_count = 0; // 条件:有人,环境:暗,灯:灭if (pir_val == 1 && light_mv > DARK_THRESHOLD && led_state == 0) {led_state = 1;no_trigger_count = 0; // 一旦开灯,倒计时清零gpio_set_level(5, led_state);ESP_LOGI(TAG, ">>> 开灯! 有人(PIR:%d), 环境: 暗(ADC:%d / %dmV) <<<", pir_val, light_adc, light_mv);// 自动开灯后发送状态给服务器if (public_client != NULL) {cJSON* root = cJSON_CreateObject();cJSON_AddStringToObject(root, "command", "control");cJSON* params = cJSON_CreateObject();cJSON_AddItemToObject(root, "params", params);cJSON_AddStringToObject(params, "lightId", "001");cJSON_AddStringToObject(params, "status", "on");cJSON_AddStringToObject(params, "data", "Auto turn ON");char *json_to_send = cJSON_PrintUnformatted(root);esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);cJSON_Delete(root);free(json_to_send); }}// 如果灯当前是亮的,去检测是否需要关灯else if (led_state == 1) {// 判断是否满足关灯条件(无人 或 环境变亮)if (pir_val == 0 || light_mv <= DARK_THRESHOLD) {no_trigger_count++; // 不满足条件,倒计时 +1// ESP_LOGI(TAG, "关灯倒计时: %d / 100", no_trigger_count); // 关灯倒计时过程if (no_trigger_count >= 100) {led_state = 0;no_trigger_count = 0; // 关灯后重置倒计时gpio_set_level(5, led_state);ESP_LOGI(TAG, "关灯! 连续10秒未检测到人/环境变亮. PIR:%d, mV:%d", pir_val, light_mv);// 自动关灯后发送状态给服务器if (public_client != NULL) {cJSON* root = cJSON_CreateObject();cJSON_AddStringToObject(root, "command", "control");cJSON* params = cJSON_CreateObject();cJSON_AddItemToObject(root, "params", params);cJSON_AddStringToObject(params, "lightId", "001");cJSON_AddStringToObject(params, "status", "off");cJSON_AddStringToObject(params, "data", "Auto turn OFF after 10s");char *json_to_send = cJSON_PrintUnformatted(root);esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);cJSON_Delete(root);free(json_to_send);}}}else{no_trigger_count = 0; }}// ================= 手动开关逻辑 =================// 读取开关状态int switch_state = gpio_get_level(4);// 开关按下时成立,切换高低电平if (switch_state == 0 && enable) {enable = false;led_state = !led_state; gpio_set_level(5, led_state); printf("手动:开关按下,LED状态: %d\n", led_state);int msg_id;if (led_state == 1) {cJSON* root = cJSON_CreateObject();cJSON_AddStringToObject(root, "command", "control");cJSON* params = cJSON_CreateObject();cJSON_AddItemToObject(root, "params", params);cJSON_AddStringToObject(params, "lightId", "001");cJSON_AddStringToObject(params, "status", "on");cJSON_AddStringToObject(params, "data", "Manual LED is ON");char *json_to_send = cJSON_PrintUnformatted(root);msg_id = esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);cJSON_Delete(root);free(json_to_send);} else if (led_state == 0) {cJSON* root = cJSON_CreateObject();cJSON_AddStringToObject(root, "command", "control");cJSON* params = cJSON_CreateObject();cJSON_AddItemToObject(root, "params", params);cJSON_AddStringToObject(params, "lightId", "001");cJSON_AddStringToObject(params, "status", "off");cJSON_AddStringToObject(params, "data", "Manual LED is OFF");char *json_to_send = cJSON_PrintUnformatted(root);msg_id = esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);cJSON_Delete(root);free(json_to_send);}}// 开关释放时,让开关按下的条件成立if (switch_state == 1 && !enable) {enable = true;}// ================= 实时数据上报逻辑  =================report_tick++;if (report_tick >= 50) {report_tick = 0; // 清零计时器if (public_client != NULL) {cJSON* root = cJSON_CreateObject();cJSON_AddStringToObject(root, "type", "sensor_report"); cJSON* data = cJSON_CreateObject();cJSON_AddItemToObject(root, "data", data);// 添加传感器数据cJSON_AddNumberToObject(data, "pir_state", pir_val);cJSON_AddNumberToObject(data, "light_adc", light_adc);cJSON_AddNumberToObject(data, "light_mv", light_mv);cJSON_AddNumberToObject(data, "led_status", led_state);char *json_to_send = cJSON_PrintUnformatted(root);esp_mqtt_client_publish(public_client, "/topic/off-line", json_to_send, 0, 0, 0);ESP_LOGI(TAG, "上报传感器数据: %s", json_to_send);cJSON_Delete(root);free(json_to_send);}}// 延时100msvTaskDelay(100 / portTICK_PERIOD_MS);}
}

结果

image

http://www.jsqmd.com/news/705717/

相关文章:

  • [具身智能-458]:从手工单张图片标注进化到自动生成海量、多样化数据,本质上是数据生产模式的一次工业革命。
  • 基于AutoGen与LangGraph的多智能体学术调研系统Paper-Agent全解析
  • 使用同一段提示词继续抽卡生成不同的页面 - AI
  • 红牌作战是什么?红牌作战的实施步骤与核心要点
  • MCP 2026负载均衡器选型决策树:对比Nginx+MCP、Envoy+MCP、自研LB内核的RPS/延迟/一致性哈希偏差率(实测数据表)
  • iOS设备iCloud激活锁绕过工具applera1n:安全解锁iOS 15-16设备的完整指南
  • WorkshopDL完整指南:无需Steam客户端,轻松下载创意工坊模组
  • blackboxai的API地址
  • 如何在Blender中实现CAD级精确建模:CAD_Sketcher完全指南
  • 大模型推理性能基准测试与NVIDIA GenAI-Perf实践指南
  • 企业级元数据平台实战:3步完成OpenMetadata Docker容器化部署
  • 成年人想学画画放松怎么办? - 云南美术头条
  • Rivet Actors:重塑有状态后端开发,实现状态、计算与网络统一
  • 大麦助手DamaiHelper:告别抢票焦虑,三分钟掌握演唱会门票自动化神器
  • 视频修复终极指南:用Untrunc高效恢复损坏的MP4/MOV文件
  • 视频
  • redis分布式锁的实现
  • 如何用PyAEDT实现电磁仿真自动化?告别重复点击的终极指南
  • Python异步编程中的上下文管理:Acontext库原理与实践
  • 轻松搞定文件压缩:7-Zip新手完全入门指南
  • 如何快速提取B站视频字幕:终极免费工具使用指南
  • Honcho开源框架:AI智能体会话状态管理与编排实践指南
  • 从零开始掌握NSC_BUILDER:Switch游戏文件管理的瑞士军刀
  • Gemma-4-26B-A4B-it-GGUF入门指南:WebUI中启用streaming响应与禁用流式输出对比体验
  • 贝叶斯定理在机器学习中的应用与实践
  • 四川盛世钢联国际贸易有限公司-全品类建筑钢材供应厂家频道 - 四川盛世钢联营销中心
  • LangGraph 源码逐行解读:Multi-Agent 状态流转与协作的底层架构
  • 如何用WebToEpub一键将网页小说转为EPUB电子书永久保存
  • DeepSeek-R1-Distill-Qwen-1.5B部署成功秘诀:日志查看与问题排查技巧
  • 自动化工作流开发:OCR识别致PDF信息提取、数学计算与Word计算书生成