在桌面端或移动端通过 RSS 阅读器获取信息,虽然高效,但依然需要主动打开应用、面对屏幕蓝光,并且容易在信息流中分心。有没有一种方式,能将你关注的 RSS 订阅内容,像一份每日更新的报纸一样,自动推送到一个低功耗、不伤眼的电子墨水屏设备上,让你在专注工作或休息时,无需拿起手机就能轻松浏览新闻摘要?这正是将 RSS 与电子墨水屏结合的魅力所在。
本文的目标,就是带你从零开始,构建一个能将任意 RSS 订阅源内容抓取、格式化,并最终显示在电子墨水屏上的完整系统。整个过程不依赖任何现成的商业服务,你将完全掌控数据源和显示设备。我们将使用 Python 作为核心开发语言,通过几个成熟的库来处理 RSS 和驱动墨水屏,最终实现一个定时自动运行的“电子墨水报”生成器。无论你是想打造一个放在桌角的“摸鱼”神器,还是想为家庭信息中心增加一个无干扰的信息窗口,这篇文章都将提供一条清晰的实现路径。
1. 理解核心组件:RSS、电子墨水屏与自动化
在动手之前,我们需要理清整个项目的技术栈和它们各自扮演的角色。这个项目本质上是一个数据管道:从源头(RSS)获取数据,经过处理(解析、格式化),最终输出到终端(电子墨水屏)。
1.1 RSS:结构化信息源
RSS(Really Simple Syndication)是一种用于发布经常更新的内容(如博客文章、新闻标题)的标准格式。它本质上是一个遵循特定 XML 结构的文件。对我们而言,RSS 提供了两个关键信息:
- 条目列表:每个条目通常包含标题、链接、发布时间和摘要。
- 结构化数据:由于是 XML 格式,我们可以用程序精确地解析出所需内容,而无需面对网页上复杂的 HTML 和 JavaScript。
在项目中,我们将使用 Python 的feedparser库来解析 RSS 源。你只需要提供一个 RSS 订阅地址,它就能帮你把复杂的 XML 转换成易于操作的 Python 字典或对象。
1.2 电子墨水屏:低功耗显示终端
电子墨水屏(E-Ink)的特性决定了它非常适合本项目:
- 双稳态显示:只在刷新内容时耗电,静态显示时不耗电。这意味着我们的设备可以长时间显示内容而无需充电。
- 反射式:依靠环境光,像纸一样,长时间阅读不伤眼。
- 刷新慢:不适合播放视频或快速滚动,但完美契合“每日报纸”或“摘要简报”这种低频更新的场景。
市面上有众多可供开发者使用的电子墨水屏模块,通常通过 SPI 或 I2C 接口与树莓派(Raspberry Pi)、ESP32 等微型计算机连接。本文将以在创客社区非常流行的Waveshare(微雪)系列电子墨水屏为例进行说明,其驱动库完善,文档丰富。
1.3 自动化:让系统自己运转
一个实用的“电子墨水报”系统必须是自动化的。我们需要解决:
- 定时抓取:使用操作系统的定时任务工具(如 Linux/macOS 的
cron,Windows 的“任务计划程序”)来定期执行我们的 Python 脚本。 - 内容处理:脚本需要能处理网络异常、解析不同格式的 RSS、过滤掉不感兴趣的内容,并将最终内容格式化为适合墨水屏显示的版式。
- 驱动屏幕:调用屏幕厂商提供的 Python 库,将处理好的内容渲染到屏幕上。
2. 环境准备与硬件选型
2.1 软件环境准备
我们将在一个安装了 Python 的环境中进行开发。以下步骤在 macOS/Linux 或 Windows(建议使用 WSL)上基本通用。
- 安装 Python 3.8+:确保你的系统已安装 Python 3.8 或更高版本。在终端输入
python3 --version或python --version检查。 - 创建项目目录并初始化虚拟环境:虚拟环境能隔离项目依赖,避免污染系统 Python 环境。
mkdir eink_rss_newspaper cd eink_rss_newspaper python3 -m venv venv - 激活虚拟环境:
- macOS/Linux:
source venv/bin/activate - Windows (CMD):
venv\Scripts\activate.bat - Windows (PowerShell):
venv\Scripts\Activate.ps1激活后,终端提示符前通常会显示(venv)。
- macOS/Linux:
- 安装核心 Python 库:我们将使用
feedparser解析 RSS,使用Pillow(PIL)处理图像,这是驱动墨水屏的基础。
如果后续使用特定型号的墨水屏,还需要安装其官方驱动库,例如微雪屏的库通常名为pip install feedparser Pillowwaveshare-epd。
2.2 硬件选型与连接
你需要准备以下硬件:
- 主控板:推荐树莓派 Zero W/WH、3B+、4B或ESP32。树莓派系统更通用,Python 环境配置简单,本文以树莓派为例。ESP32 更省电但内存和计算资源有限。
- 电子墨水屏:选择适合的尺寸和颜色。
- 尺寸:2.9英寸、4.2英寸、7.5英寸是常见尺寸。桌面摆放建议 4.2 或 7.5 英寸。
- 颜色:黑白、黑白红三色。黑白屏最便宜,三色屏可以高亮显示标题等重要信息。
- 分辨率:分辨率越高,显示文字越清晰,但刷新速度可能稍慢,对主控板内存要求也更高。
- 接口:SPI 接口最为常见。示例型号:Waveshare 4.2英寸黑白红三色电子墨水屏(型号通常为
e-Paper 4.2inch B)。
- 连接线:根据屏幕接口(通常是 SPI)准备杜邦线(母对母)。
- 其他:树莓派电源、SD卡(已刷入 Raspberry Pi OS)、可能的电平转换模块(部分5V屏连接3.3V树莓派时需要)。
硬件连接示意(以树莓派 SPI 接口为例):
| 墨水屏引脚 | 树莓派 GPIO 引脚 (BCM编号) | 功能 |
|---|---|---|
| VCC | 3.3V (Pin 1) | 电源 (3.3V) |
| GND | GND (Pin 6) | 地线 |
| DIN | MOSI (GPIO10, Pin 19) | SPI 数据输入 |
| CLK | SCLK (GPIO11, Pin 23) | SPI 时钟 |
| CS | CE0 (GPIO8, Pin 24) | SPI 片选 0 |
| DC | GPIO25 (Pin 22) | 数据/命令选择 |
| RST | GPIO17 (Pin 11) | 复位 |
| BUSY | GPIO24 (Pin 18) | 忙状态指示 |
注意:不同型号的屏幕引脚定义可能不同,务必查阅你所购买屏幕的官方 Wiki 或数据手册。连接错误可能损坏设备。
连接好后,启动树莓派,并通过 SSH 或直接连接显示器进入系统。
3. 核心代码实现:从 RSS 到屏幕图像
我们的脚本主要完成三个任务:获取并解析 RSS、将文本内容渲染为图像、将图像发送到墨水屏显示。
3.1 编写 RSS 抓取与解析模块
首先,创建一个名为rss_fetcher.py的文件。
import feedparser from datetime import datetime, timedelta import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class RSSFetcher: def __init__(self, feed_urls, max_items_per_feed=5, hours_old=24): """ 初始化 RSS 抓取器 :param feed_urls: RSS 源 URL 列表 :param max_items_per_feed: 每个源最多获取几条内容 :param hours_old: 只获取多少小时内的内容 """ self.feed_urls = feed_urls self.max_items = max_items_per_feed self.time_threshold = datetime.utcnow() - timedelta(hours=hours_old) def fetch_all_feeds(self): """获取所有 RSS 源的内容,并合并、排序、过滤""" all_entries = [] for url in self.feed_urls: try: logger.info(f"正在抓取: {url}") feed = feedparser.parse(url) if feed.bozo: # 检查解析是否出错 logger.warning(f"解析 RSS 源 {url} 时出错: {feed.bozo_exception}") continue for entry in feed.entries[:self.max_items]: # 处理发布时间,有些源可能没有 published_time = self._get_entry_time(entry) if published_time and published_time < self.time_threshold: continue # 内容太旧,跳过 item = { 'title': entry.get('title', '无标题'), 'link': entry.get('link', '#'), 'summary': self._clean_summary(entry.get('summary', entry.get('description', ''))), 'source': feed.feed.get('title', url), 'published': published_time, } all_entries.append(item) except Exception as e: logger.error(f"抓取 {url} 时发生异常: {e}") continue # 按发布时间倒序排列(最新的在前) all_entries.sort(key=lambda x: x['published'] or datetime.min, reverse=True) return all_entries def _get_entry_time(self, entry): """从条目中提取发布时间,处理多种可能的时间字段""" time_keys = ['published_parsed', 'updated_parsed', 'created_parsed'] for key in time_keys: if hasattr(entry, key) and entry[key]: return datetime.fromtimestamp(mktime(entry[key])) return None def _clean_summary(self, summary, max_length=200): """清理摘要,移除HTML标签,并截断至指定长度""" import re # 简单移除HTML标签 text = re.sub(r'<[^>]+>', '', summary) # 移除多余空白字符 text = ' '.join(text.split()) # 截断 if len(text) > max_length: text = text[:max_length] + '...' return text # 示例用法 if __name__ == '__main__': # 替换成你感兴趣的 RSS 源 my_feeds = [ 'https://rsshub.app/zhihu/daily', 'https://rsshub.app/solidot/linux', # 注意:某些源可能需要特定访问方式,此处仅为示例 ] fetcher = RSSFetcher(my_feeds, max_items_per_feed=3, hours_old=12) entries = fetcher.fetch_all_feeds() for idx, entry in enumerate(entries): print(f"{idx+1}. [{entry['source']}] {entry['title']}") print(f" 摘要: {entry['summary']}") print()关键点解释:
feedparser非常健壮,能处理各种非标准的 RSS 和 Atom 格式。_clean_summary函数用于去除摘要中的 HTML 标签,这是必须的,因为原始摘要可能包含<p>,<a>等标签,无法直接用于文本渲染。- 我们通过
hours_old参数过滤掉旧新闻,确保“报纸”的内容是新鲜的。 - 异常处理很重要,因为网络请求可能失败,RSS 源格式也可能变化。
3.2 编写内容渲染模块
接下来,创建renderer.py,负责将文本条目列表转换为一张适合墨水屏显示的 PIL 图像。
from PIL import Image, ImageDraw, ImageFont import textwrap class NewspaperRenderer: def __init__(self, width, height, font_path=None): """ 初始化渲染器 :param width: 目标图像宽度(像素) :param height: 目标图像高度(像素) :param font_path: 字体文件路径,None则使用默认字体 """ self.width = width self.height = height # 创建一张白底图像(墨水屏1表示白色,0表示黑色) self.image = Image.new('1', (width, height), 255) self.draw = ImageDraw.Draw(self.image) # 加载字体,如果找不到指定字体,回退到默认字体 try: self.font_title = ImageFont.truetype(font_path or '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 20) self.font_source = ImageFont.truetype(font_path or '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 14) self.font_summary = ImageFont.truetype(font_path or '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 16) except IOError: # 如果指定字体不存在,使用PIL的默认字体(可能像素化) logger.warning("指定字体未找到,使用默认字体。显示效果可能不佳。") self.font_title = ImageFont.load_default() self.font_source = ImageFont.load_default() self.font_summary = ImageFont.load_default() # 定义边距和行间距 self.margin = 20 self.line_spacing_title = 8 self.line_spacing_text = 6 self.current_y = self.margin # 当前绘制位置的Y坐标 def render_entries(self, entries, max_entries=10): """渲染条目列表到图像""" self.current_y = self.margin for i, entry in enumerate(entries[:max_entries]): if self.current_y > self.height - 50: # 预留底部空间 logger.warning("屏幕空间不足,未显示所有条目。") break self._render_single_entry(entry, i+1) # 条目之间画一条分隔线 self.draw.line([(self.margin, self.current_y), (self.width - self.margin, self.current_y)], fill=0, width=1) self.current_y += 10 def _render_single_entry(self, entry, index): """渲染单个新闻条目""" # 渲染序号和来源 source_text = f"{index}. [{entry['source']}]" self.draw.text((self.margin, self.current_y), source_text, font=self.font_source, fill=0) self.current_y += self.font_source.getbbox(source_text)[3] + self.line_spacing_title # 渲染标题(自动换行) title_lines = textwrap.wrap(entry['title'], width=40) # 每行大约40个字符 for line in title_lines: if self.current_y > self.height - 80: return self.draw.text((self.margin, self.current_y), line, font=self.font_title, fill=0) self.current_y += self.font_title.getbbox(line)[3] + self.line_spacing_title # 渲染摘要(自动换行) summary_lines = textwrap.wrap(entry['summary'], width=45) for line in summary_lines[:3]: # 最多显示3行摘要 if self.current_y > self.height - 50: self.draw.text((self.margin, self.current_y), "...", font=self.font_summary, fill=0) self.current_y += self.font_summary.getbbox("...")[3] break self.draw.text((self.margin + 10, self.current_y), f" {line}", font=self.font_summary, fill=0) self.current_y += self.font_summary.getbbox(line)[3] + self.line_spacing_text self.current_y += 15 # 条目底部留白 def get_image(self): """获取渲染好的图像对象""" return self.image def save_image(self, filepath): """将图像保存到文件(用于调试)""" self.image.save(filepath) logger.info(f"图像已保存至: {filepath}")关键点解释:
Image.new('1', ...)创建一张1位(黑白)图像,这是大多数黑白墨水屏所需的格式。textwrap.wrap是自动换行的关键,它根据给定的宽度将长文本分割成多行。- 字体路径 (
font_path) 需要根据你的系统环境调整。在树莓派上,可以安装fonts-dejavu-core包来获得高质量的免费字体:sudo apt install fonts-dejavu-core。 - 我们通过
current_y变量跟踪当前的绘制高度,并在绘制每个元素后更新它,以此实现流式布局。当current_y超过屏幕高度时,停止绘制。
3.3 编写墨水屏驱动与主程序
最后,创建主程序main.py,它将整合前两个模块,并调用屏幕驱动库进行显示。这里以微雪 4.2 英寸三色屏为例。
首先,根据屏幕型号安装驱动库。通常可以通过 pip 安装,或从厂商 GitHub 仓库克隆。
# 示例:安装微雪通用墨水屏驱动库(具体库名请查阅屏幕文档) pip install waveshare-epd然后编写main.py:
import logging import sys import os from rss_fetcher import RSSFetcher from renderer import NewspaperRenderer # 导入具体的屏幕驱动,这里以 epd4in2b_V2 为例 from waveshare_epd import epd4in2b_V2 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def main(): # 1. 定义你的 RSS 源列表 rss_feeds = [ 'https://rsshub.app/zhihu/daily', # 知乎日报 'https://rsshub.app/solidot/linux', # Solidot Linux 新闻 'https://rsshub.app/guokr/scientific', # 果壳网科学人 # 添加更多你喜欢的 RSS 源... # 注意:RSSHub 是一个开源项目,某些路由可能需要自建实例或注意使用限制。 ] # 2. 抓取并处理 RSS 内容 logger.info("开始抓取 RSS 内容...") fetcher = RSSFetcher(rss_feeds, max_items_per_feed=4, hours_old=24) entries = fetcher.fetch_all_feeds() if not entries: logger.warning("未获取到任何新的 RSS 条目。") # 可以选择显示一条提示信息,或者直接退出 # return logger.info(f"共获取到 {len(entries)} 条新闻。") # 3. 初始化墨水屏 try: epd = epd4in2b_V2.EPD() logger.info("初始化墨水屏...") epd.init() # 获取屏幕的宽高(像素) screen_width = epd.width # 通常为 400 screen_height = epd.height # 通常为 300 logger.info(f"屏幕尺寸: {screen_width}x{screen_height}") except Exception as e: logger.error(f"初始化墨水屏失败: {e}") # 如果不在树莓派上或屏幕未连接,可以模拟运行,将图像保存为文件 logger.info("将在模拟模式下运行,图像将保存为本地文件。") screen_width, screen_height = 400, 300 epd = None # 4. 渲染内容到图像 # 注意:三色屏需要黑白两个图像层,这里先渲染到黑白层(黑色文字) renderer = NewspaperRenderer(screen_width, screen_height) renderer.render_entries(entries, max_entries=8) # 最多显示8条 image_black = renderer.get_image() # 这是黑色层(1位) # 如果需要红色高亮(例如标题),可以创建另一个红色层图像 # 这里简单地将第一个条目标题用红色标记(假设红色层0表示红色,255表示不显示红色) image_red = Image.new('1', (screen_width, screen_height), 255) draw_red = ImageDraw.Draw(image_red) # ... 在 image_red 上绘制红色内容 ... # 5. 显示到屏幕或保存图像 if epd: try: logger.info("正在刷新屏幕...(此过程较慢,请勿断电)") # 对于三色屏,使用 display 方法传入黑白和红色两个图像 epd.display(epd.getbuffer(image_black), epd.getbuffer(image_red)) # 对于黑白屏,可能使用 epd.display(epd.getbuffer(image_black)) logger.info("屏幕刷新完成。") # 进入睡眠模式以省电 epd.sleep() except Exception as e: logger.error(f"刷新屏幕时出错: {e}") finally: # 确保程序退出前清理 GPIO(如果驱动库提供了该方法) epd.Dev_exit() else: # 模拟模式:保存图像用于调试 renderer.save_image('newspaper_output.png') logger.info("模拟模式运行完毕,图像已保存为 'newspaper_output.png'。") if __name__ == '__main__': main()关键点解释:
- 驱动库的导入和初始化 (
epd4in2b_V2.EPD()) 是型号相关的,务必根据你购买的屏幕型号修改。 epd.init()初始化屏幕,epd.sleep()让屏幕进入低功耗状态,这是延长屏幕寿命的关键。epd.display()是刷新屏幕的函数,这个过程通常需要几秒钟,期间切勿断电。- 我们在
try...except块中初始化屏幕,这样当脚本在没有连接屏幕的电脑上运行时(例如在开发机上测试),可以自动降级到“模拟模式”,将生成的图像保存为 PNG 文件,便于调试布局。
4. 系统集成与自动化运行
代码完成后,我们需要让它在树莓派上定时自动运行。
4.1 在树莓派上部署
- 将代码复制到树莓派:可以通过 Git、SCP 或直接编辑。
- 安装依赖:在树莓派上,进入项目目录,激活虚拟环境(如果使用),然后安装所需包。
cd /path/to/eink_rss_newspaper source venv/bin/activate pip install feedparser Pillow # 安装你的墨水屏驱动库,例如: # pip install waveshare-epd # 或者从厂家提供的源码安装 # git clone https://github.com/waveshare/e-Paper.git # cd e-Paper/RaspberryPi_JetsonNano/python # pip install . - 测试运行:首次运行,建议先不连接屏幕,或在
main.py中暂时注释掉屏幕初始化部分,用模拟模式测试 RSS 抓取和图像生成是否正常。
检查生成的python main.pynewspaper_output.png是否符合预期。
4.2 配置定时任务(Cron)
我们希望系统每天在固定时间(例如早上8点)自动更新“报纸”。
编辑当前用户的 crontab 文件:
crontab -e在文件末尾添加一行。假设你的脚本位于
/home/pi/eink_rss_newspaper/main.py,并且使用虚拟环境中的 Python。# 每天上午8点运行一次 0 8 * * * cd /home/pi/eink_rss_newspaper && /home/pi/eink_rss_newspaper/venv/bin/python /home/pi/eink_rss_newspaper/main.py >> /home/pi/eink_rss_newspaper/cron.log 2>&10 8 * * *表示在每天8点0分执行。cd ...确保脚本在正确的目录下运行。/home/pi/.../venv/bin/python指定使用虚拟环境中的 Python 解释器。>> .../cron.log 2>&1将脚本的标准输出和错误输出都重定向到日志文件,便于排查问题。
保存并退出。Cron 会自动加载新配置。
现在,你的“电子墨水报”系统就部署完成了。树莓派会在每天指定时间自动抓取新闻、生成版面并刷新到墨水屏上。你可以随时拿起它,像阅读一份真正的报纸一样浏览摘要,而无需打开手机或电脑。
5. 常见问题排查与优化
在搭建和运行过程中,你可能会遇到以下问题。
5.1 RSS 抓取失败或内容为空
| 问题现象 | 可能原因 | 检查与解决 |
|---|---|---|
feedparser解析出错 (bozo异常) | 1. RSS 源地址错误或失效。 2. 网络连接问题。 3. RSS 源格式不规范。 | 1. 用浏览器直接访问 RSS 地址,确认能打开且是 XML 格式。 2. 在树莓派上使用 curl -I <rss_url>检查网络可达性。3. 尝试更换其他 RSS 源测试。 |
能解析但entries为空 | 1. RSS 源本身没有内容。 2. 发布时间过滤太严格 ( hours_old参数太小)。 | 1. 打印feed.feed查看源信息,确认feed.entries长度。2. 暂时调大或取消 hours_old过滤,看是否能获取到条目。 |
| 摘要包含乱码或 HTML 标签 | RSS 源摘要字段本身包含 HTML。 | 确保_clean_summary函数被正确调用,它移除了 HTML 标签。可以临时打印清理前后的摘要进行对比。 |
5.2 屏幕初始化或显示异常
| 问题现象 | 可能原因 | 检查与解决 |
|---|---|---|
导入waveshare_epd失败,提示ModuleNotFoundError | 1. 驱动库未安装。 2. 安装的驱动库版本与屏幕型号不匹配。 3. 在虚拟环境中运行,但驱动库安装在系统 Python 下。 | 1. 确认已正确安装驱动库 (pip list | grep waveshare)。2. 从屏幕供应商官网或 GitHub 获取对应型号的驱动。 3. 在项目虚拟环境中重新安装驱动库。 |
初始化epd.init()失败,提示 GPIO 或 SPI 错误 | 1. 硬件连接错误或接触不良。 2. 树莓派 SPI 接口未启用。 3. 用户权限不足(操作 GPIO 需要 root或gpio组权限)。 | 1. 对照引脚图仔细检查接线。 2. 运行 sudo raspi-config,在Interface Options中启用 SPI。3. 将当前用户加入 gpio组:sudo usermod -a -G gpio $USER,并重启生效。或者使用sudo运行脚本(不推荐长期使用)。 |
| 屏幕刷新后全白、全黑或残留鬼影 | 1. 图像数据格式错误(如不是1位深度)。 2. 刷新流程不完整。 3. 屏幕需要全刷清除鬼影。 | 1. 确认Image.new('1', ...)创建的是1位图像。2. 确保遵循驱动库示例的完整流程: init()->display()->sleep()。3. 部分屏幕驱动提供 Clear()函数,可在每次显示前先清屏。或定期(如每24小时)进行一次全刷(epd.init()后调用epd.Clear())。 |
| 刷新过程极慢(超过30秒) | 这是电子墨水屏的正常物理特性。 | 无法改变。请确保刷新期间系统稳定,不要断电或中断程序。 |
5.3 定时任务不执行
| 问题现象 | 可能原因 | 检查与解决 |
|---|---|---|
| Cron 任务到了时间没反应 | 1. Cron 语法错误。 2. 命令中的路径错误。 3. 环境变量问题(Cron 的执行环境与 Shell 不同)。 | 1. 使用crontab -l检查任务列表。2. 查看日志文件 /home/pi/eink_rss_newspaper/cron.log,里面通常有详细的错误信息。3. 在 Cron 命令中,使用绝对路径,并可在脚本开头手动设置环境变量,如 PYTHONPATH。 |
日志文件显示ImportError | Cron 环境找不到虚拟环境中的包。 | 在 Cron 命令中,务必使用虚拟环境 Python 的绝对路径(如/home/pi/eink_rss_newspaper/venv/bin/python)。 |
5.4 内容布局与显示优化
- 文字显示不全或重叠:调整
renderer.py中的margin(边距)、textwrap.wrap的width参数(每行字符数)以及字体大小。 - 字体难看或显示为方框:确保树莓派上安装了中文字体(如果需要显示中文)。可以安装
fonts-wqy-zenhei:sudo apt install fonts-wqy-zenhei,并在代码中指定该字体路径。 - 想显示图片:部分高级墨水屏支持显示灰度图片。可以使用
Pillow处理网络图片并缩放、转换为灰度、再二值化,然后整合到版面中。但这会显著增加代码复杂度和刷新时间。 - 想增加更多样式:可以修改
renderer.py,为不同来源、不同重要性的新闻使用不同的字体、字号或(在三色屏上)颜色。
6. 生产环境建议与扩展方向
将这个小项目用于日常使用,还需要考虑一些工程化问题。
6.1 稳定性与健壮性增强
- 异常处理与日志:目前的脚本已有基础日志。在生产中,应考虑将日志按日期滚动存储,并监控日志文件大小。对于网络请求,增加重试机制和超时设置。
- 资源管理:确保脚本退出前正确清理资源(如 GPIO 状态)。使用
try...finally块保证epd.sleep()和epd.Dev_exit()被执行。 - 电源管理:如果使用电池供电,需要优化树莓派的功耗(禁用 HDMI、USB 等),或考虑使用更省电的 ESP32 等 MCU。可以为系统增加一个物理按钮,手动触发刷新,而不是一直定时刷新。
- 配置外置化:将 RSS 源列表、屏幕型号、刷新时间等配置项提取到单独的
config.ini或config.yaml文件中,方便修改而无需改动代码。
6.2 功能扩展
- 多版面与翻页:如果新闻条目很多,可以设计多页显示,并通过按钮(如 GPIO 连接的按键)进行翻页。
- 内容源扩展:除了 RSS,可以集成其他 API,如天气预报 API、日历事件、待办事项列表等,打造一个个性化的信息看板。
- 离线缓存:将抓取到的内容序列化(如用
pickle或json)保存到本地。这样即使网络暂时中断,屏幕也能显示上一次成功抓取的内容。 - Web 控制面板:在树莓派上运行一个简单的 Flask 或 FastAPI 服务,提供一个网页界面,用于动态添加/删除 RSS 源、立即触发刷新、查看抓取状态等。
- 使用更高效的框架:如果功能变得复杂,可以考虑使用像
Rich这样的库来构建更复杂的终端界面逻辑,或者使用专门的 GUI 框架来设计排版。
6.3 硬件与部署优化
- 外壳与供电:为树莓派和墨水屏设计或购买一个合适的外壳,使其看起来更像一个摆件。考虑使用移动电源或电池组供电,实现完全无线摆放。
- 无线更新:通过 Git 管理代码,并设置开机自启的脚本,方便远程更新代码和配置。
- 屏幕保护:电子墨水屏长时间显示静态图像可能导致“烧屏”(残影)。虽然比 LCD 轻微,但最好能定期(如每周)进行一次全白全黑的刷新循环,或者在不显示内容时让屏幕进入深度睡眠并完全清屏。
通过以上步骤,你不仅得到了一个可用的“电子墨水报”,更掌握了一套将网络信息流与物理显示设备结合的方法。这个项目的核心价值在于其可定制性——你可以完全控制信息源、显示内容和更新频率,打造一个真正属于自己、无干扰的信息获取终端。