From f2bd38ac61bbe6488ff1a09b4befd3fae3b88c5c Mon Sep 17 00:00:00 2001 From: Padreug Date: Mon, 1 Jun 2026 15:25:30 +0200 Subject: [PATCH] feat(hal/ebds): log validator status bytes on change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface MEI SCR's full status-flag set in the journal — diagnostic for stuck-bill / jam scenarios where the raw flag combination (jammed, stalled, transportOpen, escrowed, etc.) reveals what state the validator actually believes it's in. Dedup'd on change so 100ms polling doesn't flood logs; firmware model + revision logged once. --- .../hal/src/validators/ebds/ebds-rs232.ts | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/hal/src/validators/ebds/ebds-rs232.ts b/packages/hal/src/validators/ebds/ebds-rs232.ts index f626c30..6673a25 100644 --- a/packages/hal/src/validators/ebds/ebds-rs232.ts +++ b/packages/hal/src/validators/ebds/ebds-rs232.ts @@ -211,6 +211,26 @@ function parseStatus(data: DestructedData): string | null { return null } +/** + * Render destructed status bytes as a comma-separated list of truthy flags. + * + * Used for on-change diagnostic logging — produces a compact one-liner like + * `idling, cassetteAttached` or `escrowed, jammed, transportOpen` so the + * field operator can read the validator's full state out of the journal. + */ +function summarizeStatusFlags(dd: DestructedData): string { + const flags: string[] = [] + for (const [name, on] of Object.entries(dd[0])) if (on) flags.push(name) + for (const [name, on] of Object.entries(dd[1])) if (on) flags.push(name) + // byte 2 has both boolean flags and a numeric noteValue — skip the number + for (const [name, on] of Object.entries(dd[2])) { + if (name === 'noteValue') continue + if (on) flags.push(name) + } + for (const [name, on] of Object.entries(dd[3])) if (on) flags.push(name) + return flags.join(', ') || '(none)' +} + /** Destruct the control byte (§6.4) */ function destructCtlByte(ctl: number): { ack: number; devType: number; msgType: number } { return { @@ -293,6 +313,8 @@ export class EbdsRs232 extends EventEmitter { private serial: SerialPort | null = null private ack: number = 0x0 private enabledDenominations: number = 0x00 + private lastStatusFlags: string | null = null + private firmwareLogged: boolean = false constructor(config: EbdsRs232Config) { super() @@ -448,6 +470,7 @@ export class EbdsRs232 extends EventEmitter { validatePacket(packet) const result = interpret(packet) if (result) { + if (result.destructedData) this._logStatusOnChange(result.destructedData) this.emit('message', result) } } catch (ex) { @@ -456,6 +479,23 @@ export class EbdsRs232 extends EventEmitter { } } + /** + * Log the validator's status bytes when they change. Polling runs at 100ms + * so we dedupe identical states — otherwise the journal would be unreadable. + * Logs firmware model + revision once on first parse for field diagnostics. + */ + private _logStatusOnChange(dd: DestructedData): void { + if (!this.firmwareLogged) { + console.log(`[EBDS] firmware: model=${dd[4].modelNumber} revision=${dd[5].codeRevision}`) + this.firmwareLogged = true + } + const flags = summarizeStatusFlags(dd) + if (flags !== this.lastStatusFlags) { + console.log(`[EBDS] status: ${this.lastStatusFlags ?? '(initial)'} → ${flags}`) + this.lastStatusFlags = flags + } + } + private _acquireSync(data: Buffer): Buffer { for (let i = 0; i < data.length; i++) { if (data[i] === STX) { @@ -467,4 +507,4 @@ export class EbdsRs232 extends EventEmitter { } // Re-export for testing -export { computeChecksum, interpret, destructData, parseStatus } +export { computeChecksum, interpret, destructData, parseStatus, summarizeStatusFlags }