yourovo/mac
type-two b014205a5b macOS: raw-TSPL Urovo daemon — no vendor SDK needed
ASSESSMENT.md §7 assumed the RFID encode was locked inside the Windows-only
GTSPL_SDK.dll and would need a USBPcap sniff or the GTSPL Java jar to recover.
It didn't. The DLL is a managed .NET assembly, and its IL shows every RFID call
is a one-line String.Concat -> ASCII -> WritePrinter. Recovered from ldstr order
plus argument order, and confirmed by a second independent decompile:

    UHF WRITE H,2,12,E,"<24 hex>"    UHF READ/QUERY ...
    UHF GEN2 EPC|TID|USER|ACCESS|KILL <action>,"<pw>"
    SET RFID <tagType>,<rw_pos>,<void>,<try>,<err>,<speed>,<retry>   (dots, not mm)
    &DEFAULT,i    &CALIBRATE,A,R    PRINT <set>, <copy>

So the whole recipe is plain ASCII over the printer's bulk endpoint and ports to
macOS with no vendor binaries at all.

- mac/urovo.py   USB transport + full GTSPL command set, status decoding, the
                 ribbon-latch recovery, SKU<->EPC packing ported from daemon.cs.
- mac/label.py   PIL bitmap renderer -> TSPL BITMAP. Media-profile driven, so both
                 shop stocks work (small 51x19 and large 55x24 on the 24mm web),
                 with a safe-area inset so the design can't clip at the edges.
- mac/daemon.py  :7790 HTTP service, same routes/JSON/CORS as daemon.cs, plus a
                 /preview route that renders a label as PNG without printing one.

Verified on the bench from macOS: printer identifies (MODEL:UROVO-D812R), the
latched 0x08 "out of ribbon" clears with SET RIBBON OFF + FORMFEED, and labels
print correctly oriented and dark on the 24x55 stock.

NOT yet verified: the RFID encode itself. It is wired and the printer accepts the
command, but the loaded thermal stock has no inlays so nothing has been written to
a chip. Needs a PET RFID label at the antenna. The SKU<->EPC byte layout also
remains unconfirmed against existing Chafon-written tags.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 16:07:12 +10:00
..
daemon.py macOS: raw-TSPL Urovo daemon — no vendor SDK needed 2026-07-22 16:07:12 +10:00
label.py macOS: raw-TSPL Urovo daemon — no vendor SDK needed 2026-07-22 16:07:12 +10:00
README.md macOS: raw-TSPL Urovo daemon — no vendor SDK needed 2026-07-22 16:07:12 +10:00
setup.sh macOS: raw-TSPL Urovo daemon — no vendor SDK needed 2026-07-22 16:07:12 +10:00
start-daemon.command macOS: raw-TSPL Urovo daemon — no vendor SDK needed 2026-07-22 16:07:12 +10:00
urovo.py macOS: raw-TSPL Urovo daemon — no vendor SDK needed 2026-07-22 16:07:12 +10:00

pricegod-urovo-daemon (macOS)

The Mac counterpart to the Windows daemon.cs. Drives the Urovo D812R+ (RFID) over raw USB — printing a label and encoding the UHF chip in one pass — and serves the same localhost:7790 HTTP contract, so it is a drop-in for the PriceGod extension's daemon.js client.

No vendor SDK required. ASSESSMENT.md §7 assumed the RFID encode was locked inside the Windows-only GTSPL_SDK.dll and would need a USB sniff or the Java jar to recover. It doesn't: the DLL is a managed .NET assembly, and its IL shows every RFID call is a one-line String.Concat → ASCII → WritePrinter. The wire format below was recovered from ldstr order + argument order and independently confirmed by a second decompile.

UHF WRITE <fmt>,<start>,<len>,<bank>,"<data>"     # the RFID encode
UHF READ  <fmt>,<start>,<len>,<bank>
UHF QUERY <fmt>,<pcStatus>,<crcStatus>
UHF GEN2 EPC|TID|USER|ACCESS|KILL <action>,"<pw>"
SET RFID  <tagType>,<rw_pos>,<void_printout>,<tryEncode>,<errHandle>,<speed>,<retry>
&DEFAULT,i        # rfidSetupDefault      &CALIBRATE,A,R    # RFIDAutoCalibration
PRINT <set>, <copy>                                # note the space after the comma

All plain ASCII, one command per line, CRLF-terminated. writeUHF("H",2,12,"E",epc) emits exactly UHF WRITE H,2,12,E,"<24 hex chars>".

Setup

./setup.sh          # brew install libusb + venv with pyusb/pillow/segno

Then double-click start-daemon.command (leave it open while you work).

Files

File What
urovo.py USB transport + the full GTSPL command set, status decoding, fault recovery, SKU↔EPC packing. Runs standalone as a connection test.
label.py Renders the label to a 1-bit bitmap with PIL and emits TSPL BITMAP. Run standalone to dump previews to /tmp.
daemon.py The :7790 HTTP service.

Routes

method path body / query returns
GET /status {ok,ready,status,statusText,printer}
POST /print-encode {sku,releaseId,artist?,title?,genre?,style?,info?,price?,condition?,size?,w?,h?,gap?} {ok,epc,status,...}
POST /write-tag alias of /print-encode same
POST /print-test same body — prints only, no RFID (safe on chipless stock) same
GET /read-tag {ok,epc,sku,releaseId,recognized}
GET /read-raw every memory bank
GET /preview?size=large&artist=… PNG of the label, printing nothing
GET /profiles the media profiles below
POST /calibrate RFID auto-calibration
POST /recover clear a latched fault

Use /preview while tuning a layout — it costs no labels.

Media profiles

Both shop stocks sit on a 24 mm web and feed long-edge-first, so the landscape design is rotated 90° onto the media. Names match the extension's size dropdown.

profile label face pitch notes
small 51 × 19 mm 51 mm older stock; the rest of the 24 mm web is blank filler, design is centred across it
large 55 × 24 mm 55 mm current stock (default)
xlarge 64 × 34 mm 64 mm untested

Each inherits margin_mm (default 1.5), a safe-area inset held back from every edge — without it the design runs edge-to-edge and the least registration drift clips it. Override per request with size, or w/h/gap directly.

Gotchas confirmed on macOS

  1. 0x08 "out of ribbon" is a latch. SET RIBBON OFF alone will not clear it — it also needs a FORMFEED (costs one blank label). recover() / POST /recover does this.
  2. The status byte lies for 13 s after a print (transient 0x20/0x31/0x45). Never judge a print by the immediate post-print byte; poll until it settles to 0x00.
  3. VOID0 stamps on chipless stock. If RFID encoding is armed, every label without a chip gets voided. SET RFID OFF stops it; /print-test is the safe route on plain thermal. Note &DEFAULT,i appears to arm RFID, so don't send it casually.
  4. Bidirectional USB is required. UHF READ/QUERY reply with raw bytes on the bulk-IN endpoint, so CUPS lp -o raw is not sufficient — hence libusb.
  5. After changing stock, calibrate on the printer itself: hold FEED while powering on and wait for 5 beeps.

SKU ↔ EPC scheme (⚠ still unverified)

96-bit EPC = 6 bytes 14-digit timestamp SKU + 4 bytes release_id + 2 bytes 0xEC01 marker — ported byte-for-byte from daemon.cs. Confirm against an existing Chafon-written tag before trusting it; if it differs, change only epc_encode / epc_decode in urovo.py.