Commit 51d9c0ac authored by 学习的菜鸟's avatar 学习的菜鸟

esp32

parents
// 引入核心库
#include <WiFi.h> // WiFi 功能库
#include <WebServer.h> // Web 服务器库,用于配网
#include <Preferences.h> // Flash 非易失性存储库,用于保存WiFi配置
#include <freertos/FreeRTOS.h> // FreeRTOS 实时操作系统核心库
#include <freertos/task.h> // FreeRTOS 任务管理库
// --- MQTT 库和 JSON 库 ---
#include <PubSubClient.h> // MQTT 客户端库
#include <ArduinoJson.h> // JSON 解析库 (请确保已安装最新稳定版本,如 6.x)
#include "pcm.h" // 包含你的PCM音频数据头文件
#include "driver/i2s.h" // 包含ESP32 I2S驱动头文件
// 全局对象声明
Preferences preferences; // 创建一个 Preferences 对象用于读写 Flash
WebServer server(80); // 创建一个 WebServer 对象,监听 80 端口
// 用于存储从Flash读取或Web页面获取的SSID和密码
String ssid, password;
// --- 按键配置 ---
const int BUTTON_PIN = D10; // D10 引脚在 ESP32C3 上对应 GPIO10
// --- 模拟读入配置 ---
const int sensorPin0 = D0; // 模拟输入引脚 D0,在 ESP32-C3 上通常是 GPIO0
// -- 输出引脚 --
const int OUTPUT_PIN1 = D1; // 数字输出引脚 D1 -> GPIO1
const int OUTPUT_PIN2 = D2; // 数字输出引脚 D2 -> GPIO2
const int OUTPUT_PIN3 = D3; // 数字输出引脚 D3 -> GPIO3
const int OUTPUT_PIN4 = D4; // 数字输出引脚 D4 -> GPIO4
// -- i2s -- (这些引脚已定义,但在当前代码中并未使用)
const int I2S_BCLK = D5; // I2S 位时钟引脚
const int I2S_LRC = D6; // I2S 左右时钟引脚
const int I2S_DIN = D7; // I2S 数据输入引脚
// I2S 配置参数
const i2s_port_t I2S_PORT = I2S_NUM_0; // 使用I2S端口0
const int SAMPLE_RATE = 16000; // 音频采样率 (Hz) - 需要与你的 pcm_sound_data 匹配
// 如果播放速度不对,尝试常见的采样率如 8000, 22050, 44100
// I2S DMA 缓冲区配置
const int DMA_BUF_COUNT = 8; // DMA缓冲区数量
const int DMA_BUF_LEN = 1024; // 每个DMA缓冲区的长度 (字节)
// 全局状态变量
int ledblood = 100; // 用于控制 LED "血量" 效果的状态变量
int time_count = 0; // 通用时间计数器
int led_count = 0; // LED 闪烁效果的计数器
bool audio_index = false;
// 按键检测常量
const unsigned long LONG_PRESS_DURATION_MS = 5000; // 长按触发时间:5秒
const unsigned long DEBOUNCE_DELAY_MS = 50; // 按键去抖动时间:50毫秒
// --- MQTT 配置 ---
// 【错误修正】: C++中定义字符串常量需要使用 const char* 和双引号 ""
const char* MQTT_BROKER = "119.45.167.177"; // 你的 MQTT Broker IP 或 Hostname
const int MQTT_PORT = 1883; // MQTT 端口 (通常是 1883 或 8883 for SSL)
const char* MQTT_USERNAME = "admin"; // -- 新增:你的 MQTT 用户名
const char* MQTT_PASSWORD = "admin"; // -- 新增:你的 MQTT 密码
String MQTT_CLIENT_ID; // 客户端ID必须是唯一的,这里使用MAC地址的一部分确保唯一性
const char* MQTT_SUBSCRIBE_TOPIC = "ser2dev/13990100000001"; // 你要订阅的 MQTT 主题
const char* MQTT_RELEASE_TOPIC = "dev2ser/13990100000001"; // 你要发布的 MQTT 主题
// WiFi客户端用于MQTT
WiFiClient espClient;
PubSubClient mqttClient(espClient); // 声明 MQTT 客户端对象
// HTML 页面用于输入 SSID 和密码
// 【格式修正】: 在 HTML 标签和属性之间添加了空格,以确保浏览器正确解析
// R"rawliteral(...)rawliteral" 是 C++ 的原始字符串字面量,可以方便地包含特殊字符而无需转义
const char config_html[] = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"> <!-- 关键:确保浏览器以 UTF-8 编码解析页面 -->
<title>ESP32-C3 WiFi 配网</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
h2 { color: #0056b3; }
form { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
input[type=text], input[type=password] {
width: calc(100% - 22px);
padding: 10px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type=submit] {
background-color: #4CAF50;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
input[type=submit]:hover {
background-color: #45a049;
}
h3 { color: #333; }
.reset-info { margin-top: 20px; font-size: 0.9em; color: #666; }
</style>
</head>
<body>
<h2>ESP32-C3 WiFi 配网</h2>
<form action="/save" method="POST">
<label for="ssid">SSID:</label><br>
<input type="text" id="ssid" name="ssid" required><br>
<label for="password">密码:</label><br>
<input type="password" id="password" name="password"><br>
<input type="submit" value="保存并重启">
</form>
<p class="reset-info">提示:长按 D10 引脚 5 秒可清除 WiFi 配置并重启设备。</p>
</body>
</html>
)rawliteral";
// 处理根目录请求,显示配网页面
void handleRoot() {
// 发送 200 OK 状态码和 HTML 页面内容
server.send(200, "text/html; charset=utf-8", config_html);
}
// 处理保存 WiFi 配置的请求
void handleSave() {
// 检查请求中是否包含 "ssid" 和 "password" 参数
if (server.hasArg("ssid")) {
ssid = server.arg("ssid");
password = server.arg("password");
// 【错误修正】: Preferences 的命名空间必须是字符串
preferences.begin("wifi_config", false); // 打开 "wifi_config" 命名空间,false 表示读写模式
preferences.putString("ssid", ssid); // 将 ssid 存入 Flash
preferences.putString("password", password); // 将 password 存入 Flash
preferences.end(); // 关闭 Preferences
Serial.println("WiFi 配置已保存。SSID: " + ssid + ", Password: " + password);
// 向客户端发送成功信息
server.send(200, "text/html; charset=utf-8", "<h3>保存成功,设备重启中...</h3><p>请等待设备重启并连接新的WiFi网络。</p>");
delay(2000); // 延迟2秒,确保浏览器收到响应
ESP.restart(); // 重启 ESP32
} else {
// 如果缺少参数,返回 400 Bad Request 错误
server.send(400, "text/plain; charset=utf-8", "错误:缺少 SSID 参数!");
}
}
// 启动 AP (Access Point) 配网模式
void startConfigPortal() {
WiFi.mode(WIFI_AP); // 设置 WiFi 为 AP 模式
// 启动一个名为 "ESP32C3_Config",密码为 "12345678" 的 WiFi 热点
WiFi.softAP("ESP32C3_Config", "12345678");
IPAddress IP = WiFi.softAPIP(); // 获取 AP 的 IP 地址
Serial.println("\nAP 配网模式启动!");
Serial.println("请连接 Wi-Fi 热点: ESP32C3_Config (密码: 12345678)");
Serial.print("在浏览器中访问以下地址进行配网: http://");
Serial.println(IP);
// 设置 Web 服务器的路由
server.on("/", handleRoot); // 根目录 (/) 路由到 handleRoot 函数
server.on("/save", HTTP_POST, handleSave); // /save 路径的 POST 请求路由到 handleSave 函数
server.begin(); // 启动 Web 服务器
}
// 从 Flash 读取保存的 WiFi 信息
bool loadWiFiConfig(String& ssid_out, String& password_out) {
// 【错误修正】: Preferences 的命名空间必须是字符串,true 表示只读模式
preferences.begin("wifi_config", true);
// 【错误修正】: getString 的 key 必须是字符串
ssid_out = preferences.getString("ssid", ""); // 读取 "ssid",如果不存在则返回空字符串
password_out = preferences.getString("password", ""); // 读取 "password"
preferences.end();
Serial.print("从Flash读取到的SSID: '"); Serial.print(ssid_out); Serial.println("'");
Serial.print("从Flash读取到的密码: '"); Serial.print(password_out); Serial.println("'");
// 如果 SSID 长度大于0,则认为配置有效
return ssid_out.length() > 0;
}
// 启动 STA (Station) 模式连接 WiFi
bool connectWiFi() {
String ssid_str, password_str;
if (!loadWiFiConfig(ssid_str, password_str)) {
Serial.println("Flash中没有保存的WiFi配置。");
return false; // 如果没有配置,直接返回失败
}
WiFi.mode(WIFI_STA); // 设置 WiFi 为 STA 模式
WiFi.begin(ssid_str.c_str(), password_str.c_str()); // 开始连接
Serial.print("正在尝试连接 WiFi: ");
Serial.println(ssid_str);
// 最多尝试连接 30 次 (每次500ms,总共15秒)
for (int i = 0; i < 30; i++) {
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi 连接成功!");
Serial.print("已连接到: ");
Serial.println(WiFi.SSID());
Serial.print("设备 IP 地址: ");
Serial.println(WiFi.localIP());
return true; // 连接成功
}
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi 连接失败。");
return false; // 连接失败
}
// 启动后台网页用于重新配网(WiFi连接成功后)
void startConfigWebPortal() {
// 设置与 AP 模式下相同的路由,以便在连接 WiFi 后也能重新配置
server.on("/", handleRoot);
server.on("/save", HTTP_POST, handleSave);
server.begin(); // 启动 Web 服务器
Serial.print("后台网页服务已启动,您可以通过 IP 地址: ");
Serial.print(WiFi.localIP());
Serial.println(" 再次进行配网。");
}
void setup_i2s() {
// 1. 配置I2S总线
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // 设置为主机模式,发送数据
.sample_rate = SAMPLE_RATE, // 设置采样率
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 设置每个采样点的位数 (PCM数据通常是16位)
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // MAX98357是单声道,但I2S通常配置为双声道,
// 如果你的PCM数据是单声道,它会被复制到左右声道。
// 或者如果PCM数据本身已经是双声道(左声道数据后跟右声道数据)
// 或者可以尝试 I2S_CHANNEL_FMT_ALL_LEFT 或 I2S_CHANNEL_FMT_ALL_RIGHT 如果你的数据是纯单声道
.communication_format = I2S_COMM_FORMAT_STAND_I2S, // 标准I2S通信格式 (MSB优先)
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // 中断分配标志 (默认)
.dma_buf_count = DMA_BUF_COUNT, // DMA缓冲区数量
.dma_buf_len = DMA_BUF_LEN, // 每个DMA缓冲区的长度 (字节)
.use_apll = false, // 不使用APLL (音频锁相环)
.tx_desc_auto_clear = true, // 自动清除已发送的TX描述符
.fixed_mclk = 0 // 不使用固定的MCLK (MAX98357不需要MCLK)
};
// 2. 安装I2S驱动
esp_err_t err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
if (err != ESP_OK) {
Serial.printf("I2S驱动安装失败: %d\n", err);
return;
}
Serial.println("I2S驱动已安装");
// 3. 配置I2S引脚
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCLK, // 位时钟引脚
.ws_io_num = I2S_LRC, // 左右时钟/字选择引脚
.data_out_num = I2S_DIN, // 数据输出引脚
.data_in_num = I2S_PIN_NO_CHANGE // 不使用数据输入引脚
};
err = i2s_set_pin(I2S_PORT, &pin_config);
if (err != ESP_OK) {
Serial.printf("I2S引脚设置失败: %d\n", err);
return;
}
Serial.println("I2S引脚已设置");
}
void play_audio() {
Serial.println("开始播放音频...");
size_t bytes_written = 0;
int current_pos = 0;
// 创建一个RAM缓冲区,用于从PROGMEM拷贝数据块
// pcm_sound_data 存储在PROGMEM (Flash)中,不能直接传递给i2s_write
// DMA_BUF_LEN 是一个合适的块大小
uint8_t ram_buffer[DMA_BUF_LEN];
// 循环直到所有音频数据都被发送
while (current_pos < pcm_sound_data_len) {
// 计算本次要拷贝的字节数
int bytes_to_copy = DMA_BUF_LEN;
if (current_pos + bytes_to_copy > pcm_sound_data_len) {
bytes_to_copy = pcm_sound_data_len - current_pos;
}
// 从PROGMEM拷贝数据到RAM缓冲区
// pcm_sound_data 是 const unsigned char[] 数组,它在 pcm.h 中定义
// memcpy_P 用于从PROGMEM拷贝数据
memcpy_P(ram_buffer, &pcm_sound_data[current_pos], bytes_to_copy);
// 将RAM缓冲区中的数据写入I2S总线
// i2s_write 会阻塞直到数据被发送或超时
esp_err_t result = i2s_write(I2S_PORT, ram_buffer, bytes_to_copy, &bytes_written, portMAX_DELAY);
if (result != ESP_OK) {
Serial.printf("I2S写入错误: %d\n", result);
break;
}
if (bytes_written < bytes_to_copy) {
Serial.printf("I2S写入不足: 只写入了 %d / %d 字节\n", bytes_written, bytes_to_copy);
// 可以选择中断或继续,这里我们继续
}
current_pos += bytes_written; // 更新当前位置
// Serial.printf("已发送 %d / %d 字节\n", current_pos, pcm_sound_data_len); // 调试信息
}
// (可选)播放完毕后,可以发送一些静音数据以确保DAC输出静音
// 或者清空DMA缓冲区
i2s_zero_dma_buffer(I2S_PORT);
Serial.println("音频播放完毕。");
}
// GPIO 输出控制函数
void pin_value(int pin ,int val) {
if(val == 1){ // 如果值为 1,则输出高电平
if(pin == 1) digitalWrite(OUTPUT_PIN1, HIGH);
if(pin == 2) digitalWrite(OUTPUT_PIN2, HIGH);
if(pin == 3) digitalWrite(OUTPUT_PIN3, HIGH);
if(pin == 4) digitalWrite(OUTPUT_PIN4, HIGH);
}else{ // 否则输出低电平
if(pin == 1) digitalWrite(OUTPUT_PIN1, LOW);
if(pin == 2) digitalWrite(OUTPUT_PIN2, LOW);
if(pin == 3) digitalWrite(OUTPUT_PIN3, LOW);
if(pin == 4) digitalWrite(OUTPUT_PIN4, LOW);
}
}
// PWM 输出控制函数
void pwm_value(int pin ,int val) {
// 【错误修正】: "val2.55" 是无效语法,应为 "val * 2.55"
// analogWrite 在 ESP32 上是 8 位分辨率 (0-255),此函数将输入的 0-100 值映射到 0-255
if(pin == 1) analogWrite(OUTPUT_PIN1, val * 2.55);
if(pin == 2) analogWrite(OUTPUT_PIN2, val * 2.55);
if(pin == 3) analogWrite(OUTPUT_PIN3, val * 2.55);
if(pin == 4) analogWrite(OUTPUT_PIN4, val * 2.55);
}
// 控制 LED "血量" 效果的函数
void led_blood_count(){
if(ledblood == 6) { // 满血状态,3个灯全亮
pin_value(1,1);
pin_value(2,1);
pin_value(3,1);
}else if(ledblood == 5){ // 5格血,灯1闪烁,灯2、3常亮
if(led_count < 10) pin_value(1,1);
if(led_count >= 10){
pin_value(1,0);
if(led_count > 20) led_count = 0;
}
pin_value(2,1);
pin_value(3,1);
}else if(ledblood == 4){ // 4格血,灯1灭,灯2、3常亮
pin_value(1,0);
pin_value(2,1);
pin_value(3,1);
}else if(ledblood == 3){ // 3格血,灯2闪烁,灯3常亮,灯1灭
if(led_count < 10) pin_value(2,1);
if(led_count >= 10){
pin_value(2,0);
if(led_count > 20) led_count = 0;
}
pin_value(1,0);
pin_value(3,1);
}else if(ledblood == 2){ // 2格血,灯3常亮,灯1、2灭
pin_value(1,0);
pin_value(2,0);
pin_value(3,1);
}else if(ledblood == 1){ // 1格血,灯3闪烁,灯1、2灭
if(led_count < 10) pin_value(3,1);
if(led_count >= 10){
pin_value(3,0);
if(led_count > 20) led_count = 0;
}
pin_value(1,0);
pin_value(2,0);
}else if(ledblood == 0){ // 【注释】: 原始代码为 ledblood == 2,这里假设意图是0。如果仍是2,此逻辑块将被上面的 ledblood == 2 覆盖。
pin_value(1,0);
pin_value(2,0);
pin_value(3,0);
}
}
// --- MQTT 消息回调函数 ---
// 【错误修正】: 修正了回调函数的正确签名 (char* topic, byte* payload, unsigned int length)
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("收到 MQTT 消息 - 主题: ");
Serial.print(topic);
Serial.print(", 内容: ");
// 【错误修正】: payload 是 byte*,需要正确转换为 char* 才能用于字符串操作
// 同时确保字符串以空字符 '\0' 结尾
char messagePayload[length + 1];
strncpy(messagePayload, (char*)payload, length);
messagePayload[length] = '\0'; // 确保字符串以null结尾
Serial.println(messagePayload);
// --- 解析 JSON 数据 ---
StaticJsonDocument<256> doc; // 根据你的JSON文档大小调整
DeserializationError error = deserializeJson(doc, messagePayload);
if (error) {
Serial.print("JSON 解析失败: ");
Serial.println(error.c_str());
return;
}
int message_type = 0;
// 【错误修正】: JSON中的键(key)必须是字符串
if (doc.containsKey("head")) {
// 获取 head 对象
JsonObject head = doc["head"];
// 从 head 对象中提取 message_type
if (head.containsKey("message_type")) {
message_type = head["message_type"];
Serial.print(" - 消息类型 (head.message_type): ");
Serial.println(message_type);
}
}
// 2. 解析 body 部分
if (doc.containsKey("body")) {
// 获取 body 对象
JsonObject body = doc["body"];
if( message_type == 3 || message_type == 4){ // GPIO 或 PWM 控制
int pin=0, val=0;
// 从 body 对象中提取 pin
if (body.containsKey("pin")) {
pin = body["pin"];
Serial.print(" - 引脚 (body.pin): ");
Serial.println(pin);
}
// 从 body 对象中提取 val
if (body.containsKey("val")) {
val = body["val"];
Serial.print(" - 值 (body.val): ");
Serial.println(val);
}
if( message_type == 3 ) { // 数字IO控制
pin_value(pin, val);
}else if(message_type == 4 ){ // PWM控制
pwm_value(pin, val);
}
Serial.print("pin=");
Serial.print(pin);
Serial.print(", val=");
Serial.println(val);
} else if( message_type == 5){ // 动作控制
int val=0;
// 从 body 对象中提取 val
if (body.containsKey("val")) {
val = body["val"];
Serial.print(" - 值 (body.val): ");
Serial.println(val);
}
if (body.containsKey("action")) {
// 【错误修正】: 字符串比较需要使用双引号
if(body["action"] == "led"){
led_count=0; // 重置LED闪烁计数器
ledblood = val; // 更新血量值
}else if(body["action"] == "fog_ms"){
time_count=0; // 重置时间计数器 (此功能未完全实现)
}else if(body["action"] == "audio"){
audio_index=val; // 重置时间计数器 (此功能未完全实现)
}
Serial.print("action: ");
Serial.println(body["action"].as<String>()); // 使用 as<String>() 安全地获取字符串值
}
}
}
}
// --- MQTT 连接和订阅尝试 (不阻塞,供任务调用) ---
bool tryMqttConnect() {
// 如果 MQTT 客户端 ID 为空,则生成一个
if (MQTT_CLIENT_ID.isEmpty()) {
MQTT_CLIENT_ID = "ESP32C3-" + WiFi.macAddress().substring(9);
MQTT_CLIENT_ID.replace(":", ""); // 移除MAC地址中的冒号
Serial.print("生成 MQTT Client ID: ");
Serial.println(MQTT_CLIENT_ID);
}
Serial.print("尝试连接 MQTT Broker...");
// --- 关键修改:使用带有用户名和密码的 connect 方法 ---
if (mqttClient.connect(MQTT_CLIENT_ID.c_str(), MQTT_USERNAME, MQTT_PASSWORD)) {
Serial.println("成功连接 MQTT Broker!");
// 连接成功后,订阅指定的主题
mqttClient.subscribe(MQTT_SUBSCRIBE_TOPIC);
Serial.print("已订阅主题: ");
Serial.println(MQTT_SUBSCRIBE_TOPIC);
return true; // 返回成功
} else {
Serial.print("MQTT 连接失败,返回码: ");
Serial.println(mqttClient.state()); // 打印失败状态码
return false; // 返回失败
}
}
// --- FreeRTOS MQTT 任务 ---
void mqttTask(void* pvParameters) {
// 设置 MQTT 服务器和端口
mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
// 设置消息回调函数
mqttClient.setCallback(mqttCallback);
for (;;) { // 无限循环,这是 FreeRTOS 任务的标准写法
if (WiFi.status() == WL_CONNECTED) { // 只有在 WiFi 连接时才处理 MQTT
if (!mqttClient.connected()) { // 如果 MQTT 未连接
Serial.println("MQTT Task: MQTT 连接断开,尝试重连...");
if (!tryMqttConnect()) { // 尝试重连
Serial.println("MQTT Task: 重连失败,等待下次尝试...");
vTaskDelay(pdMS_TO_TICKS(5000)); // 失败时等待 5 秒后重试
}
} else {
mqttClient.loop(); // 保持 MQTT 连接和处理消息
}
} else {
Serial.println("MQTT Task: WiFi 未连接,无法连接 MQTT。等待 WiFi...");
}
// 任务延时,让出 CPU 给其他任务,并避免繁忙循环
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 发布 JSON 格式的 MQTT 消息
void publishJsonMessage() {
// 创建JSON对象
DynamicJsonDocument doc(256);
// 构建head部分
// 【错误修正】: JSON key 必须是字符串
JsonObject head = doc.createNestedObject("head");
head["message_type"] = 101;
// 构建body部分
JsonObject body = doc.createNestedObject("body");
body["action"] = "shoted";
body["val"] = 1;
// 序列化JSON为字符串
String jsonString;
serializeJson(doc, jsonString);
// 发布消息
mqttClient.publish(MQTT_RELEASE_TOPIC, jsonString.c_str());
Serial.println("发布消息:");
Serial.println(jsonString);
}
// --- FreeRTOS 按键监控任务 ---
void buttonMonitorTask(void* pvParameters) {
int lastButtonState = HIGH; // 上一次按键状态 (HIGH 未按下, LOW 已按下)
unsigned long buttonPressStartTime = 0; // 按键按下时的 millis() 时间
bool resetTriggered = false; // 标志,防止长按期间重复触发
for (;;) { // 无限循环
int currentButtonState = digitalRead(BUTTON_PIN);
// --- 按键去抖动 ---
if (currentButtonState != lastButtonState) {
vTaskDelay(pdMS_TO_TICKS(DEBOUNCE_DELAY_MS));
currentButtonState = digitalRead(BUTTON_PIN); // 再次读取以确认状态
}
if (currentButtonState == LOW) { // 如果按键被按下
if (lastButtonState == HIGH) { // 如果是刚按下的瞬间
buttonPressStartTime = millis();
resetTriggered = false;
Serial.println("按键 D10 被按下...");
}
// --- 检测长按 ---
if (!resetTriggered && (millis() - buttonPressStartTime >= LONG_PRESS_DURATION_MS)) {
Serial.println("长按 D10 超过 5 秒检测到!正在清除 WiFi 配置...");
//听到声音表示长按成功
play_audio();
// 【错误修正】: Preferences 命名空间必须是字符串
preferences.begin("wifi_config", false);
preferences.clear(); // 清除 "wifi_config" 命名空间下的所有数据
preferences.end();
Serial.println("WiFi 配置已清除。设备即将重启...");
resetTriggered = true; // 标记已触发,防止重复重启
delay(100); // 短暂延时
ESP.restart(); // 重启设备
}
} else { // 如果按键未被按下
if (lastButtonState == LOW) { // 如果是刚松开的瞬间
if (!resetTriggered && (millis() - buttonPressStartTime < LONG_PRESS_DURATION_MS)) {
Serial.println("按键 D10 短按 (未触发重置)。");
}
}
buttonPressStartTime = 0; // 重置计时器
}
lastButtonState = currentButtonState; // 更新按键状态
// 【错误修正】: "2.54095"前缺少运算符,且 "sensor_val =" 是赋值不是比较
// 假设意图是乘法和大于等于比较
const double sensor_val = analogRead(sensorPin0) * 2.5 / 4095.0; // 示例:将读数转换为电压(假设2.5V参考)
if(sensor_val >= 1.25 ) publishJsonMessage(); // 当传感器值超过阈值时发布消息
// 调用LED控制函数
led_blood_count();
// 更新计数器,并防止溢出
time_count++; if(time_count > 1000) time_count=1001; // 【错误修正】: 添加比较运算符
led_count++; if(led_count > 1000) led_count=1001; // 【错误修正】: 添加比较运算符
// 任务延时50毫秒
vTaskDelay(pdMS_TO_TICKS(50));
}
}
//音频播放任务
void audiopalyTask(void* pvParameters)
{
while(1){
if(audio_index==true){
play_audio();
audio_index=false;
// 创建JSON对象
DynamicJsonDocument doc(256);
// 构建head部分
// 【错误修正】: JSON key 必须是字符串
JsonObject head = doc.createNestedObject("head");
head["message_type"] = 102;
// 构建body部分
JsonObject body = doc.createNestedObject("body");
body["action"] = "audioplayend";
body["val"] = 1;
// 序列化JSON为字符串
String jsonString;
serializeJson(doc, jsonString);
// 发布消息
mqttClient.publish(MQTT_RELEASE_TOPIC, jsonString.c_str());
Serial.println("发布消息:");
Serial.println(jsonString);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void setup() {
Serial.begin(115200); // 启动串口通信,波特率为 115200
delay(1000); // 等待串口稳定
Serial.println("\n--- 系统启动 ---");
// 初始化I2S
setup_i2s();
// 配置引脚模式
pinMode(BUTTON_PIN, INPUT_PULLUP); // 按键引脚设为上拉输入
pinMode(sensorPin0, INPUT); // 传感器引脚设为输入
pinMode(OUTPUT_PIN1, OUTPUT); // 输出引脚设为输出
pinMode(OUTPUT_PIN2, OUTPUT);
pinMode(OUTPUT_PIN3, OUTPUT);
pinMode(OUTPUT_PIN4, OUTPUT);
// 创建并启动按键监控任务
xTaskCreate(
buttonMonitorTask,
"ButtonMonitor", // 任务名称(字符串)
2048, // 任务栈空间大小 (字节)
NULL, // 传递给任务的参数
1, // 任务优先级
NULL // 任务句柄
);
// 创建并启动音频播放任务
xTaskCreate(
audiopalyTask,
"audiopaly", // 任务名称(字符串)
2048, // 任务栈空间大小 (字节)
NULL, // 传递给任务的参数
1, // 任务优先级
NULL // 任务句柄
);
// 尝试连接之前保存的 WiFi 网络
if (connectWiFi()) {
play_audio();//连接成功播放一声
// 如果连接成功,启动后台网页服务以便重新配网
startConfigWebPortal();
// 在 WiFi 连接成功后,创建并启动 MQTT 任务
// 【错误修正】: 任务名称应为字符串
xTaskCreate(
mqttTask,
"MQTTTask", // 任务名称(字符串)
4096, // 任务栈空间大小 (MQTT和JSON解析可能需要更多栈,建议4096字节或更多)
NULL, // 传递给任务的参数
1, // 任务优先级
NULL // 任务句柄
);
} else {
play_audio();
delay(2000);
play_audio();
// 如果连接失败,启动 AP 配网模式
startConfigPortal();
}
}
void loop() {
// 主循环中只需要处理 Web 服务器的客户端请求
// 其他所有功能(按键、MQTT)都在后台的 FreeRTOS 任务中运行
server.handleClient();
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
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