diff --git a/Bionic_Core/CommClass/CommClass.h b/Bionic_Core/CommClass/CommClass.h index bb92cc1..af4b41e 100644 --- a/Bionic_Core/CommClass/CommClass.h +++ b/Bionic_Core/CommClass/CommClass.h @@ -3,15 +3,3 @@ // #pragma once - -#ifdef __cplusplus -extern "C" { -#endif - - - - - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/Bionic_Core/CppHandle/CppHandle.cpp b/Bionic_Core/CppHandle/CppHandle.cpp index 19574b4..dd265d1 100644 --- a/Bionic_Core/CppHandle/CppHandle.cpp +++ b/Bionic_Core/CppHandle/CppHandle.cpp @@ -6,5 +6,6 @@ #include "OTAClass.h" void Cpp_Hand() { - OTAClass::Init(); -} \ No newline at end of file + OTAClass oc; + oc.Init(); +} diff --git a/Bionic_Core/OTAClass/OTAClass.cpp b/Bionic_Core/OTAClass/OTAClass.cpp index 6322222..d034129 100644 --- a/Bionic_Core/OTAClass/OTAClass.cpp +++ b/Bionic_Core/OTAClass/OTAClass.cpp @@ -4,6 +4,141 @@ #include "OTAClass.h" #include "esp_log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono; + +const auto sleep_time = seconds{ + 5 +}; + +// 下面的这个函数可以放在任何线程中,自动打印出对应线程的信息 +void print_thread_info(const char *extra = nullptr) +{ + std::stringstream ss; + if (extra) { + ss << extra; + } + ss << "Core id: " << xPortGetCoreID() + << ", prio: " << uxTaskPriorityGet(nullptr) + << ", minimum free stack: " << uxTaskGetStackHighWaterMark(nullptr) << " bytes."; + ESP_LOGI(pcTaskGetName(nullptr), "%s", ss.str().c_str()); +} + +void thread_func_inherited() +{ + while (true) { + print_thread_info("我是 普普通通的inherited 线程"); + std::this_thread::sleep_for(sleep_time); + } +} + +void thread_func_any_core() +{ + while (true) { + print_thread_info("我是一个会跑在任意一个核的任务~"); + std::this_thread::sleep_for(sleep_time); + } +} + +esp_pthread_cfg_t create_config(const char *name, int core_id, int stack, int prio) +{ + + auto cfg = esp_pthread_get_default_config(); + cfg.thread_name = name; + cfg.pin_to_core = core_id; + cfg.stack_size = stack; + cfg.prio = prio; + return cfg; +} + +#include "ThreadManager.h" +#include "WifiConnectors.h" +#include void OTAClass::Init() { ESP_LOGI("OTA", "Init"); + + ESP_LOGI("OTAClass::Init", "当前固件版本 1.0.1"); + + // 测试Wifi + // WifiConnectors::getInstance()->log(); + // + // WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888"); + + // 1. 创建普通函数一个可以运行在任意核上的线程 + ThreadConfig config1; + config1.name = "NormalThread"; + // config1.core_id = 0; // 不指定运行在哪个核,使其自动选择 + config1.stack_size = 3072; + config1.priority = 5; // 优先级 + std::thread normal_thread = ThreadManager::createThread(config1, thread_func_any_core); + + ThreadConfig config2; + config2.name = "Thread2"; + config2.core_id = 1; // 指定运行在核1 + config2.stack_size = 3072; + config2.priority = 5; + std::thread thread2 = ThreadManager::createThread(config2, thread_func_inherited); + + + // 配置Wifi连接线程参数 + ThreadConfig wifi_config; + wifi_config.name = "WifiConnector"; // 线程名称 + wifi_config.core_id = 1; // 绑定到核心1(避免与主线程冲突) + wifi_config.stack_size = 4096; // 设置稍大的栈空间(Wifi连接可能需要) + wifi_config.priority = 6; // 设置较高优先级(确保连接及时) + // 使用单例方式创建线程,调用connectWifi成员函数 + std::thread wifi_thread = ThreadManager::createSingletonThread( + wifi_config, + &WifiConnectors::connectWifi, + "Misaki-2.4G", // SSID + "88888888", // 密码 + 5 // 最大重试次数 + ); + + ThreadConfig ota_config; + ota_config.name = "OTA"; + ota_config.stack_size = 4096; + ota_config.priority = 6; + ota_config.core_id = 0; + std::thread ota_thread = ThreadManager::createMemberThread( + ota_config, + this, + &OTAClass::Update + ); + + + while (true) { + std::stringstream ss; + ss << "core id: " << xPortGetCoreID() + << ", prio: " << uxTaskPriorityGet(nullptr) + << ", minimum free stack: " << uxTaskGetStackHighWaterMark(nullptr) << " bytes."; + ESP_LOGI(pcTaskGetName(nullptr), "%s", ss.str().c_str()); + std::this_thread::sleep_for(sleep_time); + } } + +#include "ota_ws.h" +// 启动OTA更新线程,前提是已经连接WiFi +void OTAClass::Update() { + // 测试OTA服务器192.168.1.11 + while (true) { + if (WifiConnectors::getInstance()->isWifiConnect()) { // 如果Wifi已连接 + ota_ws_start("192.168.1.11", 8080); + // 启动完就退出,删除自身 + break; + }else { + std::this_thread::sleep_for(sleep_time); + } + } + vTaskDelete(nullptr); +} + diff --git a/Bionic_Core/OTAClass/OTAClass.h b/Bionic_Core/OTAClass/OTAClass.h index a1a7900..7f354ad 100644 --- a/Bionic_Core/OTAClass/OTAClass.h +++ b/Bionic_Core/OTAClass/OTAClass.h @@ -4,18 +4,10 @@ #pragma once -#ifdef __cplusplus -extern "C" { -#endif class OTAClass { public: - static void Init(void); + void Init(void); void Update(void); }; - - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/Bionic_Core/PetBaseClass/PetBaseClass.h b/Bionic_Core/PetBaseClass/PetBaseClass.h index de0cef6..5cc45c7 100644 --- a/Bionic_Core/PetBaseClass/PetBaseClass.h +++ b/Bionic_Core/PetBaseClass/PetBaseClass.h @@ -2,15 +2,3 @@ // Created by misaki on 2025/9/2. // #pragma once - -#ifdef __cplusplus -extern "C" { -#endif - - - - - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.cpp b/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.cpp new file mode 100644 index 0000000..c4e70bb --- /dev/null +++ b/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.cpp @@ -0,0 +1,4 @@ +// +// Created by misaki on 2025/9/4. +// +#include "ThreadManager.h" diff --git a/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.h b/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.h new file mode 100644 index 0000000..68b6fad --- /dev/null +++ b/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.h @@ -0,0 +1,149 @@ +// +// Created by misaki on 2025/9/4. +// +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +/** + * @brief 线程配置结构体 + * + * 用于配置线程的各种属性,如名称、核心绑定、栈大小和优先级 + */ +struct ThreadConfig { + std::string name = "thread"; // 线程名称 + int core_id = -1; // 绑定核心ID (-1表示不绑定) + int stack_size = 3072; // 栈大小 (字节) + int priority = 5; // 优先级 + bool inherit_cfg = false; // 是否允许子线程继承此配置 +}; + +/** + * @brief 线程管理类 + * + * 封装了ESP32上的线程创建和管理功能,支持普通函数、类成员函数和单例类成员函数 + */ +class ThreadManager { +public: + /** + * @brief 创建并启动一个线程 + * + * @tparam Function 函数类型 + * @tparam Args 参数类型 + * @param config 线程配置 + * @param func 要执行的函数 + * @param args 函数参数 + * @return std::thread 创建的线程对象 + */ + template + static std::thread createThread(const ThreadConfig& config, Function&& func, Args&&... args) { + // 创建ESP32线程配置 + auto esp_cfg = create_esp_config(config); + + // 设置线程配置 + esp_pthread_set_cfg(&esp_cfg); + + // 创建并启动线程 + return std::thread(std::forward(func), std::forward(args)...); + } + + /** + * @brief 创建并启动一个执行类成员函数的线程 + * + * @tparam T 类类型 + * @tparam Method 成员函数类型 + * @tparam Args 参数类型 + * @param config 线程配置 + * @param obj 类对象指针 + * @param method 成员函数指针 + * @param args 函数参数 + * @return std::thread 创建的线程对象 + */ + template + static std::thread createMemberThread(const ThreadConfig& config, T* obj, Method&& method, Args&&... args) { + // 使用lambda表达式包装成员函数调用 + auto task = [obj, method, args...]() { + (obj->*method)(args...); + }; + + // 创建ESP32线程配置 + auto esp_cfg = create_esp_config(config); + + // 设置线程配置 + esp_pthread_set_cfg(&esp_cfg); + + // 创建并启动线程 + return std::thread(task); + } + + /** + * @brief 创建并启动一个执行单例类成员函数的线程 + * + * @tparam T 单例类类型 + * @tparam Method 成员函数类型 + * @tparam Args 参数类型 + * @param config 线程配置 + * @param method 成员函数指针 + * @param args 函数参数 + * @return std::thread 创建的线程对象 + */ + template + static std::thread createSingletonThread(const ThreadConfig& config, Method&& method, Args&&... args) { + // 获取单例实例 + T* instance = T::getInstance(); // 获取单例实例,注意这里就要求每个单例都要实现这个同名的静态函数 + + // 使用lambda表达式包装成员函数调用 + auto task = [instance, method, args...]() { + (instance->*method)(args...); + }; + + // 创建ESP32线程配置 + auto esp_cfg = create_esp_config(config); + + // 设置线程配置 + esp_pthread_set_cfg(&esp_cfg); + + // 创建并启动线程 + return std::thread(task); + } + + /** + * @brief 打印当前线程信息 + * + * @param extra 额外信息 + */ + static void printThreadInfo(const char *extra = nullptr) { + std::stringstream ss; + if (extra) { + ss << extra; + } + ss << "Core id: " << xPortGetCoreID() + << ", prio: " << uxTaskPriorityGet(nullptr) + << ", minimum free stack: " << uxTaskGetStackHighWaterMark(nullptr) << " bytes."; + ESP_LOGI(pcTaskGetName(nullptr), "%s", ss.str().c_str()); + } + +private: + /** + * @brief 创建ESP32线程配置 + * + * @param config 线程配置 + * @return esp_pthread_cfg_t ESP32线程配置 + */ + static esp_pthread_cfg_t create_esp_config(const ThreadConfig& config) { + auto cfg = esp_pthread_get_default_config(); + cfg.thread_name = config.name.c_str(); + cfg.pin_to_core = config.core_id; + cfg.stack_size = config.stack_size; + cfg.prio = config.priority; + cfg.inherit_cfg = config.inherit_cfg; + return cfg; + } +}; \ No newline at end of file diff --git a/Bionic_Core/ToolsClass/ToolsClass.cpp b/Bionic_Core/ToolsClass/ToolsClass.cpp index b88f6ea..2bef9cc 100644 --- a/Bionic_Core/ToolsClass/ToolsClass.cpp +++ b/Bionic_Core/ToolsClass/ToolsClass.cpp @@ -2,4 +2,3 @@ // Created by misaki on 2025/9/2. // -#include "ToolsClass.h" \ No newline at end of file diff --git a/Bionic_Core/ToolsClass/ToolsClass.h b/Bionic_Core/ToolsClass/ToolsClass.h index 84fb563..b8e535a 100644 --- a/Bionic_Core/ToolsClass/ToolsClass.h +++ b/Bionic_Core/ToolsClass/ToolsClass.h @@ -3,14 +3,10 @@ // #pragma once -#ifdef __cplusplus -extern "C" { -#endif +/** + * 本模块提供各种杂项工具类,基本都来源于对底层驱动的封装 + * + * + */ - - - -#ifdef __cplusplus -} -#endif diff --git a/Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp b/Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp new file mode 100644 index 0000000..39e4a87 --- /dev/null +++ b/Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp @@ -0,0 +1,74 @@ +// +// Created by misaki on 2025/9/4. +// + + +#include "WifiConnectors.h" +#include "Wireless.h" + +WifiConnectors* WifiConnectors::WifiConnectorsInstance = nullptr; /// 单例实例 +std::mutex WifiConnectors::m_mutex; + + +WifiConnectors *WifiConnectors::getInstance() { + // 双检锁(DCLP),C++11 起 atomic+mutex 组合保证线程安全 + WifiConnectors* tmp = WifiConnectorsInstance; + if (tmp == nullptr) { + std::lock_guard lock(m_mutex); + tmp = WifiConnectorsInstance; + if (tmp == nullptr) { + tmp = new WifiConnectors(); + WifiConnectorsInstance = tmp; + } + } + return tmp; +} + +WifiConnectors::WifiConnectors() { + // 在此处调用底层的Wifi初始化驱动,这样Wifi就只会被初始化一次 + Wireless_Init(); + WIFI_Init(nullptr); + ESP_LOGI("WifiConnectors", "WifiConnectors getInstance");// 在此处调用底层的Wifi初始化驱动,这样Wifi就只会被初始化一次 +} + +// 析构函数 +WifiConnectors::~WifiConnectors() { + +} + +// 连接Wifi +bool WifiConnectors::connectWifi(const std::string &ssid, const std::string &password, uint8_t max_retry) { + ESP_LOGI("WifiConnectors", "WifiConnectors connectWifi"); + ESP_LOGI("WifiConnectors", "Now Try to connect %s", ssid.c_str()); + if (WiFi_AutoConnect(ssid.c_str(), password.c_str(), max_retry)) { + ESP_LOGI("WifiConnectors", "WifiConnectors 连接WIFI %s 成功!!!", ssid.c_str()); + this->isConnected = true; // 设置已连接 + return true; + } + ESP_LOGI("WifiConnectors", "WifiConnectors 连接Wifi失败!!!"); + return false; +} + +// 断开Wifi +bool WifiConnectors::disconnectWifi() { + if (WiFi_Disconnect() == ESP_OK) { + ESP_LOGI("WifiConnectors", "WifiConnectors disconnectWifi"); + this->isConnected = false; // 设置未连接 + return true; + } + return false; +} + +bool WifiConnectors::isWifiConnect() { + return this->isConnected; +} + + + + +void WifiConnectors::log() { + ESP_LOGI("WifiConnectors", "WifiConnectors log"); + + WIFI_Scan(); +} + diff --git a/Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.h b/Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.h new file mode 100644 index 0000000..24aeb33 --- /dev/null +++ b/Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.h @@ -0,0 +1,48 @@ +// +// Created by misaki on 2025/9/4. +// +#pragma once + +#include +#include +class WifiConnectors{ + // 显然,Wifi连接必须是单例的,否则必然出现冲突 + +public: + // 获取单例的静态方法 + static WifiConnectors* getInstance(); + +public: + void log(); + + /** + * 连接Wifi + * 注意:此处只是对连接wifi接口做了C++封装,实际调用的时候需要为连接Wifi创建一个线程,不要让其阻塞主线程 + * @param ssid Wifi名称 + * @param password Wifi密码 + * @param max_retry 最大重连次数 + * @return 最终是否连接成功 + */ + bool connectWifi(const std::string& ssid, const std::string& password, uint8_t max_retry = 3); + + /** + * + * @return 是否成功断开连接 + */ + bool disconnectWifi(); + + bool isWifiConnect(); + +private: + explicit WifiConnectors(); // 构造函数私有化 + ~WifiConnectors(); // 析构函数私有化 + + WifiConnectors(const WifiConnectors&) = delete; // 禁止拷贝 + WifiConnectors& operator=(const WifiConnectors&) = delete; // 禁止赋值 + +private: + static WifiConnectors *WifiConnectorsInstance; /// 单例实例指针 + static std::mutex m_mutex; /// 互斥锁以确保线程安全 + + bool isConnected = false; /// 当前Wifi是否连接成功 +}; diff --git a/Lib/OTA_Driver/app_ota.c b/Lib/OTA_Driver/app_ota.c new file mode 100644 index 0000000..5b619d4 --- /dev/null +++ b/Lib/OTA_Driver/app_ota.c @@ -0,0 +1,87 @@ +// +// Created by misaki on 2025/9/4. +// + +#include "app_ota.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_http_client.h" +#include "esp_https_ota.h" +#include "freertos/task.h" + +static const char *TAG = "app_ota"; +#define DEFAULT_VERSION "0.0.1" + +/* 本地版本号,编译时可由构建系统注入 */ +#ifndef APP_FW_VERSION +#define APP_FW_VERSION DEFAULT_VERSION +#endif + +static const char s_version[] = APP_FW_VERSION; + +const char *app_ota_current_version(void) +{ + return s_version; +} + +/* 简单版本号比较:a.b.c 字符串比较即可 */ +static bool need_update(const char *server_ver) +{ + if (!server_ver) return false; + return strcmp(server_ver, s_version) > 0; +} + +/* HTTP 事件回调,仅打印进度 */ +static esp_err_t http_evt(esp_http_client_event_t *evt) +{ + switch (evt->event_id) { + case HTTP_EVENT_ON_DATA: + /* 数据流直接走 OTA,这里不打印 */ + break; + default: + break; + } + return ESP_OK; +} + +static void ota_task(void *pv) +{ + char url[256]; + strncpy(url, (const char *)pv, sizeof(url) - 1); + url[sizeof(url) - 1] = '\0'; + + ESP_LOGI(TAG, "开始 OTA,URL=%s", url); + + esp_http_client_config_t http_cfg = { + .url = url, + .event_handler = http_evt, + .keep_alive_enable = true, + // 如用 https,把 cert_pem 打开即可 + // .cert_pem = (const char *)server_cert_pem_start, + }; + + esp_https_ota_config_t ota_cfg = { + .http_config = &http_cfg, + }; + + /* 执行升级 */ + esp_err_t ret = esp_https_ota(&ota_cfg); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "OTA 完成,准备重启"); + esp_restart(); + } else { + ESP_LOGE(TAG, "OTA 失败,err=%s", esp_err_to_name(ret)); + } + + vTaskDelete(NULL); +} + +esp_err_t app_ota_start(const char *url) +{ + if (!url) return ESP_ERR_INVALID_ARG; + /* 内部建 Task,栈 8 KB */ + BaseType_t ok = xTaskCreate(ota_task, "ota_task", 8192, (void *)url, 5, NULL); + return ok == pdPASS ? ESP_OK : ESP_FAIL; +} + + diff --git a/Lib/OTA_Driver/app_ota.h b/Lib/OTA_Driver/app_ota.h new file mode 100644 index 0000000..a9c770c --- /dev/null +++ b/Lib/OTA_Driver/app_ota.h @@ -0,0 +1,13 @@ +// +// Created by misaki on 2025/9/4. +// + +#pragma once + +#include "esp_err.h" + +/* 一键启动 OTA(阻塞,内部建 Task) */ +esp_err_t app_ota_start(const char *url); + +/* 获取当前运行版本号(返回静态指针) */ +const char *app_ota_current_version(void); \ No newline at end of file diff --git a/Lib/OTA_Driver/ota_ws.c b/Lib/OTA_Driver/ota_ws.c new file mode 100644 index 0000000..01ac4dc --- /dev/null +++ b/Lib/OTA_Driver/ota_ws.c @@ -0,0 +1,97 @@ +// +// Created by misaki on 2025/9/4. +// + +#include "ota_ws.h" +#include "esp_log.h" +#include "esp_websocket_client.h" +#include "esp_ota_ops.h" +#include "esp_http_client.h" +#include "esp_https_ota.h" +#include "cJSON.h" +#include "esp_system.h" +#include "esp_mac.h" + +static const char *TAG = "OTA_WS"; + +#define FW_VERSION "1.0.1" // ←每次发布修改这里 +#define WS_URL_FMT "ws://%s:%d/ws?mac=%02X%02X%02X%02X%02X%02X" + +static esp_websocket_client_handle_t ws = NULL; + +static void ota_start(const char *url) +{ + esp_http_client_config_t config = { + .url = url, + .keep_alive_enable = true, + }; + esp_https_ota_config_t ota_cfg = { .http_config = &config }; + ESP_LOGI(TAG, "开始下载固件..."); + esp_err_t ret = esp_https_ota(&ota_cfg); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "下载完成,重启"); + esp_restart(); + } else { + ESP_LOGE(TAG, "OTA 失败:%s", esp_err_to_name(ret)); + } +} + +static void ws_event(void *arg, esp_event_base_t base, int32_t id, void *data) +{ + esp_websocket_event_data_t *d = (esp_websocket_event_data_t *)data; + switch (id) { + case WEBSOCKET_EVENT_CONNECTED: + ESP_LOGI(TAG, "WebSocket 已连接"); + break; + + case WEBSOCKET_EVENT_DATA: + if (d->op_code == WS_TRANSPORT_OPCODES_TEXT) { + char *json = malloc(d->data_len + 1); + if (!json) break; + memcpy(json, d->data_ptr, d->data_len); + json[d->data_len] = 0; + + cJSON *root = cJSON_Parse(json); + free(json); + if (root) { + cJSON *cmd = cJSON_GetObjectItem(root, "cmd"); + if (cmd && strcmp(cmd->valuestring, "upgrade") == 0) { + const char *url = cJSON_GetObjectItem(root, "url")->valuestring; + ESP_LOGI(TAG, "收到升级指令,URL=%s", url); + ota_start(url); + } + cJSON_Delete(root); + } + } + break; + + case WEBSOCKET_EVENT_DISCONNECTED: + ESP_LOGW(TAG, "WS 断开,5 秒后重连..."); + vTaskDelay(pdMS_TO_TICKS(5000)); + esp_websocket_client_start(ws); + break; + + default: + break; + } +} + +esp_err_t ota_ws_start(const char *server_ip, uint16_t port) +{ + if (ws) return ESP_OK; + + uint8_t mac[6]; + esp_efuse_mac_get_default(mac); + char uri[128]; + snprintf(uri, sizeof(uri), WS_URL_FMT, + server_ip, port, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + esp_websocket_client_config_t cfg = { + .uri = uri, + .ping_interval_sec = 30, + }; + ws = esp_websocket_client_init(&cfg); + esp_websocket_register_events(ws, WEBSOCKET_EVENT_ANY, ws_event, NULL); + esp_websocket_client_start(ws); + return ESP_OK; +} \ No newline at end of file diff --git a/Lib/OTA_Driver/ota_ws.h b/Lib/OTA_Driver/ota_ws.h new file mode 100644 index 0000000..9b5f501 --- /dev/null +++ b/Lib/OTA_Driver/ota_ws.h @@ -0,0 +1,23 @@ +// +// Created by misaki on 2025/9/4. +// +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" + +/** + * @brief 启动 WebSocket 长连接,接收服务器推送的升级指令 + * @param server_ip 服务器 IP 字符串,如 "192.168.1.100" + * @return esp_err_t + */ +esp_err_t ota_ws_start(const char *server_ip, uint16_t port); + + + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Lib/Wireless/Wireless.c b/Lib/Wireless/Wireless.c index a30b12a..e34b2cf 100644 --- a/Lib/Wireless/Wireless.c +++ b/Lib/Wireless/Wireless.c @@ -14,46 +14,41 @@ ble_device_info_t *ble_device_list = NULL; void Wireless_Init(void) { // Initialize 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_err_t ret = nvs_flash_init(); // 初始化Flash + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { // 如果Flash有错误,则进行修复 + ESP_ERROR_CHECK(nvs_flash_erase()); // 清空Flash + ret = nvs_flash_init(); // 重新初始化Flash } - ESP_ERROR_CHECK( ret ); - // WiFi - xTaskCreatePinnedToCore( - WIFI_Init, - "WIFI task", - 4096, - NULL, - 1, - NULL, - 0); - // BLE - xTaskCreatePinnedToCore( - BLE_Init, - "BLE task", - 4096, - NULL, - 2, - NULL, - 0); + ESP_ERROR_CHECK( ret ); // 检查错误 + // WiFi 手动调用Wifi初始化 + // xTaskCreatePinnedToCore( + // WIFI_Init, + // "WIFI task", + // 4096, + // NULL, + // 1, + // NULL, + // 0); + // BLE 当前工程不启用蓝牙 + // xTaskCreatePinnedToCore( + // BLE_Init, + // "BLE task", + // 4096, + // NULL, + // 2, + // NULL, + // 0); } void WIFI_Init(void *arg) { - esp_netif_init(); - esp_event_loop_create_default(); - esp_netif_create_default_wifi_sta(); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - esp_wifi_init(&cfg); - esp_wifi_set_mode(WIFI_MODE_STA); - esp_wifi_start(); - - WIFI_NUM = WIFI_Scan(); - printf("WIFI:%d\r\n",WIFI_NUM); - - vTaskDelete(NULL); + esp_netif_init(); // 初始化网络接口 + esp_event_loop_create_default(); // 创建事件循环 + esp_netif_create_default_wifi_sta(); // 创建默认的WiFi Station + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); // 创建WiFi初始化配置(使用默认配置) + esp_wifi_init(&cfg); // 初始化WiFi + esp_wifi_set_mode(WIFI_MODE_STA); // 设置WiFi模式为Station + esp_wifi_start(); // 启动WiFi } @@ -269,6 +264,88 @@ uint16_t BLE_Scan(void) return BLE_NUM; } + +// 下面是新增的实现 +static EventGroupHandle_t s_wifi_event_group; +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 + +static const char *TAG = "WiFi_UTIL"; + +/* --------------------------- 事件回调 --------------------------- */ +static void 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_DISCONNECTED) { + xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } +} + +/* --------------------------- 自动连接 --------------------------- */ +bool WiFi_AutoConnect(const char *ssid, const char *password, uint8_t max_retry) +{ + if (s_wifi_event_group == NULL) { + s_wifi_event_group = xEventGroupCreate(); + } + + /* 注册一次性事件监听 */ + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + WIFI_EVENT_STA_DISCONNECTED, + &event_handler, + NULL, + NULL)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &event_handler, + NULL, + NULL)); + + wifi_config_t wifi_config = { 0 }; + strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); + strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password)); + wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; + + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + + uint8_t retry_cnt = 0; + while (retry_cnt <= max_retry) { + ESP_LOGI(TAG, "WiFi connect attempt %u/%u to \"%s\"", retry_cnt + 1, max_retry + 1, ssid); + + xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT); + esp_wifi_connect(); + + EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, pdFALSE, + pdMS_TO_TICKS(6000)); /* 6 秒超时 */ + + if (bits & WIFI_CONNECTED_BIT) { + ESP_LOGI(TAG, "WiFi connected"); + goto success; + } + + /* 失败,准备重试 */ + retry_cnt++; + } + + ESP_LOGE(TAG, "WiFi connect failed after %u retries", max_retry + 1); + return false; + +success: + return true; +} + +/* --------------------------- 断开连接 --------------------------- */ +esp_err_t WiFi_Disconnect(void) +{ + ESP_LOGI(TAG, "Disconnecting WiFi..."); + return esp_wifi_disconnect(); +} + + + // 获取WiFi AP列表 uint16_t wireless_get_wifi_ap_list(wifi_ap_info_t *ap_list, uint16_t max_aps) { if (!wifi_ap_list || wifi_ap_count == 0) { diff --git a/Lib/Wireless/Wireless.h b/Lib/Wireless/Wireless.h index 1cf46f3..88aa518 100644 --- a/Lib/Wireless/Wireless.h +++ b/Lib/Wireless/Wireless.h @@ -41,7 +41,25 @@ uint16_t WIFI_Scan(void); void BLE_Init(void *arg); uint16_t BLE_Scan(void); -// 新增接口函数 +// 下面的函数为新增的函数 +// Code By Misaki + +/** + * @brief 自动连接指定 Wi-Fi,带重试 + * @param ssid 目标热点名称 + * @param password 密码 + * @param max_retry 最大重连次数(0 表示仅尝试一次) + * @return true 成功连接 + * false 达到重试上限仍未连上 + */ +bool WiFi_AutoConnect(const char *ssid, const char *password, uint8_t max_retry); + +/** + * @brief 断开当前 STA 连接 + */ +esp_err_t WiFi_Disconnect(void); + +// 接口函数 uint16_t wireless_get_wifi_ap_list(wifi_ap_info_t *ap_list, uint16_t max_aps); uint16_t wireless_get_ble_device_list(ble_device_info_t *device_list, uint16_t max_devices); void wireless_print_wifi_aps(void); diff --git a/dependencies.lock b/dependencies.lock index 6ae631b..87899c7 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -47,6 +47,16 @@ dependencies: registry_url: https://components.espressif.com/ type: service version: 1.9.5 + espressif/esp_websocket_client: + component_hash: ac62982fcf9b266409c2299d2b6b1844122105b35163a3b7f8d0adaa9f7eb989 + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.5.0 idf: source: type: idf @@ -62,8 +72,9 @@ direct_dependencies: - chmorgan/esp-audio-player - chmorgan/esp-libhelix-mp3 - espressif/esp-sr +- espressif/esp_websocket_client - idf - lvgl/lvgl -manifest_hash: 9bc1eee4314d6f75a0822d5e087c5603182eb609f99c1dd5726d7cea387e4d1d +manifest_hash: 2f09fb29ee9918e287005c3aa0d08252d2551c70eed608468245557ab01ac54b target: esp32s3 version: 2.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 4603c48..1766f26 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -25,11 +25,15 @@ idf_component_register(SRCS "Bionic_sphere.c" "../Lib/Display/Touch_Driver/esp_lcd_touch/esp_lcd_touch.c" # 触摸屏驱动库 "../Lib/PWR_Key/PWR_Key.c" # PWR按键驱动库 "../Lib/MIC_Driver/MIC_Speech.c" # 录音驱动库 + "../Lib/OTA_Driver/app_ota.c" # OTA驱动库 + "../Lib/OTA_Driver/ota_ws.c" # 业务代码(使用Cpp编写) "../Bionic_Core/PetBaseClass/PetBaseClass.cpp" # 宠物基类库 "../Bionic_Core/OTAClass/OTAClass.cpp" # OTA类库 "../Bionic_Core/CommClass/CommClass.cpp" # 通信类库 "../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库 + "../Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp" # WIFI连接类库 + "../Bionic_Core/ToolsClass/ThreadManager/ThreadManager.cpp" # 线程管理类库 "../Bionic_Core/CppHandle/CppHandle.cpp" # C++&C兼容库 INCLUDE_DIRS "." "../test/driver_test" @@ -55,10 +59,13 @@ idf_component_register(SRCS "Bionic_sphere.c" "../Lib/Display/Touch_Driver/esp_lcd_touch" "../Lib/PWR_Key" "../Lib/MIC_Driver" + "../Lib/OTA_Driver" "../Bionic_Core/PetBaseClass" "../Bionic_Core/OTAClass" "../Bionic_Core/CommClass" "../Bionic_Core/ToolsClass" + "../Bionic_Core/ToolsClass/WifiConnectors" + "../Bionic_Core/ToolsClass/ThreadManager" "../Bionic_Core/CppHandle" PRIV_REQUIRES # 私有依赖 driver @@ -70,4 +77,12 @@ idf_component_register(SRCS "Bionic_sphere.c" esp_lcd lvgl__lvgl fatfs + pthread + app_update + esp_http_client + esp_websocket_client + esp_https_ota + json ) + +target_compile_options(${COMPONENT_LIB} PUBLIC -std=gnu++17) diff --git a/main/idf_component.yml b/main/idf_component.yml index 279ebfa..55c713d 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -1,6 +1,7 @@ dependencies: - idf: ">=4.4" - lvgl/lvgl: "~8.3.0" - chmorgan/esp-audio-player: "==1.0.7" - chmorgan/esp-libhelix-mp3: "==1.0.3" - espressif/esp-sr: "~1.9.4" \ No newline at end of file + idf: '>=4.4' + lvgl/lvgl: ~8.3.0 + chmorgan/esp-audio-player: ==1.0.7 + chmorgan/esp-libhelix-mp3: ==1.0.3 + espressif/esp-sr: ~1.9.4 + espressif/esp_websocket_client: ^1.0.0 diff --git a/partitions.csv b/partitions.csv index cadd0e8..fae0cd9 100644 --- a/partitions.csv +++ b/partitions.csv @@ -8,6 +8,9 @@ # Offset:起始地址(通常不写,让系统自动计算)。 # Size:大小,比如 1M, 64K, 0x100000 等。 # Flags:可选标志,比如 encrypted(加密)。 +# 注意:因为没有设置工厂分区,那么每次烧录的时候程序会烧录在ota_0分区 +# 根据官方文档OTA 数据分区的容量是 2 个 flash 扇区的大小(0x2000 字节) +# 那么可以计算出 OTA 数据分区大小最大为 64MB (2*16**3 *0x1000 / 1024 / 1024)MB # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, , 0x6000, diff --git a/sdkconfig b/sdkconfig index 6d8b7b5..e63ec21 100644 --- a/sdkconfig +++ b/sdkconfig @@ -1413,7 +1413,7 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32 # ESP HTTPS OTA # # CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set -# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set +CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y # end of ESP HTTPS OTA # @@ -2635,6 +2635,13 @@ CONFIG_DSP_MAX_FFT_SIZE_4096=y CONFIG_DSP_MAX_FFT_SIZE=4096 # end of DSP Library +# +# ESP WebSocket client +# +# CONFIG_ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER is not set +# CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK is not set +# end of ESP WebSocket client + # # LVGL configuration # @@ -3184,7 +3191,7 @@ CONFIG_POST_EVENTS_FROM_ISR=y CONFIG_POST_EVENTS_FROM_IRAM_ISR=y CONFIG_GDBSTUB_SUPPORT_TASKS=y CONFIG_GDBSTUB_MAX_TASKS=32 -# CONFIG_OTA_ALLOW_HTTP is not set +CONFIG_OTA_ALLOW_HTTP=y CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y diff --git a/sdkconfig.old b/sdkconfig.old index 2e9b51c..6d8b7b5 100644 --- a/sdkconfig.old +++ b/sdkconfig.old @@ -778,7 +778,7 @@ CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 # CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set CONFIG_COMPILER_HIDE_PATHS_MACROS=y # CONFIG_COMPILER_CXX_EXCEPTIONS is not set -# CONFIG_COMPILER_CXX_RTTI is not set +CONFIG_COMPILER_CXX_RTTI=y CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y # CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set # CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set diff --git a/项目开发日志.md b/项目开发日志.md index eab3852..d051ff3 100644 --- a/项目开发日志.md +++ b/项目开发日志.md @@ -187,4 +187,11 @@ 实际完成任务: - [x] 1. 构建了业务层的基本框架(增加了底层驱动对于的C++兼容),业务代码采用C++编写,启用了RTTI(运行时类型识别) -- [ ] 2. \ No newline at end of file +#### Day11 2025.9.4 +##### 主要目标:完成具体业务开发&各种优化 +实际完成任务: +- [x] 1. 完成了ota功能的基本测试,测试通过 + +- [x] 2. 封装了一个模板线程类,支持创建来自单例类的成员函数线程,普通类的线程,普通函数线程 + +- [x] 3. 封装了一个Wifi模块类,支持Wifi的各种基本配置