import socket
import network
import machine
import time
from machine import Pin
全局变量
led = Pin(2, Pin.OUT) # 初始化GPIO2为输出模式
led.value(0) # 默认熄灭LED
def start_ap():
"""启动ESP32的AP模式"""
ap = network.WLAN(network.AP_IF)
ap.active(False)
time.sleep(0.5)
ap.active(True)
AP配置
ssid = 'ESP32_APTest'
password = '12345678'
ap.config(
essid=ssid,
password=password,
authmode=3, # WPA2-PSK
max_clients=10
)
等待AP启动完成
while not ap.active():
time.sleep(0.1)
print('='*20)
print('AP模式已启动')
print('SSID:', ssid)
print('密码:', password)
ip = ap.ifconfig()[0]
print('IP地址:', ip) # 通常是192.168.4.1
print('='*20)
return ip
def udp_server():
"""启动UDP服务,监听指令控制LED"""
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(("0.0.0.0", 7788))
udp_socket.settimeout(1) # 超时1秒,避免阻塞
while True:
try:
接收UDP数据
recv_data, sender_info = udp_socket.recvfrom(1024)
cmd = recv_data.decode("utf-8").strip()
执行指令
if cmd == "light on":
led.value(1)
print("来自{}的指令:点亮LED".format(sender_info))
udp_socket.sendto(b"success: LED ON", sender_info)
elif cmd == "light off":
led.value(0)
print("来自{}的指令:熄灭LED".format(sender_info))
udp_socket.sendto(b"success: LED OFF", sender_info)
else:
udp_socket.sendto(b"error: unknown command", sender_info)
兼容110和116两种超时错误码,不打印正常超时
except OSError as e:
err_code = e.args[0]
if err_code in (110, 116):
continue
else:
print("UDP服务异常: {}".format(e))
continue
except Exception as e:
print("UDP服务其他错误: {}".format(e))
continue
def web_server(ip):
"""改进版Web服务器:精确解析HTTP请求,返回标准响应"""
启动TCP Web服务器
web_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
web_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
web_socket.bind(("0.0.0.0", 80))
web_socket.listen(5)
设置服务器accept超时,避免永久阻塞
web_socket.settimeout(3)
print("API服务器已启动,地址: http://{}".format(ip))
while True:
conn = None
try:
接受客户端连接(最多等待3秒)
conn, addr = web_socket.accept()
print("客户端连接: {}".format(addr))
为每个连接设置接收超时2秒
conn.settimeout(2)
接收HTTP请求(最多1024字节)
request = conn.recv(1024).decode("utf-8").strip()
if not request:
continue
解析请求行
request_line = request.split('\r\n')[0] # 第一行是请求行
parts = request_line.split(' ')
if len(parts) < 2:
无效请求
response = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"
conn.send(response.encode("utf-8"))
continue
method, path = parts[0], parts[1]
仅处理GET请求
if method != 'GET':
response = "HTTP/1.1 405 Method Not Allowed\r\nAllow: GET\r\nContent-Length: 0\r\n\r\n"
conn.send(response.encode("utf-8"))
continue
精确匹配路径
if path == '/light/on':
led.value(1)
body = "LED已点亮"
response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nAccess-Control-Allow-Origin: *\r\n\r\n{}".format(len(body), body)
elif path == '/light/off':
led.value(0)
body = "LED已熄灭"
response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nAccess-Control-Allow-Origin: *\r\n\r\n{}".format(len(body), body)
else:
body = "API Not Found"
response = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\r\n{}".format(len(body), body)
发送响应
conn.send(response.encode("utf-8"))
except OSError as e:
忽略超时异常,继续循环
err_code = e.args[0]
if err_code in (110, 116):
continue
else:
print("Web服务器OS错误: {}".format(e))
except Exception as e:
print("Web服务器其他错误: {}".format(e))
finally:
确保连接被关闭
if conn:
try:
conn.close()
except:
pass
def main():
"""主函数:启动所有服务"""
启动AP模式
ip = start_ap()
启动UDP服务(新建线程)
import _thread
_thread.start_new_thread(udp_server, ())
启动Web服务器(主线程)
web_server(ip)
if name == "main":
main()
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 LED控制</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 50px auto;
text-align: center;
}
.control-btn {
width: 150px;
height: 60px;
font-size: 20px;
margin: 10px;
border: none;
border-radius: 8px;
cursor: pointer;
color: white;
}
#onBtn {
background-color: #4CAF50;
}
#offBtn {
background-color: #f44336;
}
#status {
margin-top: 20px;
font-size: 18px;
color: #333;
}
</style>
</head>
<body>
<h1>ESP32 LED控制器</h1>
<button id="onBtn" class="control-btn">点亮LED</button>
<button id="offBtn" class="control-btn">熄灭LED</button>
<div id="status">等待操作...</div>
<script>
// ESP32的实际IP地址(AP模式下通常是192.168.4.1)
const esp32_ip = "192.168.4.1";
// 发送请求到ESP32的API接口
async function sendCommand(cmd) {
try {
const response = await fetch(`http://${esp32_ip}/light/${cmd}`);
const result = await response.text();
document.getElementById('status').textContent = `操作结果: ${result}`;
} catch (err) {
document.getElementById('status').textContent = `操作失败: 请检查网络和ESP32 IP`;
console.error(err);
}
}
// 绑定按钮事件
document.getElementById('onBtn').addEventListener('click', () => {
sendCommand("on");
});
document.getElementById('offBtn').addEventListener('click', () => {
sendCommand("off");
});
</script>
</body>
</html>