From fc4df773003756ea668b404fc5ea73d3d2cff3ee Mon Sep 17 00:00:00 2001 From: lyrathorpe Date: Mon, 6 Jul 2026 21:35:08 +0100 Subject: [PATCH] docs(inventory): first-ever reverse-engineered Workabout MX scanner API notes --- code/inventory/SCANNER-API.md | 126 ++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 code/inventory/SCANNER-API.md diff --git a/code/inventory/SCANNER-API.md b/code/inventory/SCANNER-API.md new file mode 100644 index 0000000..9f84c82 --- /dev/null +++ b/code/inventory/SCANNER-API.md @@ -0,0 +1,126 @@ +# Workabout MX barcode scanner — API notes (reverse-engineered) + +There is no official SDK documentation for the Workabout MX integral laser scanner. +The Psion SIBO C SDK and I/O Devices Reference predate the MX and describe only the +older *external* barcode modules (wands / wand-emulation), not the integral laser. +These notes are reverse-engineered from device behaviour and from the Workabout MX +ROM `w2mx_v7.20f_eng.bin` (strings), so treat unconfirmed items as such. + +## Hardware + +- Integral **laser** scanner, Symbol engine. The demo (`DEMMAN.APP`) reports + `Type: Laser 1223` (some units `1222`). +- It is a **decoded** scanner: it decodes in hardware and lights a green good-read + LED. It does **not** emit an undecoded (HHLC) signal. +- Trigger: the keyboard **scan key**, which the Window Server reports as key + code **368**. The key alone does not fire the laser from an arbitrary app; the + scanner software arms/reads the engine. + +## Device driver and ports + +`LLDEV` on the device lists a logical driver **`wl2`** (`units=1`). The scanner is +reached through this driver. The demo's "Select Scanner" screen lets you pick both +a **decoder type** and a **port** (device driver + unit letter): + +- Port device drivers offered: **`TTY`**, **`WLS`**, **`WL2`**. + The MX integral laser uses **`WL2`**. +- Units seen in the ROM: **`WL2:A`** and **`WL2:D`**. + +Decoder types (from the demo, "chosen from those used in standard Workabout +products"): + +| Decoder | Meaning | +| --- | --- | +| `Generic` | External non-configurable reader | +| `Symbol1` | Workabout standard scanner | +| `Datalogic` | Workabout CCD | +| `HP` | Workabout Wand | +| **`Symbol2`** | **Workabout MX scanner (the integral laser)** | + +## Opening the device + +```c +VOID *h; +INT err = p_open(&h, "WL2:D", (UINT)-1); +``` + +Observed results: + +| Call | Result | Meaning | +| --- | --- | --- | +| `p_open("WL2:D", -1)` | `0` | **opens** — this is the integral-laser unit | +| `p_open("WL2:A", -1)` | `-9` (`E_GEN_INUSE`) | in use — held by the resident scanner software | +| `p_open("WL2:B" / ":C")` | (untested) | — | + +`WL2` has a single unit, so only one process may hold it. `WL2:A` is permanently +`-9` even with the demo closed, indicating a resident holder; `WL2:D` is the unit +an application opens. + +## The 11-byte parameter block + +The scanner is configured by an **eleven-byte parameter block**. The application +interprets these bytes and converts them into commands for the selected decoder +(so the same block means different wire commands for `Symbol2` vs `HP`, etc.): + +| Byte | Parameter | +| --- | --- | +| Param0 | Decode security | +| Param1 | Code type (symbology select) | +| Param2 | Decode options A | +| Param3 | Decode options B | +| Param4 | General parameters | +| Param5 | ITF length 1 | +| Param6 | ITF length 2 | +| Param7 | Preamble | +| Param8 | Postamble byte 1 | +| Param9 | Postamble byte 2 | +| Param10 | General decode options | + +The preamble/postamble bytes mean decoded output may carry configurable leading / +trailing characters — parsing code must account for them. + +## Access model + +Applications do not drive `WL2` byte-by-byte themselves; they use the ROM library +**`SCANNER.DYL`** (used by `SCANAPP.APP` and `DEMMAN.APP`). That library opens the +port, converts the 11-byte block to decoder commands, configures and enables the +engine, and returns decoded scans. + +## Paths that do NOT work for the integral laser + +Recorded so they are not retried: + +- **`bar*.ldd` decoders (`BAREAN`, `BARC39`, …) + `BAR:` device.** These are + software decoders for the *external* wand modules. Loading `BAREAN.LDD` and + opening `BAR:A`/`BAR:B` returns `-41` (`E_FILE_DEVICE`, "no interface found in + slot") — the integral laser is not a `BAR:` expansion interface. `BAR:D`/`BAR:E` + return `-38` (`E_FILE_NAME`, invalid unit). +- **`TTY:D` serial reads.** Opening `TTY:D` powers the laser (it fires briefly), + but no decoded bytes ever arrive on the serial channel — not with default + config, not after `P_FSET` to 9600/8/1, not with `P_OBEY_DSR` cleared, not with + the intelligent-reader escape commands (`-y1J` / `-y1K`), not reading + one byte at a time. The integral laser's decoded data is not on `TTY:D`. + +## Open gap (not yet solved) + +Opening `WL2:D` succeeds, but the scanner does not start scanning on a bare open — +it requires an **initialisation sequence** (set decoder type, write the 11-byte +parameter block as decoder commands, enable the engine) before a read returns +data. That sequence is the function-code / control protocol of the `WL2` driver +and is implemented in the binary `SCANNER.DYL`; it is **not** present in any ROM +strings or in the SDK. + +To close it, one of: +1. The **Workabout MX C SDK** scanner header / `SCANNER.DYL` category definition + (what `SCANAPP` was built against) — the clean answer. +2. Disassembly of `SCANNER.DYL` / `SCANAPP` from the ROM to extract the `WL2` + init sequence — large, uncertain effort. + +## Error codes seen (from `epocdefs.h`) + +| Value | Name | Meaning | +| --- | --- | --- | +| `-9` | `E_GEN_INUSE` | device already open / in use | +| `-32` | `E_FILE_EXIST` | (LDD) already loaded | +| `-38` | `E_FILE_NAME` | invalid device name / unit | +| `-41` | `E_FILE_DEVICE` | device / interface not present |