编程技术分享

分享编程知识,探讨技术创新

0%

简介:开源一款物联网控制的APP以及智能开关的项目,希望能够为广大 DIY 爱好者和物联网开发者提供参考和帮助。 设计初衷是为了方便自己控制开关等,同时也希望通过开源的方式,与更多的人交流和学习。

当然,作为一名高级嵌入式软件开发工程师,我很乐意为您详细阐述一个基于物联网的智能开关项目,并提供相应的C代码实现方案。这个项目旨在构建一个可靠、高效、可扩展的智能家居控制系统,通过开源的方式,希望能帮助到更多的开发者和DIY爱好者。
关注微信公众号,提前获取相关推文

项目概述

本项目是一个基于嵌入式系统的智能开关,它可以通过Wi-Fi连接到云平台,并使用手机APP进行远程控制。用户可以通过APP开关灯具、电器等设备,并可以查看开关状态。此外,智能开关本身也具备本地手动控制功能。整个系统涵盖了从需求分析、系统设计、软件开发、硬件调试到测试验证的全过程。

系统架构设计

为了构建一个可靠、高效且可扩展的系统,我推荐采用分层架构的设计模式。这种架构将系统分解为多个独立的层次,每个层次负责特定的功能,层与层之间通过清晰定义的接口进行通信。这种设计模式具有以下优点:

  1. 模块化设计: 每个层次都是一个独立的模块,易于开发、测试和维护。
  2. 高内聚低耦合: 模块内部功能高度相关,模块之间依赖性低,降低了修改一个模块对其他模块的影响。
  3. 可扩展性: 可以方便地添加新的功能层或替换现有层,而不会对整个系统造成重大影响。
  4. 可重用性: 某些层次(例如硬件抽象层)可以在不同的项目或硬件平台上重用。

基于以上考虑,我将系统架构设计为以下几个层次:

  • 硬件层 (Hardware Layer): 这是系统的最底层,包括微控制器单元 (MCU)、Wi-Fi模块、开关控制电路、按键输入电路、电源电路等硬件组件。
  • 硬件抽象层 (HAL - Hardware Abstraction Layer): HAL层位于硬件层之上,它提供了一组统一的接口,用于访问和控制底层的硬件资源。HAL层隔离了上层软件与具体硬件的差异,使得上层软件可以独立于硬件平台进行开发和移植。例如,HAL层会提供GPIO控制、定时器、串口通信、SPI/I2C总线等驱动接口。
  • 设备驱动层 (Device Driver Layer): 设备驱动层构建在HAL层之上,负责管理和控制特定的硬件设备。例如,Wi-Fi驱动负责Wi-Fi模块的初始化、连接和数据传输;开关驱动负责控制开关的通断;按键驱动负责检测按键输入事件。
  • 网络通信层 (Network Communication Layer): 网络通信层负责处理网络相关的任务,包括Wi-Fi连接管理、TCP/IP协议栈、MQTT协议客户端等。它向上层应用层提供网络数据收发的接口。
  • 应用逻辑层 (Application Logic Layer): 应用逻辑层是系统的核心层,负责实现智能开关的业务逻辑。它包括设备状态管理、命令解析和执行、与云平台的数据交互、本地控制逻辑等。
  • 系统服务层 (System Service Layer): 系统服务层提供一些通用的系统服务功能,例如配置管理、日志记录、错误处理、固件升级 (OTA) 等。
  • 用户界面层 (User Interface Layer): 对于智能开关设备本身,用户界面可能比较简单,例如指示灯显示设备状态。但更重要的是手机APP的用户界面,它提供了远程控制和状态查看的功能。本项目的重点是设备端的嵌入式软件,APP部分可以作为配套提供基本的控制功能,或者可以由开发者自行扩展和定制。

软件模块设计

基于分层架构,我们可以将嵌入式软件进一步划分为以下几个主要模块:

  1. 启动模块 (Bootloader/Startup): 负责系统启动、硬件初始化、加载和跳转到应用程序代码。
  2. 主循环模块 (Main Loop): 系统的核心控制流程,负责轮询事件、调度任务、处理用户输入和网络事件。
  3. Wi-Fi 管理模块 (WiFi Manager): 管理Wi-Fi连接,包括扫描AP、连接AP、断开连接、获取IP地址、处理Wi-Fi事件等。
  4. MQTT 客户端模块 (MQTT Client): 实现MQTT协议客户端功能,负责与MQTT Broker建立连接、订阅/发布消息、处理MQTT事件等。
  5. 开关驱动模块 (Switch Driver): 控制开关的通断,提供开关控制接口。
  6. 按键驱动模块 (Button Driver): 检测按键输入,处理按键事件 (按下、释放、长按等)。
  7. 配置管理模块 (Configuration Manager): 负责存储和读取系统配置信息,例如Wi-Fi SSID/密码、MQTT Broker地址、设备ID等。配置信息可以存储在Flash存储器中。
  8. 状态管理模块 (State Manager): 管理设备的状态,例如开关状态 (开/关)、网络连接状态、错误状态等。
  9. 命令处理模块 (Command Handler): 解析来自APP或云平台的控制命令,并执行相应的操作。
  10. OTA 升级模块 (OTA Updater): 实现固件在线升级功能,方便后续的维护和功能更新。
  11. 日志模块 (Log Module): 记录系统运行日志,方便调试和故障排查。
  12. 定时器模块 (Timer Module): 提供定时器功能,用于定时任务的执行,例如心跳包发送、状态上报等。

硬件选型

对于智能开关项目,我推荐使用以下硬件组件:

  • 微控制器 (MCU): ESP32-C3。ESP32-C3 是一款高性能、低功耗的Wi-Fi和蓝牙双模芯片,具有丰富的外设接口和强大的处理能力,非常适合物联网应用。此外,ESP32-C3 具有完善的开发生态和社区支持,开发资料丰富,上手容易。
  • Wi-Fi 模块: 集成在 ESP32-C3 芯片内部。
  • 开关控制电路: 可以使用继电器或可控硅 (SCR) 来控制开关的通断。继电器适用于控制较大功率的电器,可控硅适用于固态开关,寿命更长。根据实际应用场景选择合适的开关器件。
  • 按键输入电路: 使用普通按键即可,需要进行按键去抖处理。
  • 电源电路: 根据供电需求设计电源电路,例如AC-DC降压电路或DC-DC转换电路。

软件开发环境

  • 操作系统: 本项目不使用RTOS (实时操作系统),采用裸机开发方式。对于简单的智能开关应用,裸机开发可以满足需求,减少系统复杂性。如果项目功能更复杂,可以考虑使用FreeRTOS等轻量级RTOS。
  • 开发工具: ESP-IDF (ESP32 IoT Development Framework)。ESP-IDF 是乐鑫官方提供的ESP32系列芯片的开发框架,基于FreeRTOS,提供了丰富的库函数和工具,方便开发者进行ESP32的软件开发。
  • 编程语言: C 语言。
  • 编译工具链: GCC for ESP32。
  • 调试工具: JTAG 调试器、串口调试工具。
  • 代码编辑器/IDE: VS Code + ESP-IDF 插件,或者 Eclipse 等。

C 代码实现 (部分核心模块示例)

由于篇幅限制,这里只提供部分核心模块的C代码示例,包括 Wi-Fi 管理、MQTT 客户端、开关驱动、按键驱动和主循环模块。完整的项目代码会更加详细和完善,包含错误处理、配置管理、OTA 升级等功能。

(1) wifi_manager.cwifi_manager.h (Wi-Fi 管理模块)

wifi_manager.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef WIFI_MANAGER_H
#define WIFI_MANAGER_H

#include <stdbool.h>

typedef enum {
WIFI_STATUS_DISCONNECTED,
WIFI_STATUS_CONNECTING,
WIFI_STATUS_CONNECTED,
WIFI_STATUS_ERROR
} wifi_status_t;

typedef void (*wifi_event_callback_t)(wifi_status_t status);

void wifi_manager_init(wifi_event_callback_t callback);
void wifi_manager_connect(const char *ssid, const char *password);
void wifi_manager_disconnect(void);
wifi_status_t wifi_manager_get_status(void);
const char* wifi_manager_get_ip_address(void);

#endif // WIFI_MANAGER_H

wifi_manager.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include "wifi_manager.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "lwip/ip4_addr.h"

#define WIFI_TAG "WIFI_MANAGER"

static wifi_status_t wifi_status = WIFI_STATUS_DISCONNECTED;
static wifi_event_callback_t wifi_event_cb = NULL;
static char ip_address_str[16] = {0};

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
wifi_status = WIFI_STATUS_CONNECTING;
if (wifi_event_cb) {
wifi_event_cb(wifi_status);
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_status = WIFI_STATUS_DISCONNECTED;
if (wifi_event_cb) {
wifi_event_cb(wifi_status);
}
// Optional: Attempt to reconnect automatically
// esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(WIFI_TAG, "Got IP address:" IPSTR, IP2STR(&event->ip_info.ip));
sprintf(ip_address_str, IPSTR, IP2STR(&event->ip_info.ip));
wifi_status = WIFI_STATUS_CONNECTED;
if (wifi_event_cb) {
wifi_event_cb(wifi_status);
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
ESP_LOGI(WIFI_TAG, "Connected to AP");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) {
ESP_LOGI(WIFI_TAG, "Authentication mode changed");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_PIN) {
ESP_LOGI(WIFI_TAG, "WPS PIN error");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_PBC) {
ESP_LOGI(WIFI_TAG, "WPS PBC error");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_SUCCESS) {
ESP_LOGI(WIFI_TAG, "WPS success");
}
}

void wifi_manager_init(wifi_event_callback_t callback)
{
wifi_event_cb = callback;

ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &instance_got_ip));

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());

ESP_LOGI(WIFI_TAG, "WiFi initialized");
}

void wifi_manager_connect(const char *ssid, const char *password)
{
wifi_config_t wifi_config = {
.sta = {
.ssid = "",
.password = "",
.threshold.authmode = WIFI_AUTH_WPA2PSK,
.pmf_cfg = {
.capable = true,
.mgmt_frame_prot = WIFI_PMF_REQUIRED
},
},
};
strcpy((char *)wifi_config.sta.ssid, ssid);
strcpy((char *)wifi_config.sta.password, password);

ESP_LOGI(WIFI_TAG, "Connecting to SSID:%s, password:%s...", ssid);
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_connect());
wifi_status = WIFI_STATUS_CONNECTING;
if (wifi_event_cb) {
wifi_event_cb(wifi_status);
}
}

void wifi_manager_disconnect(void)
{
ESP_ERROR_CHECK(esp_wifi_disconnect());
wifi_status = WIFI_STATUS_DISCONNECTED;
if (wifi_event_cb) {
wifi_event_cb(wifi_status);
}
}

wifi_status_t wifi_manager_get_status(void)
{
return wifi_status;
}

const char* wifi_manager_get_ip_address(void)
{
return ip_address_str;
}

(2) mqtt_client.cmqtt_client.h (MQTT 客户端模块)

mqtt_client.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef MQTT_CLIENT_H
#define MQTT_CLIENT_H

#include <stdbool.h>

typedef enum {
MQTT_STATUS_DISCONNECTED,
MQTT_STATUS_CONNECTING,
MQTT_STATUS_CONNECTED,
MQTT_STATUS_ERROR
} mqtt_status_t;

typedef void (*mqtt_event_callback_t)(mqtt_status_t status);
typedef void (*mqtt_message_callback_t)(const char *topic, const char *payload);

void mqtt_client_init(mqtt_event_callback_t event_callback, mqtt_message_callback_t message_callback);
void mqtt_client_connect(const char *broker_uri, const char *client_id);
void mqtt_client_disconnect(void);
mqtt_status_t mqtt_client_get_status(void);
void mqtt_client_subscribe(const char *topic);
void mqtt_client_publish(const char *topic, const char *payload);

#endif // MQTT_CLIENT_H

mqtt_client.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include "mqtt_client.h"
#include "esp_mqtt_client.h"
#include "esp_log.h"

#define MQTT_TAG "MQTT_CLIENT"

static esp_mqtt_client_handle_t client = NULL;
static mqtt_status_t mqtt_status = MQTT_STATUS_DISCONNECTED;
static mqtt_event_callback_t mqtt_event_cb = NULL;
static mqtt_message_callback_t mqtt_message_cb = NULL;

static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
ESP_LOGD(MQTT_TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
esp_mqtt_event_handle_t event = event_data;
//esp_mqtt_client_handle_t client = event->client;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_CONNECTED");
mqtt_status = MQTT_STATUS_CONNECTED;
if (mqtt_event_cb) {
mqtt_event_cb(mqtt_status);
}
// Example: Subscribe to a topic after connection
// mqtt_client_subscribe("your_subscribe_topic");
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_DISCONNECTED");
mqtt_status = MQTT_STATUS_DISCONNECTED;
if (mqtt_event_cb) {
mqtt_event_cb(mqtt_status);
}
break;

case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_DATA");
//printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
//printf("DATA=%.*s\r\n", event->data_len, event->data);
if (mqtt_message_cb) {
char topic[128] = {0};
char payload[512] = {0}; // Adjust buffer size as needed
strncpy(topic, event->topic, event->topic_len);
strncpy(payload, event->data, event->data_len);
mqtt_message_cb(topic, payload);
}
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(MQTT_TAG, "MQTT_EVENT_ERROR");
mqtt_status = MQTT_STATUS_ERROR;
if (mqtt_event_cb) {
mqtt_event_cb(mqtt_status);
}
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGE(MQTT_TAG, "Last error system errno: = %d, error type: = %d", event->error_handle->esp_transport_sock_errno, event->error_handle->error_type);
}
break;
default:
ESP_LOGI(MQTT_TAG, "Other event id:%d", event->event_id);
break;
}
}

void mqtt_client_init(mqtt_event_callback_t event_callback, mqtt_message_callback_t message_callback)
{
mqtt_event_cb = event_callback;
mqtt_message_cb = message_callback;
}

void mqtt_client_connect(const char *broker_uri, const char *client_id)
{
esp_mqtt_client_config_t mqtt_cfg = {
.uri = broker_uri,
.client_id = client_id,
};
client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
mqtt_status = MQTT_STATUS_CONNECTING;
if (mqtt_event_cb) {
mqtt_event_cb(mqtt_status);
}
}

void mqtt_client_disconnect(void)
{
esp_mqtt_client_disconnect(client);
mqtt_status = MQTT_STATUS_DISCONNECTED;
if (mqtt_event_cb) {
mqtt_event_cb(mqtt_status);
}
}

mqtt_status_t mqtt_client_get_status(void)
{
return mqtt_status;
}

void mqtt_client_subscribe(const char *topic)
{
int msg_id;
msg_id = esp_mqtt_client_subscribe(client, topic, 0);
ESP_LOGI(MQTT_TAG, "sent subscribe successful, msg_id=%d", msg_id);
}

void mqtt_client_publish(const char *topic, const char *payload)
{
int msg_id;
msg_id = esp_mqtt_client_publish(client, topic, payload, 0, 0, 0);
ESP_LOGI(MQTT_TAG, "sent publish successful, msg_id=%d", msg_id);
}

(3) switch_driver.cswitch_driver.h (开关驱动模块)

switch_driver.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef SWITCH_DRIVER_H
#define SWITCH_DRIVER_H

#include <stdbool.h>

typedef enum {
SWITCH_STATE_OFF,
SWITCH_STATE_ON
} switch_state_t;

void switch_driver_init(int gpio_pin);
void switch_driver_set_state(switch_state_t state);
switch_state_t switch_driver_get_state(void);

#endif // SWITCH_DRIVER_H

switch_driver.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "switch_driver.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define SWITCH_TAG "SWITCH_DRIVER"

static int switch_gpio_pin;
static switch_state_t current_switch_state = SWITCH_STATE_OFF;

void switch_driver_init(int gpio_pin)
{
switch_gpio_pin = gpio_pin;
gpio_reset_pin(switch_gpio_pin);
gpio_set_direction(switch_gpio_pin, GPIO_MODE_OUTPUT);
switch_driver_set_state(SWITCH_STATE_OFF); // Initialize to OFF
ESP_LOGI(SWITCH_TAG, "Switch driver initialized on GPIO %d", switch_gpio_pin);
}

void switch_driver_set_state(switch_state_t state)
{
if (state == SWITCH_STATE_ON) {
gpio_set_level(switch_gpio_pin, 1); // High level to turn ON (adjust based on your circuit)
current_switch_state = SWITCH_STATE_ON;
ESP_LOGI(SWITCH_TAG, "Switch turned ON");
} else { // state == SWITCH_STATE_OFF
gpio_set_level(switch_gpio_pin, 0); // Low level to turn OFF (adjust based on your circuit)
current_switch_state = SWITCH_STATE_OFF;
ESP_LOGI(SWITCH_TAG, "Switch turned OFF");
}
}

switch_state_t switch_driver_get_state(void)
{
return current_switch_state;
}

(4) button_driver.cbutton_driver.h (按键驱动模块)

button_driver.h:

1
2
3
4
5
6
7
8
9
10
11
#ifndef BUTTON_DRIVER_H
#define BUTTON_DRIVER_H

#include <stdbool.h>

typedef void (*button_event_callback_t)(void);

void button_driver_init(int gpio_pin, button_event_callback_t callback);
void button_driver_task(void *pvParameters); // Task to handle button events

#endif // BUTTON_DRIVER_H

button_driver.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include "button_driver.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

#define BUTTON_TAG "BUTTON_DRIVER"
#define BUTTON_DEBOUNCE_MS 50
#define BUTTON_CHECK_INTERVAL_MS 10

static int button_gpio_pin;
static button_event_callback_t button_event_cb = NULL;
static bool button_pressed = false;
static esp_timer_handle_t debounce_timer;

static void button_debounce_timer_callback(void* arg);

void button_driver_init(int gpio_pin, button_event_callback_t callback)
{
button_gpio_pin = gpio_pin;
button_event_cb = callback;
gpio_reset_pin(button_gpio_pin);
gpio_set_direction(button_gpio_pin, GPIO_MODE_INPUT);
gpio_set_pull_mode(button_gpio_pin, GPIO_PULLUP_PULLDOWN); // Enable pull-up or pull-down resistor as needed
gpio_pullup_en(button_gpio_pin); // Example: Enable pull-up resistor

const esp_timer_create_args_t debounce_timer_args = {
.callback = &button_debounce_timer_callback,
.name = "button_debounce_timer"
};
ESP_ERROR_CHECK(esp_timer_create(&debounce_timer_args, &debounce_timer));

ESP_LOGI(BUTTON_TAG, "Button driver initialized on GPIO %d", button_gpio_pin);
}

static void button_debounce_timer_callback(void* arg)
{
if (gpio_get_level(button_gpio_pin) == 0) { // Button is still pressed after debounce time
if (!button_pressed) {
button_pressed = true;
if (button_event_cb) {
button_event_cb(); // Call the button event callback
}
}
} else {
button_pressed = false;
}
esp_timer_stop(debounce_timer); // Stop the timer after each debounce check
}

void button_driver_task(void *pvParameters)
{
while (1) {
if (gpio_get_level(button_gpio_pin) == 0) { // Button is pressed (assuming active low)
esp_timer_start_once(debounce_timer, BUTTON_DEBOUNCE_MS * 1000); // Start debounce timer
}
vTaskDelay(pdMS_TO_TICKS(BUTTON_CHECK_INTERVAL_MS));
}
}

(5) main.c (主循环模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "wifi_manager.h"
#include "mqtt_client.h"
#include "switch_driver.h"
#include "button_driver.h"
#include "config_manager.h" // Assuming you have a config manager module

#define MAIN_TAG "MAIN"

// Configuration parameters (replace with your actual values or read from config)
#define WIFI_SSID CONFIG_WIFI_SSID // Define in sdkconfig.h or replace with string literal
#define WIFI_PASSWORD CONFIG_WIFI_PASSWORD // Define in sdkconfig.h or replace with string literal
#define MQTT_BROKER_URI CONFIG_MQTT_BROKER_URI // Define in sdkconfig.h or replace with string literal
#define MQTT_CLIENT_ID "smart_switch_client"
#define MQTT_PUB_TOPIC "smart_switch/state"
#define MQTT_SUB_TOPIC "smart_switch/command"
#define SWITCH_GPIO_PIN CONFIG_SWITCH_GPIO_PIN // Define in sdkconfig.h
#define BUTTON_GPIO_PIN CONFIG_BUTTON_GPIO_PIN // Define in sdkconfig.h

static bool current_switch_state_app = false; // App-level switch state
static bool wifi_connected = false;
static bool mqtt_connected = false;

// Callback function for WiFi events
static void wifi_event_handler_cb(wifi_status_t status) {
ESP_LOGI(MAIN_TAG, "WiFi status changed: %d", status);
if (status == WIFI_STATUS_CONNECTED) {
wifi_connected = true;
ESP_LOGI(MAIN_TAG, "WiFi Connected, IP Address: %s", wifi_manager_get_ip_address());
mqtt_client_connect(MQTT_BROKER_URI, MQTT_CLIENT_ID); // Connect to MQTT after WiFi is connected
} else if (status == WIFI_STATUS_DISCONNECTED) {
wifi_connected = false;
mqtt_client_disconnect(); // Disconnect MQTT if WiFi disconnects
} else if (status == WIFI_STATUS_ERROR) {
wifi_connected = false;
mqtt_client_disconnect();
ESP_LOGE(MAIN_TAG, "WiFi Connection Error");
// Handle WiFi error, maybe retry connection
}
}

// Callback function for MQTT events
static void mqtt_event_handler_cb(mqtt_status_t status) {
ESP_LOGI(MAIN_TAG, "MQTT status changed: %d", status);
if (status == MQTT_STATUS_CONNECTED) {
mqtt_connected = true;
ESP_LOGI(MAIN_TAG, "MQTT Connected");
mqtt_client_subscribe(MQTT_SUB_TOPIC); // Subscribe to command topic after MQTT connection
// Publish initial switch state upon connection
char payload[20];
snprintf(payload, sizeof(payload), "%s", current_switch_state_app ? "ON" : "OFF");
mqtt_client_publish(MQTT_PUB_TOPIC, payload);
} else if (status == MQTT_STATUS_DISCONNECTED) {
mqtt_connected = false;
ESP_LOGI(MAIN_TAG, "MQTT Disconnected");
} else if (status == MQTT_STATUS_ERROR) {
mqtt_connected = false;
ESP_LOGE(MAIN_TAG, "MQTT Connection Error");
// Handle MQTT error, maybe retry connection
}
}

// Callback function for MQTT message received
static void mqtt_message_handler_cb(const char *topic, const char *payload) {
ESP_LOGI(MAIN_TAG, "MQTT Message Received on topic: %s, payload: %s", topic, payload);
if (strcmp(topic, MQTT_SUB_TOPIC) == 0) {
if (strcmp(payload, "ON") == 0) {
current_switch_state_app = true;
switch_driver_set_state(SWITCH_STATE_ON);
ESP_LOGI(MAIN_TAG, "Switch turned ON via MQTT");
} else if (strcmp(payload, "OFF") == 0) {
current_switch_state_app = false;
switch_driver_set_state(SWITCH_STATE_OFF);
ESP_LOGI(MAIN_TAG, "Switch turned OFF via MQTT");
} else {
ESP_LOGW(MAIN_TAG, "Unknown command received: %s", payload);
}
// Publish state update after command execution
char payload_pub[20];
snprintf(payload_pub, sizeof(payload_pub), "%s", current_switch_state_app ? "ON" : "OFF");
mqtt_client_publish(MQTT_PUB_TOPIC, payload_pub);
}
}

// Callback function for button press event
static void button_event_handler_cb(void) {
ESP_LOGI(MAIN_TAG, "Button Pressed!");
current_switch_state_app = !current_switch_state_app; // Toggle switch state
if (current_switch_state_app) {
switch_driver_set_state(SWITCH_STATE_ON);
ESP_LOGI(MAIN_TAG, "Switch turned ON via button");
} else {
switch_driver_set_state(SWITCH_STATE_OFF);
ESP_LOGI(MAIN_TAG, "Switch turned OFF via button");
}

// Publish state update after button press
if (mqtt_connected) {
char payload[20];
snprintf(payload, sizeof(payload), "%s", current_switch_state_app ? "ON" : "OFF");
mqtt_client_publish(MQTT_PUB_TOPIC, payload);
}
}


void app_main(void)
{
ESP_LOGI(MAIN_TAG, "Smart Switch Application Startup");

// Initialize NVS (Non-Volatile Storage) - required for Wi-Fi
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);

// Initialize WiFi Manager
wifi_manager_init(wifi_event_handler_cb);

// Initialize MQTT Client
mqtt_client_init(mqtt_event_handler_cb, mqtt_message_handler_cb);

// Initialize Switch Driver
switch_driver_init(SWITCH_GPIO_PIN);

// Initialize Button Driver
button_driver_init(BUTTON_GPIO_PIN, button_event_handler_cb);

// Create button task
xTaskCreate(button_driver_task, "button_task", 2048, NULL, 10, NULL);

// Connect to WiFi
wifi_manager_connect(WIFI_SSID, WIFI_PASSWORD);

ESP_LOGI(MAIN_TAG, "Application initialized and running...");
}

代码说明:

  • wifi_manager 模块: 负责 Wi-Fi 初始化、连接、事件处理。wifi_event_handler_cb 是 Wi-Fi 事件回调函数,处理连接状态变化。
  • mqtt_client 模块: 负责 MQTT 客户端初始化、连接、订阅、发布、消息处理。mqtt_event_handler_cb 是 MQTT 事件回调,mqtt_message_handler_cb 是消息接收回调。
  • switch_driver 模块: 封装了开关控制的硬件操作,switch_driver_set_state 设置开关状态。
  • button_driver 模块: 封装了按键检测和去抖动逻辑,button_event_handler_cb 是按键事件回调。button_driver_task 是一个独立的 FreeRTOS 任务,轮询检测按键状态。
  • main.c: 主应用程序入口,初始化各个模块,连接 Wi-Fi 和 MQTT Broker,处理按键和 MQTT 命令,控制开关状态,并发布状态更新。

编译和运行:

  1. 环境搭建: 确保已安装 ESP-IDF 开发环境,并配置好工具链。
  2. 配置项目: 使用 idf.py menuconfig 命令配置项目,包括 Wi-Fi SSID/密码、MQTT Broker URI、GPIO 引脚等。这些配置项在 sdkconfig.h 文件中定义,或者直接在 main.c 中替换为字符串字面量。
  3. 编译: 使用 idf.py build 命令编译项目。
  4. 烧录: 使用 idf.py flash monitor 命令烧录固件到 ESP32-C3 开发板,并打开串口监视器查看运行日志。

测试和验证:

  1. 硬件连接: 将 ESP32-C3 开发板连接到开关控制电路和按键电路。确保硬件连接正确。
  2. Wi-Fi 连接测试: 观察串口日志,确认设备是否成功连接到 Wi-Fi 网络,并获取到 IP 地址。
  3. MQTT 连接测试: 确认设备是否成功连接到 MQTT Broker。可以使用 MQTT 客户端工具 (例如 MQTT.fx, MQTT Explorer) 订阅 smart_switch/state 主题,查看设备是否发布了初始状态。
  4. APP 控制测试: 开发一个简单的手机 APP (可以使用 App Inventor, Flutter, React Native 等工具) 或者使用 MQTT 客户端工具发布 ONOFF 命令到 smart_switch/command 主题,观察开关是否被远程控制。
  5. 本地按键控制测试: 按下智能开关上的按键,观察开关状态是否切换,并检查设备是否发布了状态更新到 MQTT Broker。
  6. 稳定性测试: 长时间运行设备,观察系统是否稳定可靠,是否存在内存泄漏或其他问题。

维护和升级:

  • OTA 固件升级: 实现 OTA (Over-The-Air) 固件升级功能,方便后续的 bug 修复和功能更新。可以利用 ESP-IDF 提供的 OTA 组件,或者自行实现基于 MQTT 或 HTTP 的 OTA 升级方案。
  • 日志记录和远程诊断: 完善日志模块,记录详细的系统运行日志,方便远程诊断和故障排查。可以将日志上传到云平台或者通过串口输出。
  • 错误处理和容错机制: 增强系统的错误处理能力,例如 Wi-Fi 连接失败重试、MQTT 连接断线重连、硬件故障检测等,提高系统的鲁棒性和可靠性。
  • 安全加固: 考虑系统的安全性,例如使用安全的 Wi-Fi 连接 (WPA2/WPA3)、MQTT 连接 (TLS/SSL)、设备身份认证、数据加密等,防止设备被非法访问和控制。

总结

本项目提供了一个基于 ESP32-C3 的开源智能开关嵌入式软件框架,涵盖了分层架构设计、核心模块代码实现、硬件选型、开发环境搭建、测试验证和维护升级等方面。代码示例虽然只是核心模块的简化版本,但已经能够展示智能开关的基本功能和软件架构。开发者可以基于此框架进行扩展和定制,添加更多高级功能,例如定时开关、场景联动、功耗监测、语音控制等,构建更加完善的智能家居控制系统。

希望这个详细的方案和代码示例能够帮助您理解智能开关嵌入式软件的开发过程,并为您的 DIY 项目提供参考和帮助。 开源的目的是为了交流和学习,欢迎您提出宝贵的意见和建议,共同完善这个项目。

欢迎关注我的其它发布渠道