The remaining gap to a single service is the DYMO M10 postal scale (USB HID 0922:8003/8004, ~130 lines of the Node daemon) -- everything else the extension calls is already served. Scale was not plugged in today, so note that any port written before it is connected is untested. Also records the wanted Logitech C920 + cutting-mat dimension capture, and what still works with the Urovo unplugged (previews, label layout, Dymo, Chafon). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
252 lines
15 KiB
Markdown
252 lines
15 KiB
Markdown
# Urovo D812R+ RFID print+encode — full assessment & handover
|
||
|
||
_Session date: 2026-07-22 (Windows 11 bench machine). Goal: make the **Urovo D812R+ (RFID)**
|
||
print a record label **and** program the UHF chip **in one pass**, driven from the PriceGod
|
||
browser extension — replacing the current two-step flow (Chafon gun programs a chip → Dymo 450
|
||
prints a paper label → stick paper over chip)._
|
||
|
||
---
|
||
|
||
## TL;DR
|
||
|
||
- ✅ **Built a working Windows daemon** (`pricegod-urovo-daemon`, C#/.NET Framework, `http://localhost:7790`)
|
||
that drives the Urovo via the **GTSPL SDK** and does **print + RFID-encode in one pass**. Drop-in
|
||
for the extension's `daemon.js` (same port, same JSON, CORS `*`).
|
||
- ✅ **Printing works** — dark, correct, on real thermal stock, at the right size (54×24). The daemon
|
||
even **auto-clears the recurring "out of ribbon" latch** on every print.
|
||
- ✅ **RFID read/encode path proven** — the Urovo reads UCODE-9 chips and the `writeUHF`+`printlabel`
|
||
encode sequence is wired and fires cleanly.
|
||
- ⛔ **The blocker to the actual one-pass dream is the LABELS, not the printer:** the shop's glossy
|
||
**"U9 65×35mm PET"** RFID labels are **not direct-thermal** — a direct-thermal printer can't mark
|
||
them (only a faint emboss). Need **direct-thermal UHF RFID _paper_ labels**.
|
||
- ⛔ **Chafon H-102 on Windows is a dead end for _writing_** — it outputs as an HID keyboard, not
|
||
serial; the SDK can't reach it to program tags without a chronically-unreliable device-side mode
|
||
flip. Recommend: **Chafon = scanner (reads/looks-up), Urovo = all writing.**
|
||
|
||
---
|
||
|
||
## 1. What was built (in this repo)
|
||
|
||
| File | What |
|
||
|---|---|
|
||
| `daemon.cs` | The service. `HttpListener` on `:7790`, CORS `*`, drives the Urovo via `GTSPL_SDK.dll`. |
|
||
| `chafcheck.cs` | Probe for the Chafon reader via native `UHFPrimeReader.dll` (P/Invoke). |
|
||
| `start-daemon.cmd` / `build.cmd` | Run / rebuild (rebuild uses the always-present .NET Framework `csc.exe` — **no .NET SDK needed**). |
|
||
| `README.md` | How to run + routes. |
|
||
| `PROGRESS.md` | Running log. |
|
||
| `*.dll` | Vendor binaries needed to build/run on Windows: `GTSPL_SDK.dll`, `GTSPL_SDK_C.dll`, `zlib.net.dll` (Urovo), `UHFPrimeReader.dll`, `hidapi.dll` (Chafon). |
|
||
|
||
Build with `build.cmd` (rebuilds `pricegod-urovo-daemon.exe`; `.exe`s are git-ignored). Run with
|
||
`start-daemon.cmd`.
|
||
|
||
### Daemon routes (`:7790`, JSON)
|
||
| method | path | body | returns |
|
||
|---|---|---|---|
|
||
| GET | `/status` | — | `{ok,ready,status,statusText,printer}` |
|
||
| POST | `/print-encode` | `{sku,releaseId,artist?,title?,genre?,style?,info?,price?,condition?,w?,h?,gap?,dir?}` | `{ok,epc,status,note,...}` |
|
||
| POST | `/write-tag` | alias of `/print-encode` (the daemon.js name) | same |
|
||
| POST | `/print-test` | same body, **prints only, no RFID** (safe on plain thermal) | — |
|
||
| GET | `/read-tag` / `/read-raw` | — | tag EPC / all memory banks |
|
||
| POST | `/calibrate` / `/recover` | — | RFID auto-cal / ribbon-off+feed clear |
|
||
|
||
---
|
||
|
||
## 2. The proven Urovo print+encode recipe (⭐ portable — this is what transfers to the Mac)
|
||
|
||
Every step below is the **exact GTSPL SDK call sequence** the daemon uses. Almost all of them are
|
||
plain **TSPL commands** (`sendcommand`) — the only special one is the RFID write. This recipe is
|
||
device knowledge, not language knowledge; it should replicate on _any_ GTSPL binding (Windows .NET,
|
||
**Java/`GTSPL-1.1.8.jar`**, Android, etc.):
|
||
|
||
```
|
||
detectUSB / openport # connect to "Urovo D812Riplus(RFID)"
|
||
SET RIBBON OFF # direct thermal (no ribbon). REQUIRED, repeatedly (see §3)
|
||
<if status != "00">: formfeed; SET RIBBON OFF # clear a latched "out of ribbon"
|
||
SET TEAR ON
|
||
GAP 2 mm,0 mm # gapped die-cut labels (Dymo 54×24). For RFID rolls that
|
||
# blind the gap sensor: GAP 0 mm,0 mm (continuous)
|
||
SIZE 54 mm,24 mm # label size (was 65×35 for the PET RFID stock)
|
||
DENSITY 15 # MAX darkness — needed; lower barely marks
|
||
DIRECTION 1 # flip to 0 if the label prints rotated
|
||
CLS (clearbuffer)
|
||
writeUHF("H", 2, 12, "E", <24-hex EPC>) # ⭐ THE RFID ENCODE: hex, EPC bank ("E"), word 2, 12 bytes (96-bit EPC)
|
||
TEXT/QRCODE ... (printerfont/qrcode) # the visible label
|
||
SET RIBBON OFF # re-assert immediately before printing
|
||
printlabel("1","1") # fires PRINT + ENCODE together, one pass
|
||
<wait for status to return to "00"> # RFID+feed leaves it "busy" for ~1–3s (see §3)
|
||
readUHF("H",2,12,"E") / query_UHF # optional verify (note: the encoded tag has fed past the
|
||
# antenna by now, so on-printer read-back is unreliable —
|
||
# verify with a separate reader / Chafon scan instead)
|
||
```
|
||
|
||
**SKU ↔ EPC scheme (v1, in the `Epc` class):** the 96-bit EPC (12 bytes) = **6 bytes** 14-digit
|
||
timestamp SKU + **4 bytes** release_id + **2 bytes** `0xEC01` marker. ⚠️ **Unverified against the
|
||
Mac/Chafon scheme** — if existing tags use a different layout, change ONLY the `Epc` class; nothing
|
||
else depends on the byte order. (Best check: scan an existing Chafon-written tag and compare.)
|
||
|
||
---
|
||
|
||
## 3. Hard-won gotchas (the Urovo D812R+ specifically)
|
||
|
||
1. **PET labels aren't direct-thermal.** The glossy **"U9 65×35mm PET"** RFID labels only emboss —
|
||
they will _never_ print dark on a direct-thermal printer. This cost hours (looked like a
|
||
density/ribbon/printer fault; it was the media). Scratch test: drag a nail across a blank label —
|
||
direct-thermal paper leaves a gray streak, PET/plain leaves nothing. **Buy direct-thermal RFID
|
||
_paper_ labels** (see §5).
|
||
2. **"Out of ribbon" (status `08`) is a latch.** The printer defaults to thermal-transfer (expects a
|
||
ribbon). `SET RIBBON OFF` switches to direct thermal — but a _latched_ `08` (e.g. after opening
|
||
the head / pulling the roll back) is **NOT** cleared by `SET RIBBON OFF` alone; it needs a
|
||
**power cycle**, OR a **formfeed + SET RIBBON OFF** (which the daemon now does automatically on a
|
||
non-`00` start).
|
||
3. **The status byte lies right after a print.** During the RFID-encode + feed the reader returns
|
||
garbage codes (`20`, `31`, `45`, `FC`) for 1–3s, then settles to `00`. Do **not** judge success on
|
||
the immediate post-print byte — poll until `00` (the daemon's `WaitReady`), and verify the actual
|
||
result by eye / a separate tag read.
|
||
4. **USB drops on power-off.** Powering the printer off removes it from the USB bus; it re-enumerates
|
||
a few seconds after power-on. If `detectUSB` returns null/`no printer`, wait or replug. (COM4/COM5
|
||
on this laptop are the Urovo's _Bluetooth_ SPP links — ignore them.)
|
||
5. **Label-size calibration is on the printer:** hold the **FEED** button while powering on, wait for
|
||
**5 beeps** — it auto-calibrates gap/label size. Needed after changing stock.
|
||
6. **Talk to it via the GTSPL SDK, not the Windows spooler.** Raw TSPL through the Seagull print
|
||
driver/spooler did not reliably deliver commands (esp. `SET RIBBON OFF`); the SDK's direct USB path
|
||
does. Build without a .NET SDK using `Framework64\v4.0.30319\csc.exe`.
|
||
|
||
---
|
||
|
||
## 4. The Chafon H-102 situation (why it's parked)
|
||
|
||
The two CF-H102 guns, on Windows, put their tag reads on the **HID keyboard** channel (USB-HID, or
|
||
BLE "LE GATT HID device" when paired) — **not** the CH340 serial the SDK needs. Proven: `chafcheck`
|
||
(`OpenDevice`+`GetTagUii`) returns `0xFFFFFF12` (`COMM_TIMEOUT`, "reader not answering") at **every**
|
||
baud on **both** guns; a raw serial read with **DTR+RTS asserted** got **0 bytes** while triggering.
|
||
|
||
To _write_ tags via the SDK the gun must be in **serial/command mode**, flipped **device-side**
|
||
(config barcode / the sideloaded Android app) — which the user reports is chronically flaky (Mac too).
|
||
The Windows Chafon SDK has serial / USB-HID / network paths only — **no BLE** — so Bluetooth isn't a
|
||
path for the gun here either.
|
||
|
||
**Recommendation:** stop fighting it. Use the Chafon as a **keyboard scanner** (trigger → _types_ the
|
||
EPC → focus a search box to look up a tag; 100% reliable in the mode it's already in) and let the
|
||
**Urovo do all writing**. Once direct-thermal RFID labels arrive, the Chafon isn't needed to program
|
||
anything.
|
||
|
||
---
|
||
|
||
## 5. What to buy (unblocks the one-pass dream)
|
||
|
||
**Direct-thermal UHF RFID _paper_ labels**, all of:
|
||
- **Direct thermal** (⚠ not "thermal transfer", not "PET/PP") — search **"direct thermal RFID label"**
|
||
- **Paper face** (direct-thermal coated paper)
|
||
- **UHF RFID inlay, EPC Gen2, chip NXP UCODE 9** (matches existing tags/reader)
|
||
- **54×24 mm** (or 55×25 — the Dymo size), roll compatible with the D812R
|
||
- Ask the seller: **"compatible with Urovo/TSC D812R RFID printer"**
|
||
|
||
The existing PET tags aren't wasted — a reader can still _program_ them; you'd just keep
|
||
printing+sticking paper for those. New DT-RFID paper labels are what enable single-pass.
|
||
|
||
---
|
||
|
||
## 6. Two-mode design (agreed)
|
||
|
||
- **Mode A — "Separate" (today's flow, existing stock):** program chip + print Dymo separately, stick
|
||
paper on top. On Windows the encode step is the flaky Chafon (see §4) — recommend leaning on the
|
||
Urovo instead where possible.
|
||
- **Mode B — "Combined / one-pass" (the goal):** one button → Urovo prints + encodes together on
|
||
DT-RFID labels. Daemon path (`/print-encode`) is built.
|
||
|
||
A Settings toggle picks the mode; the extension change is tiny (same `daemon.js` contract). Wiring the
|
||
PriceGod buttons → `:7790` was not done yet (next step).
|
||
|
||
---
|
||
|
||
## 7. ⭐ For the Mac: getting the Urovo to program chips over there
|
||
|
||
> **UPDATE 2026-07-22 (later the same day) — SOLVED, and more cheaply than expected.**
|
||
> The RFID-encode command did **not** need a USB sniff or the Java jar. `GTSPL_SDK.dll` is a
|
||
> *managed .NET assembly*, so decompiling its IL reveals every RFID call is a one-line
|
||
> `String.Concat` → ASCII → `WritePrinter`. `writeUHF("H",2,12,"E",epc)` emits exactly:
|
||
>
|
||
> ```
|
||
> UHF WRITE H,2,12,E,"<24 hex chars>"\r\n
|
||
> ```
|
||
>
|
||
> The whole vocabulary (`UHF READ/QUERY`, `UHF GEN2 *`, `SET RFID`, `&DEFAULT,i`,
|
||
> `&CALIBRATE,A,R`) is in `mac/README.md`. **Option 2 below is therefore fully unblocked and
|
||
> option 1 (the Java jar) is unnecessary.** A working macOS daemon now lives in `mac/` —
|
||
> raw TSPL over libusb, same `:7790` contract, printing verified on the 24 mm stock.
|
||
> Options 1–3 below are kept for historical context.
|
||
|
||
The **daemon.cs is Windows-bound** — `GTSPL_SDK.dll` is a Windows DLL; it won't run on macOS. But the
|
||
**recipe in §2 is portable**. Options, best first:
|
||
|
||
1. **GTSPL Java SDK (most direct).** The vendor SDK ships a cross-platform Java library:
|
||
`Documents/Software V1.1.2/drive-download-.../20241129_GTSPL_Java_Library/lib/GTSPL-1.1.8.jar`
|
||
(also `gtspl_sdk.jar` in the Android samples). Java runs on macOS — write a small program that
|
||
mirrors §2 (`openport`/`sendcommand`/`writeUHF`/`printlabel`). ⚠ Verify the jar's USB transport
|
||
works on macOS (the Android build assumes Android USB; desktop USB may need a native lib or a
|
||
serial/USB-CDC path). This is the cleanest port because `writeUHF` does the RFID encoding for you.
|
||
2. **Raw TSPL over USB (no SDK).** ~90% of §2 is plain TSPL text you can send straight to the Urovo's
|
||
USB device on macOS (e.g. write to the raw printer device, or CUPS `-o raw`). The **one missing
|
||
piece is the RFID-encode command** — `writeUHF("H",2,12,"E",epc)` is an SDK helper wrapping the
|
||
printer's TSPL RFID command, and its exact bytes weren't captured this session. To get them: sniff
|
||
the USB writes while `writeUHF` runs on Windows (USBPcap/Wireshark), or check the Urovo/Gainscha
|
||
TSPL RFID command reference. Once you have that command string, the whole thing is raw TSPL and
|
||
trivially portable (even into the existing Node `rfid-daemon`).
|
||
3. **Extend the existing Mac `rfid-daemon`** (Node) to open the Urovo and emit the §2 sequence
|
||
(needs the RFID command bytes from option 2).
|
||
|
||
**Reality check for "can the Urovo program chips on Mac?":** yes, mechanically — it's the same USB
|
||
printer speaking TSPL. The only real work is the transport (get bytes to the printer on macOS) and the
|
||
RFID-encode command (use the Java `writeUHF`, or capture its bytes). Everything else — ribbon-off,
|
||
geometry, density, one-pass timing — is documented above and identical.
|
||
|
||
---
|
||
|
||
## 8. Open items / next steps
|
||
- [ ] Buy direct-thermal UHF RFID paper labels (§5) — the true unblock.
|
||
- [ ] Verify the SKU↔EPC scheme (§2) against an existing tag; adjust `Epc` class if needed.
|
||
- [ ] Wire PriceGod buttons → `:7790` + the Mode A/B toggle.
|
||
- [ ] Match the label layout to the extension's `labels.js` design more faithfully (current TSPL layout is functional, not pixel-perfect).
|
||
- [ ] (Optional) On Windows, decide Chafon = scanner-only vs. chase command mode.
|
||
- [x] (Mac) ~~Try option 1 or 2 in §7 to program chips there.~~ Done — see §7 update and `mac/`.
|
||
- [x] (Mac) ~~Prove the RFID **encode** end-to-end.~~ Done — chip written by the Urovo and
|
||
read back by the **Chafon** (independent reader): `0000044A5600D4CDA1FAB932` →
|
||
`sku=20260722180000, releaseId=424242`.
|
||
- [ ] 🔴 **Fix the `Epc` class in `daemon.cs` — it is WRONG.** The shop's real scheme (used by
|
||
`pliceclogs/rfid-daemon` and now by `mac/urovo.py`) is a single 96-bit big-endian
|
||
integer `sku * 10^9 + releaseId`, **not** 6B sku + 4B releaseId + `0xEC01`. The Node
|
||
daemon's `parseEpcData` rejects the `EC01` layout outright, so **every tag the Windows
|
||
daemon has written is invisible to the extension** and needs rewriting.
|
||
- [ ] Confirm `SET RFID OFF` is genuinely honoured by this firmware rather than silently
|
||
ignored — the absence of `VOID0` on the last test print is the only evidence so far.
|
||
- [ ] Resolve the `:7790` collision between `mac/daemon.py` and the Node `rfid-daemon`
|
||
(both bind it; the Node one owns the Chafon write path, scale and inventory routes).
|
||
|
||
---
|
||
|
||
## 9. Next session — pick up here
|
||
|
||
**The one thing standing between us and a single service.** The extension calls only six
|
||
endpoints: `/status`, `/write-tag`, `/read-tag` (all served by `mac/daemon.py`) plus
|
||
`/weight`, `/scale/start`, `/scale/stop` (Node only). Port the **DYMO M10 postal scale**
|
||
— USB HID `0922:8003`/`0922:8004`, ~130 lines around `index.js:1068-1200` — and the Node
|
||
daemon retires for the extension's purposes: one process, one venv, one launchd agent.
|
||
⚠️ The scale was **not plugged in** on 2026-07-22, so any port written before it is
|
||
connected is untested.
|
||
|
||
**Also wanted:** a **Logitech C920 over a cutting mat** to measure record/sleeve dimensions
|
||
(the mat grid gives the scale reference). Not started.
|
||
|
||
**Resuming without the Urovo attached** (it stays at the shop): the daemon degrades
|
||
cleanly — `GET /devices` reports `present: false` per device rather than failing, the
|
||
Dymo and Chafon paths are independent, and `mac/chafon.py` runs standalone. Label layout
|
||
work needs no hardware at all: `GET /preview?size=large&artist=…` renders a PNG, and
|
||
`python label.py small large` dumps both profiles to `/tmp`.
|
||
|
||
**Don't re-litigate:** the RFID encode is proven (Urovo wrote, Chafon read back
|
||
independently); the EPC scheme is settled and matches the Node daemon; the Dymo needs no
|
||
driver. All three are documented in `mac/README.md`.
|
||
|
||
See also the shop's memory notes: `urovo-d812r-ribbon-fix`, `urovo-d812r-rfid-label-gap-issue`,
|
||
`urovo-d812r-pet-labels-not-thermal`, `urovo-windows-print-encode-daemon`, `chafon-h102-windows-hid-mode`.
|