Commit 2be6a78d authored by 957dd's avatar 957dd

未完成

parents
CompileFlags:
Remove: [-f*, -m*]
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]
\ No newline at end of file
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}
\ No newline at end of file
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Directory metadata
.directory
# Temporary files
*~
*.swp
*.swo
*.bak
*.tmp
# Log files
*.log
# Build artifacts and directories
**/build/
build/
*.o
*.a
*.out
*.exe # For any host-side utilities compiled on Windows
# ESP-IDF specific build outputs
*.bin
*.elf
*.map
flasher_args.json # Generated in build directory
sdkconfig.old
sdkconfig
# ESP-IDF dependencies
# For older versions or manual component management
/components/.idf/
**/components/.idf/
# For modern ESP-IDF component manager
managed_components/
# If ESP-IDF tools are installed/referenced locally to the project
.espressif/
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
# Python environment files
*.pyc
*.pyo
*.pyd
__pycache__/
*.egg-info/
dist/
# Virtual environment folders
venv/
.venv/
env/
# Language Servers
.clangd/
.ccls-cache/
compile_commands.json
# Windows specific
Thumbs.db
ehthumbs.db
Desktop.ini
# User-specific configuration files
*.user
*.workspace # General workspace files, can be from various tools
*.suo # Visual Studio Solution User Options
*.sln.docstates # Visual Studio
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "${config:idf.toolsPathWin}\\tools\\xtensa-esp-elf\\esp-15.2.0_20250929\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe",
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
"includePath": [
"${config:idf.espIdfPath}/components/**",
"${config:idf.espIdfPathWin}/components/**",
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${config:idf.espIdfPath}/components",
"${config:idf.espIdfPathWin}/components",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}
\ No newline at end of file
{
"C_Cpp.intelliSenseEngine": "default",
"idf.espIdfPathWin": "C:\\Users\\17122\\esp\\v5.5.1\\esp-idf",
"idf.pythonInstallPath": "d:\\espidf\\esp-idf-tools\\tools\\idf-python\\3.11.2\\python.exe",
"idf.openOcdConfigs": [
"board/esp32s3-builtin.cfg"
],
"idf.toolsPathWin": "d:\\espidf\\esp-idf-tools",
"idf.customExtraVars": {
"IDF_TARGET": "esp32s3"
},
"clangd.path": "d:\\espidf\\esp-idf-tools\\tools\\esp-clang\\esp-20.1.1_20250829\\esp-clang\\bin\\clangd.exe",
"clangd.arguments": [
"--background-index",
"--query-driver=**",
"--compile-commands-dir=c:\\Users\\17122\\Desktop\\机甲大师自话\\H60\\esp32project\\RobotMiddle\\build"
],
"idf.portWin": "COM24",
"idf.flashType": "UART"
}
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(RobotMiddle)
dependencies:
espressif/cJSON:
component_hash: 9372811fb197926f522c467627cf4a8e72b681e0366e17879631da801103aef3
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.7.19
idf:
source:
type: idf
version: 5.5.1
direct_dependencies:
- espressif/cJSON
- idf
manifest_hash: 67a457718a3b3cb206e122b6946d05b7c63ba9fd523dd53bfc8925a4a062f006
target: esp32s3
version: 2.0.0
set(SOURCES "wifidevnum_config.c"
"mqttconf/mqttconf_commun.c"
#"led/gpio_led.c"
"serial/serial.c"
"main.c")
set(INCLUDE_DIRS "." "mqttconf" "serial")
idf_component_register(SRCS ${SOURCES}
INCLUDE_DIRS ${INCLUDE_DIRS}
#HOLE_ARCHIVE
)
spiffs_create_partition_image(storage ../www FLASH_IN_PROJECT)
menu "robot-esp32s3"
config ROBIOT_WIFI_SSID
string "ROBITO WIFI SSID"
default "esp32-apconfig"
help
Hotspot Name Settings.
config OTA_VERSION_URL
string "OTA Version URL"
default "wu"
help
The application will access this URL to check for updates.
config ROBOIOT_MQTT_URL
string "MQTT URL"
default "https://fcrs-api.yd-ss.com/device/getConfig?deviceNo="
help
MQTT Config URL
endmenu
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
espressif/cJSON: '*'
#include <stdio.h>
#include <string.h>
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "wifidevnum_config.h"
#include "mqttconf_commun.h"
#include "serial.h"
static const char *TAG = "MAIN";
static bool init_task_triggered = false;
static void wifi_event_handler(void* arg, esp_event_base_t base, int32_t id, void* data) {
if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) data;
ESP_LOGI(TAG, "成功获取 IP: " IPSTR, IP2STR(&event->ip_info.ip));
char devid_check[32] = {0};
// 检查 NVS 是否有设备号
if (read_from_nvs("device_id", devid_check, sizeof(devid_check)) == ESP_OK && strlen(devid_check) > 0) {
if (!init_task_triggered) {
ESP_LOGI(TAG, "条件达成:网络+设备号已就绪,启动 MQTT 流程...");
mqtt_manager_init_sequence();
init_task_triggered = true;
}
} else {
ESP_LOGE(TAG, "警告:无法启动 MQTT,因为 NVS 中 device_id 为空!");
}
} else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGW(TAG, "WiFi 断开,尝试重连...");
esp_wifi_connect();
}
}
void app_main(void) {
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(init_spiffs()); // 启动 Web 前挂载 SPIFFS
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(serial_init());//初始化串口
// 关键:创建 STA 模式的网络接口句柄
esp_netif_create_default_wifi_sta();
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL);
esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &wifi_event_handler, NULL, NULL);
xTaskCreate(button_monitor_task, "btn_task", 4096, NULL, 5, NULL);
char ssid[32] = {0}, pass[64] = {0};
if (read_from_nvs("wifi_ssid", ssid, sizeof(ssid)) == ESP_OK) {
read_from_nvs("wifi_pass", pass, sizeof(pass));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
wifi_config_t sta_cfg = {
.sta = {
.scan_method = WIFI_ALL_CHANNEL_SCAN,
.sort_method = WIFI_CONNECT_AP_BY_SIGNAL,
},
};
strncpy((char*)sta_cfg.sta.ssid, ssid, sizeof(sta_cfg.sta.ssid));
strncpy((char*)sta_cfg.sta.password, pass, sizeof(sta_cfg.sta.password));
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &sta_cfg);
esp_wifi_start();
esp_wifi_connect();
xTaskCreate(serial_receive_task, "serial_rx", 4096, NULL, 10, NULL);
}
}
\ No newline at end of file
#include "mqttconf_commun.h"
#include "wifidevnum_config.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_http_client.h"
#include "mqtt_client.h"
#include "esp_crt_bundle.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "cJSON.h"
#include "sdkconfig.h"
#include "esp_heap_caps.h"
#include "serial.h"
static const char *TAG = "MQTT_MGR";
#define DEFAULT_MQTT_HOST "119.45.167.177"
// 最多支持同时连接的服务器数量
#define MAX_MQTT_CLIENTS 10
typedef struct {
char *buffer;
int len;
} http_response_t;
static char g_device_id[32] = {0};
static char g_sta_ip[16] = "0.0.0.0";
// 存储所有活跃的 MQTT 客户端句柄,用于心跳群发
static esp_mqtt_client_handle_t client_list[MAX_MQTT_CLIENTS] = {NULL};
static int client_count = 0;
static esp_timer_handle_t g_hb_timer = NULL;
/**
* @brief 远程控制解析
*/
static void handle_remote_control(const char *data) {
if (!data) return;
cJSON *root = cJSON_Parse(data);
if (!root) return;
cJSON *head = cJSON_GetObjectItem(root, "head");
if (head && cJSON_IsObject(head)) {
cJSON *m_type = cJSON_GetObjectItem(head, "message_type");
cJSON *body = cJSON_GetObjectItem(root, "body");
if (m_type && m_type->valueint == 4 && body) {
cJSON *pin_ctrl = cJSON_GetObjectItem(body, "pin_setctrl");
if (pin_ctrl) {
int pin = cJSON_GetObjectItem(pin_ctrl, "pin")->valueint;
int val = cJSON_GetObjectItem(pin_ctrl, "val")->valueint;
gpio_reset_pin(pin);
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
gpio_set_level(pin, val);
ESP_LOGW(TAG, "GPIO控制: Pin %d -> %d", pin, val);
}
} else if (m_type && m_type->valueint == 3 && body) {
cJSON *pwm_ctrl = cJSON_GetObjectItem(body, "pwm_ctrl");
if (pwm_ctrl) {
int mode = cJSON_GetObjectItem(pwm_ctrl, "mode")->valueint;
int type = cJSON_GetObjectItem(pwm_ctrl, "type")->valueint;
int val = cJSON_GetObjectItem(pwm_ctrl, "val")->valueint;
ESP_LOGW(TAG, "PWM指令: m:%d t:%d v:%d", mode, type, val);
serial_send_command(mode, val, 0, 0,0); // 串口下发给STM32
}
}
}
cJSON_Delete(root);
}
/**
* @brief 构造心跳包内容
*/
static char* generate_hb_payload() {
cJSON *root = cJSON_CreateObject();
cJSON *body = cJSON_CreateObject();
cJSON_AddStringToObject(body, "ip", g_sta_ip);
char id_full[64], volat[16], tempvalue[16];
snprintf(id_full, sizeof(id_full), "app2dev/%s", g_device_id);
snprintf(volat, sizeof(volat), "%.2f", get_stm32_battery_voltage());
snprintf(tempvalue, sizeof(tempvalue), "%.2f", get_stm32_battery_tempvalue());
cJSON_AddStringToObject(body, "device_ID", id_full);
cJSON_AddStringToObject(body, "voltage", volat);
cJSON_AddStringToObject(body, "tempvalue", tempvalue);
cJSON_AddStringToObject(body, "version", "1.1.0");
cJSON_AddItemToObject(root, "body", body);
cJSON *head = cJSON_CreateObject();
cJSON_AddNumberToObject(head, "message_type", 1);
cJSON_AddItemToObject(root, "head", head);
char *out = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return out;
}
/**
* @brief 心跳定时器:遍历所有已连接的客户端发送心跳
*/
static void mqtt_heartbeat_timer_cb(void *arg) {
char *json = generate_hb_payload();
if (!json) return;
if(get_serial_enabled()==false) {
free(json);
return;
}
//for (int i = 0; i < client_count; i++) {
//if (client_list[i] != NULL) {
if (strlen(g_device_id) == 0) {
esp_mqtt_client_publish(client_list[0], "000000000", json, 0, 1, 0);
esp_mqtt_client_publish(client_list[0], "dev2app/000000000", json, 0, 1, 0);
free(json);
return;
}
// 向每个服务器发送心跳,确保 App 在每个 Broker 都能看到设备在线
esp_mqtt_client_publish(client_list[0], g_device_id, json, 0, 1, 0);
char t2[64];
snprintf(t2, sizeof(t2), "dev2app/%s", g_device_id);
esp_mqtt_client_publish(client_list[0], t2, json, 0, 1, 0);
// }
//}
free(json);
}
/**
* @brief MQTT 事件回调
*/
static void common_mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
esp_mqtt_event_handle_t event = event_data;
const char *ip = (const char *)handler_args;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "[%s] 已连接", ip);
char s1[64], s2[64];
snprintf(s1, sizeof(s1), "app2dev/%s", g_device_id);
snprintf(s2, sizeof(s2), "ser2dev/%s", g_device_id);
esp_mqtt_client_subscribe(event->client, s1, 1);
esp_mqtt_client_subscribe(event->client, s2, 1);
break;
case MQTT_EVENT_DATA:
if (event->data_len > 0) {
char *tmp = malloc(event->data_len + 1);
if (tmp) {
memcpy(tmp, event->data, event->data_len);
tmp[event->data_len] = '\0';
handle_remote_control(tmp);
free(tmp);
}
}
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGW(TAG, "[%s] 连接断开", ip);
break;
case MQTT_EVENT_ERROR:
ESP_LOGE(TAG, "[%s] 发生错误", ip);
break;
default: break;
}
}
/**
* @brief 初始化并启动一个 MQTT 客户端
*/
static void start_mqtt_client(const char *ip) {
if (!ip || client_count >= MAX_MQTT_CLIENTS) return;
char *persist_ip = strdup(ip);
char uri[128];
snprintf(uri, sizeof(uri), "mqtt://%s:1883", persist_ip);
ESP_LOGI(TAG, "启动并发连接: %s", uri);
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = uri,
.credentials.username = "admin",
.credentials.authentication.password = "admin",
.session.keepalive = 60,
.network.timeout_ms = 20000,
.buffer.size = 2048,
.task.stack_size = 6144,
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
if (client) {
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, common_mqtt_event_handler, (void*)persist_ip);
esp_mqtt_client_start(client);
client_list[client_count++] = client; // 加入心跳群发列表
}
}
esp_err_t _http_event_handler(esp_http_client_event_t *evt) {
http_response_t *response = (http_response_t *)evt->user_data;
if (evt->event_id == HTTP_EVENT_ON_DATA) {
char *new_buf = realloc(response->buffer, response->len + evt->data_len + 1);
if (new_buf) {
response->buffer = new_buf;
memcpy(response->buffer + response->len, evt->data, evt->data_len);
response->len += evt->data_len;
response->buffer[response->len] = '\0';
}
}
return ESP_OK;
}
/**
* @brief 初始化任务:拉取列表并同时启动所有连接
*/
static void config_pull_and_init_task(void *pvParameters) {
vTaskDelay(pdMS_TO_TICKS(5000));
esp_netif_ip_info_t ip_info;
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) {
esp_ip4addr_ntoa(&ip_info.ip, g_sta_ip, sizeof(g_sta_ip));
}
read_from_nvs("device_id", g_device_id, sizeof(g_device_id)-1);
if (strlen(g_device_id) == 0) {
start_mqtt_client(DEFAULT_MQTT_HOST);
goto start_timer;
}
char url[256];
snprintf(url, sizeof(url), "%s%s", CONFIG_ROBOIOT_MQTT_URL, g_device_id);
http_response_t resp = { .buffer = NULL, .len = 0 };
esp_http_client_config_t http_cfg = {
.url = url,
.event_handler = _http_event_handler,
.user_data = &resp,
.crt_bundle_attach = esp_crt_bundle_attach,
.timeout_ms = 8000,
};
esp_http_client_handle_t http_client = esp_http_client_init(&http_cfg);
bool dynamic_ok = false;
if (esp_http_client_perform(http_client) == ESP_OK && resp.buffer) {
cJSON *root = cJSON_Parse(resp.buffer);
if (root) {
cJSON *data_node = cJSON_GetObjectItem(root, "data");
if (data_node) {
cJSON *mqtt_list = cJSON_GetObjectItem(data_node, "mqtt");
if (cJSON_IsArray(mqtt_list)) {
dynamic_ok = true;
int sz = cJSON_GetArraySize(mqtt_list);
for (int i = 0; i < sz; i++) {
cJSON *item = cJSON_GetArrayItem(mqtt_list, i);
if (cJSON_IsString(item)) {
start_mqtt_client(item->valuestring); // 同时启动
}
}
}
}
cJSON_Delete(root);
}
}
if (resp.buffer) free(resp.buffer);
esp_http_client_cleanup(http_client);
if (!dynamic_ok) {
start_mqtt_client(DEFAULT_MQTT_HOST);
}
start_timer:
// 统一心跳定时器(每3秒群发一次)
const esp_timer_create_args_t timer_args = {
.callback = &mqtt_heartbeat_timer_cb,
.name = "hb_timer"
};
if (esp_timer_create(&timer_args, &g_hb_timer) == ESP_OK) {
esp_timer_start_periodic(g_hb_timer, 3000000);
ESP_LOGI(TAG, "并发心跳群发机制已启动");
}
vTaskDelete(NULL);
}
void mqtt_manager_init_sequence(void) {
xTaskCreate(config_pull_and_init_task, "mqtt_init", 10240, NULL, 5, NULL);
}
// 新增停止函数
void mqtt_manager_stop(void) {
ESP_LOGW(TAG, "正在停止 MQTT 服务...");
// 1. 停止并删除心跳定时器
if (g_hb_timer != NULL) {
esp_timer_stop(g_hb_timer);
esp_timer_delete(g_hb_timer);
g_hb_timer = NULL;
}
// 2. 销毁所有 MQTT 客户端
for (int i = 0; i < client_count; i++) {
if (client_list[i] != NULL) {
esp_mqtt_client_stop(client_list[i]);
esp_mqtt_client_disconnect(client_list[i]);
esp_mqtt_client_destroy(client_list[i]);
client_list[i] = NULL;
}
}
client_count = 0;
ESP_LOGI(TAG, "MQTT 服务已完全停止");
}
#ifndef __MQTTCONF_COMMUN_H__
#define __MQTTCONF_COMMUN_H__
#include "esp_err.h"
#define MQTT_SERVICE_NUM_MAX 10
// 启动所有 MQTT 相关逻辑
void mqtt_manager_init_sequence(void);
void mqtt_manager_stop(void);
#endif
\ No newline at end of file
#include "OTA.h"
\ No newline at end of file
#ifndef __OTA_H__
#define __OTA_H__
#include "esp_err.h"
#endif
\ No newline at end of file
#include "serial.h"
#include <string.h>
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
static const char *TAG = "STM32_SERIAL";
beatstatus_e beat_status = BEAT_UNKNOWN;
// 记录最后一次收到数据的时间 (单位: 微秒)
static int64_t g_last_rx_time = 0;
static float stm32_Battery_voltage = 0;
static float stm32_tempvalue=0;
static bool g_serial_enabled = true; // <--- 新增控制位
void set_serial_enabled(bool enabled) {
g_serial_enabled = enabled;
}
bool get_serial_enabled() {
return g_serial_enabled;
}
//static bool g_serial_stm32_flag = false;
/**
* @brief 串口初始化
*/
esp_err_t serial_init(void) {
uart_config_t uart_config = {
.baud_rate = STM32_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
// 安装驱动
ESP_ERROR_CHECK(uart_driver_install(STM32_UART_PORT, 1024 * 2, 0, 0, NULL, 0));
// 配置参数
ESP_ERROR_CHECK(uart_param_config(STM32_UART_PORT, &uart_config));
// 设置引脚
ESP_ERROR_CHECK(uart_set_pin(STM32_UART_PORT, STM32_TXD_PIN, STM32_RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
// 初始化时间戳为当前时间,防止启动即报错
g_last_rx_time = esp_timer_get_time();
return ESP_OK;
}
/**
* @brief 发送 8 字节指令 (协议完全匹配 STM32)
*/
void serial_send_command(uint8_t mode, uint8_t value,uint8_t data3,uint8_t data4,uint8_t data5) {
if (!g_serial_enabled) return;
uint8_t frame[8];
frame[0] = 0x01; // 帧头
frame[1] = mode; // 数据位 1
frame[2] = value; // 数据位 2
frame[3] = data3; // 预留
frame[4] = data4; // 预留
frame[5] = data5; // 预留
// 生成和校验 (Byte1 + Byte2 + Byte3 + Byte4 + Byte5)
frame[6] = (uint8_t)((frame[1] + frame[2] + frame[3] + frame[4] + frame[5]) & 0xFF);
frame[7] = 0xFF; // 停止位 (STM32 代码中 frame[7] 是 0xFF)
uart_write_bytes(STM32_UART_PORT, (const char *)frame, 8);
}
float get_stm32_battery_voltage(){
return stm32_Battery_voltage;
}
float get_stm32_battery_tempvalue(){
return stm32_tempvalue;
}
void recserial_data(uint8_t *data){
if(data[1]==0x00){
uint16_t voltage_int = (data[4] << 8) | data[5];
stm32_Battery_voltage = voltage_int / 100.0f;
uint16_t temp_int = (data[2] << 8) | data[3];
stm32_tempvalue = temp_int / 100.0f;
ESP_LOGI(TAG, "电压: %.2fV, 温度: %.2f", stm32_Battery_voltage, stm32_tempvalue);
}
}
/**
* @brief 接收与超时检测任务
*/
void serial_receive_task(void *pvParameters) {
uint8_t data[128];
uint8_t frame_buf[8];
serial_send_command(0x00,0x00,0x00,0x00,0x00);
while (1) {
if(g_serial_enabled != true){
vTaskDelay(pdMS_TO_TICKS(100));
break;
}
// 1. 读取串口数据 (非阻塞或短超时)
int len = uart_read_bytes(STM32_UART_PORT, data, sizeof(data), pdMS_TO_TICKS(100));
if (len > 0) {
// 简单协议解析逻辑:寻找 0x01 帧头且长度满足 8 字节
// 提示:在复杂环境下建议使用状态机解析
for (int i = 0; i < len; i++) {
if (data[i] == 0x01 && (i + 7) < len) {
memcpy(frame_buf, &data[i], 8);
// 校验和验证
uint8_t check = (uint8_t)((frame_buf[1] + frame_buf[2] + frame_buf[3] + frame_buf[4] + frame_buf[5]) & 0xFF);
if (frame_buf[6] == check && frame_buf[7] == 0xFF) {
recserial_data(frame_buf);
// 更新最后接收时间戳
g_last_rx_time = esp_timer_get_time();
i += 7; // 跳过处理完的帧
}
}
}
}
// 2. 超时检测 (10秒 = 10,000,000 微秒)
int64_t now = esp_timer_get_time();
if ((now - g_last_rx_time) > 10000000) {
if (g_serial_enabled) { // <--- 只有启用时才发
ESP_LOGE(TAG, "STM32 失联!尝试发送激活指令...");
serial_send_command(0x00,0x00,0x00,0x00,0x00);
}
beat_status = BEAT_STOP;
// 可选:这里可以尝试重新初始化串口或发送心跳请求
// 为了防止日志刷屏,可以稍微加长一点检测间隔
vTaskDelay(pdMS_TO_TICKS(3000));
}else beat_status = BEAT_START;
vTaskDelay(pdMS_TO_TICKS(10)); // 防止任务空转占满 CPU
}
}
\ No newline at end of file
#ifndef __SERIAL_H__
#define __SERIAL_H__
#include <stdint.h>
#include "esp_err.h"
#include <stdbool.h>
// 定义串口参数 (根据实际引脚修改)
#define STM32_UART_PORT UART_NUM_1
#define STM32_TXD_PIN (17)
#define STM32_RXD_PIN (18)
#define STM32_BAUD_RATE (115200)
typedef enum {
BEAT_STOP,
BEAT_START,
BEAT_UNKNOWN
}beatstatus_e;
extern beatstatus_e beat_status;
float get_stm32_battery_tempvalue();
float get_stm32_battery_voltage();
void set_serial_enabled(bool enabled);
bool get_serial_enabled();
// 初始化串口
esp_err_t serial_init(void);
// 发送指令 (对应 STM32 的 USART2_Send_Command)
void serial_send_command(uint8_t mode, uint8_t value,uint8_t data3,uint8_t data4,uint8_t data5);
// 接收处理任务入口 (在 app_main 中启动)
void serial_receive_task(void *pvParameters);
#endif
\ No newline at end of file
#include "wifidevnum_config.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_http_server.h"
#include "driver/gpio.h"
#include "lwip/sockets.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_spiffs.h"
#include "mqttconf_commun.h"
#include "serial.h"
static const char *TAG = "WIFI_CFG";
static bool wifi_editable = false;
static bool devid_editable = false;
// 初始化 SPIFFS
esp_err_t init_spiffs(void) {
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = "storage",
.max_files = 5,
.format_if_mount_failed = true
};
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPIFFS 挂载失败: %s", esp_err_to_name(ret));
return ret;
}
return ESP_OK;
}
// URL 解码
void url_decode(char *dst, const char *src) {
char a, b;
while (*src) {
if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) {
a = toupper((unsigned char)a); b = toupper((unsigned char)b);
a -= (a >= 'A') ? ('A' - 10) : '0';
b -= (b >= 'A') ? ('A' - 10) : '0';
*dst++ = (char)((a << 4) | b);
src += 3;
} else if (*src == '+') {
*dst++ = ' '; src++;
} else {
*dst++ = *src++;
}
}
*dst = '\0';
}
void save_to_nvs(const char* key, const char* value) {
nvs_handle_t handle;
if (nvs_open("storage", NVS_READWRITE, &handle) == ESP_OK) {
nvs_set_str(handle, key, value);
nvs_commit(handle);
nvs_close(handle);
ESP_LOGI(TAG, "NVS 写入成功 [%s]", key);
}
}
esp_err_t read_from_nvs(const char* key, char* buf, size_t len) {
nvs_handle_t handle;
esp_err_t err = nvs_open("storage", NVS_READONLY, &handle);
if (err == ESP_OK) {
err = nvs_get_str(handle, key, buf, &len);
nvs_close(handle);
}
return err;
}
static esp_err_t get_handler(httpd_req_t *req) {
char wifi_status[128], devid_status[128];
sprintf(wifi_status, wifi_editable ? "" : "disabled style='background:#eee'");
sprintf(devid_status, devid_editable ? "" : "disabled style='background:#eee'");
// 1. 从 SPIFFS 读取 HTML 模板
FILE* f = fopen("/spiffs/index.html", "r");
if (!f) {
httpd_resp_send_404(req);
return ESP_FAIL;
}
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
char *template = malloc(fsize + 1);
fread(template, 1, fsize, f);
fclose(f);
template[fsize] = 0;
// 2. 准备最终网页缓冲区 (S3 N16R8 内存大,直接给足够空间)
char *final_html = malloc(fsize + 1024);
snprintf(final_html, fsize + 1024, template,
wifi_editable ? "🔓":"🔒", wifi_status, wifi_status,
devid_editable ? "🔓":"🔒", devid_status);
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, final_html, HTTPD_RESP_USE_STRLEN);
free(template);
free(final_html);
return ESP_OK;
}
static esp_err_t post_handler(httpd_req_t *req) {
char buf[512];
int ret = httpd_req_recv(req, buf, req->content_len);
if (ret > 0) {
buf[ret] = '\0';
char *token, *saveptr;
token = strtok_r(buf, "&", &saveptr);
while (token != NULL) {
char *key = token;
char *val = strchr(token, '=');
if (val) {
*val = '\0'; val++;
char decoded_val[64];
url_decode(decoded_val, val);
if (strcmp(key, "ssid") == 0 && wifi_editable) save_to_nvs("wifi_ssid", decoded_val);
else if (strcmp(key, "pass") == 0 && wifi_editable) save_to_nvs("wifi_pass", decoded_val);
else if (strcmp(key, "devid") == 0 && devid_editable) save_to_nvs("device_id", decoded_val);
}
token = strtok_r(NULL, "&", &saveptr);
}
}
httpd_resp_send(req, "<h1>Success</h1><p>Restarting...</p>", HTTPD_RESP_USE_STRLEN);
vTaskDelay(pdMS_TO_TICKS(1500));
esp_restart();
return ESP_OK;
}
void dns_server_task(void *pvParameters) {
uint8_t rx_buffer[128];
struct sockaddr_in dest_addr;
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(53);
bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
while (1) {
struct sockaddr_in source_addr;
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer), 0, (struct sockaddr *)&source_addr, &socklen);
if (len > 12) {
rx_buffer[2] |= 0x80; rx_buffer[3] |= 0x80; rx_buffer[7] = 1;
uint8_t answer[] = { 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x04, 192, 168, 4, 1 };
memcpy(rx_buffer + len, answer, sizeof(answer));
sendto(sock, rx_buffer, len + sizeof(answer), 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
}
}
}
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err) {
httpd_resp_set_status(req, "302 Found");
httpd_resp_set_hdr(req, "Location", "/");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
void start_config_web() {
esp_wifi_stop();
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
wifi_config_t ap_cfg = {
.ap = { .ssid = CONFIG_ROBIOT_WIFI_SSID, .max_connection = 4, .authmode = WIFI_AUTH_OPEN }
};
esp_wifi_set_mode(WIFI_MODE_AP);
esp_wifi_set_config(WIFI_IF_AP, &ap_cfg);
esp_wifi_start();
xTaskCreate(dns_server_task, "dns", 3072, NULL, 5, NULL);
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
if (httpd_start(&server, &config) == ESP_OK) {
httpd_uri_t get_uri = { .uri="/", .method=HTTP_GET, .handler=get_handler };
httpd_register_uri_handler(server, &get_uri);
httpd_uri_t post_uri = { .uri="/save", .method=HTTP_POST, .handler=post_handler };
httpd_register_uri_handler(server, &post_uri);
httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, http_404_error_handler);
}
}
void button_monitor_task(void* arg) {
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << BTN_WIFI_IO) | (1ULL << BTN_DEVID_IO),
.mode = GPIO_MODE_INPUT, .pull_up_en = 1
};
gpio_config(&io_conf);
int wifi_cnt = 0, dev_cnt = 0;
bool web_started = false;
while (1) {
if (gpio_get_level(BTN_WIFI_IO) == 0) {
if (++wifi_cnt == 30) {
wifi_editable = true;
if(!web_started){
mqtt_manager_stop(); // 停止 MQTT
set_serial_enabled(false); // 停止串口主动发送
start_config_web();
web_started=true;
}
}
} else wifi_cnt = 0;
if (gpio_get_level(BTN_DEVID_IO) == 0) {
if (++dev_cnt == 30) {
devid_editable = true;
if(!web_started){
mqtt_manager_stop();
set_serial_enabled(false); // 停止串口主动发送
start_config_web();
web_started=true;
}
}
} else dev_cnt = 0;
vTaskDelay(pdMS_TO_TICKS(100));
}
}
\ No newline at end of file
#ifndef __WIFI_DEV_NUM_CONFIG_H__
#define __WIFI_DEV_NUM_CONFIG_H__
#include "esp_err.h"
// 引脚定义 (ESP32-S3 默认 BOOT 键一般是 0)
#define BTN_WIFI_IO 0
#define BTN_DEVID_IO 4
// 函数声明
void save_to_nvs(const char* key, const char* value);
esp_err_t read_from_nvs(const char* key, char* buf, size_t len);
void button_monitor_task(void* arg);
void start_config_web(void);
esp_err_t init_spiffs(void);
#endif
\ No newline at end of file
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x6000,
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
ota_0, app, ota_0, , 0x400000,
ota_1, app, ota_1, , 0x400000,
storage, data, spiffs, , 0x50000,
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
<style>
body { font-family: sans-serif; text-align: center; padding: 20px; background: #f0f2f5; }
.card { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); max-width: 380px; margin: auto; }
input { width: 90%; padding: 12px; margin: 10px 0; border-radius: 6px; border: 1px solid #ddd; }
.btn { width: 95%; padding: 15px; background: #28a745; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; }
b { display: block; text-align: left; margin-left: 5%; margin-top: 10px; color: #555; }
</style>
</head>
<body>
<div class="card">
<h1>🦉 设备配置中心</h1>
<form action='/save' method='POST'>
<b>WiFi 配置 %s</b>
<input name='ssid' placeholder='WiFi名称' %s>
<input name='pass' type='password' placeholder='WiFi密码' %s>
<br>
<b>设备 ID %s</b>
<input name='devid' placeholder='请输入设备号' %s>
<button type='submit' class='btn'>保存并重启设备</button>
</form>
</div>
</body>
</html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment