使用 Lisp 和 M5Stack 构建以隐私为先的数字相框

在大多数消费电子产品都需要云订阅并交出个人数据的时代,寻找一种简单、私密的方式来分享家庭记忆的需求竟然如此罕见。商业数字相框往往价格高昂,并且必须使用账户,这会导致私人照片泄露到互联网或被用于训练 AI 模型的风险。

为了解决这些问题,开发者 chrisjj 使用 M5Stack Tab5 和 uLisp 构建了一个自定义数字相框。通过将逻辑迁移到基于 Lisp 的固件,并将图像托管在私人服务器上,项目在高分辨率显示和完整数据主权之间取得了平衡。

硬件与显示规格

项目核心是 M5Stack Tab5,其配备 1280 x 720 的高分辨率显示屏。该屏幕提供 16:9 的宽屏比例和出色的色彩还原,使其成为昂贵商业相框的可行替代方案。

为了处理图像,作者为 uLisp 使用了自定义的 GIF 解码/编码扩展。由于硬件的内存和处理能力有限,照片被格式化为 256 色 GIF,并使用 75% 的扩散抖动。此过程通常通过 Photoshop 或在线转换工具完成,可将普通照片的大小保持在约 500KB 左右,同时保持几乎与原图无差别的视觉质量。

系统架构

相框作为网络客户端运行,从远程网站获取图像列表并循环播放。该架构使所有者能够远程更新相册,并向朋友和家人授予上传权限,而无需复杂的应用生态系统。

后端

图像通过位于 www.wackyimage.com 的简易 HTTP 内容管理系统(CMS)提供。该站点包含一个受保护的页面,列出图像标签:

<body>
<p>Insert photos here! GIF format, image size 1280 x 720.</p>
<p><img src="/pictures/k58/sleepycat.gif" alt="SleepyCat.gif" /></p>
<p><img src="/pictures/k58/wedding.gif" alt="Wedding.gif" /></p>
</body>

Lisp 实现

固件使用 uLisp 编写,遵循线性执行路径:连接、收集、显示。

1. 网络连接

设备使用 wifi-connect 函数初始化本地 Wi‑Fi 连接,随后延迟 5 秒以确保握手完成:

(wifi-connect "Mynetwork" "mypassword")
(delay 5000)

2. 获取图片列表

collect-pics-list 函数负责 HTTP 请求。它使用登录 Cookie 进行安全验证,并通过辅助函数 skip-headers 跳过 HTTP 响应头部,直接进入 HTML 正文。

(defun collect-pics-list nil
  (with-client (s "www.wackyimage.com" 80)
    (format s "GET /show?K5A HTTP/1.0~a~%" #\Return)
    (format s "Host: www.wackyimage.com~a~%" #\Return)
    (format s "Cookie: LOGIN=WI1NDlkNmE4YzBmZmE4MTQyYzNjODk2YzRkZDEwMiIp~a~%" #\Return)
    (format s "Connection: close~a~%" #\Return)
    (format s "~a~%" #\Return)
    (skip-headers s)
    (collect-pics s))))

3. 解析与显示

collect-pics 函数扫描 HTML 中的特定目录模式(/k58/),提取文件名。列表生成后,display-pic 获取对应的 GIF 文件并将数据流传递给 decode-gif 函数,在 M5Stack 屏幕上渲染。

主执行循环

整个过程被封装在 photo-frame 函数中,用于管理轮播逻辑。它记录可用图片数量,以防止访问不存在的索引,并使用可配置的 *minutes* 变量控制切换速度。

(defun photo-frame ()
  (wifi-connect "Mynetwork" "mypassword")
  (delay 5000)
  (let ((nextpic 0) (lastpics 0))
    (loop
     (let* ((pics (collect-pics-list))
            (npics (length pics)))
       (when (> npics lastpics)
         (setq nextpic lastpics)
         (setq lastpics npics))
       (display-pic (nth (mod nextpic npics) pics)))
     (incf nextpic)
     (delay (* 1000 60 *minutes*)))))

为了确保设备能够作为独立的家电运行,作者通过 (save-image 'photo-frame) 使用 uLisp 的 resetautorun 特性,使得上电后自动触发循环。

结论

通过将 Lisp 的灵活性与价格亲民的 M5Stack 硬件相结合,本项目展示了无需依赖侵入式云服务即可构建功能齐全、高分辨率的家用设备。它将一个普通的微控制器转变为可动态远程更新的相册,同时对个人数据保持严格控制。

Sources