基于PyPortal与CircuitPython的物联网游戏数据显示器开发实战 1. 项目概述如果你和我一样既是《英雄联盟》的忠实玩家又对嵌入式硬件开发充满热情那么把这两者结合起来做一个能实时展示自己召唤师等级的“实体奖杯”绝对是一件既酷又有成就感的事情。这个项目就是基于Adafruit的PyPortal开发板通过Wi-Fi连接互联网调用Riot Games的官方API将你的游戏等级数据抓取下来并显示在一块3.5英寸的触摸屏上。想象一下你的游戏成就不再只是客户端里的一串数字而是变成了桌面上一个会发光、会实时更新的实体摆件每次看到它是不是都更有动力去“上分”了这个项目的核心价值远不止于做一个游戏周边。它实际上是一个嵌入式物联网IoT设备与Web API交互的经典范例。PyPortal本质上是一个集成了ESP32 Wi-Fi协处理器、显示屏和丰富外设的微型计算机运行着CircuitPython——一个对开发者极其友好的Python变种。通过这个项目你将亲手实践如何让一个硬件设备“上网”、如何与复杂的云服务API“对话”、如何解析返回的结构化数据JSON以及如何设计一个直观的用户界面。这套技术栈可以无缝迁移到无数其他场景比如做一个显示股票价格的桌面小工具、一个展示天气预报的智能相框或者一个监控服务器状态的仪表盘。所以无论你是想为心爱的游戏做个酷炫外设还是想深入学习物联网开发这个项目都是一个绝佳的起点。2. 核心硬件与软件栈解析2.1 硬件核心PyPortal开发板PyPortal是Adafruit推出的一款“开箱即用”的物联网显示设备。它之所以成为本项目的理想选择是因为它将物联网项目中最繁琐的部分都集成好了让我们可以专注于应用逻辑。核心组件拆解主控芯片ATSAMD51这是一颗基于ARM Cortex-M4F的微控制器运行频率高达120MHz并拥有192KB RAM和512KB Flash。对于运行CircuitPython、处理网络请求和驱动显示来说性能绰绰有余。Wi-Fi协处理器ESP32这是项目的“网络门户”。PyPortal没有让主控芯片直接处理Wi-Fi协议栈而是通过SPI总线连接了一颗独立的ESP32模块。这种设计非常巧妙ESP32专精于网络连接稳定性高且拥有自己的固件主控只需通过简单的串行指令与其通信大大降低了开发复杂度和主控的资源占用。显示与触摸屏一块3.5英寸、320x240分辨率的IPS TFT显示屏色彩和可视角度都相当不错。搭配电阻式触摸屏为交互提供了可能虽然本项目未用到触摸功能。屏幕通过专用的显示控制器驱动CircuitPython库已经做好了底层封装。其他外设板载一个RGB NeoPixel LED可用于状态指示、一个microSD卡槽可用于存储大量图片或字体、一个温度传感器和一些Qwiic/STEMMA QT连接器为未来扩展留下了空间。为什么选择PyPortal而不是自己从头搭建自己用ESP32开发板屏幕模块组合也能实现类似功能但你需要自己处理电源管理、电平转换、屏幕驱动、PCB布局等一大堆硬件问题。PyPortal帮你解决了所有硬件兼容性和驱动问题提供了一个稳定、可靠的平台让你能跳过数周的硬件调试时间直接进入有趣的软件和逻辑开发阶段。对于快速原型和爱好者项目来说时间成本效益极高。2.2 软件核心CircuitPython生态系统CircuitPython是MicroPython的一个分支由Adafruit主导开发其设计哲学就是极致的易用性。它与我们熟悉的CPython桌面版Python语法高度兼容但针对微控制器资源受限的环境做了优化。CircuitPython的核心优势无需编译直接运行在PyPortal上你会看到一个名为CIRCUITPY的U盘。你的代码文件如code.py和库文件直接拖进去就能运行。修改代码后保存文件板子会自动重新加载执行开发体验如同在PC上写脚本。丰富的硬件抽象库Adafruit为旗下几乎所有硬件产品编写了高质量的CircuitPython库。例如本项目用到的adafruit_pyportal库它内部封装了网络连接、图形显示、字体渲染、触摸事件等复杂操作我们只需几行配置代码就能创建一个功能完整的网络信息显示器。强大的社区与文档Adafruit的Learn系统提供了海量的教程和项目几乎你遇到的任何问题都能找到参考。这种强大的支持体系是选择技术栈时一个非常重要的考量因素。与本项目相关的关键库adafruit_esp32spi用于通过SPI与板载ESP32 Wi-Fi模块通信。adafruit_requests一个类似于Pythonrequests库的HTTP客户端用于发起网络请求。adafruit_pyportal高级封装库是本项目显示逻辑的核心。adafruit_display_text与adafruit_bitmap_font用于在屏幕上渲染文本。adafruit_imageload用于加载和显示图像文件如背景BMP图。3. 环境搭建与基础配置3.1 安装CircuitPython固件这是让PyPortal“活”起来的第一步。整个过程非常简单可以理解为给开发板“刷入”一个微型操作系统。详细步骤与原理下载固件访问 CircuitPython官网 找到对应PyPortal的最新.uf2固件文件并下载。.uf2是UF2格式的固件它是一种由微软开发的、专为USB大容量存储设备刷机设计的文件格式其特点是无需专用刷机工具拖拽即可。进入引导加载模式用一根数据线务必确认是数据线而非仅能充电的线将PyPortal连接到电脑。快速双击板子正面的Reset按钮。此时板载的NeoPixel LED会变为绿色如果变红请检查USB线或端口。电脑上会出现一个名为PORTALBOOT的U盘。这个模式就是PyPortal的引导加载程序bootloader在运行它正在等待接收新的固件。刷入固件将下载好的.uf2文件直接拖入PORTALBOOT盘符。拖入后LED会闪烁PORTALBOOT盘符消失片刻后会出现一个名为CIRCUITPY的新盘符。这表明固件刷写成功CircuitPython系统已启动并运行。实操心得驱动与串口首次连接时系统可能会自动安装PyPortal的CDC串口驱动。如果后续你想通过串口监视器如Mu编辑器、PuTTY或screen命令查看print语句的调试输出需要连接到这个新出现的串口设备在Windows设备管理器中查看COM端口号在macOS/Linux上通常是/dev/ttyACM0或类似。这是与板子进行交互式REPL读取-求值-打印循环和查看日志的关键。3.2 安装必要的库文件固件只提供了Python解释器核心要驱动硬件和实现网络功能还需要对应的库文件。这些库文件本质上是预编译的Python模块.mpy文件。操作步骤从Adafruit的GitHub Releases页面下载对应你CircuitPython版本的 Adafruit CircuitPython Library Bundle 。解压下载的ZIP文件你会看到一个lib文件夹。将lib文件夹内本项目所需的库文件见2.2节列表复制到PyPortal的CIRCUITPY盘符下的lib文件夹中。如果lib文件夹不存在就新建一个。注意事项空间管理PyPortal的CIRCUITPY盘符实际是板载Flash存储的一部分空间有限通常约8MB。不建议将整个庞大的库Bundle全部复制进去而是按需拷贝。例如本项目至少需要adafruit_esp32spi、adafruit_requests、adafruit_pyportal、adafruit_display_text、adafruit_bitmap_font、adafruit_imageload这几个。你可以先复制这些如果运行时报错缺少某个模块再根据错误提示去Bundle里找到对应的库文件补上。3.3 配置网络与密钥settings.toml文件将敏感信息如Wi-Fi密码、API密钥与主程序代码分离是安全开发的最佳实践。CircuitPython 8及以上版本使用settings.toml文件来管理这些配置。创建与配置settings.toml在CIRCUITPY盘的根目录下用任何文本编辑器如VS Code、Notepad甚至记事本新建一个文件命名为settings.toml。将你的Wi-Fi信息和Riot API密钥按以下格式填入CIRCUITPY_WIFI_SSID 你的Wi-Fi名称 CIRCUITPY_WIFI_PASSWORD 你的Wi-Fi密码 LEAGUE_TOKEN 你的Riot开发者API密钥保存文件。代码中如何读取在你的code.py中通过os.getenv()函数来读取这些配置from os import getenv ssid getenv(CIRCUITPY_WIFI_SSID) password getenv(CIRCUITPY_WIFI_PASSWORD) api_key getenv(LEAGUE_TOKEN)这种方式既安全又灵活。当你需要分享代码时只需分享code.py而不必泄露你的个人密钥。重要安全提示API密钥的保管Riot Games的开发API密钥是访问其服务的凭证必须妥善保管。切勿将包含真实密钥的settings.toml文件上传到GitHub等公开代码仓库。你可以在.gitignore文件中加入settings.toml或者使用settings.toml.example文件存储示例格式提醒其他开发者自行创建。4. 获取与配置Riot Games API密钥4.1 注册开发者账号与获取密钥Riot Games为其游戏提供了开放的API允许开发者获取丰富的游戏数据。使用API需要一个密钥API Key进行身份验证。详细步骤拥有游戏账号你需要一个正常游玩的《英雄联盟》账号并已创建召唤师角色。访问开发者门户前往 Riot Games Developer Portal 。登录与注册使用你的游戏账号登录。首次登录会引导你注册成为开发者需要同意开发者协议。生成API密钥登录后在仪表板Dashboard页面你会看到一个“Generate API Key”或类似的按钮。点击后系统会生成一个长长的字符串这就是你的开发环境API密钥。4.2 理解API密钥的类型与限制这里有一个至关重要的细节也是新手最容易踩的坑你刚刚生成的这个密钥是开发密钥Development API Key。特性开发密钥有效期极短通常只有24小时。过期后你需要回到开发者门户重新点击生成按钮获取一个新的。用途仅用于在开发阶段进行测试和原型验证。它的调用频率限制Rate Limit也相对较低。生产环境如果你打算长期、稳定地运行这个PyPortal项目或者开发一个公开服务就必须申请一个永久密钥Permanent API Key这通常需要通过提交一个“个人应用Personal App”申请来获得。Riot会审核你的应用描述、使用场景等通过后会发放一个不过期的密钥并给予更高的调用限额。对于我们这个个人桌面摆件项目24小时过期的开发密钥在初期测试时问题不大但显然不适合长期运行。因此在项目稳定后申请一个个人应用级别的永久密钥是必须的步骤。4.3 测试API接口在将密钥写入PyPortal之前强烈建议先在电脑浏览器中测试API是否工作正常这能快速排除密钥无效、召唤师名错误等问题。测试方法在开发者门户复制你的API密钥。在浏览器地址栏输入以下URL格式以美服为例https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/你的召唤师名?api_key你的API密钥例如https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/Doublelift?api_keyRGAPI-xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx如果一切正常浏览器会返回一个JSON对象其中就包含summonerLevel: xxx这个字段。如果返回错误如403 Forbidden或404 Not Found请检查密钥是否过期、召唤师名是否准确大小写敏感、以及服务器区域na1是北美如果你是其他服务器如欧服euw1、韩服kr需要修改URL中的区域代码。5. 项目代码深度解析与实现5.1 代码结构总览项目的核心逻辑全部在code.py文件中。我们可以将其分解为几个功能模块来理解导入与配置阶段导入必要的库并从settings.toml读取配置。数据源定义阶段拼接用于请求的API URL。PyPortal对象初始化阶段创建显示对象配置背景、字体、颜色、位置等。主循环阶段定期请求数据解析并更新显示处理异常。5.2 关键代码段逐行解读让我们结合原始代码深入每个部分# SPDX-FileCopyrightText: 2019 John Edgar Park for Adafruit Industries # SPDX-License-Identifier: MIT This project will access the League of Legends API, grab a Summoners Level and display it on a screen. from os import getenv import time import board from adafruit_pyportal import PyPortal # 1. 读取Wi-Fi配置并做安全检查 ssid getenv(CIRCUITPY_WIFI_SSID) password getenv(CIRCUITPY_WIFI_PASSWORD) if None in [ssid, password]: raise RuntimeError(WiFi settings are kept in settings.toml, please add them there...) # 2. 定义要查询的召唤师名 SUMMONER_NAME YourSummonerNameHere # 改为你的召唤师名 # 3. 构建API请求URL DATA_SOURCE https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/ SUMMONER_NAME DATA_SOURCE ?api_key getenv(LEAGUE_TOKEN) # 从settings.toml读取密钥 DATA_LOCATION [summonerLevel] # 指定从JSON响应中提取summonerLevel字段的值 CAPTION SUMMONER SUMMONER_NAME # 屏幕顶部显示的标题文本 # 4. 获取当前代码文件所在目录用于定位资源文件 cwd (/__file__).rsplit(/, 1)[0] # 5. 初始化PyPortal对象——这是核心配置 pyportal PyPortal(urlDATA_SOURCE, # 数据源URL json_path(DATA_LOCATION), # JSON数据提取路径 status_neopixelboard.NEOPIXEL, # 使用板载NeoPixel作为状态灯 default_bgcwd/lol_background.bmp, # 背景图片路径 text_fontcwd/fonts/Collegiate-50.bdf, # 显示等级的大字体 text_position(135, 200), # 等级数字的屏幕坐标(X, Y) text_color0xffbe33, # 字体颜色橙色 caption_textCAPTION, # 标题文本 caption_fontcwd/fonts/Collegiate-24.bdf, # 标题字体 caption_position(5, 20), # 标题位置 caption_color0xffbe33) # 标题颜色 last_value 0 # 用于记录上一次的等级以检测升级 # 6. 主循环 while True: try: # 发起网络请求并自动根据json_path解析数据 current_level pyportal.fetch() print(Response is, current_level) # 检查是否升级 if last_value current_level: print(New level!) # 播放升级音效需要音效文件 pyportal.play_file(cwd/triode_low_fade.wav) last_value current_level except (ValueError, RuntimeError, ConnectionError, OSError) as e: # 处理所有可能发生的错误网络错误、JSON解析错误等 print(Some error occurred, retrying! -, e) # 等待2分钟后再次查询 time.sleep(60 * 2)5.3adafruit_pyportal库的魔法PyPortal类的fetch()方法是本项目简洁性的关键。它内部完成了以下复杂操作通过adafruit_esp32spi库指示ESP32连接Wi-Fi。使用adafruit_requests库向url参数指定的地址发起HTTP GET请求。接收服务器响应并将其解析为JSON格式。根据json_path参数本例中是[summonerLevel]从JSON对象中钻取到具体的数值。将这个数值转换为字符串并使用之前配置的字体、颜色和位置将其渲染到屏幕上覆盖在背景图片之上。同时如果配置了caption_text也会将其渲染到指定位置。这一切都被封装在了一行value pyportal.fetch()之中极大地简化了代码。5.4 资源文件准备代码中引用了两个外部资源文件需要从项目包中获取并放入CIRCUITPY盘背景图片 (lol_background.bmp)一张320x240像素的16位位图。你可以使用Photoshop、GIMP等工具自己制作主题可以是英雄联盟的Logo、你擅长的英雄原画等。注意必须是BMP格式并且颜色模式需要匹配PyPortal的显示能力。字体文件 (Collegiate-50.bdf和Collegiate-24.bdf)BDF是位图字体格式。Adafruit提供了工具将常见的TTF/OTF字体转换为BDF。你需要将这两个字体文件放在CIRCUITPY盘下的fonts文件夹中。音效文件 (triode_low_fade.wav)当检测到升级时播放的简短音效。需要是单声道、较低的采样率如22kHz或以下的WAV文件以节省空间和内存。6. 组装与部署实战6.1 硬件组装与供电PyPortal本身是一个完整的设备组装主要指的是为其提供一个稳固且美观的“家”。方案一使用官方底座推荐Adafruit提供了专用的桌面立式底座将PyPortal插入即可非常稳固美观。这是最省事、效果最好的方案。方案二自制奖杯式外壳这也是原项目名的由来充满DIY乐趣。材料一个小型奖杯或相框、尼龙扎带束线带、电钻/热熔胶枪。步骤在奖杯的柱身或相框背面规划PyPortal的放置位置。使用尼龙扎带穿过PyPortal四周的安装孔将其牢牢固定在奖杯上。注意不要遮挡屏幕、扬声器孔和USB接口。可以考虑在PyPortal和奖杯之间垫一些海绵或绒布防止刮伤。将USB供电线从奖杯后方引出连接到一个5V 2A的USB电源适配器上。供电注意事项PyPortal的屏幕和Wi-Fi模块工作时功耗较高尤其是屏幕背光全亮时。务必使用输出稳定、质量可靠的5V 2A或以上的USB电源适配器。使用电脑USB口或劣质充电头可能导致供电不足表现为设备反复重启、Wi-Fi连接不稳定或屏幕闪烁。6.2 首次运行与调试将写好的code.py、settings.toml、库文件、背景图、字体等全部放入CIRCUITPY盘后PyPortal会自动重启并运行新代码。如何观察运行状态状态灯NeoPixeladafruit_pyportal库会用它来指示状态。通常紫色表示正在连接Wi-Fi蓝色表示正在获取数据绿色表示成功红色表示错误。观察颜色变化可以快速判断程序进行到哪一步。串口输出最有效通过Mu编辑器或任何串口终端工具如PuTTY,screen /dev/ttyACM0 115200连接到PyPortal的串口。你将在终端里看到所有的print()输出这是最强大的调试手段。你会看到连接Wi-Fi的尝试、获取到的IP地址、API请求的响应、以及最终的等级数值。常见启动问题排查问题NeoPixel一直呈紫色串口显示反复重连Wi-Fi。排查检查settings.toml中的SSID和密码是否正确特别是特殊字符和空格。检查路由器是否正常工作PyPortal是否在信号范围内。问题连接Wi-Fi成功变蓝但很快变红串口打印HTTP错误如403、404。排查403错误几乎肯定是API密钥问题。密钥已过期24小时开发密钥或settings.toml中的LEAGUE_TOKEN变量名与代码中getenv(“LEAGUE_TOKEN”)不匹配。404错误URL错误或召唤师名不存在。检查DATA_SOURCE中的服务器区域na1是否与你的账号匹配召唤师名大小写是否完全正确。问题屏幕白屏或显示乱码。排查检查default_bg指定的背景图片路径和文件名是否正确图片格式是否为兼容的BMP。检查fonts文件夹是否存在且字体文件名与代码中引用的完全一致。检查lib文件夹中是否包含了adafruit_imageload和adafruit_bitmap_font库。6.3 功能优化与个性化基础功能运行起来后你可以进行大量个性化改造更换背景与字体制作自己喜欢的背景图或从网上下载《英雄联盟》的高清壁纸并裁剪缩放至320x240。使用Adafruit的fontconverter工具将任何TTF字体转换为BDF格式使用。显示更多信息Riot API非常强大。你可以修改DATA_LOCATION和显示逻辑同时显示等级、头像ID、近期战绩K/D/A、精通英雄等。这需要对返回的JSON结构有更深了解并可能需要调整屏幕布局。调整更新频率time.sleep(60 * 2)表示每2分钟更新一次。Riot API有调用频率限制不宜设置过短如几秒一次否则可能导致IP被临时封禁。对于等级这种变化不频繁的数据每5-10分钟更新一次也完全足够。你可以改为time.sleep(60 * 10)。增加交互功能利用PyPortal的触摸屏你可以编写代码让点击屏幕时切换显示不同的召唤师信息或者切换显示模式如只显示等级/显示详细信息。错误处理增强在主循环的except块中可以添加更细致的错误分类处理。例如网络错误可以尝试重置ESP32API密钥错误则在屏幕上显示特定提示信息。7. 进阶思考与项目扩展这个“召唤师等级显示器”只是一个引子。掌握了PyPortal CircuitPython Web API这套组合拳你可以创造出无数有趣的应用。思路扩展多游戏支持编写一个配置菜单让用户可以选择显示《英雄联盟》、《Valorant》或《云顶之弈》的不同数据。多数据源聚合不仅仅显示游戏数据。你可以让它轮询显示当地天气、空气质量指数、待办事项列表、加密货币价格等。adafruit_pyportal支持定义多个PyPortal对象或在一个循环中切换数据源URL。加入物理反馈通过PyPortal的GPIO引脚连接一个舵机当等级提升时让一个小的奖杯模型转动一下或者连接一个WS2812B灯带根据游戏内的“蓝色精粹”数量改变灯光颜色。搭建信息中枢将多个PyPortal放在家中不同位置一个显示日历和天气一个显示智能家居状态一个显示网络监控数据。它们可以通过MQTT协议从一个中心服务器获取数据实现分布式信息显示。性能与优化提示内存管理CircuitPython运行在微控制器上内存有限。避免在循环中创建大型对象如列表、字典。尽量复用对象。异常处理网络请求是项目中最不稳定的环节。务必做好异常捕获和重试机制防止因单次网络波动导致程序崩溃。电源管理如果你希望它长期插电运行可以考虑在代码中加入“夜间模式”——在特定时间段调低屏幕亮度或暂时停止数据更新以节省能源和延长屏幕寿命。这个项目的魅力在于它从一个具体的兴趣点游戏切入带你完整地走通了物联网应用从硬件选型、环境搭建、软件编码、API调用到最终部署的全流程。当你看到自己的游戏等级在亲手制作的硬件上亮起时那种跨越虚拟与现实的满足感正是硬件编程最大的乐趣所在。