feat(inventory): Phase 1 - scan and validate UPC barcodes #1

Open
lyrathorpe wants to merge 54 commits from feat/inventory-phase1-scan into main
Showing only changes of commit 4937811a01 - Show all commits
+144
View File
@@ -0,0 +1,144 @@
# Finishing the Workabout MX scanner — on-device continuation guide
This document describes exactly how to continue from the current findings to a
fully working scanner client, using on-device debugging. Read `SCANNER-API.md`
first for the findings this builds on.
## Where we are
Established (see `SCANNER-API.md` for detail):
- The integral laser is driven as an **OO library object** in `SCANNER.DYL`
(category token seen in ROM: **`oscanner`**), *not* by raw device I/O.
- The app path is the standard OLIB one: `p_getlibh``p_newsend`/`f_newsend`
(create + init) → `p_send` (configure / trigger / read).
- Internals confirmed: `WL2:D` is the driver's channel; control ops **6** then
**7** (`p_iow(chan,6)`, `p_iow(chan,7)`) fire the laser to a good decode
(green LED) on the physical device.
- The decompiled read (`FUN_858d`) creates the object via `LIBMANAGER`
(`NMLIBCREATE`/`NMLIBCREATEBYHANDLE`), sets up a buffer, then reads; the
decode lands CR/LF-terminated (default Symbol2 postamble).
Missing, and why on-device: the **message ordinals** and **parameter structs**
for the scanner's methods. OLIB assigns ordinals dynamically from the whole
class hierarchy (including base classes in `olib`/`hwim`), so they cannot be
read reliably from a static dump — but they resolve at runtime, where the
debugger can capture them. MAME cannot inject a barcode, so validation must be
on hardware anyway.
## What to capture
For a working client you need five things:
1. The scanner **category** (confirm the name/id for `p_getlibh` — candidate
`oscanner`).
2. The scanner **class** created by the app.
3. The **message ordinals** for: init, set-parameters, enable/trigger, read.
4. The **parameter/result structs** each message takes (esp. the read result
buffer and its length).
5. The exact **call sequence** the working app uses.
## Tooling: the SIBO Debugger
The SIBO Debugger (see manual `2-03`/`2-04`, "The SIBO Debugger") supports:
- **Remote debugging**: development PC connected to the Workabout by a serial
cable; debug up to 8 processes.
- **Breakpoints in dynamic libraries / shared code** — required to break inside
`SCANNER.DYL`.
- Single-step, trace, register and memory display.
Build your own code for source-level debugging with:
```
#pragma debug(vid=>full) /* in the .pr / source */
```
and produce the `.sym`/`.dbd` symbol files with EMAKE.
### Set-up
1. Connect the PC to the Workabout with a serial cable (PC serial ↔ Workabout
RS-232 port — note this is a *different* port from the barcode `TTY:D`).
2. Start the debugger on the PC and connect to the remote (Workabout) target
(Local/Remote CPU menu → Connect to Remote).
3. Have the Workabout ready to run either the ROM `DEMMAN.APP`/`SCANAPP` (for
Procedure A) or your test harness (Procedure B).
## Procedure A — trace the working ROM app (recommended first)
Goal: watch `DEMMAN`/`SCANAPP` drive the real scanner and record the ordinals.
1. On the Workabout, start `DEMMAN` and enter its Scanner (Barcode) demo.
2. From the debugger, attach to that process and set breakpoints on the OLIB
dispatch path so you catch the object creation and messages:
- `LIBMANAGER` (`INT 0x84`) — sub-functions `NMLIBCREATE` (`AH=5`),
`NMLIBCREATEBYHANDLE` (`AH=6`): captures the category/class and the object
handle.
- `MESSMANAGER` (`INT 0x83`) — `NMMESSSEND` and the send/receive variants:
captures each message ordinal and its argument pointer.
- Optionally the `SCANNER.DYL` method handlers we identified (init dispatch,
trigger) as cross-checks.
3. For each captured call record: `AH`, the category/class in registers, the
message **ordinal**, and the pointed-to **argument struct** (dump memory at
the pointer). For the read, note where the decoded bytes land and the
returned length.
4. Trigger a real scan and record the read message and its result buffer.
Result: the exact category, class, ordinals, param structs, and sequence.
## Procedure B — iterate a C test harness
Once Procedure A gives the ordinals (or to trial them), build a small OO client:
```c
#include <plib.h>
#include <p_object.h>
/* Fill these from Procedure A (placeholders until captured): */
#define SCAN_CATEGORY /* category id/handle for "oscanner" via p_getcat/p_getlibh */
#define C_SCANNER /* the scanner class */
#define O_SCAN_INIT /* init message ordinal */
#define O_SCAN_PARAMS /* set-parameters ordinal */
#define O_SCAN_READ /* read-a-barcode ordinal */
GLDEF_C INT main(VOID)
{
VOID *lib, *scanner;
/* result/param structs per Procedure A */
lib = p_getlibh(SCAN_CATEGORY);
scanner = p_newsend(SCAN_CATEGORY, C_SCANNER, O_SCAN_INIT, /*&initargs*/ 0);
/* configure Symbol2 + the default 11-byte param block:
04 3f 01 15 06 04 1e 80 0d 0a 06 (CR/LF postamble) */
p_send3(scanner, O_SCAN_PARAMS, /*&params*/ 0);
for (;;) {
/* send the read message; result is the decoded barcode, CR/LF-terminated */
p_send3(scanner, O_SCAN_READ, /*&result*/ 0);
/* display result ... */
}
}
```
Build with `#pragma debug(vid=>full)`, run under the debugger, single-step the
sends, and inspect return values / the result buffer. Adjust ordinals/structs
until a real scan returns the UPC.
## Turning captures into the shipped reader
- Replace `code/inventory/bcode.c` with the OO client: `bcodeOpen` does
`p_getlibh` + `p_newsend`(init) + params; `bcodeRead` does `p_send`(read) and
returns the decoded string (strip the CR/LF postamble; strip any preamble byte
`0x80` if present).
- `upc.c` (UPC-A check-digit validation) is unchanged and already correct.
- Cross-check the captured ordinals against the decompiled handlers in
`SCANNER-API.md` (`FUN_7ae3` init dispatch — decoder type at `[channel+10]`,
case 4 = Symbol; `FUN_7aa4` trigger = the `6`/`7` ops).
## Validation
- A valid UPC-A scan should return 12 digits (plus any pre/postamble), and
`upcIsValid` should pass. The default postamble is CR LF (`0x0d 0x0a`).
- Confirm repeated scans and clean shutdown (`p_send` a destroy/close message,
then `p_close` any channel).
## Reproduce the RE environment (for further static/dynamic work)
See `../mx-re/toolchain-and-plan.md`: MAME `psionwamx` (live trace/dasm),
radare2 (static), and Ghidra headless (decompilation of the DYLs). These remain
useful for reading further `SCANNER.DYL` methods, but the ordinals themselves
come from the on-device debugger as above.