Skip to content

这里是在Arduino UNO R4 WiFi代码中添加CORS头的完整方案,让浏览器能正常读取LED状态。


📝 完整Arduino代码(带CORS支持)

cpp
#include <WiFiS3.h>

// WiFi AP 配置
const char* ssid = "R4WIFI";
const char* password = "12345678";

// 创建WiFi服务器(端口80)
WiFiServer server(80);

// LED引脚(UNO R4 WiFi 自带LED)
const int ledPin = LED_BUILTIN;

// 存储LED状态
bool ledState = false;

void setup() {
    Serial.begin(115200);
    while (!Serial) { delay(10); }

    // 初始化LED
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, LOW);
    ledState = false;

    // 设置WiFi为AP模式
    Serial.println("正在配置AP模式...");
    
    // 方法1: 直接使用 beginAP(推荐)
    WiFi.beginAP(ssid, password);
    
    // 方法2: 使用传统方式(可选)
    // WiFi.mode(WIFI_AP);
    // WiFi.softAP(ssid, password);
    
    // 等待AP启动(使用不同的状态检查)
    Serial.print("等待AP启动");
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 30) {
        delay(500);
        Serial.print(".");
        attempts++;
    }
    
    Serial.println("\n✅ AP模式已启动");
    Serial.print("IP地址: ");
    Serial.println(WiFi.localIP());
    Serial.print("SSID: ");
    Serial.println(ssid);
    Serial.print("密码: ");
    Serial.println(password);

    // 启动服务器
    server.begin();
    Serial.println("✅ Web服务器已启动");
}

void loop() {
    // 监听客户端连接
    WiFiClient client = server.available();
    
    if (client) {
        Serial.println("📡 新客户端连接");
        
        // 读取HTTP请求
        String request = "";
        while (client.connected()) {
            if (client.available()) {
                char c = client.read();
                request += c;
                if (c == '\n' && request.endsWith("\r\n\r\n")) {
                    break;
                }
            }
        }
        
        // 解析请求路径
        String path = "";
        if (request.length() > 0) {
            int firstSpace = request.indexOf(' ');
            int secondSpace = request.indexOf(' ', firstSpace + 1);
            if (firstSpace > 0 && secondSpace > 0) {
                path = request.substring(firstSpace + 1, secondSpace);
            }
        }
        
        Serial.print("请求路径: ");
        Serial.println(path);
        
        // 处理不同路径
        if (path == "/H" || path == "/H/") {
            digitalWrite(ledPin, HIGH);
            ledState = true;
            Serial.println("💡 LED 开启");
            sendCorsResponse(client, ledState);
        } 
        else if (path == "/L" || path == "/L/") {
            digitalWrite(ledPin, LOW);
            ledState = false;
            Serial.println("💡 LED 关闭");
            sendCorsResponse(client, ledState);
        }
        else if (path == "/T" || path == "/T/") {
            ledState = !ledState;
            digitalWrite(ledPin, ledState ? HIGH : LOW);
            Serial.print("🔄 LED 翻转 -> ");
            Serial.println(ledState ? "开" : "关");
            sendCorsResponse(client, ledState);
        }
        else if (path == "/status" || path == "/status/") {
            sendJsonResponse(client, ledState);
        }
        else if (path == "/" || path == "/index.html") {
            sendHtmlPage(client, ledState);
        }
        else {
            sendNotFound(client);
        }
        
        // 关闭连接
        delay(10);
        client.stop();
        Serial.println("🔌 连接已关闭");
    }
}

// ============ 响应函数 ============

// 发送带CORS头的通用响应
void sendCorsResponse(WiFiClient& client, bool state) {
    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: text/plain");
    client.println("Access-Control-Allow-Origin: *");
    client.println("Access-Control-Allow-Methods: GET, OPTIONS");
    client.println("Access-Control-Allow-Headers: *");
    client.println("Connection: close");
    client.println();
    client.print("LED状态: ");
    client.println(state ? "ON" : "OFF");
}

// 发送JSON格式的状态
void sendJsonResponse(WiFiClient& client, bool state) {
    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: application/json");
    client.println("Access-Control-Allow-Origin: *");
    client.println("Access-Control-Allow-Methods: GET, OPTIONS");
    client.println("Access-Control-Allow-Headers: *");
    client.println("Connection: close");
    client.println();
    
    client.print("{\"status\":\"ok\",\"led\":");
    client.print(state ? "true" : "false");
    client.print(",\"message\":\"");
    client.print(state ? "LED已开启" : "LED已关闭");
    client.println("\"}");
}

// 发送HTML页面
void sendHtmlPage(WiFiClient& client, bool state) {
    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: text/html");
    client.println("Access-Control-Allow-Origin: *");
    client.println("Connection: close");
    client.println();
    
    client.println("<!DOCTYPE html>");
    client.println("<html>");
    client.println("<head><meta charset='UTF-8'><title>LED Control</title></head>");
    client.println("<body style='text-align:center;font-family:Arial;margin-top:50px;'>");
    client.println("<h1>💡 Arduino UNO R4 WiFi</h1>");
    client.println("<p>LED状态: <strong>" + String(state ? "ON" : "OFF") + "</strong></p>");
    client.println("<button onclick=\"fetch('/H')\" style='padding:15px 30px;background:#4CAF50;color:white;border:none;border-radius:5px;margin:5px;'>开灯</button>");
    client.println("<button onclick=\"fetch('/L')\" style='padding:15px 30px;background:#f44336;color:white;border:none;border-radius:5px;margin:5px;'>关灯</button>");
    client.println("<button onclick=\"fetch('/T')\" style='padding:15px 30px;background:#2196F3;color:white;border:none;border-radius:5px;margin:5px;'>翻转</button>");
    client.println("<br><br><button onclick=\"location.reload()\" style='padding:10px 20px;background:#607D8B;color:white;border:none;border-radius:5px;'>刷新状态</button>");
    client.println("</body></html>");
}

// 发送404响应
void sendNotFound(WiFiClient& client) {
    client.println("HTTP/1.1 404 Not Found");
    client.println("Content-Type: text/plain");
    client.println("Access-Control-Allow-Origin: *");
    client.println("Connection: close");
    client.println();
    client.println("404 - 路径未找到");
}

🎯 关键CORS头添加位置

在所有HTTP响应中添加这三行:

cpp
client.println("Access-Control-Allow-Origin: *");
client.println("Access-Control-Allow-Methods: GET, OPTIONS");
client.println("Access-Control-Allow-Headers: *");

🔄 更新后的HTML页面(能读取真实状态)

配合上面的Arduino代码,这里是一个能真正读取LED状态的HTML页面:

html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>LED控制(带状态读取)</title>
    <style>
        body { font-family: Arial; max-width: 500px; margin: 50px auto; text-align: center; }
        .led { display: inline-block; width: 30px; height: 30px; border-radius: 50%; 
               background: #ccc; margin: 20px; transition: 0.3s; }
        .led.on { background: #ffeb3b; box-shadow: 0 0 30px #ffeb3b; }
        button { padding: 12px 30px; margin: 5px; border: none; border-radius: 5px; 
                 cursor: pointer; font-size: 16px; }
        .btn-on { background: #4CAF50; color: white; }
        .btn-off { background: #f44336; color: white; }
        .btn-toggle { background: #2196F3; color: white; }
        .btn-status { background: #607D8B; color: white; }
        #log { margin-top: 20px; padding: 10px; background: #f0f0f0; border-radius: 5px; font-size: 14px; }
    </style>
</head>
<body>
    <h1>💡 LED控制</h1>
    <p>Arduino IP: <input type="text" id="ipInput" value="192.168.4.1" style="width:120px;text-align:center;"></p>
    
    <div class="led" id="ledIndicator"></div>
    <p>LED状态: <strong id="ledStatus">未知</strong></p>
    
    <div>
        <button class="btn-on" onclick="sendCmd('H')">🔆 开灯</button>
        <button class="btn-off" onclick="sendCmd('L')">🔅 关灯</button>
        <button class="btn-toggle" onclick="sendCmd('T')">🔄 翻转</button>
    </div>
    <br>
    <button class="btn-status" onclick="getStatus()">📡 获取真实状态</button>
    
    <div id="log">就绪</div>

    <script>
        const ipInput = document.getElementById('ipInput');
        const ledIndicator = document.getElementById('ledIndicator');
        const ledStatus = document.getElementById('ledStatus');
        const logDiv = document.getElementById('log');

        function getIP() { return ipInput.value.trim() || '192.168.4.1'; }

        function log(msg) { 
            logDiv.textContent = msg; 
            console.log(msg); 
        }

        async function sendCmd(cmd) {
            const ip = getIP();
            const url = `http://${ip}/${cmd}`;
            log(`发送: ${cmd}`);
            try {
                const response = await fetch(url);
                if (response.ok) {
                    // 命令发送成功后,延迟一下再获取状态
                    setTimeout(getStatus, 200);
                } else {
                    log(`❌ 错误: ${response.status}`);
                }
            } catch(err) {
                log(`❌ 连接失败: ${err.message}`);
            }
        }

        async function getStatus() {
            const ip = getIP();
            const url = `http://${ip}/status`;
            log('📡 获取状态...');
            try {
                const response = await fetch(url);
                if (!response.ok) {
                    log(`❌ HTTP ${response.status}`);
                    return;
                }
                const data = await response.json();
                log(`✅ 状态: LED = ${data.led ? '开启' : '关闭'}`);
                updateUI(data.led);
            } catch(err) {
                log(`❌ 获取状态失败: ${err.message}`);
            }
        }

        function updateUI(state) {
            if (state) {
                ledIndicator.className = 'led on';
                ledStatus.textContent = '开启';
                ledStatus.style.color = '#4CAF50';
            } else {
                ledIndicator.className = 'led';
                ledStatus.textContent = '关闭';
                ledStatus.style.color = '#f44336';
            }
        }

        // 页面加载时自动获取状态
        window.onload = function() {
            setTimeout(getStatus, 500);
        };
    </script>
</body>
</html>

✅ 主要改进

特性之前(无CORS)现在(有CORS)
发送命令✅ 可以(no-cors)✅ 可以(正常fetch)
读取状态❌ 无法读取✅ 可读取JSON
错误处理❌ 无法捕获✅ 可捕获HTTP错误
真实状态同步❌ 本地模拟✅ 从硬件读取

🚀 使用流程

  1. 上传Arduino代码到UNO R4 WiFi
  2. PC连接 R4WIFI 热点
  3. 打开HTML页面
  4. 点击 "获取真实状态" 或操作按钮后自动刷新

现在浏览器可以完整读取LED状态,真正做到双向通信了!🎉