From c4fadd7438a79a156743ddfe9f69376c9ddd9b01 Mon Sep 17 00:00:00 2001
From: Patrick Mulligan
Date: Mon, 16 Feb 2026 16:58:19 -0500
Subject: [PATCH] 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
---
apps/machine/electron/main.ts | 2 ++
apps/machine/electron/preload.ts | 1 +
apps/machine/src/services/hal.ts | 14 +++++++++++---
apps/machine/src/types/electron.d.ts | 4 ++++
apps/machine/src/views/CashInView.vue | 2 +-
5 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/apps/machine/electron/main.ts b/apps/machine/electron/main.ts
index 961efc6..6cc7e6f 100644
--- a/apps/machine/electron/main.ts
+++ b/apps/machine/electron/main.ts
@@ -97,6 +97,8 @@ ipcMain.handle('get-config', () => {
atmPrivateKey: process.env.VITE_ATM_PRIVATE_KEY || '',
adminToken: process.env.VITE_ADMIN_TOKEN || '',
appId: process.env.VITE_APP_ID || '',
+ appToken: process.env.VITE_APP_TOKEN || '',
+ linkingToken: process.env.VITE_LINKING_TOKEN || '',
// Hardware configuration
machineModel: process.env.VITE_LAMASSU_MACHINE_MODEL || 'sintra',
diff --git a/apps/machine/electron/preload.ts b/apps/machine/electron/preload.ts
index 506c0a1..f9622f0 100644
--- a/apps/machine/electron/preload.ts
+++ b/apps/machine/electron/preload.ts
@@ -19,6 +19,7 @@ export interface RuntimeConfig {
atmPrivateKey: string
adminToken: string
appId: string
+ appToken: string
machineModel: string
fiatCode: string
validatorDevice?: string
diff --git a/apps/machine/src/services/hal.ts b/apps/machine/src/services/hal.ts
index 489e115..77059e5 100644
--- a/apps/machine/src/services/hal.ts
+++ b/apps/machine/src/services/hal.ts
@@ -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 {
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()
diff --git a/apps/machine/src/types/electron.d.ts b/apps/machine/src/types/electron.d.ts
index 40b30e7..6610420 100644
--- a/apps/machine/src/types/electron.d.ts
+++ b/apps/machine/src/types/electron.d.ts
@@ -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
diff --git a/apps/machine/src/views/CashInView.vue b/apps/machine/src/views/CashInView.vue
index e71ac70..48c6d02 100644
--- a/apps/machine/src/views/CashInView.vue
+++ b/apps/machine/src/views/CashInView.vue
@@ -342,7 +342,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
- {{ currentQrValue.split('\n')[0].slice(0, 32) }}...
+ {{ currentQrValue.split('\n')[0]?.slice(0, 32) }}...