# 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 ,,,,"" # the RFID encode UHF READ ,,, UHF QUERY ,, UHF GEN2 EPC|TID|USER|ACCESS|KILL ,"" SET RFID ,,,,,, &DEFAULT,i # rfidSetupDefault &CALIBRATE,A,R # RFIDAutoCalibration PRINT , # 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 1–3 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`.