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>
95 lines
4.6 KiB
Markdown
95 lines
4.6 KiB
Markdown
# 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 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`.
|