yourovo/mac/README.md
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

95 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```bash
./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`.