ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于ESP32与MicroPython构建物联网Web服务器:从API消费到服务提供

基于ESP32与MicroPython构建物联网Web服务器:从API消费到服务提供 1. 项目缘起当ESP32走进酒吧一个ESP32走进酒吧酒保问“嘿伙计今天想喝点什么” ESP32回答“给我来一杯API要MicroPython调制的。” 这听起来像个冷笑话但正是我们今天要聊的核心。在物联网和嵌入式开发的世界里ESP32这颗明星芯片早已家喻户晓而MicroPython则让它变得像Python一样平易近人。但很多时候我们做的项目都停留在“点个灯”、“读个传感器”的层面总觉得少了点趣味和实用性。你有没有想过让这块小小的开发板不仅能感知物理世界还能成为一个有“灵魂”的对话者比如把它变成一个会讲笑话的“段子手”放在你的工位或者客厅随时给你来点乐子。这个想法听起来有点无厘头但实现起来却是一个绝佳的练手项目。它串联了ESP32的Wi-Fi联网能力、MicroPython的简洁开发、以及Web API的调用与构建几乎涵盖了现代物联网设备从数据获取到服务提供的完整链路。我最近就捣鼓了这么一个玩意儿一个基于ESP32和MicroPython的“冷笑话API服务器”。核心思路很简单ESP32启动后连接Wi-Fi然后定期从一个公开的笑话API获取数据解析后通过其自身搭建的一个微型Web服务器提供出来。这样你既可以通过浏览器直接访问ESP32的IP地址看笑话也可以用手机App、或者其他智能设备调用这个本地API实现更灵活的互动。这不仅仅是复现一个功能更是理解嵌入式设备如何从“数据消费者”转变为“数据服务提供者”的关键一步。下面我就把从硬件选型、环境搭建、代码编写到调试优化的完整过程以及我踩过的那些坑毫无保留地分享给你。2. 硬件与软件栈的深度选型分析为什么是ESP32为什么是MicroPython在做任何项目之前搞清楚工具的选择逻辑比盲目上手更重要。这决定了项目的可行性、开发效率和最终体验。2.1 ESP32不止于Wi-Fi的性价比之王ESP32系列芯片几乎是当前开源硬件领域的“硬通货”。对于这个项目我选择最经典的ESP32-WROOM-32模组理由非常充分双核处理器与充足内存项目需要同时处理网络连接、HTTP请求、JSON解析和Web服务。ESP32的Xtensa双核处理器通常运行在240MHz和520KB的SRAM为运行MicroPython解释器和我们的应用代码提供了坚实的性能基础。相比之下传统的ATmega2560Arduino Mega虽然引脚多但单核、内存小运行Python解释器非常吃力更别提同时处理网络任务了。集成的Wi-Fi与蓝牙这是项目的核心需求。ESP32原生支持2.4GHz Wi-Fi802.11 b/g/n并且有成熟的Station连接到路由器和AP自建热点模式支持。我们这里主要使用Station模式。虽然项目没用到蓝牙但ESP32-S3等新款芯片的蓝牙音频能力如搭配WM8960编解码器也提示了未来扩展的可能性比如让设备“念”出笑话。丰富的开发资源与生态无论是Arduino框架、ESP-IDF乐鑫官方IoT开发框架还是MicroPythonESP32都有极其庞大的社区支持和海量示例。这意味着你遇到的绝大多数问题几乎都能找到答案。像“esp32 idf例程点亮led灯”这样的基础教程遍地都是为深入开发扫清了障碍。灵活的烧录方式ESP32支持通过串口UART进行烧录这是最主流和简单的方式。市面上大多数开发板如NodeMCU、DevKitC都集成了USB转串口芯片如CH340、CP2102一根Micro-USB线就能完成供电、编程和调试。无需像某些AVR芯片那样需要单独的编程器。避坑心得注意“供电”这个老生常谈的问题。ESP32在Wi-Fi射频工作时峰值电流可能达到500mA。如果使用一些劣质的USB线或者电脑USB口供电不足可能导致设备运行不稳定、频繁重启或无法连接Wi-Fi。务必使用可靠的5V/2A以上的电源适配器并通过开发板的稳压芯片供电。直接连接电脑USB口进行最终部署是不可靠的。2.2 MicroPython让嵌入式开发像写脚本一样简单为什么不用更底层的ESP-IDF或更普及的Arduino对于这个以应用逻辑和网络交互为主的项目MicroPython的优势是决定性的开发效率的飞跃Python语法简洁交互式解释器REPL允许你实时测试代码片段。比如你可以直接连接串口输入import network、sta network.WLAN(network.STA_IF)、sta.active(True)来测试Wi-Fi连接立刻得到反馈。这种即时性极大地加快了调试速度。内置高级库支持MicroPython固件通常内置了urequests简化版requests用于HTTP请求、ujson用于JSON解析、usocket和microdot或我们可以自建简单Web服务器等库。这意味着我们不需要像在Arduino中那样手动去寻找并管理复杂的HTTPClient和ArduinoJson库还要担心内存分配问题。在MicroPython中一个response urequests.get(url).json()可能就搞定了一半的核心功能。动态内存管理与REPL调试虽然动态类型在内存受限的嵌入式系统中有碎片化风险但对于我们这个规模的项目其便利性远大于风险。更重要的是REPL在设备崩溃后往往还能接入让你可以检查变量状态、查找问题根源这是静态编译语言难以比拟的调试体验。关键决策固件选择与烧录。你需要为你的ESP32刷入MicroPython固件。不要使用太古老或来源不明的固件。推荐前往MicroPython官网下载针对ESP32的最新稳定版固件。烧录工具可以用官方的esptool.py命令行或者图形化工具如Flash Download Tools。这里有一个细节烧录时需要根据你的具体板型选择正确的“Flash Mode”通常是DIO或QIO和“Flash Size”。选错可能导致固件无法启动。最稳妥的方法是查阅你的开发板说明书。3. 核心架构设计从消费者到服务提供者这个项目的巧妙之处在于它让ESP32扮演了双重角色。理解这个数据流架构是写好代码的前提。外部互联网 (Joke API如icanhazdadjoke.com) ↓ (HTTP GET 请求) ESP32 (MicroPython) - 角色1API客户端 ↓ (解析JSON提取笑话文本) ESP32 内存中的数据结构 (如字符串变量) ↓ (等待请求) ESP32 (MicroPython) - 角色2微型Web服务器 ↓ (HTTP GET/POST 请求来自你的手机/电脑) 你的浏览器或应用程序 (获得纯文本或JSON格式的笑话)这个架构带来了几个核心挑战并发性ESP32是单线程的尽管MicroPython在某些版本支持_thread模块但通常不推荐用于复杂并发。这意味着它不能同时处理“获取新笑话”和“响应客户端请求”。我们必须用顺序或非阻塞的方式设计逻辑。资源管理网络连接、HTTP请求、Socket监听都会消耗内存和句柄。 improper的关闭会导致内存泄漏最终使设备崩溃。错误处理外部API可能超时、返回错误格式、或者Wi-Fi暂时断开。我们的代码必须足够健壮能够优雅地处理这些异常而不是死机。我的设计选择是采用一个主循环其优先级顺序为检查并维持Wi-Fi连接最高优先级网络是基础。检查是否到了预定的“更新笑话”时间例如每30分钟如果是则非阻塞地发起HTTP请求获取新笑话。检查是否有来自客户端的Web请求如果有则用当前存储的笑话进行响应。短暂休眠如time.sleep(0.1)以降低功耗并让出控制权。这种设计避免了复杂的多线程通过状态机和轮询实现了多个任务的协同。接下来我们进入具体的实现环节。4. 分步实现连接、获取、服务让我们把架构拆解成具体的代码模块。我会先给出核心代码片段然后解释其背后的原理和注意事项。4.1 第一步可靠的Wi-Fi连接管理器Wi-Fi连接是项目的生命线。一段健壮的连接代码是项目稳定的基石。import network import time import machine def connect_to_wifi(ssid, password): 连接到指定的Wi-Fi网络包含重试机制和状态指示。 wlan network.WLAN(network.STA_IF) wlan.active(True) # 激活Station接口 if not wlan.isconnected(): print(f‘正在尝试连接网络: {ssid}’) wlan.connect(ssid, password) # 等待连接最多尝试15秒 max_wait 15 while max_wait 0: if wlan.isconnected(): break max_wait - 1 print(‘等待连接...’, max_wait) time.sleep(1) # 连接结果处理 if wlan.isconnected(): status wlan.ifconfig() print(‘网络连接成功’) print(f‘IP地址: {status[0]}’) print(f‘子网掩码: {status[1]}’) print(f‘网关: {status[2]}’) print(f‘DNS: {status[3]}’) # 可以在这里点亮一个LED表示连接成功 # led machine.Pin(2, machine.Pin.OUT) # led.value(1) return True else: print(‘网络连接失败’) # 可以在这里闪烁LED表示失败 return False else: print(‘已连接到网络’) print(f‘IP地址: {wlan.ifconfig()[0]}’) return True # 在你的主程序配置部分调用 WIFI_SSID ‘你的Wi-Fi名称‘ WIFI_PASSWORD ‘你的Wi-Fi密码‘ if not connect_to_wifi(WIFI_SSID, WIFI_PASSWORD): # 连接失败可以考虑进入深度睡眠或重启 print(‘启动失败10秒后重启...’) time.sleep(10) machine.reset()关键点解析与避坑wlan.active(True)这一步必不可少。它相当于打开了设备的Wi-Fi射频模块。重试与超时while循环提供了15秒的连接等待时间。对于不稳定的网络这个时间可能需要加长。但也不宜过长以免程序卡死。ifconfig()这个方法返回一个包含 (ip, subnet, gateway, dns) 的元组。获取IP地址对于后续通过浏览器访问至关重要。错误处理如果连接失败我们选择打印日志并返回False。在主程序中我选择让设备重启machine.reset()。这是一种简单的恢复策略。更高级的做法可以是尝试连接备用Wi-Fi或者切换到AP模式让用户重新配置。功耗考虑一旦连接成功Wi-Fi模块会持续工作。如果项目是电池供电需要考虑在不需要时断开Wi-Fiwlan.disconnect()和wlan.active(False)但我们的项目需要持续服务所以保持连接。4.2 第二步获取外部API数据笑话这里我们选择一个稳定、免费、无需API密钥的公共笑话API例如icanhazdadjoke.com。它的API返回纯JSON格式非常适合嵌入式设备解析。import urequests import ujson # 定义全局变量存储当前笑话 current_joke “初始化中...请稍后刷新。” def fetch_new_joke(): 从 icanhazdadjoke.com 获取一个新的笑话。 返回成功返回笑话文本失败返回None。 global current_joke url “https://icanhazdadjoke.com/” headers {‘Accept’: ‘application/json’, ‘User-Agent’: ‘ESP32 Joke Box (https://your-project-page)’} # 礼貌地设置User-Agent try: print(‘正在获取新笑话...’) # 注意urequests.get 超时参数在有些固件版本中支持 response urequests.get(url, headersheaders) if response.status_code 200: # 解析JSON响应 joke_data ujson.loads(response.text) new_joke joke_data.get(‘joke’, ‘No joke found.’) print(f‘获取到笑话: {new_joke}’) response.close() # 非常重要必须关闭响应释放资源 current_joke new_joke return new_joke else: print(f‘API请求失败状态码: {response.status_code}’) response.close() return None except Exception as e: # 捕获所有异常包括网络超时、JSON解析错误等 print(f‘获取笑话时发生异常: {e}’) # 确保在异常时也尝试关闭响应如果response已定义 if ‘response’ in locals(): try: response.close() except: pass return None # 在主循环中定时调用例如每30分钟一次 last_joke_fetch_time 0 JOKE_UPDATE_INTERVAL 1800 # 单位秒 (30分钟) # 模拟主循环中的检查逻辑 # current_time time.time() # if current_time - last_joke_fetch_time JOKE_UPDATE_INTERVAL: # fetch_new_joke() # last_joke_fetch_time current_time核心细节与深度避坑HTTP头Headers是礼貌也是必须‘Accept’: ‘application/json’告诉服务器我们想要JSON格式的响应。‘User-Agent’标识了你的客户端这是一个良好的网络公民行为也便于API提供方统计。有些API如一些天气API会强制要求有效的User-Agent。资源泄漏的“头号杀手”——关闭响应urequests返回的响应对象占用了socket和内存。你必须显式调用response.close()来释放这些资源忘记关闭是导致ESP32运行一段时间后内存耗尽、神秘重启的最常见原因。我甚至在异常处理块中也加入了关闭操作确保万无一失。异常处理的必要性网络世界充满不确定性。urequests.get()可能因为DNS解析失败、服务器无响应、SSL证书问题注意有些老固件可能不支持某些网站的SSL而抛出异常。用try...except包裹起来可以防止一次失败的API调用导致整个程序崩溃。JSON解析ujson.loads()是MicroPython中解析JSON的高效方法。确保你获取的response.text是有效的JSON字符串。如果API返回错误比如404页面直接解析会抛出异常这就是为什么我们先检查status_code 200。关于其他API的思考热搜词里出现了deepseek api、kimi api、智谱api、百度api等大型语言模型的API。理论上你可以用ESP32调用这些API来生成更智能的对话或笑话但我不建议在这个项目中直接使用。原因有三第一这些API通常需要复杂的认证API Key且Key暴露在客户端代码中极不安全第二它们的请求和响应数据量可能很大对ESP32的内存和网络稳定性要求高第三通常是付费服务。我们的目标是展示核心原理公开、免费的简单API是更好的选择。4.3 第三步构建微型Web服务器这是项目的精华部分让ESP32从客户端变身服务器。我们将实现一个最简单的HTTP服务器响应GET请求返回笑话。import socket import time def start_micro_web_server(port80): 启动一个简单的Web服务器监听指定端口。 这是一个阻塞式服务器一次处理一个请求。 对于演示和低并发场景足够用。 addr socket.getaddrinfo(‘0.0.0.0’, port)[0][-1] # 绑定到所有网络接口 server_socket socket.socket() server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 允许地址重用 server_socket.bind(addr) server_socket.listen(1) # 允许最多1个连接在队列中等待 print(f‘Web服务器启动在 http://{addr[0]}:{port}’) while True: try: # 等待客户端连接这里会阻塞 client_socket, client_addr server_socket.accept() print(f‘收到来自 {client_addr} 的连接’) # 接收客户端请求只读取第一行简化处理 request client_socket.recv(1024).decode(‘utf-8’) print(f‘请求内容:\n{request}’) # 解析请求行获取请求方法和路径 request_line request.split(‘\r\n’)[0] method, path, _ request_line.split() # 只处理GET请求 if method ‘GET’: if path ‘/’ or path ‘/joke’: # 构建HTTP响应 response_body current_joke response_headers ( ‘HTTP/1.1 200 OK\r\n’ ‘Content-Type: text/plain; charsetutf-8\r\n’ ‘Connection: close\r\n’ f‘Content-Length: {len(response_body)}\r\n’ ‘\r\n’ ) response response_headers response_body else: # 404 Not Found response_body ‘404 - Not Found’ response_headers ( ‘HTTP/1.1 404 Not Found\r\n’ ‘Content-Type: text/plain; charsetutf-8\r\n’ ‘Connection: close\r\n’ f‘Content-Length: {len(response_body)}\r\n’ ‘\r\n’ ) response response_headers response_body else: # 405 Method Not Allowed response_body ‘405 - Method Not Allowed’ response_headers ( ‘HTTP/1.1 405 Method Not Allowed\r\n’ ‘Content-Type: text/plain; charsetutf-8\r\n’ ‘Connection: close\r\n’ f‘Content-Length: {len(response_body)}\r\n’ ‘\r\n’ ) response response_headers response_body # 发送响应 client_socket.sendall(response.encode(‘utf-8’)) # 关闭客户端连接 client_socket.close() print(‘连接已关闭’) except Exception as e: print(f‘处理请求时发生错误: {e}’) try: client_socket.close() except: pass # 注意这里不退出循环服务器继续运行服务器实现的深度剖析阻塞式 vs 非阻塞式上面的代码是典型的阻塞式服务器。server_socket.accept()和client_socket.recv()都会阻塞程序执行直到有连接或数据到来。这意味着在服务一个请求时它无法去获取新的笑话或做其他事。对于个人使用、低并发的场景这完全没问题。如果你想实现非阻塞需要使用socket.setblocking(False)并配合select模块来轮询多个socket复杂度会大大增加。HTTP协议的最小化实现我们只实现了协议的核心部分。一个合法的HTTP响应必须包含状态行如HTTP/1.1 200 OK、头部Headers和可选的正文Body。头部和正文之间用空行\r\n\r\n分隔。我们提供了Content-Type内容类型这里是纯文本和Content-Length正文长度这是良好实践。Connection: close告诉浏览器处理完这个请求后就关闭连接。资源管理Again和HTTP客户端一样服务器端也必须妥善管理Socket资源。每个客户端连接client_socket在处理完请求后必须close()。否则端口和内存会被持续占用。安全性这是一个极其简单的服务器没有考虑任何安全措施如请求大小限制、防止缓冲区溢出、认证等。切勿将其暴露在公网它只适合在安全的本地网络如你的家庭Wi-Fi中运行。如何让服务器与主循环协同工作上面的start_micro_web_server函数包含一个while True循环这意味着一旦调用程序就会永远阻塞在那里。这显然与我们“定时获取笑话”的需求冲突。解决方案有两种方案A使用_thread模块谨慎将Web服务器放在一个单独的线程中运行。MicroPython的_thread模块支持有限且线程调试困难容易导致全局变量竞争和系统不稳定不推荐新手使用。方案B非阻塞Socket轮询推荐重构我们的服务器使其不阻塞主循环。这是更优雅、更标准的嵌入式网络编程方式。由于篇幅所限其核心思想是将server_socket设置为非阻塞在主循环中使用select.select()来检查server_socket和可能的client_socket是否有可读事件然后进行相应的accept()或recv()操作。这样主循环就能同时处理“检查Wi-Fi”、“定时获取笑话”和“服务Web请求”三件事。考虑到教程的清晰度和初学者的可接受度上面的阻塞式版本更容易理解。在实际部署中如果你的需求只是偶尔通过浏览器访问阻塞式也完全可行。你可以将“获取新笑话”的操作放在每次Web请求处理完之后或者通过一个特殊的URL路径如/update来手动触发更新。5. 系统集成与高级优化将上述模块组合起来形成一个健壮、可用的系统还需要考虑一些工程细节。5.1 主程序逻辑与状态管理我们需要一个主循环来协调所有任务。下面是一个综合了非阻塞Web服务器思路的简化框架import network import time import socket import uselect as select from machine import Pin, Timer # 全局状态 current_joke “系统启动中...” wlan None server_socket None last_fetch_time 0 UPDATE_INTERVAL 1800 # 30分钟 # 初始化Wi-Fi (使用之前定义的connect_to_wifi函数) # 初始化Web服务器Socket def setup(): global wlan, server_socket # 连接Wi-Fi... wlan network.WLAN(network.STA_IF) # ... (连接代码) # 创建非阻塞服务器Socket addr socket.getaddrinfo(‘0.0.0.0’, 80)[0][-1] server_socket socket.socket() server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.setblocking(False) # 设置为非阻塞 server_socket.bind(addr) server_socket.listen(5) print(f“服务器就绪: http://{wlan.ifconfig()[0]}”) # 首次获取笑话 fetch_new_joke_safe() def fetch_new_joke_safe(): global current_joke, last_fetch_time joke fetch_new_joke() # 调用之前定义的函数 if joke: current_joke joke last_fetch_time time.time() else: print(“获取新笑话失败保留旧内容。”) def handle_web_request(): global server_socket # 使用select检查是否有新的客户端连接 rlist, _, _ select.select([server_socket], [], [], 0) # 超时设为0立即返回 if server_socket in rlist: try: client_socket, addr server_socket.accept() client_socket.setblocking(False) # 这里应该将client_socket加入一个列表进行管理并设置一个状态机来处理其请求 # 为了简化我们假设一次只处理一个连接并立即读取可能不完整 try: request client_socket.recv(1024).decode() if request: # 简化处理只响应根路径 if b‘GET /’ in request.encode(): response f“HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n{current_joke}” client_socket.send(response.encode()) client_socket.close() except Exception as e: print(f“处理客户端数据错误: {e}”) client_socket.close() except Exception as e: print(f“接受连接错误: {e}”) def main_loop(): setup() while True: # 1. 维护Wi-Fi连接简化假设连接稳定 if not wlan.isconnected(): print(“Wi-Fi断开尝试重连...”) # ... 重连逻辑 # 2. 定时更新笑话 current_time time.time() if current_time - last_fetch_time UPDATE_INTERVAL: print(“定时更新笑话...”) fetch_new_joke_safe() # 3. 处理Web请求非阻塞 handle_web_request() # 4. 短暂休眠降低CPU占用 time.sleep(0.1) # 100毫秒 if __name__ ‘__main__’: main_loop()这个框架给出了一个更接近实际项目的结构引入了非阻塞Socket和select轮询的思想。handle_web_request函数现在不会阻塞它只是快速检查一下是否有新连接有就处理没有就立刻返回。5.2 功耗优化与稳定性增强对于需要长期运行的设备稳定性至关重要。看门狗定时器Watchdog TimerESP32内置看门狗。在MicroPython中你可以通过machine.WDT来使用它。定期“喂狗”可以防止程序跑飞导致死机。from machine import WDT wdt WDT(timeout8000) # 8秒看门狗 # 在主循环中定期喂狗 wdt.feed()深度睡眠Deep Sleep如果你的设备只需要每小时更新一次笑话那么让ESP32在大部分时间进入深度睡眠可以极大节省功耗。但深度睡眠会断开Wi-Fi并重启因此适合数据上报型设备对于需要随时响应的Web服务器不适用。我们的项目需要持续监听所以不适合深度睡眠。异常重启在最外层的main_loop或关键位置添加try...except捕获未处理的异常并记录到文件系统如果有或打印出来然后延时重启。try: main_loop() except Exception as e: print(f“主循环崩溃: {e}”) time.sleep(10) machine.reset()5.3 扩展思考从文本到语音与显示热搜词中提到了esp32 wm8960、esp32 音乐频谱 led、esp32 lvgl这给了我们扩展项目的方向。语音输出TTS你可以将获取到的笑话文本通过ESP32的I2S接口输出到WM8960这类音频编解码器再连接扬声器播放出来。这需要集成一个TTS文本转语音引擎例如在ESP32上运行一个轻量级的合成引擎或者将文本发送到云端TTS API但会引入延迟和网络依赖。这样你的“笑话盒”就真的能开口讲笑话了。图形化显示LVGLLVGL是一个强大的嵌入式图形库。你可以用一块SPI或I2C接口的屏幕如ILI9341、SSD1306配合LVGL制作一个精美的界面来显示笑话甚至加上翻页动画、历史记录等功能。这会让项目从“极客玩具”升级为“桌面艺术品”。物理交互增加一个按钮。按一下按钮就触发一次获取新笑话并显示/播报的过程。这比定时更新更有互动感。6. 部署、测试与故障排除代码写完了烧录到ESP32但事情还没完。部署和测试阶段才是真正考验项目的时候。6.1 完整部署流程硬件连接将ESP32开发板通过USB线连接到电脑。刷入MicroPython固件下载最新的ESP32 MicroPython固件.bin文件。使用esptool.py擦除Flash并刷入固件。esptool.py --chip esp32 --port COM3 erase_flash # 将COM3替换为你的端口 esptool.py --chip esp32 --port COM3 --baud 460800 write_flash -z 0x1000 firmware.bin上传代码使用如Thonny、uPyCraft或rshell、ampy等工具将你的主Python文件如main.py或boot.py上传到ESP32的文件系统中。boot.py会在启动时自动运行。配置Wi-Fi通常有两种方式。一是在代码中硬编码SSID和密码方便但不安全。二是首次启动时让ESP32进入AP模式你连接上它的热点后通过一个网页来配置家庭Wi-Fi信息然后它保存到文件系统并重启连接。后者更产品化。上电运行断开USB用手机充电器或电池给ESP32供电。观察串口输出如果有条件连接看启动日志是否正常是否获取到IP地址。6.2 常见问题与排查指南问题无法连接Wi-Fi。排查检查串口日志。确认SSID和密码正确特别是大小写和特殊字符。检查路由器是否设置了MAC地址过滤。尝试将ESP32靠近路由器。检查代码中wlan.active(True)是否被调用。问题可以连接Wi-Fi但无法获取外部API笑话。排查首先在电脑浏览器中访问https://icanhazdadjoke.com/确认网络可达。在ESP32的REPL中手动逐行运行urequests.get()代码看错误信息。可能是SSL证书问题尝试使用http而非https的API但很多已不支持也可能是DNS解析失败尝试使用IP地址而非域名或在代码中先测试socket.getaddrinfo(‘icanhazdadjoke.com’, 443)。问题Web服务器无法访问浏览器显示“无法连接”。排查确认ESP32获取到的IP地址如192.168.1.100。确认你的手机/电脑和ESP32在同一个局域网连接同一个Wi-Fi。在浏览器输入http://[ESP32的IP地址]例如http://192.168.1.100。检查防火墙有时电脑或路由器的防火墙会阻止内部设备间的访问。在ESP32的REPL中检查服务器Socket是否成功创建并监听server_socket是否不为None。问题设备运行一段时间后自动重启或无响应。排查这是典型的内存泄漏或软件看门狗触发。内存泄漏首要怀疑对象是网络资源未关闭。用import gc; gc.mem_free()打印剩余内存观察其是否在持续减少。重点检查所有urequests响应是否都close()所有客户端Socket是否都close()。看门狗如果你的代码中有长时间阻塞的操作如一个没有超时的socket.recv()或一个复杂的、耗时很长的计算硬件看门狗或MicroPython的软件看门狗可能会因为长时间没有喂狗而重启系统。确保主循环每次执行时间不会太长或者在长时间操作中插入wdt.feed()。电源问题用万用表测量ESP32供电电压在Wi-Fi发射时是否跌落到3.0V以下。使用质量好的电源和USB线。问题响应速度慢或者处理请求时无法获取新笑话。分析这很可能是因为你使用了阻塞式的Web服务器。当一个浏览器请求正在被处理时尤其是如果浏览器开启了Keep-Alive连接没有立即关闭主循环就卡在accept()或recv()那里自然无法执行定时更新笑话的任务。解决方案就是如前所述使用非阻塞Socket和select轮询。这个项目麻雀虽小五脏俱全。它不仅仅是一个“笑话盒子”更是一个理解嵌入式设备如何接入互联网、如何消费和提供网络服务的绝佳范例。当你看到浏览器上显示出由ESP32从万里之外获取并服务出来的那句冷笑话时那种成就感远比点亮一个LED要强烈得多。希望这篇超详细的拆解能帮你绕过我踩过的那些坑顺利打造出属于你自己的、有“灵魂”的物联网小装置。
返回列表