feat(hal/ebds): log validator status bytes on change

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.
This commit is contained in:
Padreug 2026-06-01 15:25:30 +02:00
commit f2bd38ac61

View file

@ -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 }