Skip to content

Fix dual-mode printers (SC03h) and add desktop and web apps - #90

Open
GabrielCatDev wants to merge 8 commits into
rbaron:mainfrom
TetradogOther:main
Open

GabrielCatDev wants to merge 8 commits into
rbaron:mainfrom
TetradogOther:main

Conversation

@GabrielCatDev

Copy link
Copy Markdown

Hi! I have an SC03h printer and this project didn't work with it on Debian 13. This PR fixes what was going wrong and adds two optional apps on top of the library.

What was wrong

  • It couldn't connect. The SC03h offers both classic Bluetooth and BLE. BlueZ picked classic Bluetooth and failed with br-connection-profile-unavailable. catprinter now asks BlueZ to use BLE (needs Experimental = true in /etc/bluetooth/main.conf on BlueZ ≥ 5.80) and retries if BlueZ picks classic Bluetooth anyway.
  • Prints came out light or blank. I compared the commands with the official iPrint app (1.3.87) and found a few differences:
    • Energy was sent big-endian; iPrint sends it little-endian. Only symmetric values like the default 0xffff worked.
    • The print speed was never set. cmd_feed_paper() actually sent the speed command (0xbd), not a paper feed. It's now cmd_set_speed() and produces the same bytes.
    • The "update device" command (0xa9) that applies energy and speed was missing.
    • Data went out in 509-byte packets (BlueZ negotiated a large MTU), which can overflow the printer. Packets are now capped at 180 bytes like iPrint, and the printer's pause/resume notifications are respected.
  • Every print ended with a 30 s timeout. The SC03h never sends the "ready" notification, so the answer to the final state query is now used as the "done" signal.
  • It wouldn't install on Python 3.13: numpy<2.0 has no wheels for it.

What's new

  • --speed option in print.py.
  • Battery level and printer state (no paper, cover open, overheating, low battery, charging). A low battery turned out to be one cause of blank prints.
  • PrinterConnection, so apps can keep a connection open between prints.
  • An optional GTK 4 desktop app packaged as a ~1 MB AppImage (appimage/build.sh). Its UI is in Spanish.
  • An optional web app in docs/ that prints from Chrome/Edge over Web Bluetooth. Its command builder produces byte-identical output to the Python code.

The README documents all of this.

Things to know

  • Code that passed a custom, non-symmetric energy will now send different bytes, since the byte order changed to match iPrint.
  • I've only tested with an SC03h. Printing from print.py works on it. I couldn't test the web app on real hardware, and the desktop app's persistent connection only briefly.
  • The two flake8 warnings left (ble.py E203, img.py E302) were already there.

🤖 Generated with Claude Code

GabrielCatDev and others added 8 commits October 15, 2026 05:39
Bug: requirements pinned numpy<2.0, which has no wheels for Python 3.13
(Debian 13). pip had to build it from source and failed, so the CLI could
not be installed. The code works with numpy 2, which opencv-python 4.x
supports.

Changes made by Claude (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Compared against iPrint 1.3.87, which is what these printers are made for:

- Energy was sent big-endian; iPrint sends it little-endian (5000 ->
  88 13). Only symmetric values such as the 0xffff default worked.
- The print speed (0xbd) was never set before printing, so the printer
  kept whatever speed it had. Add cmd_set_speed() and a speed argument
  (re-sent every 200 rows, as iPrint does), plus --speed in print.py.
- cmd_feed_paper() did not feed paper: it sent 0xbd, the speed command.
  Replace it with cmd_set_speed(FEED_SPEED), which produces the same bytes.
- Send "update device" (0xa9) so the printer applies energy and speed.
- Select image mode (0xbe 00) or text mode (0xbe 01) instead of always
  using text mode through the misnamed cmd_apply_energy().
- Add the battery query command (0xba).

Changes made by Claude (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugs found with an SC03h on Debian 13 (BlueZ 5.82):

- The SC03h is dual-mode (classic SPP + BLE). BlueZ tried a classic
  connection and failed with "br-connection-profile-unavailable". Set the
  device's PreferredBearer to "le" before connecting (needs
  "Experimental = true" in /etc/bluetooth/main.conf) and retry a couple of
  times if BlueZ still picks the classic bearer.
- BlueZ negotiated an MTU of 512 and the whole job was written in ~1 s
  using 509-byte packets, which can overflow the printer's buffer and
  give blank prints. Cap packets at 180 bytes like iPrint.
- The flow control notifications (pause/resume) were ignored. Pause
  sending while the printer reports that its buffer is full.
- The SC03h never sends the "ready" notification, so every print ended
  with a 30 s timeout. Use the answer to the state query that ends every
  job as the "done" signal instead.

New features:

- PrinterConnection keeps a connection open between prints and reports
  disconnections, so apps can stay connected like the phone app does.
- Parse the printer state (no paper, cover open, overheating, low battery,
  charging) and the battery level into PrinterStatus.

Changes made by Claude (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A libadwaita app (Spanish UI) that:

- Finds the printer, connects and stays connected like the phone app,
  reconnecting on its own if the link drops.
- Shows the battery level and warns about low battery, no paper, open
  cover or overheating (the SC03h prints blank pages with low battery).
- Prints images (SVG too), PDFs (pdftoppm), office documents (LibreOffice)
  and text files, with an exact preview of the dots that will be printed.
- Offers photo dithering or sharp text, brightness, contrast, margin
  cropping, rotation, thicker strokes and a page range.
- Uses the official app's concentration levels and print speed.
- Can integrate itself with the desktop and the context menu of Files.

Image processing is pure Python plus GdkPixbuf so the app runs on the
system Python without numpy or OpenCV.

Changes made by Claude (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
appimage/build.sh produces dist/CatPrinter-<version>-x86_64.AppImage
(about 1 MB). It bundles the app and its pure Python dependencies (bleak
0.14, dbus-next) and runs on the system's Python 3 and GTK 4, so it keeps
working when the distribution upgrades Python. AppRun explains which
Debian packages to install if GTK 4 or libadwaita are missing.

Running the AppImage with --instalar (or using the app menu) copies it to
~/.local/bin and adds it to the application menu, to "Open With" and to
the context menu of Files.

Changes made by Claude (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Explain how to build and install the AppImage, the new --speed option, how
to fix "br-connection-profile-unavailable" on dual-mode printers such as
the SC03h, and that blank prints usually mean a low battery.

Changes made by Claude (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/ is a static web app, ready for GitHub Pages:

- js/protocol.js ports catprinter/cmds.py and status.py; its output is
  byte-for-byte identical to the Python version.
- js/printer.js connects over Web Bluetooth with the same behavior as
  PrinterConnection: 180-byte packets, flow control, the state answer as
  the "done" signal, battery and state flags, and automatic reconnection.
- js/imaging.js ports the GUI's processing: images, PDFs (pdf.js) and
  text, margin cropping, rotation, dithering and thicker strokes.
- The PWA works offline, can be installed, receives files from the
  Android share sheet and handles "Open With" when installed.
- Browsers without Web Bluetooth (Firefox, Safari, iOS, Chrome on Linux
  without the flag) get a page explaining the alternatives.

Changes made by Claude (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugs:

- The service worker cached every same-origin GET response, not only the
  app's own files, so other requests ended up stored on disk.
- Files shared from other apps were written to a cache until the page read
  them; if the page never read them, they stayed there.

Now only the app shell (and the versioned pdf.js library) is cached.
Shared files are kept in the service worker's memory and handed to the
page with postMessage. The old shared-files cache is deleted on update.
Files opened, dropped or pasted were never stored and still aren't.

Changes made by Claude (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@GabrielCatDev

Copy link
Copy Markdown
Author
  • I've only tested with an SC03h. Printing works from print.py, and the web app works on Chrome on Linux and on Android. The desktop app's persistent connection has only had brief testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant