一、ESP32 WiFi模式
首先我们需要了解ESP32的WIFI模式。
一)、基础模式定义
-
STA模式(Station)
- 设备作为客户端连接现有WiFi网络(如路由器),获取IP地址后可与互联网通信
- 适用场景:需要访问外网的物联网设备(如气象站上传数据到云端)
-
AP模式(Access Point)
- 设备自建WiFi热点供其他设备连接,形成本地网络
- 默认IP地址为
192.168.4.1
,客户端分配192.168.4.x
段地址 - 适用场景:无路由器的局域网控制(如智能家居设备本地配置)
-
STA+AP混合模式
- 同时运行STA和AP接口,实现内外网双通道通信
- 典型应用:物联网网关(本地控制+云端同步)
二)、模式特性对比
特性 | STA模式 | AP模式 |
---|---|---|
网络角色 | 客户端 | 服务器 |
IP获取方式 | DHCP动态分配 | 固定IP(手动设置) |
最大连接数 | 仅连接1个AP | 支持最多4个客户端 |
典型功耗 | 低(仅接收数据) | 较高(需广播热点) |
代码初始化 | network.WLAN(network.STA_IF) | network.WLAN(network.AP_IF) |
三)、MicroPython实现代码
1、STA模式
import network
sta = network.WLAN(network.STA_IF)
sta.active(True) # 激活STA接口
sta.connect("HomeWiFi", "password123") # 连接目标网络
print("STA IP:", sta.ifconfig()[0]) # 输出动态分配的IP
2、AP模式
ap = network.WLAN(network.AP_IF)
ap.config(
essid="ESP32-AP",
password="ap_password",
authmode=network.AUTH_WPA2_PSK # 强制加密
)
ap.active(True) # 激活AP接口
ap.ifconfig(('192.168.4.1', '255.255.255.0', '192.168.4.1', '8.8.8.8')) # 固定IP
authmode
可设置为:
network.AUTH_OPEN
(无密码)network.AUTH_WEP
(已过时)network.AUTH_WPA_PSK
或AUTH_WPA2_PSK
(推荐)
默认情况下,AP会自动分配IP(如192.168.4.x
),也可通过ap.ifconfig()
自定义。
3、混合模式
# 同时激活两个接口
sta.active(True)
ap.active(True)
# 分别配置参数(互不影响)
sta.connect("OfficeWiFi", "company123")
ap.config(essid="ESP32-Gateway", password="gateway888")
混合模式下需确保STA和AP使用不同信道(避免干扰) ,后面我们会详细讲解如何设置信道。
四)、模式选择指南
-
STA模式
- 需访问互联网资源
- 设备部署在已有WiFi覆盖环境
-
AP模式
- 无可用路由器时的设备直连场景
- 需要本地快速配置参数(如Web配网界面)
-
混合模式
- 需要同时支持本地控制与云端同步
- 实现网络数据中继(如ESP32作为IoT网关)
二、WIFI信道设置
1、STA模式指定信道连接
import network
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.connect("HomeWiFi", "password", bssid=b'\xaa\xbb\xcc\xdd\xee\xff', channel=6) # 指定MAC和信道
2、AP模式固定信道
ap = network.WLAN(network.AP_IF)
ap.config(essid="ESP_AP", channel=11, authmode=network.AUTH_WPA2_PSK) # 强制信道11
ap.active(True)
3、动态信道选择
import network, time
sta = network.WLAN(network.STA_IF)
ap = network.WLAN(network.AP_IF)
def channel_optimize():
sta.active(True)
scan_res = sta.scan() # 执行全信道扫描
channel_load = {ch:0 for ch in range(1,14)}
for ap_info in scan_res:
channel_load[ap_info[2]] += 1
best_ch = min(channel_load, key=channel_load.get) # 选择负载最低信道
ap.config(channel=best_ch) # AP切换至最优信道
print(f"AP切换至信道{best_ch}")
三、修改 AP 模式 Web 页面
这部分内容比较复杂,我们这里只是简单的用两个实例阐述ESP32的AP模式下web页面的应用。
HTML 页面设计与嵌入
- 编写包含动态交互元素的 HTML 文件,如 GPIO 控制按钮或传感器数据显示模块
- 将 HTML 代码直接嵌入 MicroPython 代码中,或通过文件系统托管静态资源(需使用
SPIFFS
或外部存储)
一)、GPIO 控制功能
以下是基于 MicroPython 的 AP 模式网页控制示例,整合了 HTML 页面交互与 GPIO 控制功能:
1、基础代码框架
import network
import socket
from machine import Pin
from neopixel import NeoPixel
from ws2812 import WS2812,WS2812_POWEROFF,WS2812_POWERON
# AP 模式配置
ap = network.WLAN(network.AP_IF)
ap.active(True)
ap.config(essid='MyESP32AP', password='mypassword123', authmode=3) # WPA2 加密
print("AP IP:", ap.ifconfig()[0]) # 输出 IP (通常为 192.168.4.1)
# GPIO 初始化
led_state = "OFF"
wsled = WS2812(Pin(48, Pin.OUT),1,3,1)
wsled.ws2812_Power(WS2812_POWEROFF)
关于WS2812灯珠控制,请参考:MicroPython 开发基于ESP32S3控制ws2812灯带的程序
2、HTTP 服务器与页面处理
html ="""
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {font-family: Arial; text-align: center; margin-top: 50px;}
.btn {padding: 10px 20px; font-size: 16px;}
</style>
</head>
<body>
<h1>ESP32 LED 控制</h1>
<p>当前状态: <strong>{state}</strong></p>
<form action="/submit">
<button class="btn" name="cmd" value="on">打开LED</button>
<button class="btn" name="cmd" value="off">关闭LED</button>
</form>
</body>
</html>
"""
#以上是我们通常的web页面代码
# 🔄 请求处理逻辑
def handle_request(client_sock):
global led_state
request = client_sock.recv(1024).decode()
if "GET /submit?cmd=on" in request:
# 解析控制指令
# print (request)
wsled.ws2812_Power(1)
led_state = "ON"
elif 'GET /submit?cmd=off' in request:
wsled.ws2812_Power(0)
led_state = "OFF"
# 返回动态页面
response = html.replace("{state}", led_state) #动态修改LED灯的状态
client_sock.send("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
client_sock.send(response) #更新web页面
client_sock.close()
3、启动服务
# 启动 HTTP 服务器
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80)) # 监听 80 端口
s.listen(3) #设置最大监听
print("等待客户端连接...")
while True:
client, addr = s.accept()
# print('客户端来自:', addr)
handle_request(client)
完整代码请参考:Micropythonforesp32AP,web页面控制WS2812灯珠资源-优快云文库
二)、web页面配网
1、配网页面代码:
# Captive portal的HTML页面
HTML = """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>ESP32 WiFi Setup</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
form {
display: flex;
flex-direction: column;
height: 100vh;
width:600px;
align-items: center;
background-color: #f0f0f0;
padding: 20px;
border-radius: 5px;
}
label, input {
font-size: 24px;
font-weight: 700;
display: flex;
flex-direction: column;
height: 60px;
width:400px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<form action="/connect" method="post">
<label for="ssid">SSID:</label>
<input type="text" id="ssid" name="ssid">
<label for="password">Password:</label>
<input type="password" id="password" name="password">
<input type="submit" value="Connect" style="
text-align: center; /* 水平居中 */
width: 150px; /* 自定义宽度 */
padding: 8px; /* 对称内边距 */
">
</form>
</body>
</html>
"""
2、配网成功返回页面
HTMLSuccess = """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>ESP32 WiFi Setup</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<hl>已连接到WiFi。正在关闭AP模式……</h1>
</body>
</html>
3、配网失败返回页面
"""
HTMLFail = """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>ESP32 WiFi Setup</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<hl>连接失败,请重试……</h1>
</body>
</html>
"""
4、完整代码
import network
import socket,json
from machine import Pin, Timer
import time
# 配置热点模式
AP_SSID = '913IOT'
AP_PASSWORD = '913AIRobot'
# Captive portal的HTML页面
HTML = """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>ESP32 WiFi Setup</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
form {
display: flex;
flex-direction: column;
height: 100vh;
width:600px;
align-items: center;
background-color: #f0f0f0;
padding: 20px;
border-radius: 5px;
}
label, input {
font-size: 24px;
font-weight: 700;
display: flex;
flex-direction: column;
height: 60px;
width:400px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<form action="/connect" method="post">
<label for="ssid">SSID:</label>
<input type="text" id="ssid" name="ssid">
<label for="password">Password:</label>
<input type="password" id="password" name="password">
<input type="submit" value="Connect" style="
text-align: center; /* 水平居中 */
width: 150px; /* 自定义宽度 */
padding: 8px; /* 对称内边距 */
">
</form>
</body>
</html>
"""
HTMLSuccess = """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>ESP32 WiFi Setup</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<hl>已连接到WiFi。正在关闭AP模式……</h1>
</body>
</html>
"""
HTMLFail = """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>ESP32 WiFi Setup</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<hl>连接失败,请重试……</h1>
</body>
</html>
"""
wifi_conn = False
wifi_config = None
def start_ap():
ap = network.WLAN(network.AP_IF)
ap.active(False)
ap.active(True)
ap.config(essid=AP_SSID, password=AP_PASSWORD)
# print("AP模式已启用: SSID '{}' 密码 '{}'".format(AP_SSID, AP_PASSWORD))
return ap
# 从 JSON 文件中读取 WiFi 信息
def read_wifi_config():
try:
with open("wifi_config.json", 'r') as file:
data = json.load(file)
if data['ssid'] == "" and data['password'] == "":
return False
return data['ssid'], data['password']
except Exception as e:
print(e)
return False
def connect_wifi(ssid, password):
global wifi_conn,wifi_config
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
try:
if not wlan.isconnected():
wlan.connect(ssid, password)
max_wait = 10
while max_wait > 0:
if wlan.isconnected():
break
max_wait -= 1
# print("等待连接……",max_wait)
time.sleep(1)
if not wlan.isconnected():
return False
else:
wifi_conn = True
wifi_config=wlan.ifconfig()
server_socket.close()
return True
except Exception as e:
return "wifi not find"
def handle_connection(client, addr, ap):
global wifi_config
# print('客户端连接来自', addr)
request = client.recv(1024).decode()
# print('请求内容:', request)
if 'POST /connect' in request:
# 从请求中提取SSID和密码
ssid_start = request.find('ssid=') + 5
ssid_end = request.find('&', ssid_start)
password_start = request.find('password=') + 9
ssid = request[ssid_start:ssid_end]
password = request[password_start:]
# print('尝试连接WiFi SSID:', ssid)
wifi_data = {
'ssid': ssid,
'password': password
}
# 重置并连接到新WiFi网络
if connect_wifi(ssid, password):
response = HTMLSuccess
client.send(response)
client.close()
# 关闭AP模式
# print('关闭AP模式...')
time.sleep(2)
ap.active(False)
with open("wifi_config.json", 'w') as file:
json.dump(wifi_data, file)
return True # 表示连接成功
else:
response = HTMLFail
client.send(response)
else:
response = 'HTTP/1.1 200 OK\nContent-Type: text/html\n\n' + HTML
client.send(response)
client.close()
return False # 表示连接失败
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connected = False
def conn_wifi():
global connected
ap = start_ap()
server_socket.bind(('0.0.0.0', 80))
server_socket.listen(1)
print('Web服务器已启动,等待客户端连接...')
try:
# 主程序部分
while not connected:
client_socket, addr = server_socket.accept()
connected = handle_connection(client_socket, addr, ap)
except KeyboardInterrupt:
print("Exiting program...")
finally:
# 确保关闭服务器套接字
server_socket.close()
# led_pin.value(0)
def main():
global wifi_conn,wifi_config
while True:
if read_wifi_config() ==False:
conn_wifi()
else:
ssid,password = read_wifi_config()
if wifi_conn != True:
connect_wifi(ssid,password)
if wifi_conn == True:
return True
if __name__ == "__main__":
print(main())