feat(inventory): Phase 1 - scan and validate UPC barcodes #1
@@ -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, /*¶ms*/ 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.
|
||||||
Reference in New Issue
Block a user