Appearance
这里是在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错误 |
| 真实状态同步 | ❌ 本地模拟 | ✅ 从硬件读取 |
🚀 使用流程
- 上传Arduino代码到UNO R4 WiFi
- PC连接
R4WIFI热点 - 打开HTML页面
- 点击 "获取真实状态" 或操作按钮后自动刷新
现在浏览器可以完整读取LED状态,真正做到双向通信了!🎉