import { useDrizzle } from '@/lib/db.js' import dayjs from 'dayjs' import { chargePoint } from '@/db/schema.js' import type { BootNotificationRequest, BootNotificationResponse, OcppConnectionContext, } from '../types.ts' const DEFAULT_HEARTBEAT_INTERVAL = 60 const BOOT_NOTIFICATION_DB_TIMEOUT_MS = 3000 function withTimeout(promise: Promise, timeoutMs: number, label: string) { return Promise.race([ promise, new Promise((_, reject) => { setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs) }), ]) } export async function handleBootNotification( payload: BootNotificationRequest, ctx: OcppConnectionContext, ): Promise { const db = useDrizzle() const currentTime = dayjs().toISOString() console.log(`[OCPP] BootNotification ${ctx.chargePointIdentifier} received`) try { const [cp] = await withTimeout( db .insert(chargePoint) .values({ id: crypto.randomUUID(), chargePointIdentifier: ctx.chargePointIdentifier, chargePointVendor: payload.chargePointVendor, chargePointModel: payload.chargePointModel, chargePointSerialNumber: payload.chargePointSerialNumber ?? null, firmwareVersion: payload.firmwareVersion ?? null, iccid: payload.iccid ?? null, imsi: payload.imsi ?? null, meterType: payload.meterType ?? null, meterSerialNumber: payload.meterSerialNumber ?? null, // New, unknown devices start as Pending — admin must manually accept them registrationStatus: 'Pending', heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL, lastBootNotificationAt: dayjs().toDate(), }) .onConflictDoUpdate({ target: chargePoint.chargePointIdentifier, set: { chargePointVendor: payload.chargePointVendor, chargePointModel: payload.chargePointModel, chargePointSerialNumber: payload.chargePointSerialNumber ?? null, firmwareVersion: payload.firmwareVersion ?? null, iccid: payload.iccid ?? null, imsi: payload.imsi ?? null, meterType: payload.meterType ?? null, meterSerialNumber: payload.meterSerialNumber ?? null, // Do NOT override registrationStatus — preserve whatever the admin set heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL, lastBootNotificationAt: dayjs().toDate(), updatedAt: dayjs().toDate(), }, }) .returning(), BOOT_NOTIFICATION_DB_TIMEOUT_MS, `BootNotification persistence for ${ctx.chargePointIdentifier}`, ) const status = cp.registrationStatus ctx.isRegistered = status === 'Accepted' console.log(`[OCPP] BootNotification ${ctx.chargePointIdentifier} status=${status}`) return { currentTime, interval: DEFAULT_HEARTBEAT_INTERVAL, status, } } catch (error) { ctx.isRegistered = false console.error(`[OCPP] BootNotification ${ctx.chargePointIdentifier} persistence failed:`, error) return { currentTime, interval: DEFAULT_HEARTBEAT_INTERVAL, status: 'Pending', } } }