brainbaking/content/post/2023/02/phomemo-thermal-printing-on...

5.7 KiB

title date tags categories
Phomemo Thermal Printing On MacOS 2023-02-03T10:00:00+01:00
printer
software

My wife bought another set of mini printers for scrapbooking, including the Phomemo M02 mini printer. Phomemo is a Chinese brand I've never heard of before, and sadly, as I expected, it requires the use of a proprietary mobile app in order to send something to the printer. That very much stinks---and I've complained about this before in 2021 when I replaced my mini printer with a HP Sprocket one. I just don't get it why a simple "share via Bluetooth" functionality can't be implemented. It's just a few lines of extra code in that hardware. But no.

Anyway, I thought I'd put on my hacking hat and try to see if I could come up with some code that would be able to send stuff to the thermal printer, without the usage of their app. A quick search on the good ol' web taught me vivier at GitHub already wrote a set of Phomemo tools that connects it to a CUPS printer server on Linux using a few Python filters---great! But does it work on macOS? Drivers being hardware-dependent, the answer is a probable no. After an hour of trying, the answer was a definite no.

The problem is twofold:

  1. vivier's scripts use Python's deprecated Pybluez, that has been forked a couple of times, but ultimately, doesn't work on macOS (at least not on my M1 with Ventura 13.2). Mac's Bluetooth drivers, CoreBluetooth, aren't properly supported. Great.
  2. The part where the script is connected to a virtual printer driver doesn't work on macOS. Granted, Ventura also runs a CUPS service; but its file/driver locations are different, as is its structure. Great.

The first problem was fixed by turning to Go and utilizing TinyGo's Bluetooth modules that sit on top of CoreBluetooth1 and is also compatible with Linux, Windows, and bare metal microcontrollers. I had no idea how Bluetooth---or a printer driver---works, and I still barely do, but "it just works". adapter.Scan() found our Phomemo M02, connecting to the MAC works, discovering services works (there's only one), and finding characteristics works (0000ff01-x for reading results, 0000ff02-x for writing, and 0000ff03-x for... no idea!).

The source code is available at my Git repository called phomemo-printer. It's very hacky and works for our specific situation but could do with a week of fine-tuning---which I don't have. For the second problem, I've had to cut many corners. For instance; I wanted my wife to be able to use it as a "printer" to select in a generic print dialog instead of running a CLI script. The options were:

  • Try to get it to integrate with Mac's version of CUPS. My knowledge and time is just too limited to do that.
  • Create a virtual printer driver using for example IPP or the Internet Printing Protocol. That involves implementing a lot of HTTP endpoints and would probably work but the effort is just too high. I only found existing IPP clients, not servers (one in Java, though).
  • In macOS, you can modify the drop-down menu of the "PDF" print button; these are called "PDF Services" and are apps/scripts/folders located in ~/Library/PDF\ Services. I read about it on John M. Simpson's blog, but of course, that doesn't work anymore in modern macOS versions, because printtool runs in a sandbox and has access to pretty much nothing. I gave up after two hours of debugging and digging through Mac system logs. I couldn't get past:
[kernel] Sandbox: zsh(34634) deny(1) file-read-data /Users/user/.zshenv
[printtool] Workflow (344546) stopped with status 1

Using Automator to wrap the script in a "native" workflow or app folder didn't work, granting printtool disc access didn't work, adding Sandboxing Relaxed to a mysterious cupsd config file didn't work.

I settled with a sup-par but working solution: drop images in ~/Downloads/phomemo, and have a cron job---I'm sorry, a pretty XML launchd job---pick up, print, then delete these files every minute. I'm annoyed I have to settle with this, but it's been a constant struggle between putting in enough time and going (too) deep trying to understand how every moving part works. It turned out to be surprisingly complex.

The most important part was already done by vivier. You can't simply send a JPG/PNG byte stream to the printer: it expects EPSON ESC/POS commands, and thus a header, a footer, and every block of 255 bytes, a block marker in 16-bit little-endian. Images can't be more than 384 points per line, so scaling and transforming is needed as well, as is grayscale conversion. I currently simply run the Python script from within my Go script; but of course it should have been ported; yet the "batteries included" part of Python's image processing library PIL is just too difficult to quickly do that.

Still, at least something rolls out of the printer with the help of some code I wrote:

Yay! Feel free to improve/steal/hack away.


  1. I later learned that, in Python, through PyObjC, you can also call CoreBluetooth directly. I would probably have preferred to keep everything in Python. ↩︎