feat: Raspberry Pi 4 (aarch64) target #110
1 changed files with 48 additions and 10 deletions
fix(machine): make the dispenser optional, as the validator already was
initializeHal created and initialised the dispenser unconditionally, so a
missing dispenser device threw and aborted the WHOLE of HAL init — taking
the validator down with it, even when the validator was present and
working.
The Pi bring-up hit exactly that. With a Pyramid Apex correctly wired and
enumerated on /dev/ttyValidator0:
[ATM] Validator device: /dev/ttyValidator0
[ATM] Dispenser device: /dev/ttyDispenser-not-fitted
[Electron] HAL init failed: cannot open /dev/ttyDispenser-not-fitted
[Recovery] Reloading renderer to re-attempt initialization
and round again, forever, with a perfectly good acceptor attached.
The validator has been optional since it was written — it checks the
device exists, catches init failures, and logs "running dispenser-only".
The dispenser had no equivalent. That asymmetry was the bug, not the
placeholder device path that exposed it: a cash-in-only machine is a
legitimate configuration, and the Raspberry Pi reference build is one.
Mirrors the validator's handling exactly: existence check, try/catch,
null on failure, and a log line saying what the machine will do instead
("running cash-in only"). Three call sites then need guarding —
dispenseCash returns a clear "No dispenser fitted on this machine —
cash-out unavailable" rather than dereferencing null, setCassettes still
records the layout but skips the re-init, and cleanup uses an optional
call.
This also removes the sharp edge from the rpi4/rpi5 presets added in the
previous commit. Their dispenser block points at a path that does not
exist because DispenseType has no 'none' variant and DeviceConfig
requires the field. That is still worth fixing properly with a real
'none' variant, but the machine no longer has to care.
commit
ea4c1f406b
|
|
@ -87,19 +87,40 @@ export async function initializeHal(config: HalConfig): Promise<HalInstance> {
|
|||
|
||||
const { validator: valConfig, dispenser: dispConfig } = config
|
||||
|
||||
// Create hardware instances
|
||||
const dispenser: BillDispenser = hal.createDispenser(dispConfig.type, {
|
||||
device: dispConfig.device,
|
||||
})
|
||||
// Start dispenser (optional — mirrors the validator handling below).
|
||||
//
|
||||
// A cash-in-only machine is a legitimate configuration: the Raspberry Pi
|
||||
// reference build has a bill acceptor and no dispenser at all. This used to
|
||||
// create and init the dispenser unconditionally, so a missing device threw
|
||||
// and aborted the WHOLE of initializeHal — taking the validator with it,
|
||||
// even though the validator was present and working. The Pi bring-up hit
|
||||
// exactly that: "cannot open /dev/ttyDispenser-not-fitted", then an endless
|
||||
// renderer-reload loop, with a perfectly good acceptor on ttyValidator0.
|
||||
//
|
||||
// The validator has been optional since it was written; the asymmetry was
|
||||
// the bug.
|
||||
let dispenser: BillDispenser | null = null
|
||||
|
||||
// Initialize dispenser. `dispenserInitData` is `let` because
|
||||
// `setCassettes` swaps it in to re-init with a new layout (also used by
|
||||
// the on-error re-init path at dispenseCash).
|
||||
// `dispenserInitData` is `let` because `setCassettes` swaps it in to re-init
|
||||
// with a new layout (also used by the on-error re-init path at dispenseCash).
|
||||
let dispenserInitData = {
|
||||
fiatCode: valConfig.fiatCode,
|
||||
cassettes: dispConfig.cassettes,
|
||||
}
|
||||
|
||||
try {
|
||||
const fs = await import('node:fs')
|
||||
if (dispConfig.device && fs.existsSync(dispConfig.device)) {
|
||||
dispenser = hal.createDispenser(dispConfig.type, { device: dispConfig.device })
|
||||
await dispenser.init(dispenserInitData)
|
||||
console.log('[HAL] Dispenser started')
|
||||
} else {
|
||||
console.log('[HAL] Dispenser device not found, running cash-in only')
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[HAL] Dispenser failed to start, running cash-in only:', err)
|
||||
dispenser = null
|
||||
}
|
||||
console.log('[HAL] Dispenser initialized')
|
||||
|
||||
// Start validator (optional — proceed without if device is missing or fails)
|
||||
|
|
@ -251,6 +272,17 @@ export async function initializeHal(config: HalConfig): Promise<HalInstance> {
|
|||
dispenseCash: async (amounts): Promise<DispenseResult> => {
|
||||
console.log('[HAL] Dispensing:', amounts)
|
||||
|
||||
// Cash-in-only machine: refuse the ask rather than throwing a null
|
||||
// dereference into the renderer's dispense path.
|
||||
if (!dispenser) {
|
||||
return {
|
||||
bills: [],
|
||||
cassettes: [],
|
||||
dispensed: false,
|
||||
error: 'No dispenser fitted on this machine — cash-out unavailable',
|
||||
}
|
||||
}
|
||||
|
||||
// Re-initialize dispenser if it was closed after a previous error
|
||||
if (!dispenser.initialized) {
|
||||
console.log('[HAL] Dispenser not initialized, re-initializing...')
|
||||
|
|
@ -391,6 +423,12 @@ export async function initializeHal(config: HalConfig): Promise<HalInstance> {
|
|||
count: c.count ?? 0,
|
||||
}))
|
||||
dispenserInitData = { fiatCode: valConfig.fiatCode, cassettes }
|
||||
// Without a dispenser the layout is still worth recording (the operator
|
||||
// config consumer keeps calling this), but there is nothing to re-init.
|
||||
if (!dispenser) {
|
||||
console.log('[HAL] Cassettes recorded; no dispenser fitted, nothing to re-init')
|
||||
return
|
||||
}
|
||||
// Close + re-init the dispenser so its internal per-bay state matches
|
||||
// the new layout. Errors here surface to the caller (operator-config
|
||||
// consumer) — the renderer can decide whether to retry.
|
||||
|
|
@ -407,7 +445,7 @@ export async function initializeHal(config: HalConfig): Promise<HalInstance> {
|
|||
return new Promise<void>((resolve) => {
|
||||
validator?.disable()
|
||||
validator?.lightOff()
|
||||
dispenser.close()
|
||||
dispenser?.close()
|
||||
if (validator) {
|
||||
validator.close((err?: Error) => {
|
||||
if (err) console.error('[HAL] Validator close error:', err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue