1. 项目概述打造你的桌面级云成本“仪表盘”在云计算项目里最让人心里没底的事情之一可能就是月底收到账单时的“惊喜”了。虽然Azure Portal提供了成本分析工具但总得主动登录后台去查看对于日常监控来说不够直观也容易遗忘。有没有一种方法能把关键的成本预测像天气预报一样静静地显示在你的桌面上抬眼就能看到这就是我们今天要动手实现的项目一个基于Adafruit MagTag和CircuitPython的Azure成本监控器。它的核心思路非常清晰利用一块自带Wi-Fi和低功耗电子墨水屏的硬件定期比如每24小时自动从Azure Cost Management API拉取你订阅的当日成本预测并显示在屏幕上。整个设备由一块锂电池供电充一次电可以运行数周真正做到了“设置好就忘掉”让你对云支出有一个持续、静默的感知。我选择Adafruit MagTag作为硬件平台主要看中它“开箱即用”的特性。它集成了ESP32-S2 Wi-Fi芯片、2.9英寸灰度电子墨水屏、四个物理按键和RGB NeoPixel指示灯并且原生支持CircuitPython。CircuitPython是一种基于MicroPython的解释型语言其最大优势在于开发体验如同操作U盘你将代码以文本文件形式保存到设备上它就能实时运行调试和迭代速度极快特别适合物联网原型开发。这个项目的技术栈虽然涉及硬件和云但每一步都被CircuitPython和Azure的标准化接口大大简化了。你不需要焊接不需要复杂的嵌入式编译环境核心工作就是配置凭证和编写逻辑。最终你会得到一个安静的“成本看门狗”它不联网时屏幕内容保持不变联网后自动更新数据完美契合电子墨水屏超低功耗刷新的特性。2. 核心组件与工作原理深度解析2.1 硬件选型为什么是Adafruit MagTag在众多物联网开发板中MagTag是一个为“信息显示”场景高度优化的产品。我们拆解一下它的核心部件及其在本项目中的作用ESP32-S2主控芯片这是项目的“大脑”。它提供了强大的Wi-Fi连接能力支持802.11 b/g/n和足够的计算资源来运行CircuitPython解释器、处理网络请求和解析JSON数据。与经典的ESP32相比S2版本缺少蓝牙但单核性能更强且功耗控制更优秀对于这种间歇性联网的应用非常合适。2.9英寸电子墨水屏E-Ink这是项目的“脸面”也是实现超长续航的关键。电子墨水屏的特性是只在刷新图像时消耗电能静态显示时功耗几乎为零。这意味着我们的设备99%的时间都处于极低功耗状态只有每天联网获取数据并刷新屏幕的那几秒钟会消耗较多电量。灰度显示足以清晰呈现数字和文字的成本信息。锂电池管理电路MagTag板载了充电芯片和JST PH电池接口。你可以连接一块3.7V的锂聚合物电池例如1200mAh设备即可脱机运行。当电池电量低时通过USB-C接口即可充电实现了能源的自循环。四个物理按键与NeoPixel RGB LED这些是重要的交互和状态指示部件。在代码中我们可以编程定义按键功能例如手动触发一次数据更新。NeoPixel LED则可以用颜色来直观反馈状态比如绿色代表获取数据成功红色代表网络或API错误蓝色代表正在连接让设备状态一目了然。注意务必区分MagTag的版本。2025年及之后的新版MagTag电路板正面为黑色必须使用CircuitPython 10.x.x或更高版本。早期的版本白色面板可以兼容9.x.x但建议都升级到最新版以获得更好的功能和安全性。2.2 软件核心CircuitPython与Azure API的握手项目的软件逻辑围绕两个核心交互展开设备与本地Wi-Fi网络的连接、设备与云端Azure API的认证与通信。CircuitPython的网络栈CircuitPython通过wifi和socketpool库提供网络能力。wifi库负责扫描和连接无线网络其凭证通过settings.toml文件安全管理。连接成功后socketpool会管理网络套接字资源而adafruit_requests库则在此基础上提供了一个非常人性化的HTTP客户端接口让我们可以用类似Pythonrequests库的语法去调用API极大降低了开发难度。Azure Cost Management API的认证流程这是项目中最需要谨慎处理的一环。我们不能也不应该将个人Azure账号密码直接写在设备代码里。正确的做法是使用服务主体Service Principal。你可以把它理解为一个专门为程序或设备创建的、拥有特定权限的“机器人账号”。创建这个服务主体的过程就是在Azure Active Directory中注册一个应用并为其分配访问成本数据的角色如“成本管理读者”。完成后你会得到三样关键凭证应用(客户端) ID、客户端密码和租户ID。设备代码使用这三者通过OAuth 2.0客户端凭证流获取一个访问令牌Access Token后续的所有API请求都携带这个令牌Azure便会识别出是那个“机器人账号”在请求数据并返回其被授权访问的信息。数据流闭环MagTag从深度睡眠中唤醒或定时器触发。加载secrets.py中的Wi-Fi和Azure凭证。连接预设的Wi-Fi网络。使用Azure凭证向Azure AD请求访问令牌。使用获取到的令牌向https://management.azure.com/下的成本预测API端点发起HTTPS GET请求。解析API返回的JSON响应提取出当日的成本预测值。在电子墨水屏上格式化并显示该数值如“今日预测: $2.34”。断开Wi-Fi连接设备重新进入低功耗状态等待下一个更新周期24小时后。这个闭环设计确保了设备绝大部分时间处于“离线”节能状态只在必要时进行短暂的、认证安全的云端通信。3. 从零开始的详细搭建步骤3.1 第一步在Azure云端创建服务主体这是整个项目安全性的基石请严格在Azure门户中操作。登录与打开Cloud Shell使用你的Azure账号登录 Azure 门户 。在顶部工具栏找到 Cloud Shell 图标通常是一个_符号点击打开。如果是首次使用系统会提示你创建存储账户按指引操作即可产生的费用微乎其微。创建服务主体在打开的Cloud Shell默认为Bash环境中输入以下命令az ad sp create-for-rbac --name magtag-cost-monitor --role Cost Management Reader --scopes /subscriptions/你的订阅ID--name给你的服务主体起个名字例如magtag-cost-monitor。--role Cost Management Reader这是关键。这个内置角色仅授予读取成本和管理数据的权限遵循最小权限原则。--scopes指定权限作用范围。/subscriptions/你的订阅ID表示这个服务主体可以读取整个订阅的成本。如果你想限制到某个资源组可以将其替换为/subscriptions/订阅ID/resourceGroups/资源组名。保存关键凭证命令执行成功后会输出一串JSON。请立即、妥善地保存以下四个值{ appId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, // 这是“应用(客户端) ID” displayName: magtag-cost-monitor, password: 非常复杂的随机字符串, // 这是“客户端密码” tenant: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx // 这是“租户ID” }重要警告password客户端密码只会显示这一次关闭窗口后就无法再次查看。请务必复制保存到安全的地方如密码管理器。丢失后只能创建新的服务主体。获取订阅ID如果不知道订阅ID在Cloud Shell中运行az account list --output table从输出表格中找到SubscriptionId列对应的值这就是你的SUBSCRIPTION_ID。至此云端配置完成。你拥有了访问成本数据所需的全部四把“钥匙”APP_ID,CLIENT_SECRET,TENANT_ID,SUBSCRIPTION_ID。3.2 第二步为MagTag安装CircuitPython固件这是让硬件“活”起来的操作系统。下载固件访问 CircuitPython官网下载页面 找到对应MagTag的最新稳定版.uf2文件并下载。再次确认你的MagTag版本选择正确的固件10.x.x for 2025 Edition。进入UF2引导模式用一根数据线非充电线将MagTag连接到电脑。快速双击MagTag板上的Reset按钮位于USB-C口旁边。如果成功电脑会弹出一个名为MAGTAGBOOT的可移动磁盘。刷写固件将下载好的.uf2文件直接拖拽或复制到MAGTAGBOOT磁盘中。复制完成后设备会自动重启。几秒钟后电脑上会出现一个新的名为CIRCUITPY的磁盘。这表明CircuitPython系统已安装成功。3.3 第三步配置网络与安装依赖库现在需要让设备能上网并具备必要的软件功能。配置Wi-Fi在CIRCUITPY磁盘的根目录下找到或创建一个名为settings.toml的文本文件。用文本编辑器打开输入你的Wi-Fi信息CIRCUITPY_WIFI_SSID 你的Wi-Fi名称 CIRCUITPY_WIFI_PASSWORD 你的Wi-Fi密码保存文件。这个文件是CircuitPython标准的安全凭证存储方式代码通过os.getenv()函数读取避免将密码硬编码在主要程序里。验证网络连接将以下基础的网络测试代码保存为CIRCUITPY磁盘根目录下的code.py它会覆盖之前的示例代码。设备将自动运行此代码。import os import wifi import socketpool import adafruit_requests import time # 连接Wi-Fi print(连接Wi-Fi...) wifi.radio.connect(os.getenv(CIRCUITPY_WIFI_SSID), os.getenv(CIRCUITPY_WIFI_PASSWORD)) print(已连接IP地址:, wifi.radio.ipv4_address) # 测试网络请求 pool socketpool.SocketPool(wifi.radio) requests adafruit_requests.Session(pool) response requests.get(http://httpbin.org/get) print(网络测试成功状态码:, response.status_code) response.close()打开串口监视器如VS Code的CircuitPython插件、PuTTY或screen/moserial工具波特率115200你应该能看到连接成功和HTTP请求成功的打印信息。这步验证至关重要能排除网络配置的基础问题。安装必要的库文件从 Adafruit CircuitPython库包 页面下载最新的adafruit-circuitpython-bundle-py-*.zip或-mpy-*.zip文件并解压。根据项目需要将以下库文件或文件夹复制到CIRCUITPY磁盘的lib目录下必需库文件夹adafruit_magtag/,adafruit_portalbase/,adafruit_bitmap_font/,adafruit_display_text/,adafruit_io/。必需库文件.mpy或.pyadafruit_requests.mpy,adafruit_minimqtt.mpy,neopixel.mpy,simpleio.mpy。Azure项目特定库从项目GitHub仓库的src目录获取azure.py文件它封装了与Azure API交互的复杂逻辑。3.4 第四步编写与部署主程序这是项目的灵魂我们将把云、硬件和逻辑串联起来。创建 secrets.py 文件在CIRCUITPY磁盘根目录下创建secrets.py文件。此文件包含所有敏感信息绝对不要分享或上传到Git等公开平台。secrets { # Wi-Fi 配置 ssid: 你的Wi-Fi名称, password: 你的Wi-Fi密码, # Azure 服务主体配置 appId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, # 替换为你的应用ID clientSecret: 你的客户端密码, # 替换为你的密码 tenant: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, # 替换为你的租户ID subscription: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # 替换为你的订阅ID }部署主程序 code.py将以下整合后的主程序代码保存为CIRCUITPY磁盘根目录下的code.py。这个代码实现了完整的业务逻辑初始化硬件、连接网络、获取令牌、调用成本API、解析数据显示、并进入低功耗等待。import time import board from adafruit_magtag.magtag import MagTag import azure # 这是从项目仓库获取的 azure.py 库 # 初始化MagTag对象设置E-Ink刷新和NeoPixel magtag MagTag() magtag.peripherals.neopixels.brightness 0.1 try: # 1. 连接Wi-Fi (使用secrets.py中的配置) magtag.peripherals.neopixels.fill(0x0000FF) # 蓝色正在连接 magtag.network.connect() print(Wi-Fi Connected!) magtag.peripherals.neopixels.fill(0x00FF00) # 绿色连接成功 # 2. 初始化Azure客户端 # 从secrets.py导入配置需在secrets.py中定义secrets字典 from secrets import secrets azure_client azure.AzureCostManagement( secrets[appId], secrets[clientSecret], secrets[tenant], secrets[subscription] ) # 3. 获取成本预测 print(Fetching cost forecast...) forecast_data azure_client.get_cost_forecast() # 假设API返回的JSON中预测值在 properties.totalCost 路径下 daily_forecast forecast_data[properties][totalCost] currency forecast_data[properties][currency] # 获取货币单位 # 4. 在屏幕上显示 magtag.add_text( text_position(magtag.graphics.display.width // 2, magtag.graphics.display.height // 2 - 10), text_scale3, text_anchor_point(0.5, 0.5) # 中心对齐 ) magtag.set_text(fToday: {currency}{daily_forecast:.2f}) # 添加标题和日期 magtag.add_text( text_position(magtag.graphics.display.width // 2, 20), text_scale2, text_anchor_point(0.5, 0.5) ) magtag.set_text(Azure Cost Forecast, index1) # index1 表示设置第二个文本框 # 刷新E-Ink屏幕 magtag.refresh() print(Display updated.) # 5. 成功指示 magtag.peripherals.neopixels.fill(0x00FF00) time.sleep(2) # 保持绿灯2秒示意成功 except Exception as e: # 任何错误亮红灯并在串口打印错误 print(Error:, e) magtag.peripherals.neopixels.fill(0xFF0000) # 在屏幕上显示错误 magtag.add_text(text_position(10, 10), text_scale1) magtag.set_text(fError: {str(e)[:30]}...) magtag.refresh() time.sleep(10) # 错误状态显示10秒 finally: # 6. 进入深度睡眠24小时86400秒后唤醒 magtag.peripherals.neopixels.fill(0x000000) # 关闭NeoPixel print(Entering deep sleep for 24 hours...) # 注意ESP32-S2的深度睡眠会断开Wi-Fi唤醒后需要重新连接 magtag.enter_light_sleep(86400) # 对于深度睡眠可能需要使用特定引脚唤醒或使用定时器 # 实际项目中更可靠的做法是使用MagTag的RTC闹钟或外部中断来唤醒 # 此处为简化示例可使用 time.sleep 模拟但实际功耗较高。 # 推荐实现使用 alarm 库的 TimeAlarm # import alarm # time_alarm alarm.time.TimeAlarm(monotonic_timetime.monotonic() 86400) # alarm.exit_and_deep_sleep_until_alarms(time_alarm) # 如果未使用深度睡眠则循环等待用于调试 # while True: # time.sleep(1)部署辅助文件确保从项目GitHub仓库获取的azure.py文件也已放置在CIRCUITPY磁盘的根目录下。这个文件包含了与Azure AD认证和Cost Management API交互的具体实现。完成以上步骤后断开MagTag与电脑的USB连接接上锂电池。设备将自动运行code.py尝试连接Wi-Fi、获取数据并显示。第一次运行建议通过串口监视器观察输出以便调试。4. 调试、优化与进阶玩法4.1 串口调试与常见问题排查当设备行为不符合预期时串口监视器是你的第一诊断工具。以下是一些常见问题及排查思路现象可能原因排查步骤无法连接Wi-Fi1.settings.toml中SSID/密码错误。2. Wi-Fi网络隐藏或使用了企业级认证。3. 信号太弱。1. 检查settings.toml文件格式和内容确保无多余空格。2. 在代码中尝试使用wifi.radio.start_scanning_networks()查看是否能扫描到目标网络。3. 打印wifi.radio.ap_info.rssi检查信号强度。Azure API返回4xx错误1.secrets.py中Azure凭证错误或过期。2. 服务主体未被授予“成本管理读者”角色。3. 订阅ID错误。1.仔细核对appId,clientSecret,tenant,subscription四个值确保与CloudShell输出完全一致。2. 在Azure门户中进入“订阅” - “访问控制(IAM)” - “角色分配”确认服务主体已存在且角色正确。3. 在代码中打印出获取到的访问令牌前几位然后在 jwt.ms 网站解码检查aud受众和scp权限范围是否正确。屏幕无显示或显示乱码1. 库文件缺失或版本不兼容。2. E-Ink刷新未成功完成。3. 字体文件问题。1. 确认lib文件夹下已正确放置所有必需的库特别是adafruit_magtag和adafruit_display_text。2. E-Ink刷新需要较长时间约2秒且刷新期间不能断电。确保代码中在magtag.refresh()后留有足够延时。3. MagTag使用内置位图字体如果自定义字体需确保字体文件已正确加载。设备运行一次后不再更新1. 深度睡眠/唤醒配置错误。2. 代码中存在未捕获的异常导致程序停止。1. 检查深度睡眠代码。ESP32-S2深度睡眠后程序会从头开始执行。确保所有初始化逻辑在每次唤醒后都能正确运行。2. 在代码开始处和关键步骤加入print语句观察程序执行到哪一步停止。使用try...except捕获所有异常并打印。电池消耗过快1. 未成功进入深度睡眠。2. Wi-Fi连接后未断开。3. NeoPixel等外设未关闭。1. 使用magtag.enter_light_sleep()或alarm库进入真正的低功耗模式而非time.sleep()。2. 在完成数据获取和显示后调用magtag.network._wifi.radio.stop_station()主动断开Wi-Fi连接。3. 在finally块中确保将NeoPixel亮度设为0。实操心得调试物联网项目最有效的方法是“分而治之”。先写一个最简单的Wi-Fi连接测试程序通了之后再单独测试Azure令牌获取然后再测试API调用最后整合显示逻辑。每一步都通过串口打印关键信息如IP地址、HTTP状态码、JSON片段能快速定位问题所在。另外务必利用好try...except语句将可能出错的网络操作包裹起来并在except块中点亮红灯和打印错误详情这样设备在现场出问题时你也能远程“看到”错误。4.2 功能优化与扩展思路基础功能实现后你可以根据个人需求进行深度定制多订阅/资源组监控修改azure.py中的API请求URL。默认是监控整个订阅/subscriptions/{subId}/providers/Microsoft.CostManagement/forecast。你可以将其更改为特定资源组/subscriptions/{subId}/resourceGroups/{rgName}/providers/Microsoft.CostManagement/forecast。这样就能聚焦于某个项目的花费。数据可视化增强除了显示当日预测还可以在屏幕上绘制简单的趋势图。例如通过API获取过去7天的实际成本/queryAPI然后在E-Ink屏上以柱状图或折线图的形式展示直观反映成本变化趋势。这需要更复杂的数据处理和图形绘制逻辑。阈值告警与主动通知结合MagTag的四个按键和NeoPixel灯实现交互式告警。例如在代码中设置一个每日成本阈值如5美元。当预测成本超过阈值时让NeoPixel闪烁红光并在屏幕上高亮显示数值。你甚至可以按下某个按键来确认告警。更进一步可以集成Adafruit IO或IFTTT在超支时向你的手机发送推送通知。优化功耗与唤醒策略目前的示例使用简单的延时睡眠。为了极致省电应使用ESP32-S2的深度睡眠Deep Sleep模式并通过RTC定时器或外部中断如按键唤醒。在CircuitPython中可以使用alarm库来设置一个24小时的TimeAlarm。深度睡眠下整个系统的电流消耗可降至微安级别使电池续航从数周延长至数月。美化显示界面利用adafruit_display_text和adafruit_bitmap_font库你可以使用更美观的字体在屏幕上布局更多信息比如同时显示“今日预测”、“本月至今累计”、“昨日实际”等并配上图标需要先将图标转换为位图格式打造一个专业的桌面成本仪表盘。这个项目的魅力在于它不仅仅是一个成本监控器更是一个通用的“云数据硬件显示器”模板。你可以将Azure Cost Management API替换成任何其他提供RESTful API的服务比如股票价格、天气信息、待办事项列表、服务器状态监控等MagTag都能成为一个安静、省电的实体化信息终端。