MaopaiJD Esp8266 代码

#include <ESP8266WiFi.h>      // ESP8266 WiFi功能库
#include <PubSubClient.h>     // MQTT客户端库
#include <EEPROM.h>           // EEPROM存储库
#include <ESP8266WebServer.h> // Web服务器库

// -------------------------- 基础配置参数 --------------------------
const int EEPROM_SIZE = 512;          // EEPROM存储大小
const int FIRMWARE_VERSION = 0x07;    // 固件版本号(每次修改代码递增)
const char* AP_SSID = "==="; // 配网热点名称
const char* AP_PASS = "===";     // 配网热点密码
const IPAddress AP_IP(192, 168, 1, 234); // AP固定IP

// -------------------------- 巴法云配置(每个引脚独立主题) --------------------------
const char* MQTT_SERVER = "bemfa.com";    // 巴法云服务器
const int MQTT_PORT = 9501;               // 巴法云端口
const char* CLIENT_ID = "==="; // 替换为你的私钥
// 每个引脚的专属MQTT主题(需替换为你自己的巴法云主题)
const char* TOPIC_D0 = "ceshi002_d0";
const char* TOPIC_D1 = "ceshi002_d1";
const char* TOPIC_D2 = "ceshi002_d2";
const char* TOPIC_D3 = "ceshi002_d3";
const char* TOPIC_D4 = "ceshi002_d4";

// -------------------------- 网络与指令参数 --------------------------
const unsigned long RECONNECT_DELAY = 10000; // 重连间隔(10秒)
const unsigned long CMD_DEBOUNCE_TIME = 1000; // 指令防抖
const int WIFI_RETRY_LIMIT = 2;        // 最大重试次数
const unsigned long CONFIG_HOLD_TIME = 3000;  // 按键长按时间(3秒)

// -------------------------- 引脚1:D0(GPIO16)配置与状态 --------------------------
const int PIN_D0 = 16;
bool state_D0 = HIGH;          // 初始状态:关闭(HIGH=关闭,LOW=开启)
unsigned long lastCmd_D0 = 0;  // 最后指令时间(防抖)

// -------------------------- 引脚2:D1(GPIO5)配置与状态 --------------------------
const int PIN_D1 = 5;
bool state_D1 = HIGH;
unsigned long lastCmd_D1 = 0;

// -------------------------- 引脚3:D2(GPIO4)配置与状态 --------------------------
const int PIN_D2 = 4;
bool state_D2 = HIGH;
unsigned long lastCmd_D2 = 0;

// -------------------------- 引脚4:D3(GPIO0)配置与状态 --------------------------
const int PIN_D3 = 0;
bool state_D3 = HIGH;
unsigned long lastCmd_D3 = 0;

// -------------------------- 引脚5:D4(GPIO2)配置与状态 --------------------------
const int PIN_D4 = 2;
bool state_D4 = HIGH;
unsigned long lastCmd_D4 = 0;

// -------------------------- 全局变量 --------------------------
WiFiClient espClient;                // WiFi客户端
PubSubClient client(espClient);      // MQTT客户端
ESP8266WebServer server(80);         // Web服务器
String wifiSSID, wifiPassword;       // WiFi账号密码
unsigned long lastConnectTime = 0;   // 最后连接时间
bool isWiFiConnected = false;        // WiFi连接状态
int wifiRetryCount = 0;              // 重试计数器
const int CONFIG_PIN = 13;           // 配置重置按键(GPIO13)
unsigned long configPressStart = 0;  // 按键按下时间
bool configButtonPressed = false;    // 按键状态

// -------------------------- HTML页面模板(多引脚控制页面) --------------------------
const char PIN_CONTROL_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <meta name="theme-color" content="#2196F3">
    <title>MaopaiHome </title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
        body { background-color: #f8f9fa; min-height: 100vh; padding: 20px; }
        .container { max-width: 400px; margin: 20px auto; background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); padding: 25px 20px; }
        .header { text-align: center; margin-bottom: 30px; }
        .title { font-size: 22px; font-weight: 600; color: #333; }
        
        /* 单个引脚控制模块 */
        .pin-module { margin-bottom: 25px; padding-bottom: 20px; border-bottom: 1px solid #f0f0f0; }
        .pin-name { font-size: 18px; font-weight: 500; color: #333; margin-bottom: 15px; }
        .led-indicator { display: flex; justify-content: center; align-items: center; margin-bottom: 15px; }
        .led-light {
            width: 60px; height: 60px; border-radius: 50%; border: 2px solid #eee;
            transition: all 0.3s ease; display: flex; align-items: center; justify-content: center;
        }
        .led-light.on { background-color: #4CAF50; box-shadow: 0 0 15px rgba(76, 175, 80, 0.4); border-color: #c8e6c9; }
        .led-light.off { background-color: #f5f5f5; box-shadow: 0 0 5px rgba(0,0,0,0.05); border-color: #eee; }
        .led-text { font-size: 12px; color: white; font-weight: 500; }
        .led-light.off .led-text { color: #999; }
        
        /* 控制按钮 */
        .control-buttons { display: flex; gap: 15px; }
        .control-btn {
            flex: 1; height: 45px; border-radius: 8px; border: none;
            font-size: 15px; font-weight: 500; cursor: pointer; transition: all 0.2s;
        }
        .on-btn { background-color: #4CAF50; color: white; }
        .on-btn:active { background-color: #388E3C; }
        .off-btn { background-color: #f44336; color: white; }
        .off-btn:active { background-color: #D32F2F; }
        
        /* 状态信息与底部 */
        .status-bar {
            background-color: #f5f5f5; border-radius: 8px;
            padding: 12px 15px; font-size: 13px; color: #666; margin-bottom: 20px;
        }
        .bottom-bar { display: flex; justify-content: space-between; }
        .action-link { font-size: 13px; color: #2196F3; text-decoration: none; padding: 5px 0; }
        .action-link:hover { text-decoration: underline; }
        
        /* 加载动画 */
        .loader {
            width: 18px; height: 18px; border: 2px solid rgba(255,255,255,0.5);
            border-radius: 50%; border-top-color: white; animation: spin 1s linear infinite;
            display: none; margin-left: 8px;
        }
        @keyframes spin { to { transform: rotate(360deg); } }
        .loading .loader { display: inline-block; }
        .loading .btn-text { display: none; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div class="title">MaopaiHome</div>
        </div>
        
        <!-- 状态信息 -->
        <div class="status-bar">
            <div class="status-item"><span class="status-label">WiFi状态:</span> {wifi_status}</div>
            <div class="status-item"><span class="status-label">最后更新:</span> {update_time}</div>
        </div>
        
        <!-- D0 控制模块 -->
        <div class="pin-module">
            <div class="pin-name">D0 引脚</div>
            <div class="led-indicator">
                <div id="led-d0" class="led-light {d0_class}">
                    <div class="led-text">{d0_text}</div>
                </div>
            </div>
            <div class="control-buttons">
                <button class="control-btn on-btn" onclick="controlPin('d0', 'on')">
                    <span class="btn-text">开启</span>
                    <div class="loader"></div>
                </button>
                <button class="control-btn off-btn" onclick="controlPin('d0', 'off')">
                    <span class="btn-text">关闭</span>
                    <div class="loader"></div>
                </button>
            </div>
        </div>
        
        <!-- D1 控制模块 -->
        <div class="pin-module">
            <div class="pin-name">D1 引脚</div>
            <div class="led-indicator">
                <div id="led-d1" class="led-light {d1_class}">
                    <div class="led-text">{d1_text}</div>
                </div>
            </div>
            <div class="control-buttons">
                <button class="control-btn on-btn" onclick="controlPin('d1', 'on')">
                    <span class="btn-text">开启</span>
                    <div class="loader"></div>
                </button>
                <button class="control-btn off-btn" onclick="controlPin('d1', 'off')">
                    <span class="btn-text">关闭</span>
                    <div class="loader"></div>
                </button>
            </div>
        </div>
        
        <!-- D2 控制模块 -->
        <div class="pin-module">
            <div class="pin-name">D2 引脚</div>
            <div class="led-indicator">
                <div id="led-d2" class="led-light {d2_class}">
                    <div class="led-text">{d2_text}</div>
                </div>
            </div>
            <div class="control-buttons">
                <button class="control-btn on-btn" onclick="controlPin('d2', 'on')">
                    <span class="btn-text">开启</span>
                    <div class="loader"></div>
                </button>
                <button class="control-btn off-btn" onclick="controlPin('d2', 'off')">
                    <span class="btn-text">关闭</span>
                    <div class="loader"></div>
                </button>
            </div>
        </div>
        
        <!-- D3 控制模块 -->
        <div class="pin-module">
            <div class="pin-name">D3 引脚</div>
            <div class="led-indicator">
                <div id="led-d3" class="led-light {d3_class}">
                    <div class="led-text">{d3_text}</div>
                </div>
            </div>
            <div class="control-buttons">
                <button class="control-btn on-btn" onclick="controlPin('d3', 'on')">
                    <span class="btn-text">开启</span>
                    <div class="loader"></div>
                </button>
                <button class="control-btn off-btn" onclick="controlPin('d3', 'off')">
                    <span class="btn-text">关闭</span>
                    <div class="loader"></div>
                </button>
            </div>
        </div>
        
        <!-- D4 控制模块 -->
        <div class="pin-module">
            <div class="pin-name">D4 引脚</div>
            <div class="led-indicator">
                <div id="led-d4" class="led-light {d4_class}">
                    <div class="led-text">{d4_text}</div>
                </div>
            </div>
            <div class="control-buttons">
                <button class="control-btn on-btn" onclick="controlPin('d4', 'on')">
                    <span class="btn-text">开启</span>
                    <div class="loader"></div>
                </button>
                <button class="control-btn off-btn" onclick="controlPin('d4', 'off')">
                    <span class="btn-text">关闭</span>
                    <div class="loader"></div>
                </button>
            </div>
        </div>
        
        <!-- 底部操作 -->
        <div class="bottom-bar">
            <a href="/wifi-config" class="action-link">WiFi配置</a>
            <a href="javascript:location.reload()" class="action-link">刷新状态</a>
        </div>
    </div>

    <script>
        // 控制指定引脚
        function controlPin(pin, cmd) {
            // 重置所有按钮加载状态
            document.querySelectorAll('.control-btn').forEach(btn => btn.classList.remove('loading'));
            
            // 查找当前操作的按钮并设置加载状态
            const buttons = document.querySelectorAll('.control-btn');
            let targetBtn = null;
            for (let i = 0; i < buttons.length; i++) {
                if (buttons[i].getAttribute('onclick') === `controlPin('${pin}', '${cmd}')`) {
                    targetBtn = buttons[i];
                    break;
                }
            }
            if (targetBtn) targetBtn.classList.add('loading');
            
            // 发送请求
            fetch('/control?pin=' + pin + '&cmd=' + cmd)
                .then(response => {
                    if (!response.ok) throw new Error('网络响应不正常');
                    return response.text();
                })
                .then(data => {
                    if (targetBtn) targetBtn.classList.remove('loading');
                    setTimeout(updateAllStatus, 200);
                })
                .catch(error => {
                    console.error('控制失败:', error);
                    if (targetBtn) targetBtn.classList.remove('loading');
                    alert('操作失败: ' + error.message);
                });
        }
        
        // 更新所有引脚状态
        function updateAllStatus() {
            fetch('/get-status')
                .then(response => {
                    if (!response.ok) throw new Error('获取状态失败');
                    return response.json();
                })
                .then(data => {
                    // 更新WiFi和时间状态
                    document.querySelector('.status-bar .status-item:nth-child(1)').innerHTML = 
                        '<span class="status-label">WiFi状态:</span> ' + data.wifi;
                    document.querySelector('.status-bar .status-item:nth-child(2)').innerHTML = 
                        '<span class="status-label">最后更新:</span> ' + data.time;
                    
                    // 更新各引脚状态
                    updatePinStatus('d0', data.d0);
                    updatePinStatus('d1', data.d1);
                    updatePinStatus('d2', data.d2);
                    updatePinStatus('d3', data.d3);
                    updatePinStatus('d4', data.d4);
                })
                .catch(error => {
                    console.error('更新状态失败:', error);
                });
        }
        
        // 更新单个引脚状态
        function updatePinStatus(pin, status) {
            const ledElement = document.getElementById('led-' + pin);
            if (ledElement) {
                ledElement.className = 'led-light ' + status.class;
                const textElement = ledElement.querySelector('.led-text');
                if (textElement) textElement.textContent = status.text;
            }
        }
        
        // 页面加载完成后初始化
        window.addEventListener('DOMContentLoaded', function() {
            updateAllStatus();
            // 每3秒自动更新一次状态
            setInterval(updateAllStatus, 3000);
        });
    </script>
</body>
</html>
)rawliteral";

// -------------------------- HTML页面模板(WiFi配置/成功页面) --------------------------
const char WIFI_CONFIG_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <meta name="theme-color" content="#4CAF50">
    <title>MaopaiWiFi配置</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
        body { background-color: #f8f9fa; min-height: 100vh; padding: 20px; }
        .container { max-width: 400px; margin: 30px auto; background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); padding: 25px 20px; }
        .header { text-align: center; margin-bottom: 25px; }
        .title { font-size: 20px; font-weight: 600; color: #333; }
        .form-group { margin-bottom: 20px; }
        label { display: block; font-size: 14px; color: #666; margin-bottom: 6px; }
        input { width: 100%; height: 45px; padding: 0 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 15px; }
        input:focus { outline: none; border-color: #4CAF50; }
        .btn-group { display: flex; gap: 12px; margin-bottom: 20px; }
        .submit-btn { flex: 1; height: 48px; background-color: #4CAF50; color: white; border: none; border-radius: 6px; font-size: 16px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; }
        .submit-btn:active { background-color: #388E3C; }
        .clear-btn { flex: 1; height: 48px; background-color: #f44336; color: white; border: none; border-radius: 6px; font-size: 16px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; }
        .clear-btn:active { background-color: #D32F2F; }
        .back-link { display: block; text-align: center; margin-top: 15px; font-size: 14px; color: #2196F3; text-decoration: none; }
        .back-link:hover { text-decoration: underline; }
        .footer { text-align: center; margin-top: 20px; font-size: 12px; color: #999; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header"><div class="title">WiFi网络配置</div></div>
        <form method="POST" action="/save-wifi">
            <div class="form-group">
                <label for="ssid">WiFi名称</label>
                <input type="text" id="ssid" name="ssid" required placeholder="请输入WiFi名称">
            </div>
            <div class="form-group">
                <label for="pass">WiFi密码</label>
                <input type="password" id="pass" name="pass" placeholder="无密码则留空">
            </div>
            <div class="btn-group">
                <button type="submit" class="submit-btn">提交配置</button>
                <button type="button" class="clear-btn" onclick="confirmClear()">清除配置</button>
            </div>
            <a href="/" class="back-link">返回控制页</a>
        </form>
    </div>
    <div class="footer">重置热点名: MaopaiJD-Peiwang | 重置访问: 192.168.1.234</div>
    <script>
        function confirmClear() {
            if (confirm("确定要清除所有配置吗?\n清除后设备将重启并恢复初始状态!")) {
                fetch('/clear-eeprom')
                    .then(response => {
                        if (!response.ok) throw new Error('清除失败');
                        return response.text();
                    })
                    .then(() => alert("配置清除成功,设备即将重启..."))
                    .catch(() => alert("清除失败,请重试!"));
            }
        }
    </script>
</body>
</html>
)rawliteral";

const char CONFIG_SUCCESS_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
     <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>配置成功</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
        body { background-color: #f8f9fa; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
        .container { max-width: 350px; width: 100%; background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); padding: 25px 20px; text-align: center; }
        .success-icon { width: 60px; height: 60px; margin: 0 auto 20px; background-color: #4CAF50; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 30px; }
        h1 { font-size: 18px; color: #333; margin-bottom: 10px; }
        p { font-size: 14px; color: #666; margin-bottom: 25px; }
        .back-btn { display: inline-block; padding: 12px 30px; background-color: #2196F3; color: white; border-radius: 6px; text-decoration: none; font-size: 14px; font-weight: 500; }
    </style>
</head>
<body>
    <div class="container">
        <div class="success-icon">✓</div>
        <h1>WiFi配置成功</h1>
        <p>设备已尝试连接到指定WiFi网络</p>
        <a href="/" class="back-btn">返回控制页</a>
    </div>
</body>
</html>
)rawliteral";

// -------------------------- 工具函数 --------------------------
void clearEEPROM() {
    EEPROM.begin(EEPROM_SIZE);
    Serial.println("===== 开始彻底清除EEPROM所有数据 =====");
    for (int i = 0; i < EEPROM_SIZE; i++) { 
        EEPROM.write(i, 0xFF); 
        delay(1); 
    }
    EEPROM.commit();
    for (int i = 0; i < EEPROM_SIZE; i++) { 
        EEPROM.write(i, 0x00); 
        delay(1); 
    }
    EEPROM.commit();
    EEPROM.end();
    Serial.println("===== EEPROM所有数据已彻底清除 =====");
}

void saveWiFiConfig(String ssid, String pass) {
    EEPROM.begin(EEPROM_SIZE);
    for (int i = 0; i < EEPROM_SIZE; i++) { 
        EEPROM.write(i, 0); 
    }
    for (int i = 0; i < ssid.length() && i < 128; i++) { 
        EEPROM.write(i, ssid[i]); 
    }
    int passStart = 129;
    for (int i = 0; i < pass.length() && i < 256; i++) { 
        EEPROM.write(passStart + i, pass[i]); 
    }
    EEPROM.write(EEPROM_SIZE - 1, FIRMWARE_VERSION);
    EEPROM.commit();
    EEPROM.end();
}

bool loadWiFiConfig() {
    EEPROM.begin(EEPROM_SIZE);
    wifiSSID = "";
    wifiPassword = "";
    for (int i = 0; i < 128; i++) { 
        char c = EEPROM.read(i); 
        if (c == 0) break; 
        wifiSSID += c; 
    }
    int passStart = 129;
    for (int i = 0; i < 256; i++) { 
        char c = EEPROM.read(passStart + i); 
        if (c == 0) break; 
        wifiPassword += c; 
    }
    EEPROM.end();
    return wifiSSID.length() > 0;
}

bool isNewFirmwareFirstRun() {
    EEPROM.begin(EEPROM_SIZE);
    byte storedVersion = EEPROM.read(EEPROM_SIZE - 1);
    EEPROM.end();
    return storedVersion != FIRMWARE_VERSION;
}

void initAPMode() {
    WiFi.disconnect(true);
    delay(1000);
    WiFi.mode(WIFI_AP_STA);
    WiFi.softAPConfig(AP_IP, AP_IP, IPAddress(255, 255, 255, 0));
    bool apStarted = WiFi.softAP(AP_SSID, AP_PASS);
    if (apStarted) {
        Serial.println("\n===== AP模式已启动 =====");
        Serial.print("热点名称: "); Serial.println(AP_SSID);
        Serial.print("热点密码: "); Serial.println(AP_PASS);
        Serial.print("访问地址: "); Serial.println(WiFi.softAPIP().toString());
        Serial.println("========================");
    } else {
        Serial.println("AP模式启动失败,将重试...");
        delay(2000);
        initAPMode();
    }
}

String getCurrentTime() {
    unsigned long sec = millis() / 1000;
    unsigned long min = sec / 60;
    unsigned long hour = min / 60;
    sec %= 60;
    min %= 60;
    hour %= 24;
    String timeStr = (hour < 10 ? "0" : "") + String(hour) + ":" + 
                     (min < 10 ? "0" : "") + String(min) + ":" + 
                     (sec < 10 ? "0" : "") + String(sec);
    return timeStr;
}

// -------------------------- 核心控制函数(每个引脚独立函数) --------------------------
/**
 * 控制D0引脚
 * @param targetState 目标状态(LOW=开启,HIGH=关闭)
 * @param isFromMqtt 是否来自MQTT指令
 */
void controlD0(bool targetState, bool isFromMqtt = false) {
    if (millis() - lastCmd_D0 < CMD_DEBOUNCE_TIME || targetState == state_D0) {
        return;
    }
    lastCmd_D0 = millis();
    digitalWrite(PIN_D0, targetState);
    state_D0 = targetState;
    Serial.println("D0 状态切换为: " + String(targetState == LOW ? "开" : "关"));
    // MQTT同步
    if (!isFromMqtt && client.connected()) {
        client.publish(TOPIC_D0, targetState == LOW ? "on" : "off");
    }
}

/**
 * 控制D1引脚
 */
void controlD1(bool targetState, bool isFromMqtt = false) {
    if (millis() - lastCmd_D1 < CMD_DEBOUNCE_TIME || targetState == state_D1) {
        return;
    }
    lastCmd_D1 = millis();
    digitalWrite(PIN_D1, targetState);
    state_D1 = targetState;
    Serial.println("D1 状态切换为: " + String(targetState == LOW ? "开" : "关"));
    if (!isFromMqtt && client.connected()) {
        client.publish(TOPIC_D1, targetState == LOW ? "on" : "off");
    }
}

/**
 * 控制D2引脚
 */
void controlD2(bool targetState, bool isFromMqtt = false) {
    if (millis() - lastCmd_D2 < CMD_DEBOUNCE_TIME || targetState == state_D2) {
        return;
    }
    lastCmd_D2 = millis();
    digitalWrite(PIN_D2, targetState);
    state_D2 = targetState;
    Serial.println("D2 状态切换为: " + String(targetState == LOW ? "开" : "关"));
    if (!isFromMqtt && client.connected()) {
        client.publish(TOPIC_D2, targetState == LOW ? "on" : "off");
    }
}

/**
 * 控制D3引脚
 */
void controlD3(bool targetState, bool isFromMqtt = false) {
    if (millis() - lastCmd_D3 < CMD_DEBOUNCE_TIME || targetState == state_D3) {
        return;
    }
    lastCmd_D3 = millis();
    digitalWrite(PIN_D3, targetState);
    state_D3 = targetState;
    Serial.println("D3 状态切换为: " + String(targetState == LOW ? "开" : "关"));
    if (!isFromMqtt && client.connected()) {
        client.publish(TOPIC_D3, targetState == LOW ? "on" : "off");
    }
}

/**
 * 控制D4引脚
 */
void controlD4(bool targetState, bool isFromMqtt = false) {
    if (millis() - lastCmd_D4 < CMD_DEBOUNCE_TIME || targetState == state_D4) {
        return;
    }
    lastCmd_D4 = millis();
    digitalWrite(PIN_D4, targetState);
    state_D4 = targetState;
    Serial.println("D4 状态切换为: " + String(targetState == LOW ? "开" : "关"));
    if (!isFromMqtt && client.connected()) {
        client.publish(TOPIC_D4, targetState == LOW ? "on" : "off");
    }
}

// -------------------------- Web服务器与MQTT(核心功能) --------------------------
/**
 * 生成多引脚控制页面HTML
 */
String generatePinControlPage() {
    String html = String(PIN_CONTROL_HTML);
    String wifiStatus = isWiFiConnected ? "已连接: " + WiFi.SSID() : "未连接";
    String currentTime = getCurrentTime();
    
    // 替换D0状态
    html.replace("{d0_text}", state_D0 == LOW ? "已开启" : "已关闭");
    html.replace("{d0_class}", state_D0 == LOW ? "on" : "off");
    // 替换D1状态
    html.replace("{d1_text}", state_D1 == LOW ? "已开启" : "已关闭");
    html.replace("{d1_class}", state_D1 == LOW ? "on" : "off");
    // 替换D2状态
    html.replace("{d2_text}", state_D2 == LOW ? "已开启" : "已关闭");
    html.replace("{d2_class}", state_D2 == LOW ? "on" : "off");
    // 替换D3状态
    html.replace("{d3_text}", state_D3 == LOW ? "已开启" : "已关闭");
    html.replace("{d3_class}", state_D3 == LOW ? "on" : "off");
    // 替换D4状态
    html.replace("{d4_text}", state_D4 == LOW ? "已开启" : "已关闭");
    html.replace("{d4_class}", state_D4 == LOW ? "on" : "off");
    // 替换WiFi和时间
    html.replace("{wifi_status}", wifiStatus);
    html.replace("{update_time}", currentTime);
    
    return html;
}

/**
 * MQTT消息回调(匹配每个引脚的主题)
 */
void mqttCallback(char* topic, byte* payload, unsigned int length) {
    String topicStr = String(topic);
    String msg = "";
    for (int i = 0; i < length; i++) { 
        msg += (char)payload[i]; 
    }
    msg.trim();
    Serial.print("收到MQTT指令 ["); 
    Serial.print(topicStr); 
    Serial.print("]: "); 
    Serial.println(msg);
    
    // 匹配D0主题
    if (topicStr.equals(TOPIC_D0)) {
        if (msg.equalsIgnoreCase("on")) {
            controlD0(LOW, true);
        } else if (msg.equalsIgnoreCase("off")) {
            controlD0(HIGH, true);
        }
    }
    // 匹配D1主题
    else if (topicStr.equals(TOPIC_D1)) {
        if (msg.equalsIgnoreCase("on")) {
            controlD1(LOW, true);
        } else if (msg.equalsIgnoreCase("off")) {
            controlD1(HIGH, true);
        }
    }
    // 匹配D2主题
    else if (topicStr.equals(TOPIC_D2)) {
        if (msg.equalsIgnoreCase("on")) {
            controlD2(LOW, true);
        } else if (msg.equalsIgnoreCase("off")) {
            controlD2(HIGH, true);
        }
    }
    // 匹配D3主题
    else if (topicStr.equals(TOPIC_D3)) {
        if (msg.equalsIgnoreCase("on")) {
            controlD3(LOW, true);
        } else if (msg.equalsIgnoreCase("off")) {
            controlD3(HIGH, true);
        }
    }
    // 匹配D4主题
    else if (topicStr.equals(TOPIC_D4)) {
        if (msg.equalsIgnoreCase("on")) {
            controlD4(LOW, true);
        } else if (msg.equalsIgnoreCase("off")) {
            controlD4(HIGH, true);
        }
    }
}

/**
 * 连接MQTT服务器(订阅所有引脚主题)
 */
bool connectMQTT() {
    client.setServer(MQTT_SERVER, MQTT_PORT);
    client.setCallback(mqttCallback);
    if (client.connect(CLIENT_ID)) {
        // 订阅每个引脚的主题
        client.subscribe(TOPIC_D0);
        client.subscribe(TOPIC_D1);
        client.subscribe(TOPIC_D2);
        client.subscribe(TOPIC_D3);
        client.subscribe(TOPIC_D4);
        Serial.println("MQTT连接成功,已订阅所有引脚主题");
        
        // 同步初始状态到巴法云
        client.publish(TOPIC_D0, state_D0 == LOW ? "on" : "off");
        client.publish(TOPIC_D1, state_D1 == LOW ? "on" : "off");
        client.publish(TOPIC_D2, state_D2 == LOW ? "on" : "off");
        client.publish(TOPIC_D3, state_D3 == LOW ? "on" : "off");
        client.publish(TOPIC_D4, state_D4 == LOW ? "on" : "off");
        return true;
    } else {
        Serial.print("MQTT连接失败,错误代码: ");
        Serial.println(client.state());
        return false;
    }
}

/**
 * 初始化Web服务器路由
 */
void initWebServer() {
    // 根路由:多引脚控制页面
    server.on("/", []() {
        server.send(200, "text/html", generatePinControlPage());
    });
    
    // WiFi配置页面
    server.on("/wifi-config", []() {
        server.send_P(200, "text/html", WIFI_CONFIG_HTML);
    });
    
    // 保存WiFi配置
    server.on("/save-wifi", []() {
        if (server.hasArg("ssid")) {
            String ssid = server.arg("ssid");
            String pass = server.arg("pass");
            saveWiFiConfig(ssid, pass);
            server.send_P(200, "text/html", CONFIG_SUCCESS_HTML);
            delay(2000);
            ESP.restart();
        } else {
            server.send(400, "text/html", "<meta name='viewport' content='width=device-width'><body style='text-align:center;color:red;margin-top:50px;'>配置失败,请填写WiFi名称</body>");
        }
    });
    
    // 清除EEPROM
    server.on("/clear-eeprom", []() {
        clearEEPROM();
        server.send(200, "text/plain", "EEPROM cleared successfully");
        delay(2000);
        ESP.restart();
    });
    
    // 核心路由:控制指定引脚
    server.on("/control", []() {
        if (server.hasArg("pin") && server.hasArg("cmd")) {
            String pin = server.arg("pin");
            String cmd = server.arg("cmd");
            bool targetState = (cmd == "on") ? LOW : HIGH;
            
            // 根据引脚名执行控制
            if (pin == "d0") controlD0(targetState);
            else if (pin == "d1") controlD1(targetState);
            else if (pin == "d2") controlD2(targetState);
            else if (pin == "d3") controlD3(targetState);
            else if (pin == "d4") controlD4(targetState);
            else {
                server.send(400, "text/plain", "error: invalid pin");
                return;
            }
            
            server.send(200, "text/plain", "success");
        } else {
            server.send(400, "text/plain", "error: missing pin or cmd");
        }
    });
    
    // 核心路由:获取所有引脚状态(JSON)
    server.on("/get-status", []() {
        String json = "{"
            "\"wifi\":\"" + (isWiFiConnected ? "已连接: " + WiFi.SSID() : "未连接") + "\","
            "\"time\":\"" + getCurrentTime() + "\","
            "\"d0\":{\"text\":\"" + (state_D0 == LOW ? "已开启" : "已关闭") + "\",\"class\":\"" + (state_D0 == LOW ? "on" : "off") + "\"},"
            "\"d1\":{\"text\":\"" + (state_D1 == LOW ? "已开启" : "已关闭") + "\",\"class\":\"" + (state_D1 == LOW ? "on" : "off") + "\"},"
            "\"d2\":{\"text\":\"" + (state_D2 == LOW ? "已开启" : "已关闭") + "\",\"class\":\"" + (state_D2 == LOW ? "on" : "off") + "\"},"
            "\"d3\":{\"text\":\"" + (state_D3 == LOW ? "已开启" : "已关闭") + "\",\"class\":\"" + (state_D3 == LOW ? "on" : "off") + "\"},"
            "\"d4\":{\"text\":\"" + (state_D4 == LOW ? "已开启" : "已关闭") + "\",\"class\":\"" + (state_D4 == LOW ? "on" : "off") + "\"}"
        "}";
        server.send(200, "application/json", json);
    });
    
    // 处理404错误
    server.onNotFound([]() {
        server.send(404, "text/plain", "Not found");
    });
    
    server.begin();
    Serial.println("Web服务器已启动");
}

// -------------------------- 配置按键检查 --------------------------
void checkConfigButton() {
    if (digitalRead(CONFIG_PIN) == LOW) {
        if (!configButtonPressed) {
            configButtonPressed = true;
            configPressStart = millis();
            Serial.println("配置按键已按下,长按3秒将重置配置...");
        } else {
            if (millis() - configPressStart >= CONFIG_HOLD_TIME) {
                Serial.println("长按时间达标,开始重置配置...");
                // 闪烁D4作为提示
                for (int i = 0; i < 5; i++) {
                    digitalWrite(PIN_D4, LOW); delay(200);
                    digitalWrite(PIN_D4, HIGH); delay(200);
                }
                clearEEPROM();
                delay(2000);
                ESP.restart();
            }
        }
    } else {
        configButtonPressed = false;
    }
}

// -------------------------- 初始化与主循环 --------------------------
void setup() {
    Serial.begin(115200);
    delay(100); // 确保串口初始化完成
    
    // 初始化所有引脚为输出模式并设置初始状态
    pinMode(PIN_D0, OUTPUT);
    pinMode(PIN_D1, OUTPUT);
    pinMode(PIN_D2, OUTPUT);
    pinMode(PIN_D3, OUTPUT);
    pinMode(PIN_D4, OUTPUT);
    
    // 设置初始状态(关闭)
    digitalWrite(PIN_D0, HIGH);
    digitalWrite(PIN_D1, HIGH);
    digitalWrite(PIN_D2, HIGH);
    digitalWrite(PIN_D3, HIGH);
    digitalWrite(PIN_D4, HIGH);
    
    // 初始化配置按键
    pinMode(CONFIG_PIN, INPUT_PULLUP);
    
    // 新固件首次运行处理
    if (isNewFirmwareFirstRun()) {
        Serial.println("检测到新固件首次运行,执行EEPROM彻底清除...");
        // 闪烁D4作为提示
        for (int i = 0; i < 3; i++) {
            digitalWrite(PIN_D4, LOW); delay(500);
            digitalWrite(PIN_D4, HIGH); delay(500);
        }
        clearEEPROM();
        // 写入当前固件版本
        EEPROM.begin(EEPROM_SIZE);
        EEPROM.write(EEPROM_SIZE - 1, FIRMWARE_VERSION);
        EEPROM.commit();
        EEPROM.end();
        Serial.println("新固件初始化完成,准备进入配网模式");
    }
    
    // 启动AP模式
    initAPMode();
    
    // 加载WiFi配置
    bool hasConfig = loadWiFiConfig();
    if (hasConfig) {
        connectWiFi();
    } else {
        Serial.println("无WiFi配置,请通过网页设置");
        isWiFiConnected = false;
    }
    
    // 初始化Web服务器
    initWebServer();
    
    // 连接MQTT
    if (isWiFiConnected) {
        connectMQTT();
        lastConnectTime = millis();
    }
}

bool connectWiFi() {
    if (wifiSSID.length() == 0) return false;
    Serial.print("连接WiFi: ");
    Serial.println(wifiSSID);
    WiFi.mode(WIFI_STA);
    WiFi.begin(wifiSSID.c_str(), wifiPassword.c_str());
    
    // 等待连接,超时时间10秒
    unsigned long start = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) {
        delay(500);
        Serial.print(".");
        // 连接过程中闪烁D4作为提示
        digitalWrite(PIN_D4, !digitalRead(PIN_D4));
    }
    
    // 恢复D4初始状态
    digitalWrite(PIN_D4, state_D4);
    
    if (WiFi.status() == WL_CONNECTED) {
        Serial.println("\nWiFi连接成功,IP: " + WiFi.localIP().toString());
        isWiFiConnected = true;
        wifiRetryCount = 0;
        return true;
    } else {
        Serial.println("\nWiFi连接失败");
        isWiFiConnected = false;
        wifiRetryCount++;
        return false;
    }
}

void loop() {
    // 处理Web请求
    server.handleClient();
    
    // 检查配置按键
    checkConfigButton();
    
    // WiFi重连逻辑
    if (WiFi.status() != WL_CONNECTED) {
        isWiFiConnected = false;
        
        // 如果有配置且达到最大重试次数,清除配置并重启AP
        if (wifiSSID.length() > 0 && wifiRetryCount >= WIFI_RETRY_LIMIT) {
            Serial.println("已达到最大重试次数,清除WiFi配置并重启AP...");
            WiFi.mode(WIFI_AP);
            delay(1000);
            initAPMode();
            clearEEPROM();
            delay(2000);
            ESP.restart();
        }
        
        // 定时重连
        if (millis() - lastConnectTime > RECONNECT_DELAY) {
            Serial.println("尝试重新连接WiFi...");
            if (wifiSSID.length() > 0) { // 只有有配置时才尝试重连
                connectWiFi();
            }
            lastConnectTime = millis();
        }
    } else {
        isWiFiConnected = true;
    }
    
    // MQTT重连逻辑
    if (isWiFiConnected && !client.connected()) {
        Serial.println("尝试重新连接MQTT...");
        if (!connectMQTT()) {
            delay(5000);
        }
    }
    
    // 处理MQTT消息
    if (client.connected()) {
        client.loop();
    }
    
    delay(10);
}

 

posted @ 2025-10-01 16:39  MaopaiJD  阅读(15)  评论(0)    收藏  举报