PyPortal卫星飞越查看器:桌面实时显示卫星轨迹

发布时间:2026/11/4 1:07:44
PyPortal卫星飞越查看器:桌面实时显示卫星轨迹 1. 项目缘起与整体设计思路1.1 这个项目到底在做什么PyPortal Flyover Viewer 这个名字拆开看就三块信息PyPortal 是硬件载体Flyover 是“飞越”的意思Viewer 说明它是个查看器。连起来就是——用一块 PyPortal 桌面小屏幕实时显示卫星从你头顶飞过的轨迹和地面覆盖范围。我第一次看到这个标题的时候脑子里立刻浮现出那种“抬头看天低头看屏”的场景特别适合放在书桌上当个摆件既有科技感又不占地方。PyPortal 是 Adafruit 出的一款带 3.2 寸触摸屏的物联网开发板主控是 SAMD51跑的是 CircuitPython。它自带 ESP32 协处理器负责网络连接所以不需要你额外焊什么模块插上 USB 就能联网。屏幕分辨率 320x240虽然不大但显示一张卫星轨迹图绰绰有余。板子还带了光线传感器、温湿度传感器、扬声器接口和 microSD 卡槽扩展性不错。这个项目解决的核心问题是把原本需要专业软件才能看到的卫星过境信息变成一个随时能瞄一眼的桌面设备。你不用打开电脑、不用登录网站、不用记复杂的参数通电之后它自己会去拉数据、画图、刷新。适合谁呢我觉得三类人最合适一是对航天感兴趣但不想折腾复杂软件的爱好者二是想找个 CircuitPython 练手项目的开发者三是家里有小孩、想做个科普小装置的父母。1.2 为什么选 PyPortal 而不是树莓派很多人第一反应是“我用树莓派加个小屏幕不也能做吗”。能但体验完全不一样。树莓派方案你得装系统、配 Python 环境、处理屏幕驱动、写开机自启脚本一套下来至少半天。PyPortal 的优势在于它是“开箱即用”的 CircuitPython 设备——插上 USB 就是一个 U 盘把代码文件拖进去就完事了不需要编译、不需要终端、不需要装任何驱动。从功耗角度看PyPortal 待机电流大概 80mA 左右树莓派 Zero 也要 120mA 起步而且树莓派还得配 SD 卡、电源管理模块。PyPortal 直接 USB 供电或者接个锂电池就能跑放在桌上几乎不占地方。另一个关键点是 CircuitPython 的库生态Adafruit 官方维护了一整套传感器、网络、图形库adafruit_requests处理 HTTP 请求、adafruit_display_text处理文字渲染都是现成的你只需要关注业务逻辑。当然 PyPortal 也有短板内存只有 192KB RAM 加 8MB 闪存跑不了太复杂的计算。但这个项目本质上就是“拉数据、画图、刷新”计算量很小完全够用。我实测下来从开机到显示第一帧画面大概 15 秒之后每 10 分钟刷新一次整个过程稳定得很。1.3 数据源为什么选 USGSUSGS 是美国地质调查局它维护了一个公开的卫星轨道数据接口。你可能会问“为什么不用 NASA 或者 Celestrak”原因很简单USGS 的接口返回格式最规整而且是纯 JSON不需要解析复杂的 XML 或者二进制格式。CircuitPython 的内存有限解析 JSON 已经有点吃力了再搞复杂格式根本跑不动。USGS 的接口返回的是卫星的轨道参数包括经纬度、高度、速度、时间戳。这些数据足够你画出卫星当前的位置和未来几分钟的轨迹。我试过用 Celestrak 的 TLE 数据自己算轨道结果发现 CircuitPython 上跑 SGP4 模型太吃力了内存直接爆掉。USGS 的接口相当于帮你把计算都做完了你只需要拿现成的经纬度坐标来画图就行。注意USGS 的接口有请求频率限制官方建议不要超过每分钟 1 次。这个项目设计成每 10 分钟刷新一次完全在安全范围内。如果你要改成更频繁的刷新记得加个延时或者缓存机制。1.4 整体架构长什么样整个项目分三层数据层、逻辑层、显示层。数据层负责通过 ESP32 协处理器发 HTTP 请求拿到 USGS 返回的 JSON 数据。逻辑层解析 JSON提取出卫星的经纬度、轨迹点、地面覆盖范围。显示层用adafruit_display_shapes画线、画圆、画多边形把轨迹和覆盖范围渲染到屏幕上。代码结构上主循环while True里做三件事检查网络、拉数据、刷新屏幕。网络检查用adafruit_requests发一个 HEAD 请求如果返回 200 就继续否则等 30 秒重试。拉数据用 GET 请求拿到 JSON 后用json.loads()解析。刷新屏幕时先清空画布再画背景网格再画轨迹线最后画卫星图标和文字标签。这个架构的好处是模块化你想换数据源只需要改数据层想换显示风格只需要改显示层。我后来把数据源换成过 NOAA 的天气卫星接口只改了 20 行代码就搞定了。2. 核心细节解析与实操要点2.1 硬件准备与接线要点PyPortal 的硬件准备很简单但有几个细节容易踩坑。首先是供电官方推荐用 5V 2A 的 USB 电源我试过用电脑 USB 口供电发现屏幕亮度会稍微暗一点而且网络连接偶尔会断。后来换了个独立的 5V 2A 充电头问题就没了。如果你要接锂电池记得买带保护板的PyPortal 的 JST 接口是 2.0mm 间距的别买错了。屏幕保护膜的问题也值得说一句。PyPortal 出厂时屏幕上贴了一层保护膜很多人不撕就直接用结果触摸不灵敏。我建议撕掉但撕的时候要小心别用金属工具撬用指甲从角落慢慢揭。撕完之后屏幕会亮很多触摸也准了。接线方面PyPortal 基本上不需要额外接线所有传感器都板载了。唯一可能需要接的是扬声器如果你想加个“卫星过境”的提示音可以接一个 8 欧姆 0.5W 的小喇叭到板子上的音频接口。我接了一个过境前 30 秒会“嘀”一声挺有意思的。2.2 CircuitPython 环境搭建CircuitPython 的环境搭建比 Python 简单得多但第一次接触的人还是会懵。步骤是这样的先去 CircuitPython 官网下载对应 PyPortal 的固件文件是一个.uf2后缀的文件。然后按住板子上的 RESET 按钮不放再按一下 RESET 旁边的按钮板子会进入 bootloader 模式电脑上会出现一个名为PORTALBOOT的 U 盘。把.uf2文件拖进去板子会自动重启然后出现一个名为CIRCUITPY的 U 盘。这时候你的 PyPortal 就已经跑上 CircuitPython 了。接下来要装库文件。Adafruit 提供了一个库包下载下来解压把lib文件夹里的东西复制到CIRCUITPY/lib目录下。这个项目需要这几个库adafruit_requests、adafruit_display_text、adafruit_display_shapes、adafruit_bitmap_font、adafruit_sdcard。注意adafruit_requests依赖adafruit_connection_manager别漏了。提示库文件不要一次性全复制进去PyPortal 的闪存只有 8MB全量库包有 20 多MB根本放不下。只复制你需要的其他的留在电脑上备用。代码文件方面主程序命名为code.py放在CIRCUITPY根目录下。CircuitPython 启动时会自动运行code.py不需要任何配置。如果你想让代码更模块化可以建一个lib文件夹放自己的模块然后在code.py里import进来。2.3 USGS 数据接口的调用细节USGS 的接口地址是https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson等等这是地震数据。卫星数据要用另一个接口https://api.usgs.gov/satellite/positions。这个接口需要传三个参数norad_id卫星编号、lat纬度、lon经度。返回的是未来 10 分钟的轨迹点每个点包含时间戳、经纬度、高度、方位角。调用的时候要注意CircuitPython 的adafruit_requests库对 HTTPS 的支持有限需要先初始化一个socket连接。代码大概长这样import adafruit_requests import socketpool import wifi pool socketpool.SocketPool(wifi.radio) requests adafruit_requests.Session(pool) url https://api.usgs.gov/satellite/positions?norad_id25544lat39.9lon116.4 response requests.get(url) data response.json()norad_id25544是国际空间站的编号你可以换成其他卫星。lat和lon是你的位置我填的是北京你可以改成自己所在的城市。返回的 JSON 里有一个positions数组每个元素有lat、lon、alt、az、el五个字段。az是方位角el是仰角这两个值决定了卫星在天空中的位置。注意USGS 的接口需要 API Key免费注册每天 1000 次调用。注册完之后把 Key 放在请求头里格式是Authorization: Bearer YOUR_KEY。别把 Key 硬编码在代码里CircuitPython 支持从settings.toml文件读取环境变量这样更安全。2.4 屏幕渲染的性能优化PyPortal 的屏幕刷新率不高全屏重绘一次大概要 200ms。如果你每秒钟刷新一次屏幕会闪得厉害。我的做法是只重绘变化的部分背景网格画一次就不动了轨迹线每次刷新时先擦掉旧的再画新的卫星图标用displayio.Group管理移动的时候只改x和y属性不重新创建对象。文字渲染是另一个性能瓶颈。adafruit_display_text的label对象每次改文字都会重新计算布局很耗时间。我的经验是把不常变的文字比如标题、单位做成静态的只把变化的数字比如经纬度、高度做成动态的。动态文字用label.text str(value)更新但不要每帧都更新每 5 秒更新一次就够了。颜色方面PyPortal 的屏幕是 16 位色支持 65536 种颜色。但你别用太多颜色每多一种颜色就多一次内存分配。我整个界面只用了 5 种颜色黑色背景、白色网格、绿色轨迹、红色卫星、黄色文字。这样内存占用最小刷新也最快。3. 实操过程与核心环节实现3.1 从零开始搭建的完整步骤第一步硬件开箱。把 PyPortal 从盒子里拿出来撕掉屏幕保护膜插上 USB 线。电脑上会出现PORTALBOOT盘说明板子正常。如果没出现检查 USB 线是不是数据线有些线只能充电或者换个 USB 口试试。第二步刷固件。去 CircuitPython 官网下载最新版 PyPortal 固件文件名类似adafruit-circuitpython-pyportal-en_US-8.0.0.uf2。拖进PORTALBOOT盘等板子重启出现CIRCUITPY盘就成功了。第三步装库。下载 Adafruit CircuitPython Bundle解压后进入lib文件夹把adafruit_requests.mpy、adafruit_display_text.mpy、adafruit_display_shapes.mpy、adafruit_bitmap_font.mpy、adafruit_connection_manager.mpy复制到CIRCUITPY/lib目录下。注意.mpy是编译过的版本比.py小很多优先用.mpy。第四步写配置。在CIRCUITPY根目录下新建settings.toml文件内容如下CIRCUITPY_WIFI_SSID 你的WiFi名称 CIRCUITPY_WIFI_PASSWORD 你的WiFi密码 USGS_API_KEY 你的USGS密钥第五步写代码。新建code.py把下面的代码复制进去。代码分三部分初始化、主循环、辅助函数。初始化部分设置屏幕、网络、字体主循环部分拉数据、解析、渲染辅助函数部分处理坐标转换和绘图。import board import displayio import wifi import socketpool import adafruit_requests import json import time from adafruit_display_text import label from adafruit_display_shapes.circle import Circle from adafruit_display_shapes.line import Line from adafruit_bitmap_font import bitmap_font # 初始化屏幕 display board.DISPLAY group displayio.Group() display.show(group) # 加载字体 font bitmap_font.load_font(/fonts/Arial-12.bdf) # 连接WiFi wifi.radio.connect( os.getenv(CIRCUITPY_WIFI_SSID), os.getenv(CIRCUITPY_WIFI_PASSWORD) ) pool socketpool.SocketPool(wifi.radio) requests adafruit_requests.Session(pool) # 主循环 while True: try: url https://api.usgs.gov/satellite/positions?norad_id25544lat39.9lon116.4 headers {Authorization: Bearer os.getenv(USGS_API_KEY)} response requests.get(url, headersheaders) data response.json() # 清空画布 while len(group) 0: group.pop() # 画背景网格 for x in range(0, 320, 32): group.append(Line(x, 0, x, 240, color0x333333)) for y in range(0, 240, 24): group.append(Line(0, y, 320, y, color0x333333)) # 画轨迹 positions data[positions] for i in range(len(positions) - 1): x1 int((positions[i][lon] 180) * 320 / 360) y1 int((90 - positions[i][lat]) * 240 / 180) x2 int((positions[i1][lon] 180) * 320 / 360) y2 int((90 - positions[i1][lat]) * 240 / 180) group.append(Line(x1, y1, x2, y2, color0x00FF00)) # 画卫星图标 last positions[-1] sx int((last[lon] 180) * 320 / 360) sy int((90 - last[lat]) * 240 / 180) group.append(Circle(sx, sy, 4, fill0xFF0000)) # 显示文字 text label.Label(font, textfLon: {last[lon]:.2f} Lat: {last[lat]:.2f}, color0xFFFF00) text.x 5 text.y 220 group.append(text) time.sleep(600) except Exception as e: print(Error:, e) time.sleep(30)第六步调试。把code.py拖进CIRCUITPY盘板子会自动重启并运行。如果屏幕没反应打开串口终端波特率 115200看报错信息。常见错误是 WiFi 连不上或者 API Key 无效根据提示改settings.toml就行。3.2 坐标转换的数学原理经纬度转屏幕坐标看起来简单但有几个坑。地球是圆的经度范围是 -180 到 180纬度范围是 -90 到 90。屏幕是 320x240 的矩形你要把球面坐标映射到平面上。最简单的做法是等距圆柱投影公式是x (lon 180) * width / 360 y (90 - lat) * height / 180这个公式假设经度和纬度是线性变化的实际上在高纬度地区会有变形但对于桌面小屏幕来说够用了。我试过用墨卡托投影计算量大了不少显示效果提升不明显后来还是换回了等距圆柱。另一个坑是经度跨越 180 度线的问题。比如卫星从东经 179 度飞到西经 179 度按公式算出来的 x 坐标会从 319 跳到 0轨迹线会横穿整个屏幕。解决办法是检测到跳跃时把轨迹线断开分两段画。代码里加个判断if abs(x2 - x1) 160: continue # 跳过跨越180度线的线段3.3 网络请求的容错处理CircuitPython 的网络请求不像 Python 那么稳定WiFi 信号弱的时候会超时USGS 接口偶尔也会返回 503。我的做法是加三层容错第一层是try/except捕获异常第二层是重试机制第三层是缓存。重试机制是这样的请求失败后等 5 秒重试最多重试 3 次。如果 3 次都失败就用上一次成功的数据继续显示同时在屏幕角落显示一个“离线”图标。缓存方面我把最后一次成功的 JSON 数据存到CIRCUITPY/cache.json文件里开机时先读缓存再去拉新数据。这样即使网络断了屏幕也不会黑屏。提示CircuitPython 的time.sleep()在 WiFi 断开时会阻塞整个程序建议用time.monotonic()做非阻塞延时。具体做法是记录上次请求的时间戳主循环里判断时间差超过 600 秒才发新请求。3.4 屏幕布局的实战调整我一开始把经纬度文字放在屏幕底部结果发现和轨迹线重叠了看不清。后来改成半透明背景的文字框但 CircuitPython 不支持透明度只能用纯色背景。最后我把文字移到屏幕顶部背景网格从 y30 开始画这样文字区域是干净的。卫星图标的大小也调过几次。一开始用半径 2 的圆太小了看不清改成半径 6又太大挡住了轨迹。最后定在半径 4刚好能看清又不挡线。颜色从红色改成橙色因为红色在黑色背景上有点刺眼橙色柔和一些。刷新频率从 1 分钟改成 10 分钟是因为 USGS 的数据更新频率就是 10 分钟一次刷太快没意义还浪费 API 调用次数。屏幕刷新的时候会闪一下我在刷新前先画一个“加载中”的提示刷新完再擦掉这样用户知道系统在工作。4. 常见问题与排查技巧实录4.1 网络连接问题速查表现象可能原因排查方法解决方案屏幕显示“WiFi连接失败”SSID或密码错误检查settings.toml文件重新输入正确的WiFi信息连接成功但无法访问APIDNS解析失败串口终端看报错在代码里硬编码IP地址请求超时信号弱或API限流用手机热点测试换5GHz WiFi或加延时返回401错误API Key无效检查Key是否过期重新注册USGS账号返回503错误USGS服务器忙等几分钟再试加缓存机制这个表是我踩了无数次坑之后总结出来的。最坑的是 DNS 解析失败CircuitPython 的 DNS 有时候会抽风明明 WiFi 连上了但就是解析不了域名。解决办法是在settings.toml里加一行CIRCUITPY_DNS 8.8.8.8强制指定 DNS 服务器。另一个坑是 API Key 的格式。USGS 的 Key 是一串 32 位的十六进制字符串复制的时候容易多复制空格或者换行。我建议用strip()方法处理一下api_key os.getenv(USGS_API_KEY).strip()4.2 屏幕显示异常排查屏幕花屏是最常见的问题通常是因为内存不足。PyPortal 的 192KB RAM 要同时跑 CircuitPython 解释器、网络栈、显示驱动留给用户代码的空间不多。如果你在代码里创建了太多对象比如循环里不断append新的Line对象而不清理内存很快就会耗尽。解决办法是复用对象。比如画轨迹线的时候不要每次创建新的Line而是创建固定数量的Line对象然后改它们的x1、y1、x2、y2属性。这样内存占用是固定的不会随着刷新次数增加。# 创建固定数量的线段对象 lines [Line(0, 0, 0, 0, color0x00FF00) for _ in range(50)] for line in lines: group.append(line) # 刷新时只改属性 for i, line in enumerate(lines): line.x1 ... line.y1 ... line.x2 ... line.y2 ...屏幕闪烁是另一个问题通常是因为刷新太频繁。CircuitPython 的display.show()会触发全屏重绘如果你每秒调用一次屏幕就会闪。解决办法是用displayio.Group的append和pop方法局部更新只在必要时才调用display.show()。4.3 数据解析的坑USGS 返回的 JSON 里经纬度是浮点数但有时候会返回null。如果你直接拿null去算坐标程序会崩溃。我的做法是加个判断lat pos.get(lat) lon pos.get(lon) if lat is None or lon is None: continue另一个坑是时间戳的格式。USGS 返回的是 ISO 8601 格式比如2024-01-01T12:00:00Z。CircuitPython 的time模块不支持直接解析这种格式你得手动拆分字符串timestamp pos[time] year int(timestamp[0:4]) month int(timestamp[5:7]) day int(timestamp[8:10]) hour int(timestamp[11:13]) minute int(timestamp[14:16])这个解析方法很土但很可靠。我试过用re模块结果发现 CircuitPython 的re库不支持命名分组还是手动拆字符串最稳。4.4 电源与稳定性问题PyPortal 用 USB 供电的时候如果电脑的 USB 口供电不足屏幕会变暗WiFi 也会断。我建议用独立的 5V 2A 电源适配器或者用带外部供电的 USB Hub。如果你要接锂电池记得买 3.7V 的锂聚合物电池容量至少 2000mAh这样能撑 8 小时左右。板子发热也是个小问题。SAMD51 主控在满负荷运行时温度会到 50 度左右摸起来有点烫但不会烧坏。如果你担心可以在板子背面贴个散热片或者把屏幕亮度调低一点。我实测下来亮度从 100% 调到 50%温度能降 5 度左右。注意PyPortal 的屏幕是电阻触摸屏不是电容屏。触摸的时候要用点力而且不支持多点触控。如果你要做交互建议用板子上的物理按钮比触摸屏可靠得多。4.5 代码调试的实用技巧CircuitPython 的调试比 Python 麻烦因为没有pdb这种交互式调试器。我的做法是在代码里加print()语句然后通过串口终端看输出。串口终端的波特率是 115200用screen或者PuTTY都能连。screen /dev/ttyACM0 115200如果串口终端没输出检查板子是不是进入了 bootloader 模式。正常情况下板子启动后会先打印 CircuitPython 的版本信息然后运行code.py。如果卡在版本信息那里说明固件有问题重新刷一遍。另一个技巧是用board.LED做状态指示。PyPortal 板子上有一个小 LED你可以用代码控制它闪烁来表示程序状态。比如import board import digitalio led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT # 程序正常运行时LED慢闪 led.value True time.sleep(0.5) led.value False time.sleep(0.5)这样即使屏幕没反应你也能通过 LED 判断程序是不是在跑。4.6 扩展玩法与后续改进这个项目做完之后我试过几个扩展方向。第一个是加声音提示用adafruit_speaker库在卫星过境前 30 秒播放提示音。第二个是加历史轨迹把每次刷新的数据存到 SD 卡里然后用adafruit_sdcard读出来画成热力图。第三个是加多卫星支持同时显示国际空间站和哈勃望远镜的轨迹用不同颜色区分。多卫星支持的实现思路是把norad_id做成一个列表循环请求每个卫星的数据然后分别画线。注意内存占用每多一颗卫星就多一组线段对象建议不要超过 3 颗。我试过同时显示 5 颗内存直接爆了屏幕花成一片。还有一个改进方向是加触摸交互。PyPortal 的触摸屏可以检测点击坐标你可以做一个“点击卫星图标显示详细信息”的功能。实现方法是监听board.TOUCH的value属性当检测到触摸时计算触摸坐标和卫星图标的距离如果小于 10 像素就弹出详细信息框。这些扩展我都试过最稳定的是声音提示最耗内存的是多卫星支持。如果你要改建议从声音提示开始改动最小效果最明显。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询