From dd542eda88b197dfe89779c9f60cfd0be3c3566d Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 29 Sep 2026 21:08:24 +0200 Subject: [PATCH] fix(hal): seed the validator's fiat code from config, or it rejects every note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Apex returned every bill inserted. Cause is not wiring or DIP switches: the driver had no fiat code, so no credit channel could resolve to a value. ApexValidator initialises `fiatCode` to null and only assigns it in setFiatCode(). NOTHING in this repo calls setFiatCode() — not hal-service, not the store, nothing. It is declared on the BillValidator interface and implemented three times, and it is dead code. So the resolver installed in run(): this.rs232.setDenomResolver((ch) => denomForChannel(this.fiatCode, ch)) is always called with null, and denomForChannel bails on its first line (`if (!fiatCode || channel < 1) return null`). Every note is read, resolves to null denomination, hits the `!bill.denomination` branch, and is handed straight back. id003 is unaffected because it never relies on the field: run() threads `config.fiatCode` into the rs232 config, so the value reaches the layer that needs it regardless. Apex and EBDS both read `this.fiatCode` instead, so both are broken the same way. EBDS is fixed here too — it has the identical dead-field dependency in _denominations() and would fail identically the first time it met hardware. Both constructors now seed from `config.fiatCode ?? config.rs232.fiatCode`, which is what the callers have been passing all along. setFiatCode() stays as a later override rather than the only path in. Also makes the failure loud, because the old log line is what sent us looking at the acceptor instead of the driver. "Bill rejected: unsupported/unmapped channel" reads as a dataset/hardware mismatch and gives no hint that the driver simply has no currency. It now names which of the two causes it is, and run() logs an error up front when there is no fiat code at all, since in that state every note is guaranteed to be returned. --- packages/hal/src/validators/apex/index.ts | 27 ++++++++++++++++++++++- packages/hal/src/validators/ebds/index.ts | 5 +++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/hal/src/validators/apex/index.ts b/packages/hal/src/validators/apex/index.ts index 463ee06..5d98c03 100644 --- a/packages/hal/src/validators/apex/index.ts +++ b/packages/hal/src/validators/apex/index.ts @@ -48,6 +48,11 @@ export class ApexValidator extends EventEmitter implements BillValidator { constructor(config: ValidatorConfig) { super() this.config = config + // Seed from config, as id003 effectively does by threading config.fiatCode + // into its rs232 config. Nothing in the app calls setFiatCode(), so a + // driver that relies on it alone resolves every credit channel to null and + // rejects every note. setFiatCode() stays available as a later override. + this.fiatCode = config.fiatCode ?? config.rs232.fiatCode ?? null this._throttledError = throttle((err: Error) => this.emit('error', err), 2000) } @@ -76,7 +81,20 @@ export class ApexValidator extends EventEmitter implements BillValidator { this.fsm.on('billsAccepted', () => this.emit('billsAccepted')) this.fsm.on('billsRead', (bill: { denomination: number | null; code: string }) => { if (!bill.denomination) { - console.log('[APEX] Bill rejected: unsupported/unmapped channel') + // Say WHICH of the two causes this is. "unmapped channel" alone reads + // as a hardware/dataset mismatch and sent us looking at DIP switches + // when the real cause was a null fiat code rejecting every note. + if (!this.fiatCode) { + console.error( + '[APEX] Bill rejected: no fiat code set on the driver, so NO channel ' + + 'can resolve to a value. Every note will be returned until this is fixed.' + ) + } else { + console.log( + `[APEX] Bill rejected: channel ${bill.code || '?'} is not mapped in the ` + + `${this.fiatCode} dataset — check the acceptor's configuration card` + ) + } this.rs232?.reject() return } @@ -92,6 +110,13 @@ export class ApexValidator extends EventEmitter implements BillValidator { this.fsm.on('standby', () => this.emit('standby')) this.fsm.on('error', (err: Error) => this.emit('error', err)) + if (!this.fiatCode) { + console.error( + '[APEX] starting with NO fiat code — denomination lookup will return null ' + + 'for every credit channel and the acceptor will reject every note.' + ) + } + this.rs232.open((err) => { if (err) return cb(err) this.rs232!.reset() // start disabled diff --git a/packages/hal/src/validators/ebds/index.ts b/packages/hal/src/validators/ebds/index.ts index 44dd540..52a749b 100644 --- a/packages/hal/src/validators/ebds/index.ts +++ b/packages/hal/src/validators/ebds/index.ts @@ -53,6 +53,11 @@ export class EbdsValidator extends EventEmitter implements BillValidator { constructor(config: ValidatorConfig) { super() this.config = config + // Seed from config, as id003 effectively does by threading config.fiatCode + // into its rs232 config. Nothing in the app calls setFiatCode(), so a + // driver that relies on it alone resolves every credit channel to null and + // rejects every note. setFiatCode() stays available as a later override. + this.fiatCode = config.fiatCode ?? config.rs232.fiatCode ?? null this._throttledError = throttle((err: Error) => this.emit('error', err), 2000) }