保姆级教程:MicroPython+W5500 实现 HTTP 服务器,网页一键控灯

HTTP 服务器实验

使用树莓派 Pico 和 w5500 模块实现 HTTP 服务器时,要经过以下步骤:

  1. 建立连接:
    1. Web 浏览器(客户端)与 Web 服务器通过 TCP 连接建立起一个虚拟的"套接字"。这个套接字就成为了客户端与服务器间的通信通道
  2. 发送请求:
    1. 客户端通过这个套接字发送 HTTP 请求,通常是 GETPOST 方法
    2. GET 请求用于获取资源,POST 请求用于提交数据
  3. 服务器处理:
    1. 服务器接收到请求后,会根据请求的 URL 查找并读取相应的资源(如 HTML 文件)
    2. 对于 POST 请求,服务器还会解析请求体中包含的表单数据
  4. 返回响应:
    1. 服务器将资源数据以 HTTP 响应的形式通过套接字返回给客户端
    2. 响应包含 HTTP 头信息和实际的 HTML 文件内容
  5. 关闭连接:
    1. 在数据交换完成后,客户端和服务器会关闭这个 TCP 连接

以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\47 ETH_HTTP_Server 文件夹找到。

在以下示例代码中,我们搭建了一个简单的 HTTP 服务器,并通过网页控制开发板上的 LED 灯,代码如下:

Python 复制代码
# Python env   : MicroPython v1.23.0 on Wiznet W5500
# -*- coding: utf-8 -*-        
# @Time    : 2024/8/8 下午10:33   
# @Author  : 李清水            
# @File    : main.py       
# @Description : 以太网类实验,进行Http服务器测试

# ======================================== 导入相关模块 ========================================

# 导入硬件相关模块
from machine import Pin,SPI
# 导入时间相关模块
import time
# 导入网络相关模块
import network
# 导入用于创建和管理网络连接的套接字模块
from usocket import socket

# ======================================== 全局变量 ============================================

# 设备IP地址
ip = '192.168.1.20'
sn = '255.255.255.0'
gw = '192.168.1.1'
dns= '8.8.8.8'

# 用于网络连接的元组对象
netinfo=(ip, sn, gw, dns)

# LED灯标志
on_flag = 0
off_flag = 0
blink_flag = 0

# ======================================== 功能函数 ============================================

# 初始化网络连接
def w5x00_init() -> 'network.WIZNET5K':
    """
​    初始化 network.WIZNET5K 实例,设置静态 IP 地址并进行连接。

​    Returns:
​        network.WIZNET5K: 初始化并连接成功的 network.WIZNET5K 实例。

​    Raises:
​        None: 该函数不抛出异常。
​    """

​    ​# 声明全局变量
    global netinfo

    # 初始化SPI对象
    spi = SPI(0, 2_000_000, mosi=Pin(19), miso=Pin(16), sck=Pin(18))
    # 实例化network.WIZNET5K类,传入使用的片选引脚CS和复位引脚RST
    nic = network.WIZNET5K(spi, Pin(17), Pin(20))
    # 激活网络接口
    nic.active(True)
    # 如果DHCP获取失败,使用静态IP地址配置
    nic.ifconfig(netinfo)

    # 若是没有连接网络,则循环执行
    while not nic.isconnected():
        time.sleep(1)
        # 输出寄存器信息
        print(nic.regs())

    # 打印设置的IP地址
    print('ip :', nic.ifconfig()[0])
    print('sn :', nic.ifconfig()[1])
    print('gw :', nic.ifconfig()[2])
    print('dns:', nic.ifconfig()[3])

    # 返回network.WIZNET5K实例
    return nic

def web_page() -> str:
    """
​    生成网页内容,根据 LED 状态动态显示内容。

​    Returns:
​        str: 生成的 HTML 网页内容。

​    Raises:
​        None: 该函数不抛出异常。
​    """

​    ​# 声明全局变量led
    global led

    # 根据LED的状态标志位生成对应的文本
    if on_flag == 1:
        led_state = "ON"
    elif off_flag == 1:
        led_state = "OFF"
    else:
        led_state = "BLINK"

    # 生成HTML网页内容,根据LED状态动态显示内容
    html = """
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title> Freak Studio Raspberry Pi Pico Web Server Test </title>
    <style>
        body {
            background-color: #2c3e50;
            color: #ecf0f1;
            font-family: Arial, sans-serif;
            text-align: center;
            margin-top: 50px;
        }
        h1 {
            color: #e74c3c;
            font-size: 3em;
        }
        h2 {
            color: #3498db;
            font-size: 2em;
        }
        .button {
            background-color: #27ae60;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 12px;
            transition: background-color 0.3s ease;
        }
        .button:hover {
            background-color: #2ecc71;
        }
        .button2 {
            background-color: #e74c3c;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 12px;
            transition: background-color 0.3s ease;
        }
        .button2:hover {
            background-color: #c0392b;
        }
        .button3 {
            background-color: #90448e;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 12px;
            transition: background-color 0.3s ease;
        }
        .button3:hover {
            background-color: #c0392b;
        }
    </style>
    </head>
    <body>
    <div>
    <H1>Freak Studio Raspberry Pi Pico Web Server Test</H1>
    <h2>Control LED</h2>
    <p>PICO LED state: <strong>""" + led_state + """</strong></p>
    <p><a href="/?led=on"><button class="button">ON</button></a></p>
    <p><a href="/?led=off"><button class="button button2">OFF</button></a></p>
    <p><a href="/?led=blink"><button class="button button3">BLINK</button></a></p>
    </div>
    </body>
    </html>
    """

    # 返回生成的HTML内容
    return html

def web_server() -> None:
    """
​    运行 Web 服务器,处理客户端请求并控制 LED 状态。

​    Returns:
​        None: 该函数没有返回值。

​    Raises:
​        None: 该函数不抛出异常。
​    """

​    ​# 声明全局变量ip
    global ip
    # LED灯状态全局变量
    global on_flag, off_flag, blink_flag

    # 创建套接字对象
    s = socket()
    # 绑定IP地址和端口号80
    s.bind((ip, 80))
    # 监听最多5个客户端连接
    s.listen(5)

    while True:
        # 接受客户端的连接
        conn, addr = s.accept()
        # 打印客户端的IP地址和端口号
        print('Connect from %s' % str(addr))

        # 接收客户端的请求数据,最多1024个字节
        request = conn.recv(1024)
        # 将请求数据转换为字符串
        request = str(request)
        # 打印接收到的请求内容
        print('Content = %s' % request)

        # 查找LED打开命令在请求字符串中的位置
        led_on = request.find('/?led=on')
        # 查找LED关闭命令在请求字符串中的位置
        led_off = request.find('/?led=off')
        # 查找LED闪烁命令在请求字符串中的位置
        led_blink = request.find('/?led=blink')

        # 如果找到了打开命令,打开LED
        if led_on == 6:
            print("LED ON")
            led.value(1)

            on_flag = 1
            off_flag = 0
            blink_flag = 0

        # 如果找到了关闭命令,关闭LED
        if led_off == 6:
            print("LED OFF")
            led.value(0)

            on_flag = 0
            off_flag = 1
            blink_flag = 0

        # 如果找到了闪烁命令,闪烁LED
        if led_blink == 6:
            print("LED BLINK")
            for i in range(3):
                led.value(1)
                time.sleep(0.2)
                led.value(0)
                time.sleep(0.2)

            on_flag = 0
            off_flag = 0
            blink_flag = 1

        # 生成HTML响应内容
        response = web_page()

        # 发送HTTP响应头
        conn.send('HTTP/1.1 200 OK\n')
        conn.send('Connection: close\n')
        conn.send('Content-Type: text/html\n')
        conn.send('Content-Length: %s\n\n' % len(response))

        # 发送HTML内容
        conn.send(response)

        # 关闭连接
        conn.close()

# ======================================== 自定义类 ============================================

# ======================================== 初始化配置 ==========================================

# 延时3s,等待设备上电
time.sleep(3)
# 打印调试信息
print("FreakStudio : Using W5x00 Ethernet to do HTTP Server test")

# 将GPIO25配置为输出引脚,用于控制LED
led = Pin(25, Pin.OUT)
# 初始化w5500模块
nic = w5x00_init()

# ========================================  主程序  ===========================================

# 运行Web服务器
web_server()

基本工作流程可描述为:

  • 初始化网络接口 :使用 w5x00_init() 函数初始化网络接口,设置 IP 地址并进行连接;
  • 创建 Web 服务器 :定义 web_server() 函数,创建一个套接字对象并绑定到 IP 地址和端口 80,然后监听最多 5 个客户端连接;
  • 处理客户端请求 :在 web_server() 函数中,接受客户端的连接,接收请求数据,并根据请求内容控制 LED 的状态;
  • 生成网页内容 :使用 web_page() 函数生成网页内容,根据 LED 状态动态显示内容;
  • 发送 HTTP​​响应 :将生成的 HTML 内容作为 HTTP 响应发送给客户端;
  • 关闭连接:完成客户端请求后,关闭与客户端的连接。

web_page() 函数中我们生成一个 HTML 网页,允许用户通过点击按钮来控制 LED 的状态。网页提供了三个按钮:ONOFFBLINK,分别用于控制 LED 的亮灭和闪烁状态,在 HTML 网页内容部分,我们主要做了以下工作:

  • 使用多行字符串("""...""")来生成网页的 HTML 代码
  • HTML 内容包括基础的网页结构,如 <!DOCTYPE html> 声明、<html>, <head><body> 标签
  • 页面包含了一些 CSS 样式,用于美化网页,包括背景色、字体样式、按钮样式等
  • 其中有三个主要按钮,每个按钮都是一个 <a> 标签,其中包含一个 href 属性,用于定义点击按钮时发送的请求路径:
    • ON​​ 按钮:​ 点击后发送 /led=on 请求路径
    • OFF​​ 按钮:​ 点击后发送 /led=off 请求路径
    • BLINK​​ 按钮:​ 点击后发送 /led=blink 请求路径

当用户点击这些按钮时,Web 服务器将会收到相应的 HTTP 请求,并根据请求路径对 LED 进行操作,如打开、关闭或让 LED 闪烁。

web_server() 函数中,我们主要进行如下操作:

  1. 创建服务器套接字
    1. **s = socket()**:创建一个套接字对象,用于服务器端的网络通信
    2. **s.bind((ip, 80))**:绑定 IP 地址和端口号 80,表示服务器将通过这个 IP 地址和端口接受 HTTP 请求
    3. **s.listen(5)**:设置服务器最多可以接受 5 个客户端的连接请求
  2. 进入主循环,等待客户端连接
    1. **while True**:服务器进入一个无限循环,持续运行,等待客户端连接
    2. **conn, addr = s.accept()**:服务器等待并接受客户端连接,conn 代表连接对象,addr 是客户端的 IP 地址和端口号
    3. **print('Connect from %s' % str(addr))**:打印连接的客户端的 IP 地址和端口号
  3. 处理客户端请求
    1. **request = conn.recv(1024)**:接收客户端发送的 HTTP 请求数据,最多 1024 个字节
    2. **request = str(request)**:将请求数据转换为字符串,以便后续处理
    3. **print('Content = %s' % request)**:打印接收到的请求内容
  4. 解析和处理 LED 控制命令
    1. **led_on = request.find('/?led=on')**:查找请求字符串中 LED 打开命令的位置
    2. **led_off = request.find('/?led=off')**:查找 LED 关闭命令的位置
    3. **led_blink = request.find('/?led=blink')**:查找 LED 闪烁命令的位置

根据客户端发送的请求:

  • **LED ON**:
    • 如果找到了 LED 打开命令(led_on == 6),则打开 LED(led.value(1));
    • on_flag 设为 1,表示 LED 处于打开状态;同时将 off_flagblink_flag 设为 0;
  • **LED OFF**:
    • 如果找到了 LED 关闭命令(led_off == 6),则关闭 LED(led.value(0));
    • off_flag 设为 1,表示 LED 处于关闭状态;同时将 on_flagblink_flag 设为 0;
  • **LED BLINK**:
    • 如果找到了 LED 闪烁命令(led_blink == 6),则使 LED 闪烁三次,每次亮灭间隔 0.2 秒;
    • blink_flag 设为 1,表示 LED 处于闪烁状态;同时将 on_flagoff_flag 设为 0;
  1. 生成和发送 HTTP响应:
    1. **response = web_page()**:调用 web_page() 函数,生成包含 LED 状态的 HTML 网页内容;
    2. 发送 HTTP 响应头
      • conn.send('HTTP/1.1 200 OK\n')​:​发送状态行,表示请求处理成功;
      • conn.send('Connection: close\n')​:​告知客户端关闭连接;
      • conn.send('Content-Type: text/html\n')​:​指定响应内容的类型为 HTML;
      • conn.send('Content-Length: %s\n\n' % len(response))​:​指定响应内容的长度;
    3. 发送 HTML 内容
      • conn.send(response)​:​发送生成的 HTML 页面;
  2. 关闭连接:
    1. **conn.close()**:关闭与客户端的连接,等待下一个客户端请求;

其中,HTTP 请求包括以下内容:

Python 复制代码
Content = b'GET /favicon.ico HTTP/1.1\r\n
Host: 192.168.1.20\r\nConnection: keep-alive\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0\r\n
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\r\n
Referer: http://192.168.1.20/?led=off\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\r\n\r\n'

各个部分含义如下:

  1. GET /favicon.ico HTTP/1.1:这是一个 HTTP GET 请求方法,用于请求资源,/favicon.ico 是请求的资源路径,通常浏览器会请求网站的图标文件以显示在地址栏或标签页标题旁,HTTP/1.1 指定了请求使用的 HTTP 协议版本;
  2. Host: 192.168.1.20:这一行指定了服务器的域名或 IP 地址,这里是 192.168.1.20
  3. Connection: keep-alive:这行指示客户端希望使用持久连接(keep-alive),这样可以在同一个 TCP 连接上发送多个 HTTP 请求,减少了建立和关闭连接的开销;
  4. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0:这是用户代理字符串,它告诉服务器发起请求的客户端是什么,这里是一个模拟的 Chrome 浏览器的用户代理字符串;
  5. Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8:这一行指定了客户端能够接收的内容类型,以及它们的优先级。在这个例子中,客户端首选 AVIFWebPAPNG 格式的图像,其次是所有类型的图像,但是质量因子为 0.8;
  6. Referer: http://192.168.1.20/?led=off:这行指定了请求的引用页面,即用户从哪个页面链接到当前请求的页面;
  7. Accept-Encoding: gzip, deflate:这一行告诉服务器客户端支持哪些内容编码,以便服务器可以提供压缩的数据,加快传输速度;
  8. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6:这一行指定了客户端接受的语言类型,以及它们的优先级。在这个例子中,客户端首选简体中文(zh-CN),其次是其他各种英语变体;

可以看到,我们上面代码中使用如下命令判断是否改变 LED 状态:

Python 复制代码
# 如果找到了打开命令,打开LED
if led_on == 6:
    ...

# 如果找到了关闭命令,关闭LED
if led_off == 6:
    ...

# 如果找到了闪烁命令,闪烁LED
if led_blink == 6:
    ...

是因为请求字符串中 /?led=on 等表示 LED 状态的子字符串的起始位置索引就是 6。

整体工作流程图如下所示:

烧录程序,打开终端,可以看到输出如下:

在浏览器中输入该地址,显示如下页面:

点击"ON"按钮,可以看到 LED 灯点亮,终端输出如下信息:

点击其他按钮,可以看到都可以正常执行:

需要注意的是,在 ​**LED 灯闪烁中,我们使用阻塞执行,在 LED灯闪烁完成之前,我们点击所有按钮都不会执行操作。**

相关推荐
Freak嵌入式1 小时前
【Pico 以太网实战系列 4/6】UDP 协议实操:MicroPython 实现回显服务器
嵌入式
Freak嵌入式1 小时前
MicroPython 以太网实战:Pico+W5500 TCP 客户端详解
嵌入式
Freak嵌入式1 小时前
MicroPython W5500 以太网实验|ping 测试:原理 + 实操全解析
嵌入式
Freak嵌入式5 小时前
USB-CDC 通信实战应用:树莓派 Pico 虚拟串口 + select+MicroPython 全拆解
嵌入式
Freak嵌入式1 天前
PIO 入门:手把手实现板载 LED 闪烁,原理与代码全解析
嵌入式
Freak嵌入式1 天前
一文搞懂树莓派 Pico 任务调度器:基于定时器的 MicroPython 多任务实现
嵌入式
花 满 楼2 天前
国芯科技CCFC3008PCS BIST (内建自测试) 分析总结
mcu·嵌入式·bist
虎王物联2 天前
ESP32 FreeRTOS多任务优先级翻转与互斥量保护实战
物联网·嵌入式·esp32·freertos
与你4033 天前
CLion 中为什么要重写 _write,而不是 fputc?
嵌入式