yourovo/ASSESSMENT.md
type-two 6d9a49ea42 Fix the EPC scheme; verify encode independently with the Chafon
The SKU<->EPC layout inherited from daemon.cs was WRONG. daemon.cs packs 6 bytes
of SKU + 4 bytes of release_id + an 0xEC01 marker, and flagged itself as
unverified. The shop's actual scheme -- in pliceclogs/rfid-daemon/index.js
(buildEpcPayload/parseEpcData), which is what the extension and every existing
tag use -- is a single 96-bit big-endian integer:

    value = sku(14 digits) * 10^9 + releaseId

Checked against that file's own worked example: 20260321001001 + 35332 ->
20260321001001000035332. epc_decode also reproduces its guard, returning None
when value/10^9 exceeds 14 digits, so a factory tag (E28069...) can never be
reported to the operator as a real SKU.

This matters: parseEpcData rejects the EC01 layout outright, so every tag the
Windows daemon has written is invisible to the extension. ASSESSMENT.md now
carries that as a red open item against daemon.cs.

Verified end-to-end with an INDEPENDENT reader. ASSESSMENT.md sec.4 parked the
Chafon as HID-keyboard-only, but that is Windows-specific -- on macOS it exposes
a CH340 bridge and its documented serial protocol works. New chafon.py (read-only:
inventory + read EPC, CRC-16/0x8408 framing) scans the field and sees:

    0000044A5600D4CDA1FAB932  -> sku=20260722180000 rel=424242   (Urovo-written)
    E28069950000600625F600D3  -> factory/blank  (x12, rest of roll)
    126D5125FFA000067932EC01  -> factory/blank  (earlier EC01 tag, now rejected)

That last line is the bug demonstrated: a tag written earlier today under the old
scheme is unrecognised, exactly as the extension would have treated it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 16:45:07 +10:00

227 lines
14 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.

# 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 ~13s (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 13s, 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 13 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).
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`.