1. 项目概述:打造你的专属iOS通知“小秘书”
你是否也经历过这样的场景:手机放在包里或口袋里,每次有消息进来,都得掏出来看一眼,结果可能只是个无关紧要的推送,不仅打断了手头的工作,还白白消耗了注意力。对于需要专注的开发者、创作者,或者只是单纯不想被手机频繁打扰的人来说,一个能过滤并轻量化提示重要通知的桌面设备,就显得非常实用。
今天分享的这个项目,正是为了解决这个痛点。它利用一块小巧的Adafruit Circuit Playground Bluefruit开发板,搭配一个圆形的TFT Gizmo显示屏,通过蓝牙低功耗(BLE)技术,与你的iPhone配对,专门接收并显示来自指定应用(如Slack、短信、日历)的通知图标。你可以把它看作一个高度定制化的“通知指示灯”或“信息看板”。当手机收到新消息时,这个设备会亮起对应应用的图标,你只需瞥一眼就知道是哪个App有动静,再决定是否需要拿起手机处理,从而有效减少不必要的干扰。
这个项目的核心,是苹果为蓝牙配件提供的一套标准化服务——苹果通知中心服务(Apple Notification Center Service, ANCS)。ANCS允许像Apple Watch这样的BLE设备,安全、规范地从iOS设备获取通知内容。我们正是利用CircuitPython对ANCS协议的支持,让这块开源硬件也能“听懂”iPhone的通知。整个方案硬件成本可控,软件完全开源,并且基于CircuitPython,使得开发过程像写Python脚本一样简单,非常适合作为学习嵌入式开发、BLE通信和物联网设备交互的入门实践。
2. 核心硬件选型与功能解析
2.1 硬件清单与角色定位
这个项目的硬件架构非常清晰,主要由三部分组成:主控、显示和供电。下面我们来逐一拆解每个部件的选型理由和关键参数。
1. Adafruit Circuit Playground Bluefruit (CPB)这是整个项目的大脑和通信中心。选择它而非其他Arduino或ESP32板卡,有几个决定性原因:
- 内置BLE支持:CPB搭载了Nordic nRF52840芯片,原生支持蓝牙5.0低功耗协议栈。这意味着我们无需额外添加蓝牙模块,简化了硬件设计和连接。
- CircuitPython原生支持:Adafruit官方为CPB提供了深度优化的CircuitPython固件和丰富的库,包括我们项目核心所需的
adafruit_ble和adafruit_ble_apple_notification_center库。这让我们能用高级的Python语法直接操作蓝牙服务和特征值,避开了底层嵌入式C开发的复杂性。 - 丰富的板载资源:除了BLE,CPB还集成了加速度计、温度传感器、光线传感器、蜂鸣器以及10个可编程RGB NeoPixel灯。虽然本项目主要用其蓝牙功能,但这些额外的传感器为未来功能扩展(如根据环境光调节屏幕亮度、通过手势切换通知)留下了巨大空间。
- 物理按键:板载的两个按钮(A和B)被我们用来实现通知图标的翻页浏览,提供了无需触摸屏的直观交互方式。
2. TFT Gizmo 圆形显示屏显示部分选择了与CPB外形完美匹配的圆形TFT Gizmo,这是一个关键的设计决策。
- 即插即用:Gizmo通过边缘的插针直接扣在CPB上,无需焊接,构成了一个坚固的整体。其驱动芯片(ST7789)已有成熟的CircuitPython库(
adafruit_gizmo)支持,初始化只需几行代码。 - 尺寸与分辨率:1.54英寸、240x240像素的圆形屏幕,对于显示图标类信息绰绰有余。圆形屏幕也与通知图标的圆形设计语言相匹配,视觉上更协调。
- 集成音频放大器:Gizmo还板载了一个小功率音频放大器,可以驱动其背面的微型扬声器。这为我们实现通知音效提供了硬件基础,而无需外接模块。
3. 350mAh锂聚合物电池供电选择了带短接线的3.7V 350mAh锂电池。
- 续航考量:整个系统的功耗主要来自显示屏背光。代码中设置了自动调光(一段时间无操作后大幅降低亮度)和息屏逻辑,结合BLE的低功耗特性,这块小电池可以支持数小时至一天的间歇性使用,满足桌面摆件的需求。
- 安全与便捷:CPB板载了锂电池充电管理电路,可以通过Micro USB接口直接为电池充电,实现了“充电宝式”的便捷供电方案。
注意:兼容性提醒本项目严格依赖iOS设备的ANCS服务,因此仅适用于iPhone、iPad等苹果设备。安卓系统有类似的“蓝牙HID通知”或各厂商私有协议,但并未标准化为类似ANCS的通用服务,因此无法直接兼容。这是由协议层决定的,并非代码限制。
2.2 ANCS协议:蓝牙通知的“翻译官”
理解ANCS是理解本项目如何工作的关键。你可以把它想象成iPhone和蓝牙配件之间的一位专业“翻译官”。
在BLE架构中,通信基于“服务(Service)”和“特征值(Characteristic)”模型。一个设备(服务器)提供多种服务,每个服务包含多个特征值,用于读写具体的数据。iPhone在连接BLE配件时,会扮演一个包含ANCS服务的服务器角色。
ANCS服务定义了多个特征值,其中最重要的两个是:
- 通知源(Notification Source):这是一个“通知”特征值。当iPhone有新的通知到达、修改或删除时,它会自动向已订阅此特征的配件发送一个简短的数据包,其中包含通知事件的类型(新增、修改、删除)和通知ID。
- 控制点(Control Point)和数据源(Data Source):这两个特征值配合工作。当配件从“通知源”得知有新通知后,如果想获取这个通知的详细信息(如应用ID、标题、内容),就需要向“控制点”特征写入一个请求命令,指明想要哪个通知的哪些信息。随后,iPhone会通过“数据源”特征值,将请求的详细信息发送给配件。
我们的CircuitPython代码,通过adafruit_ble_apple_notification_center库,完美封装了上述复杂的交互过程。我们只需要调用notification_service.active_notifications来获取当前所有活动通知的列表,或者监听wait_for_new_notifications()事件,库底层会自动完成特征值的订阅、请求和解析工作。这种抽象让我们可以专注于业务逻辑:根据通知里的app_id,找到对应的图标文件并显示在屏幕上。
app_id是一个类似com.tinyspeck.chatlyio(Slack)或com.apple.MobileSMS(短信)的字符串,它是iOS系统内部识别应用的唯一标识符,通常与应用商店的Bundle Identifier一致。
3. 软件环境搭建与核心代码剖析
3.1 CircuitPython固件与库部署详解
要让CPB运行我们的Python代码,第一步是将其从默认的UF2引导模式切换为CircuitPython解释器环境。
固件烧录步骤:
- 访问CircuitPython官网,找到Circuit Playground Bluefruit的页面,下载最新的
.uf2固件文件。 - 用一条数据线(务必确认,很多手机充电线只能供电)将CPB连接至电脑。
- 双击CPB中央的复位按钮。此时,周围的10个RGB LED会先变红,再变绿。如果只变红不变绿,通常是USB线或端口问题。成功后,电脑会出现一个名为
CPLAYBTBOOT的U盘。 - 将下载好的
.uf2文件拖入CPLAYBTBOOT盘符。CPB会自动重启,CPLAYBTBOOT盘符消失,取而代之的是一个名为CIRCUITPY的新盘符。这表明CircuitPython系统已成功启动。
库文件安装的注意事项:CIRCUITPY盘符就是我们的“硬盘”,代码和库都存放在这里。
- 在
CIRCUITPY根目录下,创建一个名为lib的文件夹(如果不存在)。 - 下载与CircuitPython版本匹配的“库捆绑包”(Library Bundle)。这是一个包含所有官方库的ZIP文件。
- 解压后,进入其中的
lib文件夹,将项目所需的库文件或文件夹复制到CIRCUITPY盘的lib文件夹内。核心库包括:adafruit_ble/:蓝牙通信的核心库。adafruit_ble_apple_notification_center.mpy:ANCS服务的具体实现库(注意是单独的.mpy文件)。adafruit_gizmo/:驱动TFT Gizmo显示屏的库。adafruit_bus_device/:底层总线支持。neopixel.mpy:控制板载LED(虽然本项目未使用,但某些基础依赖可能需要)。
实操心得:库版本兼容性务必确保CircuitPython固件版本与库捆绑包的版本大致匹配(主要版本号相同)。使用过旧或过新的库可能会导致无法导入模块或运行时错误。最稳妥的方式是去Adafruit的学习页面,其项目教程通常会指明测试通过的固件和库版本。
3.2 主程序代码深度解读
项目的核心逻辑都在code.py文件中。我们分段解析其工作原理。
初始化与配置阶段:
import time import board import digitalio import displayio import adafruit_ble from adafruit_ble.advertising.standard import SolicitServicesAdvertisement from adafruit_ble_apple_notification_center import AppleNotificationCenterService from adafruit_gizmo import tft_gizmo开头导入了所有必要的模块。SolicitServicesAdvertisement用于创建广播数据,明确告知外界“本设备寻求连接ANCS服务”,这能加快iPhone的发现和配对流程。
应用白名单与硬件设置:
APP_ICONS = { "com.tinyspeck.chatlyio": "/ancs_slack.bmp", "com.basecamp.bc3-ios": "/ancs_basecamp.bmp", "com.apple.MobileSMS": "/ancs_sms.bmp", ... }APP_ICONS字典是项目的“大脑映射表”,它将iOS系统的app_id映射到我们存储在板子上的图标文件路径。这是自定义显示哪些应用通知的关键配置。如果你希望显示微信通知,就需要在这里添加微信的app_id和对应的图标文件名。
BLOCKLIST列表则相反,用于屏蔽特定应用的通知。
蓝牙连接管理:find_connection()函数遍历当前所有BLE连接,寻找已配对且提供了ANCS服务的连接。如果找到未配对的连接,则调用connection.pair()发起配对。这个函数确保了设备能重连或处理多个连接(虽然本项目通常是一对一)。
显示与亮度管理:Dimmer类是一个简单的状态机,用于管理屏幕背光超时熄灭的逻辑。它检查按键(A/B)是否被按下,以及距离最后一次操作的时间。如果超时(DIM_TIMEOUT秒),则将屏幕亮度降至DIM_LEVEL(如5%)以省电;一旦有按键或新通知,亮度立即恢复至100%。
主循环逻辑剖析:主循环是整个程序的中枢,其状态流转如下图所示(用文字描述):
- 等待连接状态:如果没有活动连接,则开始广播。持续调用
find_connection()并检查超时,直到iPhone连接并配对成功。连接成功后,播放提示音,显示“无通知”图标。 - 连接保持与通知处理状态:在连接保持期间,不断从
notification_service.active_notifications获取当前通知列表。- 过滤:遍历列表,只处理
app_id存在于APP_ICONS中且不在BLOCKLIST里的通知。 - 排序:将过滤后的通知ID按原始日期排序,确保最新通知在列表末尾(便于按键浏览逻辑)。
- 显示逻辑:
- 如果没有活动通知,显示“无通知”图标。
- 如果有通知,检查是否有按键(A/B)按下。按键用于在通知列表中向前(B)或向后(A)浏览。当前显示的通知ID由
current_notification变量记录。 - 如果当前显示的通知已被手机端移除(
notification.removed为True),则清除当前显示。
- 图标更新:根据当前选定的通知ID,从其
app_id映射到对应的BMP图标文件,并使用displayio库的TileGrid方法将图标更新到屏幕的第二个图层(第一个图层是背景)。
- 过滤:遍历列表,只处理
- 断开重连:如果检测到连接断开,则清理显示,重置连接变量,跳回步骤1,重新开始广播。
这个循环巧妙地平衡了实时性(及时响应通知)和低功耗(无操作时调暗屏幕),是嵌入式事件驱动编程的一个典型范例。
4. 图标定制与功能扩展实战
4.1 获取未知应用的App ID
项目自带的图标只覆盖了少数应用。要为你常用的App添加支持,首先需要知道它的app_id。教程中提到的print_code.py文件就是为此而生的“侦察兵”程序。
操作步骤:
- 将
print_code.py的内容复制并替换CIRCUITPY盘上的code.py。 - 重启CPB或按复位键,确保新代码运行。
- 打开Mu编辑器或其他串口终端,连接到CPB的串行端口(REPL)。
- 用iPhone蓝牙设置配对并连接“CIRCUITPY”设备。
- 此时,让你的目标App(比如微信)产生一条通知。在串口终端里,你将看到类似以下的输出:
这里的关键信息就是------------------------------------ Msg #5 - Category incoming From app: com.tencent.xin Title: 张三 Message: 晚上一起吃饭吗?From app: com.tencent.xin。这个com.tencent.xin就是微信的app_id(不同地区版本可能略有不同,请以实际输出为准)。
记录下这个ID,它就是你通往自定义通知的钥匙。
4.2 设计与制作自定义图标
有了app_id,下一步是制作对应的240x240像素、16位的BMP图标。
制作规范与技巧:
- 尺寸与格式:必须为240x240像素,颜色深度为**16位(RGB565)**的BMP文件。这是TFT Gizmo屏幕硬件和CircuitPython
displayio库OnDiskBitmap模块的直接要求。 - 设计建议:为了保持视觉统一,可以沿用项目提供的“白圈黑底”模板。使用Photoshop、GIMP或免费的在线编辑器(如Photopea)进行操作:
- 找一个清晰的应用图标(PNG格式,带透明背景)。
- 将其放置在黑色背景上,并套入一个白色的圆形遮罩或描边内。
- 调整图标大小,使其在圆形区域内清晰可见。
- 最终导出时,务必选择“16位(5-6-5)”RGB模式和BMP格式。
- 快速转换工具:如果你已有现成的PNG或JPG图标,可以使用在线图像转换网站(如online-convert.com),在上传时指定输出格式为“BMP”,颜色为“16位(高彩色)”,尺寸为240x240。
文件部署:将制作好的BMP文件,例如ancs_wechat.bmp,复制到CIRCUITPY盘的根目录。
4.3 代码集成与测试
最后一步,修改主程序code.py,将新的app_id和图标文件加入映射表。
在APP_ICONS字典中添加一行:
APP_ICONS = { "com.tinyspeck.chatlyio": "/ancs_slack.bmp", "com.tencent.xin": "/ancs_wechat.bmp", # 新增行 # ... 其他原有条目 }保存文件。CircuitPython会自动重新加载代码。现在,当你的iPhone收到微信消息时,这块小屏幕就应该会显示你自定义的微信图标了。
注意事项:图标文件名与路径确保字典中的文件名与
CIRCUITPY根目录下的BMP文件名完全一致,包括大小写和扩展名。在CircuitPython的文件系统中,路径是区分大小写的。使用/ancs_wechat.bmp这样的绝对路径(从根目录开始)是最可靠的方式。
5. 物理组装、使用与问题排查
5.1 硬件组装要点
组装过程非常简单,但顺序很重要:
- 确保CPB和TFT Gizmo都已断电(未连接USB或电池)。
- 对齐接口:将TFT Gizmo背面的插针与CPB边缘的母座仔细对齐。Gizmo的扬声器开孔应朝向CPB板外侧(有丝印“D4”和“D5”的一侧)。
- 扣合:轻轻但均匀用力地将两者压合,直到听到轻微的“咔哒”声或感觉完全贴合。切忌使用蛮力或错位按压,以免弯折或损坏插针。
- 连接电池:将锂电池的JST插头连接到CPB板上标有“BAT”的端口。通常红线对应“+”,黑线对应“-”。你可以选择将电池夹在两层板之间,或者使用3D打印外壳将其收纳在内。
关于3D打印外壳:Adafruit教程页面提供了由Ruiz Bros设计的精美外壳文件。打印并使用外壳不仅能保护电路,其“怀表式”的设计也让设备更像一个精致的桌面摆件,方便随时查看。
5.2 日常使用流程
- 上电:将电池连接到CPB,或者通过Micro USB线供电。设备启动后,屏幕会首先显示“蓝牙连接”图标。
- iPhone配对:
- 进入iPhone的“设置” > “蓝牙”,确保蓝牙已开启。
- 在设备列表中,寻找名为“CIRCUITPY”的设备并点击。
- 点击“配对”。配对通常只需一次,以后设备开机且iPhone蓝牙开启时,会自动重连。
- 接收通知:配对成功后,屏幕会显示一个铃铛图标,表示已连接但暂无通知。当白名单内的App有新通知时,对应图标会立即显示。
- 浏览通知:如果有多个未读通知,可以按CPB上的A键(查看更早的)和B键(查看更新的)进行滚动浏览。
- 清除通知:当你在iPhone上滑动清除某个通知后,Gizmo屏幕上对应的图标也会在几秒内消失。这是通过ANCS协议同步的。
5.3 常见问题与排查技巧
在实际制作和使用的过程中,你可能会遇到以下问题。这里提供一套排查思路:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 屏幕无显示,CPB LED不亮 | 供电问题 | 1. 检查电池是否已充电,或用USB线直接供电测试。 2. 检查电池插头是否插反或接触不良。 |
| 屏幕只显示“连接蓝牙”图标,iPhone搜不到设备 | 1. 代码未运行 2. 蓝牙广播未启动 | 1. 确认code.py文件已在CIRCUITPY根目录。2. 按一下CPB复位键,观察串口输出(如果连接了电脑)。 3. 检查 adafruit_ble等库文件是否已正确安装到lib文件夹。 |
| iPhone能配对但很快断开,或通知不显示 | 1. 配对失败 2. ANCS服务未正确订阅 3. 图标文件缺失 | 1. 在iPhone蓝牙设置中,忘记“CIRCUITPY”设备,重启CPB,重新配对。 2. 使用 print_code.py测试,看串口能否打印出通知信息。如果不能,可能是库版本或兼容性问题。3. 检查 CIRCUITPY盘根目录下是否存在代码中指定的所有.bmp和.wav文件,文件名是否完全匹配。 |
| 自定义App图标不显示 | 1.app_id不正确2. 图标格式错误 3. 代码未生效 | 1. 使用print_code.py精确获取目标App的app_id。2. 确认BMP文件为240x240,16位色深。可用看图软件检查属性。 3. 修改 code.py后,按复位键或等待自动重启(约几秒后)。 |
| 屏幕一直常亮,不进入省电模式 | 按键卡住或代码逻辑问题 | 1. 检查A/B按键是否有物理卡滞。 2. 检查代码中 DIM_TIMEOUT的值(单位秒),确认调光逻辑dimmer.check_timeout()在主循环中被正确调用。 |
| 通知显示有延迟 | BLE通信延迟或iPhone系统限制 | ANCS通知的推送由iOS系统管理,本身就有少许延迟(通常1-3秒)。这是正常现象,无法做到完全实时。确保iPhone和CPB之间距离不要太远,中间无严重遮挡。 |
进阶调试技巧:当遇到复杂问题时,串口REPL是你的最佳朋友。通过Mu编辑器连接到CPB,你可以在代码中插入print()语句,输出变量状态、函数执行流程或错误信息。例如,在find_connection()函数后打印连接状态,在显示图标前打印app_id,都能帮你快速定位问题环节。
这个项目从一个简单的想法出发,通过巧妙地组合成熟的硬件、高效的开发语言和标准的协议,最终变成了一个既实用又有趣的实体产品。它最吸引我的地方在于,整个开发流程高度抽象,让我能专注于“让设备做什么”的逻辑,而不是深陷“如何让设备工作”的底层细节。CircuitPython极大地降低了嵌入式开发的门槛,而ANCS协议则提供了一个稳定、合法的数据通道。当你第一次看到手机上的通知同步到这块自制的小屏幕上时,那种连接数字世界与物理世界的成就感,正是创客精神的乐趣所在。