8.7 KiB
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) reportsType: Laser 1223(some units1222). - 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 usesWL2. - Units seen in the ROM:
WL2:AandWL2: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
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*.ldddecoders (BAREAN,BARC39, …) +BAR:device. These are software decoders for the external wand modules. LoadingBAREAN.LDDand openingBAR:A/BAR:Breturns-41(E_FILE_DEVICE, "no interface found in slot") — the integral laser is not aBAR:expansion interface.BAR:D/BAR:Ereturn-38(E_FILE_NAME, invalid unit).TTY:Dserial reads. OpeningTTY:Dpowers the laser (it fires briefly), but no decoded bytes ever arrive on the serial channel — not with default config, not afterP_FSETto 9600/8/1, not withP_OBEY_DSRcleared, not with the intelligent-reader escape commands (<Esc>-y1J/<Esc>-y1K), not reading one byte at a time. The integral laser's decoded data is not onTTY:D.
Initialisation sequence (reverse-engineered from the ROM)
Opening WL2:D is not enough — the scanner must be configured and enabled first.
The sequence was recovered by disassembling SCANNER.DYL in the v7.20f ROM
(around file offset 0xD7AA4). It uses the SIBO I/O executive int 0xCF,
whose convention here is: CL = I/O function code, BX = channel handle,
DX = argument (by value), result in AX. From C this is the p_iow(chan, func, ...) layer.
Function codes observed on the WL2 channel (from p_file.h, plus WL2-specific
ones above the standard range):
| CL | Meaning |
|---|---|
| 6 | (P_FDETACH slot) used as a scanner enable/control op |
| 7 | (P_FSET) used as a scanner enable/control op |
| 8 | P_FSENSE |
| 9 | P_FFLUSH |
| 0x0C (12) | WL2-specific: write a config/command byte (byte passed in DX) |
The driver keeps a per-channel structure; the param block sits at channel+4,
with the decoder type at offset +6 (dispatch values 1..4) and the 11-byte
parameter block at +8..+0x12. Configuration is pushed to the engine as a series
of cl=0x0C writes (one command byte per call in DX), then enable ops
(cl=6, cl=7).
Default Symbol2 parameter block (from the ROM)
The routine at 0xD7AB3 fills the 11-byte block with these defaults for the
Workabout MX (Symbol2) scanner:
| Byte | Param | Value |
|---|---|---|
| +8 | Param0 Decode security | 0x04 |
| +9 | Param1 Code type | 0x3F (all symbologies) |
| +0xA | Param2 Decode options A | 0x01 |
| +0xB | Param3 Decode options B | 0x15 |
| +0xC | Param4 General params | 0x06 |
| +0xD | Param5 ITF length 1 | 0x04 |
| +0xE | Param6 ITF length 2 | 0x1E |
| +0xF | Param7 Preamble | 0x80 |
| +0x10 | Param8 Postamble 1 | 0x0D (CR) |
| +0x11 | Param9 Postamble 2 | 0x0A (LF) |
| +0x12 | Param10 General decode | 0x06 |
So decoded output is terminated by CR LF (0x0D 0x0A), and Code type 0x3F
enables all symbologies (UPC included). A reader should assemble bytes until
CR/LF and strip the preamble/postamble.
Confirmed on-device
p_open(&h, "WL2:D", -1)returns0.- Issuing control ops 6 then 7 on the channel (
p_iow(h, 6),p_iow(h, 7)) triggers a scan: the laser fires and decodes (green good-read LED). This is reproducible. The trigger is one-shot — it must be re-issued for each scan.
NOT yet solved: retrieving the decoded data
After a confirmed good read, the decoded barcode could not be retrieved from
the WL2:D channel by any tried method:
p_iow(h, P_FREAD, buf)(count-in-buf[0]style) — no data.p_read(h, buf, len)(synchronous, with length) — no data.p_ioc(h, P_FREAD, &stat, buf, &len)+ trigger +p_iowait()(async) — no data.
The retrieval logic in SCANNER.DYL/SCANAPP fetches the decoded data on a
separate handle (a driver global, e.g. [0x13b5]) obtained through
OS-service stubs of the form mov ah,N; int 0x84/0x85/0x86/0x87; ret — not a
plain P_FREAD on the WL2:D control channel. This resolves an earlier puzzle:
the SIBO executive uses different register conventions per interrupt vector —
the int 0x84 service family passes the function in AH, whereas the int 0xCF
I/O executive uses CL.
SCANNER.DYL is an object-oriented ROM library (a DYL). This strongly
suggests the scanner is driven by creating a scanner object and sending it
messages (the OLIB/OO model), not by raw device I/O on WL2:D. That would
explain why every raw WL2:D read returned no data — wrong paradigm.
Confirmed working on device: p_open("WL2:D") + control ops 6 then 7
trigger the laser and a good decode (green LED). Only retrieving the decoded
bytes is unsolved.
To close it, one of:
- The Workabout MX C SDK
SCANNER.DYLheader / OO category definition (whatSCANAPPwas built against) — the clean answer, and likely the only practical one, because the interface is object-oriented. - A deep RE effort under MAME (
psionwamx): automate the UI to launchSCANAPP, breakpoint the read routine, and trace it. Caveats: MAME cannot inject a real barcode (no laser input), so only the read setup is observable in emulation; the reconstructed sequence must still be validated on the physical device.
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 |