Commit 509bf8cd authored by 学习的菜鸟's avatar 学习的菜鸟

进行了分文件何加入一些逻辑(比如烟雾)

parent 51d9c0ac
#include "AudioManager.h"
#include "PubSubClient.h" // MQTTManager 内部使用
// 构造函数实现
AudioManager::AudioManager(int bckPin, int lrcPin, int dinPin, int sampleRate, int dmaBufCount, int dmaBufLen)
: _bckPin(bckPin), _lrcPin(lrcPin), _dinPin(dinPin),
_sampleRate(sampleRate), _dmaBufCount(dmaBufCount), _dmaBufLen(dmaBufLen) {
// 构造函数体
}
// 初始化 I2S 实现
bool AudioManager::begin() {
Serial.println("Initializing I2S...");
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // 设置为主机模式,发送数据
.sample_rate = _sampleRate, // 设置采样率
.bits_per_sample = (i2s_bits_per_sample_t)_bitsPerSample, // 设置每个采样点的位数
.channel_format = _channelFormat, // 通道格式
.communication_format = _commFormat, // 通信格式
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // 中断分配标志
.dma_buf_count = _dmaBufCount, // DMA缓冲区数量
.dma_buf_len = _dmaBufLen, // 每个DMA缓冲区的长度
.use_apll = false, // 不使用APLL
.tx_desc_auto_clear = true, // 自动清除已发送的TX描述符
.fixed_mclk = 0 // 不使用固定的MCLK
};
// 安装I2S驱动
esp_err_t err = i2s_driver_install(_i2sPort, &i2s_config, 0, NULL);
if (err != ESP_OK) {
Serial.printf("I2S driver install failed: %d\n", err);
return false;
}
Serial.println("I2S driver installed.");
// 配置I2S引脚
i2s_pin_config_t pin_config = {
.bck_io_num = _bckPin, // 位时钟引脚
.ws_io_num = _lrcPin, // 左右时钟/字选择引脚
.data_out_num = _dinPin, // 数据输出引脚
.data_in_num = I2S_PIN_NO_CHANGE // 不使用数据输入引脚
};
err = i2s_set_pin(_i2sPort, &pin_config);
if (err != ESP_OK) {
Serial.printf("I2S pin setting failed: %d\n", err);
return false;
}
Serial.println("I2S pins set.");
return true;
}
// 请求播放实现
void AudioManager::requestPlay() {
_playRequested = true;
}
// 检查是否正在播放(简单实现)
bool AudioManager::isPlaying() const {
// 更精确的实现需要跟踪播放状态,这里只是基于请求标志
return _playRequested; // 在play()函数结束时需要将此标志设回false
}
// 内部播放函数实现
void AudioManager::play() {
Serial.println("Start playing audio...");
size_t bytes_written = 0;
int current_pos = 0;
// 创建一个RAM缓冲区,用于从PROGMEM拷贝数据块
uint8_t ram_buffer[_dmaBufLen];
// 循环直到所有音频数据都被发送
while (current_pos < pcm_sound_data_len) {
int bytes_to_copy = _dmaBufLen;
if (current_pos + bytes_to_copy > pcm_sound_data_len) {
bytes_to_copy = pcm_sound_data_len - current_pos;
}
// 从PROGMEM拷贝数据到RAM缓冲区
memcpy_P(ram_buffer, &pcm_sound_data[current_pos], bytes_to_copy);
// 将RAM缓冲区中的数据写入I2S总线
esp_err_t result = i2s_write(_i2sPort, ram_buffer, bytes_to_copy, &bytes_written, portMAX_DELAY);
if (result != ESP_OK) {
Serial.printf("I2S write error: %d\n", result);
break;
}
// if (bytes_written < bytes_to_copy) {
// Serial.printf("I2S write incomplete: Wrote %d / %d bytes\n", bytes_written, bytes_to_copy);
// }
current_pos += bytes_written; // 更新当前位置
}
// 播放完毕后,清空DMA缓冲区确保静音
i2s_zero_dma_buffer(_i2sPort);
Serial.println("Audio playback finished.");
_playRequested = false; // 重置播放请求标志
// 播放结束后,如果 MQTTManager 指针有效,发送播放结束消息
// if (_mqttManager) {
// Serial.println("Publishing audio play end message...");
// _mqttManager->publishJson("audioplayend", 1);
// }
}
// FreeRTOS 任务函数实现
// 这个函数运行在一个单独的核心上,负责检查播放请求并调用播放函数
void audioPlayTask(void* pvParameters) {
AudioManager* audioManager = static_cast<AudioManager*>(pvParameters);
if (!audioManager) {
Serial.println("AudioPlay Task: Invalid parameter!");
vTaskDelete(NULL); // 删除当前任务
return;
}
for (;;) {
if (audioManager->_playRequested) {
audioManager->play();
}
vTaskDelay(pdMS_TO_TICKS(50)); // 短暂延时,避免忙等
}
}
\ No newline at end of file
#ifndef AUDIO_MANAGER_H
#define AUDIO_MANAGER_H
#include <Arduino.h>
#include "pcm.h" // 包含你的PCM音频数据头文件
#include "driver/i2s.h" // 包含ESP32 I2S驱动头文件
#include "MQTTManager.h" // 需要与 MQTTManager 交互以发送播放结束消息
class MQTTManager; // 前向声明,避免循环包含
class AudioManager {
private:
// I2S 配置参数
i2s_port_t _i2sPort = I2S_NUM_0; // 使用I2S端口0 (可配置)
int _sampleRate = 16000; // 音频采样率 (Hz) (可配置)
int _bitsPerSample = I2S_BITS_PER_SAMPLE_16BIT; // 每个采样点的位数
i2s_channel_fmt_t _channelFormat = I2S_CHANNEL_FMT_RIGHT_LEFT; // 通道格式
i2s_comm_format_t _commFormat = I2S_COMM_FORMAT_STAND_I2S; // 通信格式
int _dmaBufCount = 8; // DMA缓冲区数量 (可配置)
int _dmaBufLen = 1024; // 每个DMA缓冲区的长度 (字节) (可配置)
// I2S 引脚
int _bckPin;
int _lrcPin;
int _dinPin; // MAX98357A 不需要 DIN
bool _playRequested = false; // 播放请求标志
MQTTManager* _mqttManager = nullptr; // MQTT 管理器指针,用于发送播放结束消息
// 内部播放函数(在任务中调用)
void play();
public:
// 构造函数
AudioManager(int bckPin, int lrcPin, int dinPin, int sampleRate = 16000, int dmaBufCount = 8, int dmaBufLen = 1024);
// 初始化 I2S
bool begin();
// 请求播放音频(任务会检查此标志)
void requestPlay();
// 检查当前是否正在播放(如果play()是阻塞的,这个可能没意义,除非在play()内部设置标志)
bool isPlaying() const;
// 设置 MQTTManager 指针
void setMQTTManager(MQTTManager* mqttManager) { _mqttManager = mqttManager; }
// FreeRTOS 任务函数(需要在主文件或单独的.cpp中实现,并友元声明)
// Friend declaration to allow the task function to access private members if needed
friend void audioPlayTask(void* pvParameters);
};
extern AudioManager audioManager;
// FreeRTOS 任务函数声明
void audioPlayTask(void* pvParameters);
#endif // AUDIO_MANAGER_H
\ No newline at end of file
#include "DeviceControl.h"
#include "AudioManager.h"
#include <ArduinoJson.h> // 需要用于 handleMQTTCommand
// 构造函数实现
DeviceControl::DeviceControl(int pin1, int pin2, int pin3, int pin4, int sensorPin)
: _sensorPin(sensorPin) {
_outputPins[0] = pin1;
_outputPins[1] = pin2;
_outputPins[2] = pin3;
_outputPins[3] = pin4;
}
// begin 方法实现
void DeviceControl::begin() {
Serial.println("DeviceControl initializing...");
// 配置引脚模式
for (int i = 0; i < 4; ++i) {
pinMode(_outputPins[i], OUTPUT);
digitalWrite(_outputPins[i], LOW); // 确保启动时为低电平
}
pinMode(_sensorPin, INPUT); // 传感器引脚设为输入
Serial.println("DeviceControl initialized.");
}
// 设置数字输出实现 (1-4 对应 _outputPins 数组索引 0-3)
void DeviceControl::setPinValue(int pinIndex, int val) {
if (pinIndex >= 1 && pinIndex <= 4) {
//Serial.printf("Setting digital pin %d (%d) to %d\n", pinIndex, _outputPins[pinIndex - 1], val);
digitalWrite(_outputPins[pinIndex - 1], val == 1 ? HIGH : LOW);
} else {
Serial.printf("Warning: Invalid pin index for digital write: %d\n", pinIndex);
}
}
// 设置 PWM 输出实现 (1-4 对应 _outputPins 数组索引 0-3)
void DeviceControl::setPWMValue(int pinIndex, int val) {
if (pinIndex >= 1 && pinIndex <= 4) {
// analogWrite 在 ESP32 上是 8 位分辨率 (0-255)
int analogVal = constrain(val * 255 / 100, 0, 255); // 将输入的 0-100 值映射到 0-255
//Serial.printf("Setting PWM pin %d (%d) to %d (scaled to %d)\n", pinIndex, _outputPins[pinIndex - 1], val, analogVal);
analogWrite(_outputPins[pinIndex - 1], analogVal);
} else {
Serial.printf("Warning: Invalid pin index for PWM write: %d\n", pinIndex);
}
}
// 更新 LED 血量效果实现
void DeviceControl::updateLedBlood() {
// 这个函数需要在周期性的任务中被调用,比如每几十毫秒一次
_led_count++; // 每次调用都递增计数器
if (_led_count > 20) _led_count = 0; // 闪烁周期计数器
if (_ledblood >= 6) { // 满血或更高,3个灯全亮
setPinValue(1,1);
setPinValue(2,1);
setPinValue(3,1);
} else if (_ledblood == 5){ // 5格血,灯1闪烁,灯2、3常亮
setPinValue(1, (_led_count < 10)); // 计数器 < 10 时为高电平
setPinValue(2,1);
setPinValue(3,1);
} else if (_ledblood == 4){ // 4格血,灯1灭,灯2、3常亮
setPinValue(1,0);
setPinValue(2,1);
setPinValue(3,1);
} else if (_ledblood == 3){ // 3格血,灯2闪烁,灯3常亮,灯1灭
setPinValue(2, (_led_count < 10));
setPinValue(1,0);
setPinValue(3,1);
} else if (_ledblood == 2){ // 2格血,灯3常亮,灯1、2灭
setPinValue(1,0);
setPinValue(2,0);
setPinValue(3,1);
} else if (_ledblood == 1){ // 1格血,灯3闪烁,灯1、2灭
setPinValue(3, (_led_count < 10));
setPinValue(1,0);
setPinValue(2,0);
} else { // ledblood <= 0,所有灯灭
setPinValue(1,0);
setPinValue(2,0);
setPinValue(3,0);
}
}
// 读取传感器值实现
double DeviceControl::readSensor() {
int rawValue = analogRead(_sensorPin);
// 将读数转换为电压(假设 ADC 12位,参考电压 3.3V 或 2.5V)
// ESP32C3 ADC 可以是 12位 (0-4095),参考电压通常是 3.3V 内部或外部。
// 原始代码使用了 2.5,我们假设是 2.5V 参考电压,或者原始代码有误。
// 使用 3.3V 参考电压是更常见的做法: rawValue * 3.3 / 4095.0
// 如果你的电路使用了外部 2.5V 参考或其他方式,请调整下面的乘数
const double referenceVoltage = 3.3; // 假设使用 3.3V 参考电压
double voltage = (double)rawValue * referenceVoltage / 4095.0;
// Serial.printf("Sensor raw: %d, Voltage: %.2fV\n", rawValue, voltage); // 调试信息
return voltage;
}
// 处理来自 MQTT 的控制指令
void DeviceControl::handleMQTTCommand(int message_Type, JsonObject body) {
message_type msgType = static_cast<message_type>(message_Type); ;
if (msgType == message_type::PIN_CONTROL || msgType == message_type::PWM_CONTROL) { // GPIO 或 PWM 控制
int pin = 0, val = 0;
if (body.containsKey("pin")) {
pin = body["pin"]; // 获取引脚编号 (1-4)
}
if (body.containsKey("val")) {
val = body["val"]; // 获取值
}
if (msgType == message_type::PIN_CONTROL) { // 数字IO控制
setPinValue(pin, val);
} else if (msgType == message_type::PWM_CONTROL) { // PWM控制
setPWMValue(pin, val);
}
Serial.printf("Processed command: pin=%d, val=%d (Type %d)\n", pin, val, msgType);
} else if (msgType == message_type::SERSOR_CONTROL) { // 动作控制
if (body.containsKey("action")) {
String action = body["action"].as<String>();
int val = 0;
if (body.containsKey("val")) {
val = body["val"]; // 获取值
}
if (action == "led") {
setLedBlood(val); // 设置 LED 血量Connected
Serial.printf("Processed action: led, val=%d\n", val);
} else if (action == "fog_ms") {
// 原代码中有 time_count=0,这里可以根据需要实现烟雾机控制逻辑
Serial.printf("Processed action: fog_ms, val=%d (Not fully implemented)\n", val);
} // audio 动作在 MQTT 回调中处理并请求 AudioManager 播放
else if (action == "audio") {
audioManager.requestPlay(); // 请求音频播放
_mqttManager->publishJson("audioplayend", 102);
// 原代码中有 time_count=0,这里可以根据需要实现烟雾机控制逻辑
Serial.printf("audio playend\n", val);
}else if (action == "smoke_ms") {
_smoke_count=0;
_smoke_ms=val;
_smoke_index=true;
// 原代码中有 time_count=0,这里可以根据需要实现烟雾机控制逻辑
Serial.printf("smoke set\n");
}else {
Serial.printf("Unknown action: %s\n", action.c_str());
}
}
}
}
void DeviceControl::control_smoke() {
int smoke_i = _smoke_ms / 50;
if(_smoke_count < smoke_i){
setPinValue(4,1);
}else setPinValue(4,0);
}
// 更新方法,在周期性任务中调用
void DeviceControl::update() {
// 更新 LED 状态
updateLedBlood();
if(_smoke_index = true){
control_smoke();
//更新烟雾模块
_smoke_count++;
if(_smoke_count>2000) _smoke_count =2001;
}
// 读取传感器并发布数据 (可以设置一个频率,而不是每次 update() 都发布)
// 简单的实现:每隔一定时间或阈值触发时发布
// static unsigned long lastSensorPublishTime = 0;
// const unsigned long sensorPublishInterval = 50; // 每 1 秒发布一次传感器数据
// if (millis() - lastSensorPublishTime >= sensorPublishInterval) {
double sensorVoltage = readSensor();
// 原始代码中的阈值判断
if (sensorVoltage >= 1.25&& mqttManager.wifi_connected_is()==0) { // 传感器值超过阈值
Serial.println("Sensor threshold reached, publishing shoted message.");
if (_mqttManager) {
// 发布 "shoted" 消息
_mqttManager->publishJson("shoted", 101);
} else {
Serial.println("MQTTManager not set, cannot publish sensor data.");
}
}
// lastSensorPublishTime = millis();
// }
}
// FreeRTOS 任务函数实现
void devicePeriodicTask(void* pvParameters) {
DeviceControl* deviceControl = static_cast<DeviceControl*>(pvParameters);
if (!deviceControl) {
Serial.println("Device Periodic Task: Invalid parameter!");
vTaskDelete(NULL);
return;
}
for (;;) {
deviceControl->update(); // 调用 DeviceControl 的 update 方法
vTaskDelay(pdMS_TO_TICKS(50)); // 每 50 毫秒更新一次
}
}
\ No newline at end of file
#ifndef DEVICE_CONTROL_H
#define DEVICE_CONTROL_H
#include <Arduino.h>
#include "MQTTManager.h" // 需要与 MQTTManager 交互以发布传感器数据
class MQTTManager; // 前向声明
class DeviceControl {
private:
int _outputPins[4]; // 存储输出引脚的 GPIO 编号
int _sensorPin; // 存储传感器输入引脚的 GPIO 编号
int _ledblood = 6; // 用于控制 LED "血量" 效果的状态变量
int _led_count = 0; // LED 闪烁效果的计数器 (用于 updateLedBlood)
int _smoke_count = 0;
int _smoke_ms = 0;
bool _smoke_index =false;
MQTTManager* _mqttManager = nullptr; // MQTT 管理器指针,用于发布传感器数据
// 内部函数
void setPinValue(int pinIndex, int val); // 控制数字输出
void setPWMValue(int pinIndex, int val); // 控制 PWM 输出
// 更新 LED 血量效果
void updateLedBlood();
// 读取传感器值
double readSensor();
public:
// 构造函数
// 传入输出引脚和传感器引脚的 GPIO 编号
DeviceControl(int pin1, int pin2, int pin3, int pin4, int sensorPin);
// 初始化引脚模式等
void begin();
// 设置 MQTTManager 指针
void setMQTTManager(MQTTManager* mqttManager) { _mqttManager = mqttManager; }
// 从外部设置 LED 血量值 (例如来自 MQTT)
void setLedBlood(int blood) { _ledblood = constrain(blood, 0, 6); _led_count = 0; } // 限制范围,重置计数器
// 处理来自 MQTT 的控制指令(例如设置引脚或 PWM)
void handleMQTTCommand(int message_type, JsonObject body);
// 在周期性任务中调用,更新LED状态并读取/发布传感器数据
void update();
// FreeRTOS 任务函数(需要在主文件或单独的.cpp中实现,并友元声明)
friend void devicePeriodicTask(void* pvParameters);
//烟雾传感器
void control_smoke();
// 枚举类作为 MqttClient 的成员
enum class message_type : int {
PIN_CONTROL = 3,
PWM_CONTROL = 4,
SERSOR_CONTROL = 5
};
};
// FreeRTOS 任务函数声明
void devicePeriodicTask(void* pvParameters);
#endif // DEVICE_CONTROL_H
\ No newline at end of file
#include "MQTTManager.h"
#include <WiFi.h> // 需要检查 WiFi 连接状态
#include <string> // For std::string
// 初始化静态成员指针
MQTTManager* MQTTManager::_instance = nullptr;
// 构造函数实现
MQTTManager::MQTTManager(const char* broker, int port, const char* username, const char* password,
const char* subscribeTopic, const char* publishTopic)
: _mqttClient(_espClient), // 初始化 PubSubClient
_broker(broker), _port(port), _username(username), _password(password),
_subscribeTopic(subscribeTopic), _publishTopic(publishTopic) {
// 在构造函数中设置静态实例指针
_instance = this;
}
// begin 方法实现
void MQTTManager::begin() {
Serial.println("MQTTManager initializing...");
_mqttClient.setServer(_broker.c_str(), _port);
// 设置 PubSubClient 的回调函数为我们的 wrapper
_mqttClient.setCallback(MQTTManager::pubSubCallbackWrapper);
// 生成 MQTT 客户端ID
uint8_t mac[6];
WiFi.macAddress(mac);
char clientIdChar[20]; // 需要足够的空间
sprintf(clientIdChar, "ESP32-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
_clientId = clientIdChar;
Serial.print("Generated MQTT Client ID: ");
Serial.println(_clientId.c_str());
}
// 设置消息回调函数
void MQTTManager::setCallback(MqttCallbackType callback) {
_callback = callback;
}
// PubSubClient 回调包装函数实现
// 这是 PubSubClient 调用的函数,它再调用我们存储的用户回调
void MQTTManager::pubSubCallbackWrapper(char* topic, byte* payload, unsigned int length) {
if (_instance && _instance->_callback) {
// 调用存储的用户回调函数
_instance->_callback(topic, payload, length);
}
}
// MQTT 连接尝试实现
bool MQTTManager::tryConnect() {
Serial.print("Attempting MQTT connection...");
// 使用客户端 ID、用户名和密码进行连接
if (_mqttClient.connect(_clientId.c_str(), _username.c_str(), _password.c_str())) {
Serial.println("connected!");
// 订阅主题
_mqttClient.subscribe(_subscribeTopic.c_str());
Serial.print("Subscribed to topic: ");
Serial.println(_subscribeTopic.c_str());
return true;
} else {
Serial.print("failed, rc=");
Serial.print(_mqttClient.state());
Serial.println(" try again in 5 seconds");
return false;
}
}
// loop 方法实现 (由任务调用)
void MQTTManager::loop() {
if (WiFi.status() == WL_CONNECTED) { // 只有在 WiFi 连接时才处理 MQTT
if (!_mqttClient.connected()) { // 如果 MQTT 未连接
unsigned long now = millis();
if (now - lastReconnectAttempt > 5000) {
lastReconnectAttempt = now;
if (tryConnect()) {
lastReconnectAttempt = 0;
}
}
} else {
_mqttClient.loop(); // 保持 MQTT 连接和处理消息
}
}
// 如果 WiFi 未连接,等待 WiFi 连接的任务处理
}
// 发布消息 (纯文本) 实现
bool MQTTManager::publish(const char* topic, const char* payload) {
if (_mqttClient.connected()) {
return _mqttClient.publish(topic, payload);
}
Serial.println("MQTT not connected, cannot publish.");
return false;
}
// 发布 JSON 消息实现
bool MQTTManager::publishJson(const char* action, int value) {
if (!_mqttClient.connected()) {
Serial.println("MQTT not connected, cannot publish JSON.");
return false;
}
// 创建JSON对象
DynamicJsonDocument doc(256); // 根据你的JSON文档大小调整
// 构建head部分
JsonObject head = doc.createNestedObject("head");
if(action == "audioplayend"){
head["message_type"] = 102; // 根据你的协议调整
}else if(action == "shoted"){
head["message_type"] = 101; // 根据你的协议调整
}
// 构建body部分
JsonObject body = doc.createNestedObject("body");
body["action"] = action; // 传入 action 字符串
body["val"] = value; // 传入 val 值
// 序列化JSON为字符串
String jsonString;
serializeJson(doc, jsonString);
Serial.print("Publishing JSON message to ");
Serial.print(_publishTopic.c_str());
Serial.print(": ");
Serial.println(jsonString);
// 发布消息
return _mqttClient.publish(_publishTopic.c_str(), jsonString.c_str());
}
// FreeRTOS 任务函数实现
void mqttLoopTask(void* pvParameters) {
MQTTManager* mqttManager = static_cast<MQTTManager*>(pvParameters);
if (!mqttManager) {
Serial.println("MQTT Task: Invalid parameter!");
vTaskDelete(NULL);
return;
}
for (;;) {
mqttManager->loop(); // 调用 MQTTManager 的 loop 方法
vTaskDelay(pdMS_TO_TICKS(100)); // 短暂延时
}
}
int MQTTManager::wifi_connected_is() {
return _mqttClient.state();
}
\ No newline at end of file
#ifndef MQTT_MANAGER_H
#define MQTT_MANAGER_H
#include <Arduino.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <ArduinoJson.h> // 需要用于发布JSON
#include <functional> // For std::function
#include <string> // For std::string
// 定义 MQTT 回调函数类型
using MqttCallbackType = std::function<void(char* topic, byte* payload, unsigned int length)>;
class MQTTManager {
private:
WiFiClient _espClient;
PubSubClient _mqttClient; // 必须在构造函数初始化列表中初始化
std::string _broker;
int _port;
std::string _username;
std::string _password;
std::string _clientId; // 使用 std::string 以便动态生成
std::string _subscribeTopic;
std::string _publishTopic;
MqttCallbackType _callback; // 存储用户提供的回调函数
unsigned long lastReconnectAttempt = 0;
// PubSubClient 的回调函数需要静态或全局
// static void staticMqttCallback(char* topic, byte* payload, unsigned int length); // 备选方案
// 使用类的 friend 任务函数或一个公共的静态方法来桥接 PubSubClient 回调
// 内部连接尝试函数
bool tryConnect();
public:
// 构造函数
// 传入 MQTT Broker 地址、端口、用户名、密码和话题
MQTTManager(const char* broker, int port, const char* username, const char* password,
const char* subscribeTopic, const char* publishTopic);
// 初始化方法
void begin();
// 设置消息回调函数
void setCallback(MqttCallbackType callback);
// 在 FreeRTOS 任务中调用,保持 MQTT 连接并处理消息
void loop();
// 发布消息 (纯文本)
bool publish(const char* topic, const char* payload);
// 发布 JSON 消息
bool publishJson(const char* action, int value);
// 检查 MQTT 连接状态
bool isConnected() { return _mqttClient.connected(); }
// 提供一个静态函数,作为 PubSubClient 的回调入口
// 这个函数会将调用转发到当前活跃的 MQTTManager 实例的实际回调函数
static void pubSubCallbackWrapper(char* topic, byte* payload, unsigned int length);
// 需要一个指向当前活跃实例的静态指针,以便回调 wrapper 可以访问
static MQTTManager* _instance;
// FreeRTOS 任务函数(需要在主文件或单独的.cpp中实现,并友元声明)
friend void mqttLoopTask(void* pvParameters);
int wifi_connected_is();
};
// FreeRTOS 任务函数声明
void mqttLoopTask(void* pvParameters);
extern MQTTManager mqttManager;
#endif // MQTT_MANAGER_H
\ No newline at end of file
#include "WiFiConfigManager.h"
#include <functional> // Needed for std::bind
// 构造函数实现
WiFiConfigManager::WiFiConfigManager(int port) : _server(port) {
// Preferences 对象不需要在这里初始化,在 begin() 或 load/save 中初始化
}
// begin 方法实现
bool WiFiConfigManager::begin() {
Serial.println("WiFiConfigManager initializing...");
// 加载保存的 WiFi 凭据
loadCredentials();
// 尝试连接 WiFi (如果凭据有效)
bool connected = false;
if (_ssid.length() > 0) {
Serial.print("Attempting to connect to saved WiFi: ");
Serial.println(_ssid);
WiFi.mode(WIFI_STA); // 设置为站点模式
WiFi.begin(_ssid.c_str(), _password.c_str());
// 等待连接或超时
int timeout_sec = 15; // 15秒超时
while (timeout_sec > 0 && WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
timeout_sec--;
}
Serial.println(); // 换行
if (WiFi.status() == WL_CONNECTED) {
Serial.println("WiFi connected successfully!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
connected = true;
} else {
Serial.println("WiFi connection failed.");
// 连接失败,清除凭据并进入 AP 模式
clearCredentials();
_ssid = ""; _password = ""; // 清除内存中的凭据
}
}
// 如果没有连接成功,启动 AP 配网模式
if (!connected) {
startConfigPortal();
} else {
// 如果连接成功,为了方便用户随时重新配置,也可以启动 WebServer
// 但是此时 WebServer 运行在 Station 模式下,需要通过分配的 IP 访问
//startConfigPortal(); // 启动 WebServer,但 WiFi 保持 STA 模式
if (WiFi.getMode() == WIFI_STA && WiFi.isConnected()) {
_server.on("/", std::bind(&WiFiConfigManager::handleRoot, this));
_server.on("/save", HTTP_POST, std::bind(&WiFiConfigManager::handleSave, this));
// _server.onNotFound(std::bind(&WiFiConfigManager::handleNotFound, this)); // 如果需要
// 启动 Web 服务器 (如果尚未启动)
_server.begin();
Serial.print("Web config available at http://");
Serial.println(WiFi.localIP());
}
}
return connected;
}
// loop 方法实现
void WiFiConfigManager::loop() {
_server.handleClient(); // 处理 WebServer 的客户端请求
}
// 启动 AP 配网模式实现
void WiFiConfigManager::startConfigPortal() {
Serial.println("Starting AP config portal...");
// 如果当前不是 AP 模式,切换到 AP 模式
if(WiFi.getMode() != WIFI_AP) {
WiFi.mode(WIFI_AP); // 设置 WiFi 为 AP 模式
// 启动一个名为 "ESP32C3_Config",密码为 "12345678" 的 WiFi 热点
WiFi.softAP("ESP32C3_Config", "12345678");
delay(100); // 给 AP 一点时间启动
IPAddress apIP = WiFi.softAPIP(); // 获取 AP 的 IP 地址
Serial.println("AP started!");
Serial.println("Connect to SSID: ESP32C3_Config (Password: 12345678)");
Serial.print("Open browser and visit: http://");
Serial.println(apIP);
}
// 设置 Web 服务器的路由
// 使用 std::bind 将成员函数绑定到 WebServer 回调
_server.on("/", std::bind(&WiFiConfigManager::handleRoot, this));
_server.on("/save", HTTP_POST, std::bind(&WiFiConfigManager::handleSave, this));
// _server.onNotFound(std::bind(&WiFiConfigManager::handleNotFound, this)); // 如果需要
// 启动 Web 服务器 (如果尚未启动)
_server.begin();
Serial.println("WebServer started.");
}
// 加载 WiFi 凭据实现
bool WiFiConfigManager::loadCredentials() {
Serial.println("Loading WiFi credentials from Preferences...");
_preferences.begin("wifi_config", true); // 打开 "wifi_config" 命名空间,true 表示只读模式
_ssid = _preferences.getString("ssid", ""); // 读取 "ssid"
_password = _preferences.getString("password", ""); // 读取 "password"
_preferences.end(); // 关闭 Preferences
Serial.print("Loaded SSID: '"); Serial.print(_ssid); Serial.println("'");
Serial.print("Loaded Password: '"); Serial.print(_password); Serial.println("'");
return _ssid.length() > 0; // 如果 SSID 不为空,认为加载成功
}
// 保存 WiFi 凭据实现
void WiFiConfigManager::saveCredentials() {
Serial.println("Saving WiFi credentials to 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("Credentials saved.");
}
// 清除 WiFi 凭据实现
void WiFiConfigManager::clearCredentials() {
Serial.println("Clearing WiFi credentials from Preferences...");
_preferences.begin("wifi_config", false);
_preferences.clear(); // 清除 "wifi_config" 命名空间下的所有数据
_preferences.end();
Serial.println("Credentials cleared.");
}
// WebServer 处理函数实现 (根目录)
void WiFiConfigManager::handleRoot() {
Serial.println("Handling / request");
_server.send(200, "text/html; charset=utf-8", _configHtml);
}
// WebServer 处理函数实现 (保存)
void WiFiConfigManager::handleSave() {
Serial.println("Handling /save request...");
if (_server.hasArg("ssid")) {
_ssid = _server.arg("ssid");
_password = _server.arg("password"); // password 可能为空
saveCredentials(); // 保存到 Flash
Serial.println("WiFi config saved. Restarting...");
// 向客户端发送成功信息
_server.send(200, "text/html; charset=utf-8", "<h3>保存成功,设备重启中...</h3><p>请等待设备重启并连接新的WiFi网络。</p>");
delay(2000); // 延迟2秒,确保浏览器收到响应
ESP.restart(); // 重启 ESP32
} else {
Serial.println("Error: Missing SSID argument in /save request.");
_server.send(400, "text/plain; charset=utf-8", "错误:缺少 SSID 参数!");
}
}
// 如果需要,可以实现 handleNotFound
// void WiFiConfigManager::handleNotFound() {
// Serial.println("Handling Not Found");
// _server.send(404, "text/plain", "Not found");
// }
\ No newline at end of file
#ifndef WIFI_CONFIG_MANAGER_H
#define WIFI_CONFIG_MANAGER_H
#include <Arduino.h>
#include <WiFi.h> // WiFi 功能库
#include <WebServer.h> // Web 服务器库,用于配网
#include <Preferences.h> // Flash 非易失性存储库,用于保存WiFi配置
#include <functional> // 需要 std::bind 或 lambda
class WiFiConfigManager {
private:
String _ssid = "";
String _password = "";
Preferences _preferences;
WebServer _server; // 必须在构造函数初始化列表中初始化
// HTML 配置页面内容(作为内部常量)
const char* _configHtml = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="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";
// WebServer 处理函数需要绑定到类的实例
void handleRoot();
void handleSave();
// void handleNotFound(); // 如果需要可以添加
// 内部辅助函数
bool loadCredentials(); // 从 Preferences 加载
void saveCredentials(); // 保存到 Preferences
public:
// 构造函数,指定 WebServer 监听端口
WiFiConfigManager(int port = 80);
// 初始化方法:加载配置,尝试连接,或启动 AP
// 返回 true 如果最终成功连接 WiFi
bool begin();
// 在 loop() 中调用,处理 WebServer 客户端
void loop();
// 启动 AP 配网模式
void startConfigPortal();
// 清除 WiFi 配置
void clearCredentials();
// 获取当前连接的 SSID 和密码 (如果已连接或加载)
String getSSID() const { return _ssid; }
String getPassword() const { return _password; }
// 检查 WiFi 连接状态
bool isWiFiConnected() const { return WiFi.status() == WL_CONNECTED; }
// 获取 WebServer 实例的引用,供需要额外路由的地方使用 (可选)
// WebServer& getServer() { return _server; }
};
#endif // WIFI_CONFIG_MANAGER_H
\ No newline at end of file
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
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