diff --git a/apps/machine/src/composables/useAvailabilityBroadcast.ts b/apps/machine/src/composables/useAvailabilityBroadcast.ts index 1cc5e55..ccbdb8c 100644 --- a/apps/machine/src/composables/useAvailabilityBroadcast.ts +++ b/apps/machine/src/composables/useAvailabilityBroadcast.ts @@ -93,9 +93,22 @@ export function useAvailabilityBroadcast(options: UseAvailabilityBroadcastOption { deep: true } ) + // Heartbeat: republish every 5 minutes so monitors know the machine is alive. + // If the latest Kind 30078 event is older than ~10 minutes, the machine is offline. + const heartbeatInterval = setInterval( + () => { + publish(computeAvailability()) + }, + 5 * 60 * 1000 + ) + // Publish immediately on setup const initial = computeAvailability() publish(initial) - return { computeAvailability } + function stop() { + clearInterval(heartbeatInterval) + } + + return { computeAvailability, stop } } diff --git a/apps/machine/src/stores/atm.ts b/apps/machine/src/stores/atm.ts index 1bcd1ec..1dc58ce 100644 --- a/apps/machine/src/stores/atm.ts +++ b/apps/machine/src/stores/atm.ts @@ -21,6 +21,7 @@ import { type ManagementResponse, } from '@lamassu/clink' import type { TransactionRecord } from '@/types/state' +import { useAvailabilityBroadcast } from '@/composables/useAvailabilityBroadcast' // Check if we're running in Electron const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined @@ -308,6 +309,10 @@ export const useAtmStore = defineStore('atm', () => { return snapshot.value?.context ?? null }) + const inventory = computed((): Record => { + return context.value?.inventory ?? {} + }) + const fiatSymbol = computed(() => { const symbols: Record = { USD: '$', GTQ: 'Q', EUR: '€', GBP: '£', MXN: '$' } return symbols[fiatCode.value] || fiatCode.value @@ -557,6 +562,9 @@ export const useAtmStore = defineStore('atm', () => { // Initialize state machine with real services initialize(servicesWithInventory) + + // Start broadcasting availability (Kind 30078) with 5-minute heartbeat + startAvailabilityBroadcast(services.nostrClient, services.identity, 'atm') } catch (error) { console.error('[ATM] Failed to connect to Lightning.Pub:', error) connectionStatus.value = 'error' @@ -847,6 +855,9 @@ export const useAtmStore = defineStore('atm', () => { } }) + // Start broadcasting availability (Kind 30078) + startAvailabilityBroadcast(lightning.nostrClient, lightning.identity, 'atm') + console.log('[ATM] Fully initialized with HAL + Lightning') } catch (error) { console.error('[ATM] HAL initialization failed:', error) @@ -1099,6 +1110,9 @@ export const useAtmStore = defineStore('atm', () => { // Real hardware connected — disable mock bill simulator debugMode.value = false + // Start broadcasting availability (Kind 30078) + startAvailabilityBroadcast(lightning.nostrClient, lightning.identity, 'atm') + console.log('[ATM] Fully initialized with HAL (IPC) + Lightning') } catch (error) { console.error('[ATM] HAL initialization failed:', error) @@ -1225,6 +1239,23 @@ export const useAtmStore = defineStore('atm', () => { } } + /** Start broadcasting ATM availability (Kind 30078) with 5-minute heartbeat */ + let stopAvailabilityBroadcast: (() => void) | null = null + + function startAvailabilityBroadcast(nostrClient: any, identity: any, model: string) { + if (stopAvailabilityBroadcast) return + + const { stop } = useAvailabilityBroadcast({ + nostrClient, + identity, + inventory, + balanceSats, + fiatCode: fiatCode.value, + model, + }) + stopAvailabilityBroadcast = stop + } + return { // State actor, diff --git a/deploy/nixos/hardware/batm3.nix b/deploy/nixos/hardware/batm3.nix index 363c3c9..b12e2bb 100644 --- a/deploy/nixos/hardware/batm3.nix +++ b/deploy/nixos/hardware/batm3.nix @@ -68,6 +68,43 @@ hybrid-sleep.enable = false; }; + # WiFi: read credentials from /var/lib/lamassu-atm/wifi.conf (not in repo) + # Format: SSID=MyNetwork\nPSK=MyPassword + system.activationScripts.wifi-setup = '' + WIFI_CONF="/var/lib/lamassu-atm/wifi.conf" + if [ -f "$WIFI_CONF" ]; then + SSID=$(grep '^SSID=' "$WIFI_CONF" | cut -d= -f2-) + PSK=$(grep '^PSK=' "$WIFI_CONF" | cut -d= -f2-) + if [ -n "$SSID" ] && [ -n "$PSK" ]; then + CONN_FILE="/etc/NetworkManager/system-connections/$SSID.nmconnection" + if [ ! -f "$CONN_FILE" ]; then + cat > "$CONN_FILE" << EOF + [connection] + id=$SSID + type=wifi + autoconnect=true + + [wifi] + mode=infrastructure + ssid=$SSID + + [wifi-security] + key-mgmt=wpa-psk + psk=$PSK + + [ipv4] + method=auto + + [ipv6] + method=auto + EOF + chmod 600 "$CONN_FILE" + echo "[WiFi] Created connection for $SSID" + fi + fi + fi + ''; + # eGalax touchscreen (Dell 9030 AIO built-in panel) # The eGalax HID descriptor confuses libinput (treats it as touchpad). # Fix: unbind from usbhid at boot, bind to usbtouchscreen kernel module,