基于CircuitPython与Azure API打造低成本云成本监控硬件仪表盘
2026/5/16 18:57:13 网站建设 项目流程

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是一个为“信息显示”场景高度优化的产品。我们拆解一下它的核心部件及其在本项目中的作用:

  1. ESP32-S2主控芯片:这是项目的“大脑”。它提供了强大的Wi-Fi连接能力(支持802.11 b/g/n)和足够的计算资源来运行CircuitPython解释器、处理网络请求和解析JSON数据。与经典的ESP32相比,S2版本缺少蓝牙但单核性能更强,且功耗控制更优秀,对于这种间歇性联网的应用非常合适。

  2. 2.9英寸电子墨水屏(E-Ink):这是项目的“脸面”,也是实现超长续航的关键。电子墨水屏的特性是只在刷新图像时消耗电能,静态显示时功耗几乎为零。这意味着我们的设备99%的时间都处于极低功耗状态,只有每天联网获取数据并刷新屏幕的那几秒钟会消耗较多电量。灰度显示足以清晰呈现数字和文字的成本信息。

  3. 锂电池管理电路:MagTag板载了充电芯片和JST PH电池接口。你可以连接一块3.7V的锂聚合物电池(例如1200mAh),设备即可脱机运行。当电池电量低时,通过USB-C接口即可充电,实现了能源的自循环。

  4. 四个物理按键与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通过wifisocketpool库提供网络能力。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便会识别出是那个“机器人账号”在请求数据,并返回其被授权访问的信息。

数据流闭环

  1. MagTag从深度睡眠中唤醒(或定时器触发)。
  2. 加载secrets.py中的Wi-Fi和Azure凭证。
  3. 连接预设的Wi-Fi网络。
  4. 使用Azure凭证向Azure AD请求访问令牌。
  5. 使用获取到的令牌,向https://management.azure.com/下的成本预测API端点发起HTTPS GET请求。
  6. 解析API返回的JSON响应,提取出当日的成本预测值。
  7. 在电子墨水屏上格式化并显示该数值(如“今日预测: $2.34”)。
  8. 断开Wi-Fi连接,设备重新进入低功耗状态,等待下一个更新周期(24小时后)。

这个闭环设计确保了设备绝大部分时间处于“离线”节能状态,只在必要时进行短暂的、认证安全的云端通信。

3. 从零开始的详细搭建步骤

3.1 第一步:在Azure云端创建服务主体

这是整个项目安全性的基石,请严格在Azure门户中操作。

  1. 登录与打开Cloud Shell:使用你的Azure账号登录 Azure 门户 。在顶部工具栏找到 Cloud Shell 图标(通常是一个>_符号),点击打开。如果是首次使用,系统会提示你创建存储账户,按指引操作即可,产生的费用微乎其微。

  2. 创建服务主体:在打开的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/<资源组名>
  3. 保存关键凭证:命令执行成功后,会输出一串JSON。请立即、妥善地保存以下四个值:

    { "appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // 这是“应用(客户端) ID” "displayName": "magtag-cost-monitor", "password": "非常复杂的随机字符串", // 这是“客户端密码” "tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" // 这是“租户ID” }

    重要警告password(客户端密码)只会显示这一次,关闭窗口后就无法再次查看。请务必复制保存到安全的地方(如密码管理器)。丢失后只能创建新的服务主体。

  4. 获取订阅ID:如果不知道订阅ID,在Cloud Shell中运行az account list --output table,从输出表格中找到SubscriptionId列对应的值,这就是你的SUBSCRIPTION_ID

至此,云端配置完成。你拥有了访问成本数据所需的全部四把“钥匙”:APP_ID,CLIENT_SECRET,TENANT_ID,SUBSCRIPTION_ID

3.2 第二步:为MagTag安装CircuitPython固件

这是让硬件“活”起来的操作系统。

  1. 下载固件:访问 CircuitPython官网下载页面 ,找到对应MagTag的最新稳定版.uf2文件并下载。再次确认你的MagTag版本,选择正确的固件(10.x.x for 2025+ Edition)

  2. 进入UF2引导模式

    • 用一根数据线(非充电线)将MagTag连接到电脑。
    • 快速双击MagTag板上的Reset按钮(位于USB-C口旁边)。如果成功,电脑会弹出一个名为MAGTAGBOOT的可移动磁盘。
  3. 刷写固件:将下载好的.uf2文件直接拖拽或复制到MAGTAGBOOT磁盘中。复制完成后,设备会自动重启。几秒钟后,电脑上会出现一个新的名为CIRCUITPY的磁盘。这表明CircuitPython系统已安装成功。

3.3 第三步:配置网络与安装依赖库

现在需要让设备能上网,并具备必要的软件功能。

  1. 配置Wi-Fi:在CIRCUITPY磁盘的根目录下,找到或创建一个名为settings.toml的文本文件。用文本编辑器打开,输入你的Wi-Fi信息:

    CIRCUITPY_WIFI_SSID = "你的Wi-Fi名称" CIRCUITPY_WIFI_PASSWORD = "你的Wi-Fi密码"

    保存文件。这个文件是CircuitPython标准的安全凭证存储方式,代码通过os.getenv()函数读取,避免将密码硬编码在主要程序里。

  2. 验证网络连接:将以下基础的网络测试代码保存为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请求成功的打印信息。这步验证至关重要,能排除网络配置的基础问题。

  3. 安装必要的库文件:从 Adafruit CircuitPython库包 页面下载最新的adafruit-circuitpython-bundle-py-*.zip(或-mpy-*.zip)文件并解压。根据项目需要,将以下库文件或文件夹复制到CIRCUITPY磁盘的lib目录下:

    • 必需库文件夹adafruit_magtag/,adafruit_portalbase/,adafruit_bitmap_font/,adafruit_display_text/,adafruit_io/
    • 必需库文件(.mpy或.py)adafruit_requests.mpy,adafruit_minimqtt.mpy,neopixel.mpy,simpleio.mpy
    • Azure项目特定库:从项目GitHub仓库的src目录获取azure.py文件,它封装了与Azure API交互的复杂逻辑。

3.4 第四步:编写与部署主程序

这是项目的灵魂,我们将把云、硬件和逻辑串联起来。

  1. 创建 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 }
  2. 部署主程序 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_scale=3, text_anchor_point=(0.5, 0.5) # 中心对齐 ) magtag.set_text(f"Today: {currency}{daily_forecast:.2f}") # 添加标题和日期 magtag.add_text( text_position=(magtag.graphics.display.width // 2, 20), text_scale=2, text_anchor_point=(0.5, 0.5) ) magtag.set_text("Azure Cost Forecast", index=1) # index=1 表示设置第二个文本框 # 刷新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_scale=1) magtag.set_text(f"Error: {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_time=time.monotonic() + 86400) # alarm.exit_and_deep_sleep_until_alarms(time_alarm) # 如果未使用深度睡眠,则循环等待(用于调试) # while True: # time.sleep(1)
  3. 部署辅助文件:确保从项目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_magtagadafruit_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 功能优化与扩展思路

基础功能实现后,你可以根据个人需求进行深度定制:

  1. 多订阅/资源组监控:修改azure.py中的API请求URL。默认是监控整个订阅(/subscriptions/{subId}/providers/Microsoft.CostManagement/forecast)。你可以将其更改为特定资源组:/subscriptions/{subId}/resourceGroups/{rgName}/providers/Microsoft.CostManagement/forecast。这样就能聚焦于某个项目的花费。

  2. 数据可视化增强:除了显示当日预测,还可以在屏幕上绘制简单的趋势图。例如,通过API获取过去7天的实际成本(/queryAPI),然后在E-Ink屏上以柱状图或折线图的形式展示,直观反映成本变化趋势。这需要更复杂的数据处理和图形绘制逻辑。

  3. 阈值告警与主动通知:结合MagTag的四个按键和NeoPixel灯,实现交互式告警。例如,在代码中设置一个每日成本阈值(如5美元)。当预测成本超过阈值时,让NeoPixel闪烁红光,并在屏幕上高亮显示数值。你甚至可以按下某个按键来确认告警。更进一步,可以集成Adafruit IO或IFTTT,在超支时向你的手机发送推送通知。

  4. 优化功耗与唤醒策略:目前的示例使用简单的延时睡眠。为了极致省电,应使用ESP32-S2的深度睡眠(Deep Sleep)模式,并通过RTC定时器或外部中断(如按键)唤醒。在CircuitPython中,可以使用alarm库来设置一个24小时的TimeAlarm。深度睡眠下,整个系统的电流消耗可降至微安级别,使电池续航从数周延长至数月。

  5. 美化显示界面:利用adafruit_display_textadafruit_bitmap_font库,你可以使用更美观的字体,在屏幕上布局更多信息,比如同时显示“今日预测”、“本月至今累计”、“昨日实际”等,并配上图标(需要先将图标转换为位图格式),打造一个专业的桌面成本仪表盘。

这个项目的魅力在于,它不仅仅是一个成本监控器,更是一个通用的“云数据硬件显示器”模板。你可以将Azure Cost Management API替换成任何其他提供RESTful API的服务,比如股票价格、天气信息、待办事项列表、服务器状态监控等,MagTag都能成为一个安静、省电的实体化信息终端。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询