最近有不少朋友问我ESP32 跑 MicroPython 到底能不能发 HTTP 请求答案是不仅能而且比很多人想象的要简单。网上很多教程都是 Arduino 或者 ESP-IDF 的 C 写法对于习惯 Python 的开发者来说MicroPython 的 HTTP 请求其实是更顺手的一条路。不过这里面坑也不少比如内存不够、中文编码乱码、服务器返回 400 等等。这篇文章就把 ESP32 上 MicroPython 的 HTTP GET 和 POST 请求从零到落地讲透包含了我的实测过程和踩坑记录希望对你有用。适合的人群想用 ESP32 做数据上报、远程控制、对接云平台或简单 API 的开发者已经会一点 MicroPython 但没搞过网络请求的人以及被各种网络库搞晕想直接抄作业的人。这篇文章不是单纯贴代码而是把为什么这么写、遇到问题怎么排查讲清楚。1. 动手前的准备硬件、固件和开发环境1.1 硬件选择与串口通道做 HTTP 请求至少需要一块带 WiFi 的板子ESP32 全系列都能干这事比如常见的 ESP32 DevKitC、NodeMCU-32S还有带 OLED 的板子也都可以。这里要提一句ESP8266 虽然也能跑 MicroPython但内存小、网络栈弱做简单的 GET 还行POST 大一点的 JSON 就容易崩。所以如果条件允许建议直接用 ESP32。连接开发板需要一根能传数据的 USB 线很多线只能充电接上去电脑没反应这个我踩过好几次。Windows 下需要装串口驱动老的 ESP32 板子用 CP2102 芯片新的用 CH340驱动装不上就去看设备管理器里有没有出现 COM 口。macOS 和 Linux 一般免驱但可能会有权限问题给串口设备加一下权限就行。1.2 烧录 MicroPython 固件官方固件可以从 MicroPython 官网下载选择 ESP32 对应的 bin 文件。烧录工具推荐用 esptool命令行方式最稳pip install esptool esptool.py --port COM4 erase_flash esptool.py --port COM4 write_flash -z 0x1000 ESP32_GENERIC-20240602-v1.23.0.bin注意端口号改成你电脑上的实际端口macOS 一般是/dev/cu.usbserial-xxxx。烧录完成后打开串口终端波特率设置成 115200复位一下开发板应该能看到 MicroPython 的交互提示符说明固件已经跑起来了。如果你不想用命令行也可以用 Thonny 或 uPyCraft图形界面操作适合新手。不过我个人的习惯是先用 esptool 擦除再烧写因为部分开发板出厂自带示例程序不擦除会有冲突。1.3 编辑器与文件管理我日常用的工具是 Thonny因为它自带文件浏览器可以把本地的 Python 文件上传到板子也能从板子下载文件回来。也可以用 mpfshell、ampy 这类命令行工具但说实话都没有 Thonny 直观。要让 ESP32 发 HTTP 请求需要用到urequests这个模块。有人会问MicroPython 里没有内置requests吗没有。标准库只带了socket、usocket这些底层接口urequests是由社区提供的功能比 CPython 的requests精简但对于比较简单的 GET/POST 已经足够。Thonny 的默认解释器里通常没有这个文件需要手动下载urequests.py放到板子的lib目录下。如果你用的是较新版本固件有的固件已经内置了urequests可以先在交互环境里跑一下import urequests如果报错就说明需要自己上传。注意MicroPython 的urequests模块名字比 CPython 少一个字母不要写成requests。当然有些固件也兼容requests别名但为了保险统一用urequests最稳妥。2. 先搞懂 HTTP GET 和 POST 的差别再写代码2.1 HTTP 协议其实很简单HTTP 的本质就是一个请求-响应的过程。客户端ESP32把一段文本发给服务器这段文本包含了请求方法、路径、请求头、请求体服务器解析之后返回一段文本包含状态码、响应头和响应体。GET 请求通常用来从服务器获取数据。比如读取传感器数据、查询设备状态、获取天气信息。GET 的特点是参数拼在 URL 后面比如http://api.example.com/temp?citybeijing服务器从 URL 的 query string 里提取参数。它的语义是“幂等”的意思是多次请求同一个 URL理论上结果是一样的不会改变服务器上的资源。POST 请求通常用来向服务器提交数据。比如上报传感器数据、推送日志、创建任务。POST 的参数放在请求体body里通过Content-Type告诉服务器数据的格式。POST 不是幂等的多次提交可能创建多个资源。对于 ESP32 这种设备来说POST 最常见的作用就是往云平台推送消息。2.2 数据格式选 JSON 还是表单在物联网场景里大部分 API 走的是 JSON。ESP32 端把传感器数据打包成一个字典然后通过ujson.dumps()序列化成 JSON 字符串再作为 POST 请求体发送。服务器端拿到 JSON 后解码。也有一些老系统只支持application/x-www-form-urlencoded这就在 POST 请求体里写key1value1key2value2这种格式然后设置Content-Type: application/x-www-form-urlencoded。具体用哪种取决于服务器的接口文档。我建议优先用 JSON因为结构化更清晰微控制器上处理也不复杂。2.3 为什么 MicroPython 不直接支持标准 requestsCPython 的requests库依赖了很多 Python 标准库的特性比如ssl、urllib3、chardet这些在 MicroPython 的底层环境里实现不全所以官方只提供了精简版urequests。它用的还是 socket但是帮你封装了 DNS 解析、连接、编码、响应对象等逻辑省去了直接裸写 socket 的工作量。有人建议直接用socket发原始 HTTP 请求这样最省内存也最可控。但代价是要自己处理连接、请求头、响应解析代码一多就容易出 bug。我的建议是如果只是做一两个请求可以直接用urequests如果对实时性、内存占用有极端要求再考虑裸 socket。实际上urequests底层也没有做太多事性能差距对于普通传感器上报完全可忽略。3. 实操五个步骤在 ESP32 上完成 HTTP GET 请求3.1 第一步连接 WiFi先写一个通用的连接 WiFi 函数。ESP32 连接 WiFi 本质上就是一个 STA 模式初始化然后等待STAT_GOT_IP状态。注意要设置wlan.active(True)很多人漏了这一步导致一直连不上。import network import time def connect_wifi(ssid, password, timeout15): wlan network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print(fconnecting to {ssid}...) wlan.connect(ssid, password) start time.time() while not wlan.isconnected() and time.time() - start timeout: time.sleep(0.5) if wlan.isconnected(): print(network config:, wlan.ifconfig()) return wlan else: print(wifi connect failed) return None这个函数里我加了一个超时判断避免死循环。实际使用中如果连接失败可以调用wlan.disconnect()后重试或者重启设备。3.2 第二步使用 urequests 发送 GET 请求连接 WiFi 之后直接用urequests.get(url)发起请求。这里以请求一个返回 JSON 的 API 为例。import urequests response urequests.get(http://httpbin.org/get?citybeijing) print(response.status_code) print(response.text) response.close()注意response.close()这行非常关键。MicroPython 的内存本来就紧张不关响应对象会导致 socket 连接不释放多次请求之后内存泄漏最终程序崩溃或者卡死。response.status_code是 HTTP 状态码比如 200 表示成功404 表示资源不存在。response.text是响应体字符串。如果响应体是 JSON可以再用ujson.loads(response.text)转成字典。3.3 第三步处理 JSON 响应并提取关键数据下面是一个更贴近实际场景的例子比如从一个天气接口获取温度import ujson def fetch_temperature(city): url fhttp://httpbin.org/json?city{city} # 这里换成真实接口 try: r urequests.get(url, timeout5) if r.status_code 200: data ujson.loads(r.text) temp data.get(temp) print(f{city} temperature: {temp}) return temp else: print(HTTP error, r.status_code) except Exception as e: print(Request failed, e) finally: r.close()有几个小技巧timeout5表示 5 秒超时避免服务器无响应时卡住程序。try...except放在整个请求外面捕获网络异常、JSON 解析异常确保程序不会因为一次请求失败就重启。finally里关掉响应对象这一步无论如何都要执行。3.4 第四步GET 请求参数编码问题GET 请求的参数如果包含中文或特殊字符需要做 URL 编码。比如城市名“北京”直接拼在 URL 里服务器可能解析不了。MicroPython 没有标准的urllib.parse.quote但可以用简单的编码表或者手动替换。其实大多数简单的场景不需要中文直接拼接 ASCII 参数就够了。如果一定要编码可以用urllib.parse.quote部分固件有没有的话自己写一个def url_encode(s): res for ch in s: if ch.isalnum() or ch in -_.~: res ch else: res % hex(ord(ch))[2:].upper() return res但说实话能在嵌入式设备上用的 API 尽量别传递中文参数服务器端用 ID 或者拼音更好。这个编码函数只是应急用。3.5 第五步加上请求头模拟客户端有些 API 会校验 User-Agent 或者接受 JSON 响应格式这时候需要自定义 headersheaders {User-Agent: ESP32-MicroPython/1.0} r urequests.get(url, headersheaders)如果服务器要求认证可以加Authorization: Bearer token。请求头是一个字典urequests会帮你拼到请求里。注意 header 的 key 大小写不敏感但是建议按照常规写法。4. 实操用 POST 向服务器提交传感器数据4.1 准备一个接收 POST 的服务器在做 POST 之前你得先有一个能接收请求的服务。没有服务器的话本地电脑跑一个简单的 Python Flask 服务就行ESP32 和电脑处于同一个局域网内直接用电脑的局域网 IP 地址访问。为了演示我写了一个最简单的 Flask 接口from flask import Flask, request, jsonify app Flask(__name__) app.route(/data, methods[POST]) def receive(): data request.get_json() print(Received:, data) return jsonify({status: ok, received: data}) if __name__ __main__: app.run(host0.0.0.0, port5000)运行后电脑的防火墙需要允许 5000 端口。ESP32 连接同一个 WiFi 后就可以向http://电脑IP:5000/data发 POST 请求。4.2 POST JSON 格式的代码下面这段代码模拟读取一个温度传感器这里用随机数代替然后每 5 秒上报一次import urequests import ujson import random import time url http://192.168.1.100:5000/data def post_temperature(value): payload { device_id: esp32-01, value: value, unit: celsius } headers { Content-Type: application/json } try: r urequests.post(url, jsonpayload, headersheaders, timeout5) print(Status code:, r.status_code) print(Response:, r.text) r.close() except Exception as e: print(POST failed:, e) while True: temp random.uniform(20.0, 30.0) post_temperature(round(temp, 2)) time.sleep(5)注意urequests.post的参数jsonpayload这个关键字参数很关键urequests内部会自动做ujson.dumps并设置Content-Type为application/json。如果你传datapayload而 payload 是字典它会自动编码成 form 格式。所以用对关键字能少写不少代码。4.3 POST 表单格式的代码如果服务器接口要求的是application/x-www-form-urlencoded那就用data传参payload device_idesp32-01value25.6unitcelsius headers {Content-Type: application/x-www-form-urlencoded} r urequests.post(url, datapayload, headersheaders)或者直接传一个字典给dataurequests也会自动变成表单格式payload {device_id: esp32-01, value: 25.6} r urequests.post(url, datapayload)从个人习惯来看建议显式写清楚Content-Type避免服务器端解析出来是空 body 或者格式错误。4.4 服务器端验证收到的数据在上面的 Flask 服务里收到 POST 后会把 JSON 打出来。如果 ESP32 端发送成功串口终端会打印服务器返回的{status:ok,received:...}。这个过程最好用串口监视器和 Flask 控制台对照看。有一个比较容易踩的坑是Content-Type不对会导致request.get_json()返回None。所以如果服务器端解析不到数据先检查 ESP32 端设置的 headers 是否与服务端期望一致。4.5 POST 请求常见错误补充400 Bad Request通常是因为请求体格式不对服务器解析失败。413 Request Entity Too Large上传的 body 太大微控制器一般不会遇到但别发太长的字符串。415 Unsupported Media TypeContent-Type 服务器不支持。405 Method Not Allowed服务器该路径不支持 POST。排查思路先用电脑上的工具比如在线接口测试工具发同样格式的请求确认服务器没问题再检查 ESP32 端代码。5. 常见问题排查与避坑速查表5.1 连接超时排查如果你在urequests.get时程序长时间卡住然后报TimeoutError大概率是网络不通或 DNS 解析失败。先确认能被局域网内其他设备 ping 通吗能不能 ping 通外网域名ESP32 没有 ping 命令可以在板子上用socket.getaddrinfo测试 DNS。服务器地址是不是写成了https://MicroPython 固件默认不带 HTTPS 支持某些新固件带ssl但证书校验麻烦如果服务器是 HTTPS 且没有处理证书你会失败。所以调试阶段尽量用http://。测试 DNS 的代码import socket info socket.getaddrinfo(www.example.com, 80) print(info)如果这个函数能返回地址说明网络和 DNS 基本没问题。5.2TLSServer或证书错误怎么办MicroPython 的 SSL 实现很简略默认对证书校验不严格或者根本不校验。有的固件里urequests是支持ssl参数的但使用 HTTPS 仍有大量坑。从实测经验看嵌入式设备对接云平台尽量使用局域网 HTTP 或者定制证书否则调试成本会很高。如果真的必须用 HTTPS可以试试传递ssl上下文import ssl ctx ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) r urequests.get(https://api.example.com, sslctx)但不同版本固件 API 略有差异需要翻阅对应固件文档。5.3 内存不足导致重启或异常ESP32 有 320KB 左右的 RAM但 MicroPython 运行时和固件占据大部分留给 Python 的可用堆内存可能只有几十 KB 到一百多 KB。当响应体很大时r.text会把整个响应加载到内存很容易 MemoryError。解决办法用r.content流式读取urequests并不支持真正的流式。尽量把响应体控制在几 KB 以内。不要频繁创建大量字符串、字典发完请求立刻r.close()。可以监控剩余内存import gc print(Free memory:, gc.mem_free())在循环里定期调用gc.collect()释放垃圾。5.4 中文乱码问题当我们请求的接口返回 UTF-8 编码的中文时r.text还原成字符串后打印到串口可能正常但如果你把它写入文件或者通过别的模块处理可能变成奇怪的字符。这是因为 MicroPython 的r.text默认按 UTF-8 解码但有些服务器返回的是 GBK 或 GB2312MicroPython 不支持这些编码的完整解码。解决方案请求时在 headers 里带上Accept-Charset: utf-8或者要求服务器端统一返回 UTF-8。另外r.content返回的是 bytes可以自己做解码。如果你确定服务器返回 GBK而 MicroPython 不支持那就只能转成 UTF-8 再编码或者放弃中文显示。个人建议嵌入式端尽量不直接处理中文字符串统一用 ASCII 或 Unicode 转义。5.5 排除问题的调试工具速查问题现象快速排查方法连接不上 WiFi检查 SSID/密码确认是否wlan.active(True)用wlan.status()查看请求卡住设置timeout确认服务器地址从 ESP32 能访问用getaddrinfo测 DNS返回 404路径写错服务器是否支持该路径URL 里 base 是否对返回 JSON 解析失败先用r.text打印原始内容看看是否真的是 JSON注意编码POST 数据服务器收不到检查 Content-Type确认 body 是正确格式Flask 里打印request.get_data()内存逐渐变少检查是否有对象忘了close()使用gc.collect()避免大列表/大字典6. 进阶HTTPS、长连接和设备状态管理6.1 使用 HTTPS 的注意事项如果服务器只提供 HTTPS 接口那就要重视底层的问题。MicroPython 的 SSL 模块在 ESP32 上可用但不同的固件版本差异很大。我试过在旧版本里把ssl.wrap_socket的cert_reqs设成ssl.CERT_NONE来绕过证书验证但某些新固件强制校验反而连不上。能做的办法升级到较新的 MicroPython 固件新版本对 SSL 支持更好。如果只是测试可以在服务端临时关闭证书验证但不推荐用于生产。考虑购买带硬件加密的模块其实 ESP32 本身就有硬件 AES 和 RSA 加速但 MicroPython 接口没怎么利用。实际项目中很多团队会把证书烧录到板子里固定证书指纹进行校验但代码量不小。如果你的设备是自己可控的服务器直接改成 HTTP 或者改造网关转发 HTTPS 是更务实的思路。6.2 发送多条请求时的连接重用urequests每次请求都是新建 TCP 连接服务端关闭后下一次重新建立连接。频繁的短连接会有一定的开销但在物联网场景下几秒一次完全没问题。如果要提高效率可以用底层 socket 维护长连接但那要自己实现 HTTP keep-alive工作量不小。我个人的建议是如果每个请求间隔在 10 秒以上直接用urequests就好不必过度优化。如果需要在短时间内发送大量请求可以创建一个请求函数统一设置 headers 和超时减少重复代码。6.3 低功耗场景下怎么处理 HTTP 请求这可能是很多人没想到的ESP32 的 WiFi 模块功耗不低如果每隔几秒请求一次电池撑不了多久。所以低功耗设计基本思路是设备平时进入深度睡眠deepsleep()唤醒后先连 WiFi发请求再回去睡觉。唤醒到发送完成大概需要 2~3 秒其中 WiFi 连接占了大头。连接 WiFi 时使用省电模式wlan.config(pmwlan.PM_SAVE)但注意降低功耗的同时也可能增加延迟。批量上报数据比如攒 10 条再一次性 POST减少请求次数。这里分享我的一个实测数据ESP32 跑 MicroPython 在深度睡眠模式下电流约 10uA 到 20uA唤醒后连 WiFi 发一个 POST 请求峰值电流可以到 300mA 左右整个发送窗口大约 2 秒。如果你的电池容量是 2000mAh算下来每次上报消耗的容量很小能撑很久。6.4 与云平台对接的小经验很多云平台提供了设备接入的 SDK但 MicroPython 版不一定齐全。更通用的做法是通过 HTTP REST API 直接上报。一般流程是先从平台获取设备 token 或 API key。配置平台的 endpoint 和数据格式。用urequests.post发送 JSON。根据平台返回的 status 判断是否上报成功失败就重试。这里的重试逻辑要加延时避免平台被高频请求打爆。可以对失败次数做上限超过之后记录错误并降低上报频率。7. 我实测中的一点体会这个项目我从裸 socket 写到用 urequests再到封装成类前前后后改了好几版。感受最深的一点是MicroPython 的网络编程尽量别秀技巧越简单越不容易出问题。有人觉得urequests太高层担心性能但实际上让一个设备每隔几秒发一次数据瓶颈从来不是库而是网络稳定性、服务器响应速度和内存管理。另外调试 HTTP 请求时一定要开串口日志。把每次请求的 URL、headers、响应状态码、响应体前几十个字符都打印出来问题会清楚很多。遇到状态码 200 但数据不对再去看 JSON 解析遇到超时先去检查 DNS再去检查服务器防火墙。如果你真的想在 ESP32 上用 HTTP 请求做点东西建议先搭一个本地 Flask 服务把代码跑通再切换到真实服务器接口这个顺序能帮你少走很多弯路。ESP32 加 MicroPython 的组合足够你玩转大部分 HTTP 通信场景剩下的就是多踩几次坑然后把这些坑记录下来下次就不怕了。