feat(ocpp): implement BootNotification, Heartbeat, and StatusNotification actions with database integration
This commit is contained in:
104
apps/csms/src/ocpp/actions/status-notification.ts
Normal file
104
apps/csms/src/ocpp/actions/status-notification.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { eq, and } from 'drizzle-orm'
|
||||
import { useDrizzle } from '@/lib/db.js'
|
||||
import { chargePoint, connector, connectorStatusHistory } from '@/db/schema.js'
|
||||
import type {
|
||||
StatusNotificationRequest,
|
||||
StatusNotificationResponse,
|
||||
OcppConnectionContext,
|
||||
} from '../types.ts'
|
||||
|
||||
// Mirror the enum values defined in ocpp-schema.ts for type safety
|
||||
type ConnectorStatus =
|
||||
| 'Available'
|
||||
| 'Preparing'
|
||||
| 'Charging'
|
||||
| 'SuspendedEVSE'
|
||||
| 'SuspendedEV'
|
||||
| 'Finishing'
|
||||
| 'Reserved'
|
||||
| 'Unavailable'
|
||||
| 'Faulted'
|
||||
|
||||
type ConnectorErrorCode =
|
||||
| 'NoError'
|
||||
| 'ConnectorLockFailure'
|
||||
| 'EVCommunicationError'
|
||||
| 'GroundFailure'
|
||||
| 'HighTemperature'
|
||||
| 'InternalError'
|
||||
| 'LocalListConflict'
|
||||
| 'OtherError'
|
||||
| 'OverCurrentFailure'
|
||||
| 'OverVoltage'
|
||||
| 'PowerMeterFailure'
|
||||
| 'PowerSwitchFailure'
|
||||
| 'ReaderFailure'
|
||||
| 'ResetFailure'
|
||||
| 'UnderVoltage'
|
||||
| 'WeakSignal'
|
||||
|
||||
export async function handleStatusNotification(
|
||||
payload: StatusNotificationRequest,
|
||||
ctx: OcppConnectionContext,
|
||||
): Promise<StatusNotificationResponse> {
|
||||
const db = useDrizzle()
|
||||
|
||||
// Retrieve the internal charge point id
|
||||
const [cp] = await db
|
||||
.select({ id: chargePoint.id })
|
||||
.from(chargePoint)
|
||||
.where(eq(chargePoint.chargePointIdentifier, ctx.chargePointIdentifier))
|
||||
.limit(1)
|
||||
|
||||
if (!cp) {
|
||||
throw new Error(`ChargePoint not found: ${ctx.chargePointIdentifier}`)
|
||||
}
|
||||
|
||||
const statusTimestamp = payload.timestamp ? new Date(payload.timestamp) : new Date()
|
||||
const connStatus = payload.status as ConnectorStatus
|
||||
const connErrorCode = payload.errorCode as ConnectorErrorCode
|
||||
|
||||
// Upsert connector and return the internal id for history insertion
|
||||
const [upsertedConnector] = await db
|
||||
.insert(connector)
|
||||
.values({
|
||||
id: crypto.randomUUID(),
|
||||
chargePointId: cp.id,
|
||||
connectorId: payload.connectorId,
|
||||
status: connStatus,
|
||||
errorCode: connErrorCode,
|
||||
info: payload.info ?? null,
|
||||
vendorId: payload.vendorId ?? null,
|
||||
vendorErrorCode: payload.vendorErrorCode ?? null,
|
||||
lastStatusAt: statusTimestamp,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: [connector.chargePointId, connector.connectorId],
|
||||
set: {
|
||||
status: connStatus,
|
||||
errorCode: connErrorCode,
|
||||
info: payload.info ?? null,
|
||||
vendorId: payload.vendorId ?? null,
|
||||
vendorErrorCode: payload.vendorErrorCode ?? null,
|
||||
lastStatusAt: statusTimestamp,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
})
|
||||
.returning({ id: connector.id })
|
||||
|
||||
if (upsertedConnector) {
|
||||
await db.insert(connectorStatusHistory).values({
|
||||
id: crypto.randomUUID(),
|
||||
connectorId: upsertedConnector.id,
|
||||
connectorNumber: payload.connectorId,
|
||||
status: connStatus,
|
||||
errorCode: connErrorCode,
|
||||
info: payload.info ?? null,
|
||||
vendorId: payload.vendorId ?? null,
|
||||
vendorErrorCode: payload.vendorErrorCode ?? null,
|
||||
statusTimestamp,
|
||||
})
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
Reference in New Issue
Block a user