如何制造打印机
How to build a printer

原始链接: https://nishantjosh.dev/blogs/how-to-build-a-fking-printer/

受 Xteink X3 电子墨水阅读器可编程特性的启发,作者着手将该设备改造为一台功能性的网络打印机。他们不再使用繁琐的网页上传方式,而是希望在 MacBook 上实现原生的“打印”体验。 该项目涉及在 X3 上使用 ESP-IDF 的 mDNS API 实现互联网打印协议(IPP),从而在 macOS 上实现免驱动发现。一个重大的技术障碍是该设备仅有 400 KB 的内存,无法容纳整页图像缓冲区。作者通过创建一个流式转换流水线绕过了这一限制:传入的数据被逐行解码、缩放和抖动处理,并直接写入显示内存,而不是在内存中拼合完整的图像。 这一优化成功降低了内存占用,使网络协议栈能够与打印服务同时运行。最终实现了一个无缝的工作流程:该阅读器在 Mac 上显示为一台标准打印机(“penguin”)。文档可以即时打印,完成后的页面会保存到设备的 SD 卡中,作为数字“出纸盒”,从而有效地将这款电子墨水阅读器变成了一台像纸张一样的打印机。

最近的一场 Hacker News 讨论介绍了一个创新项目:将电子墨水屏设备(Xteink)改造为网络打印机。通过利用 IPP 和 AirPrint 等标准的“无驱动”打印协议,开发者绕过了手动将文件上传至设备内置 Web 服务器的繁琐过程。 社区对这种巧妙利用通用操作系统标准的方法表示赞赏,因为它允许用户直接从任何应用程序“打印”内容到电子墨水屏上。虽然有人质疑为何该设备不直接渲染 PDF,但作者解释称,受限于硬件的内存和 CPU 性能,完整的 PDF 渲染并不现实。因此,该系统采用轻量级打印服务器的方式运行,通过接收栅格化数据来实现功能。 在技术层面,讨论帖深入探讨了打印协议栈的“噩梦”现状,专家们提出了诸如使用“每像素 1 位”(1-bit-per-pixel)模式来节省内存并提升性能等优化建议。用户认为该项目非常实用,例如在厨房查看食谱时无需使用手机;许多人将这一概念比作《星际迷航》中的便携式 PADD 设备,并对这种转向开放、硬件无关的工作流程表示欢迎。
相关文章

原文

I was planning to build an e-ink display when I came across the Xteink X3 online. Somewhere in the fine print, it said the thing was fully programmable.

I was sold.

When it arrived, I liked how flat it was. It felt solid in my hand. I started adding things to the CrossPoint firmware: a different boot animation, dice I could roll by shaking the reader, a LinkedIn QR code for SF networking events.

But getting stuff onto it was tedious. I had to join its hotspot and open a little upload website in my browser.

Yuck.

While looking for a nicer way to send things to it, a thought struck me. If it looks like paper, it should act like paper.

I should be able to print on it.

The Xteink X3 displaying a black-and-white drawing of a printer.

What makes a printer a printer?

I wanted to open something on my MacBook, press Print, and pick the Xteink. That meant finding out what my computer expected to find at the other end.

I ended up in the Internet Printing Protocol, or IPP. It lets a computer ask a printer what it supports, submit a document, and ask what happened to the job. The messages travel over HTTP. An operation such as Get-Printer-Attributes asks about capabilities; Print-Job sends the work.

I advertised monochrome output, 300 dpi, one copy, and one-sided printing. For document formats, I accepted Apple raster and PWG raster. That meant the Mac had to turn the document into pixels before sending it. penguin would shrink the result to fit its screen.

I declared A5 and Letter paper, media type stationery, and an output bin called face-up.

I called it penguin. It had the right color scheme.

I used Bonjour to announce an _ipp._tcp service under that name. The advertisement included the formats I accepted and the address where print jobs should go.

To get driverless discovery on macOS, I also had to add the _universal subtype. That meant calling ESP-IDF’s mDNS API directly, because the Arduino wrapper didn’t expose it.

Getting the computer to send a page was only part of the job. I still had to receive it on this thing.

Where do I put the page?

A Letter page at 300 dpi is 2,550 × 3,300 pixels. At one byte per grayscale pixel, that’s about 8.4 MB uncompressed.

The X3 has 400 KB of RAM, with 16 KB reserved for cache. I needed to run Wi-Fi, run a printer server, and somehow receive an entire fucking page.

With Wi-Fi running and the printer’s page image allocated, I had 6.8 KB of heap left.

I remembered mmap on Linux. Could I do something like that with the SD card and pretend I had more RAM? The C3’s memory-mapping support was for flash, not files on the SD card.

But wait. Could I make the display my storage?

What if I passed the incoming page through a transformation pipeline and wrote the result straight to the display? Decode the pixels, shrink them to fit, dither them into black and white. As soon as a row was ready, put it in its place on the display and reuse the working space. Keep going until I have a page.

The display already had RAM reserved for its screen image. I could build the page right there as it arrived. Until then, I had been assembling a whole second image just to copy it over.

My decoder already worked row by row. I changed the scaler to hand over finished rows too and wired those into the display’s screen image. At first, I let the page appear in bands, like paper feeding out of a printer. Each intermediate refresh took roughly half a second, so I switched to showing the finished page all at once.

I saved the finished page as a BMP on the SD card using the existing screenshot writer.

Image-buffer RAM Before After
Out of the chip’s 400 KB ~113 KB ~62 KB

That gave the network stack room for its socket buffers.

There was a penguin in Preview

I had a sample manga image from Mushoku Tensei on my MacBook for some reason. I opened it in Preview and went to print it.

There was penguin in the printer list.

Holy shit.

I selected it and printed. The manga page looked really good on the Xteink. From memory, it took about a second to appear. It’s still there.

A manga page printed on the Xteink X3, resting on a closed MacBook.

I spent an evening getting to that first print.

The printer server runs on the reader itself. I can have it join a Wi-Fi network or start its own hotspot, literate-penguin.

My penguin can read now.

The code is in my CrossPoint fork, including the printer implementation.

Saved printouts stay on the SD card, and I can browse them on the reader. My printer has an output tray after all. It’s a folder.

联系我们 contact @ memedata.com