feat(machine): add Electron serial port IPC for hardware access

Expose serialport APIs through Electron preload for bill validator
and dispenser communication. Update HAL service with device path
configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-02-16 16:58:19 -05:00
commit c4fadd7438
5 changed files with 19 additions and 4 deletions

View file

@ -35,6 +35,8 @@ export interface HalConfig {
}
export interface ValidatorCallbacks {
/** Called when a bill is read but not yet accepted. Return true to accept, false to reject. */
shouldAcceptBill: (denomination: number) => boolean
onBillInserted: (denomination: number) => void
onBillRejected: (reason: string) => void
onError: (error: string) => void
@ -157,9 +159,15 @@ export async function initializeHalServices(config: HalConfig): Promise<HalServi
connectValidator: (callbacks: ValidatorCallbacks) => {
validator.on('billsRead', (data: { denomination: number | null; code: number }) => {
if (data.denomination !== null) {
// Auto-stack the bill (accept it into the cash box)
validator.stack()
callbacks.onBillInserted(data.denomination)
// Check if we should accept this bill (balance check)
if (callbacks.shouldAcceptBill(data.denomination)) {
validator.stack()
callbacks.onBillInserted(data.denomination)
} else {
console.log('[HAL] Bill rejected: insufficient ATM balance for', data.denomination)
validator.reject()
callbacks.onBillRejected('insufficient_balance')
}
} else {
console.log('[HAL] Unknown denomination, rejecting. Code: 0x' + data.code.toString(16))
validator.reject()

View file

@ -6,8 +6,12 @@ export interface RuntimeConfig {
relayUrl: string
lightningPubPubkey: string
lightningPubApiUrl: string
extensionApiUrl: string
atmPrivateKey: string
adminToken: string
appId: string
appToken: string
linkingToken: string
machineModel: string
fiatCode: string
validatorDevice?: string

View file

@ -342,7 +342,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
</p>
<div v-if="currentQrValue" class="flex items-center justify-center gap-2">
<code class="text-xs text-muted-foreground truncate max-w-[280px]">
{{ currentQrValue.split('\n')[0].slice(0, 32) }}...
{{ currentQrValue.split('\n')[0]?.slice(0, 32) }}...
</code>
<Button variant="ghost" size="sm" class="h-6 w-6 p-0" @click="copyQrValue">
<span v-if="copied">✓</span>