type IncomingRequestCommand,
MessageType,
MeterValueMeasurand,
- OCPPVersion,
+ type OCPPVersion,
type OutgoingRequest,
PowerUnits,
RegistrationStatusEnumType,
checkEvsesConfiguration,
checkStationInfoConnectorStatus,
checkTemplate,
- createBootNotificationRequest,
createSerialNumber,
getAmperageLimitationUnitDivider,
getBootConnectorStatus,
} from './Helpers.js'
import { IdTagsCache } from './IdTagsCache.js'
import {
+ buildBootNotificationRequest,
+ createOCPPServices,
flushQueuedTransactionMessages,
- OCPP16IncomingRequestService,
- OCPP16RequestService,
- OCPP16ResponseService,
- OCPP20IncomingRequestService,
- OCPP20RequestService,
- OCPP20ResponseService,
OCPPAuthServiceFactory,
type OCPPIncomingRequestService,
type OCPPRequestService,
this.configuredSupervisionUrl
)
}
- const bootNotificationRequest = createBootNotificationRequest(this.stationInfo)
+ const bootNotificationRequest = buildBootNotificationRequest(this.stationInfo)
if (bootNotificationRequest == null) {
const errorMsg = 'Error while creating boot notification request'
logger.error(`${this.logPrefix()} ${errorMsg}`)
private initializeOcppServices (): void {
const ocppVersion = this.stationInfo?.ocppVersion
- switch (ocppVersion) {
- case OCPPVersion.VERSION_16:
- this.ocppIncomingRequestService =
- OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>()
- this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
- OCPP16ResponseService.getInstance<OCPP16ResponseService>()
- )
- break
- case OCPPVersion.VERSION_20:
- case OCPPVersion.VERSION_201:
- this.ocppIncomingRequestService =
- OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>()
- this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
- OCPP20ResponseService.getInstance<OCPP20ResponseService>()
- )
- break
- default:
+ if (ocppVersion == null) {
+ this.handleUnsupportedVersion(ocppVersion)
+ return
+ }
+ try {
+ const services = createOCPPServices(ocppVersion)
+ this.ocppIncomingRequestService = services.incomingRequestService
+ this.ocppRequestService = services.requestService
+ } catch (error) {
+ if (error instanceof OCPPError && error.code === ErrorType.INTERNAL_ERROR) {
this.handleUnsupportedVersion(ocppVersion)
- break
+ }
+ throw error
}
}
import {
AmpereUnits,
AvailabilityType,
- type BootNotificationRequest,
- BootReasonEnumType,
type ChargingProfile,
ChargingProfileKindType,
ChargingProfilePurposeType,
return connectorStatus
}
-export const createBootNotificationRequest = (
- stationInfo: ChargingStationInfo,
- bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
-): BootNotificationRequest | undefined => {
- const ocppVersion = stationInfo.ocppVersion
- switch (ocppVersion) {
- case OCPPVersion.VERSION_16:
- return {
- chargePointModel: stationInfo.chargePointModel,
- chargePointVendor: stationInfo.chargePointVendor,
- ...(stationInfo.chargeBoxSerialNumber != null && {
- chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
- }),
- ...(stationInfo.chargePointSerialNumber != null && {
- chargePointSerialNumber: stationInfo.chargePointSerialNumber,
- }),
- ...(stationInfo.firmwareVersion != null && {
- firmwareVersion: stationInfo.firmwareVersion,
- }),
- ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
- ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
- ...(stationInfo.meterSerialNumber != null && {
- meterSerialNumber: stationInfo.meterSerialNumber,
- }),
- ...(stationInfo.meterType != null && {
- meterType: stationInfo.meterType,
- }),
- } satisfies BootNotificationRequest
- case OCPPVersion.VERSION_20:
- case OCPPVersion.VERSION_201:
- return {
- chargingStation: {
- model: stationInfo.chargePointModel,
- vendorName: stationInfo.chargePointVendor,
- ...(stationInfo.firmwareVersion != null && {
- firmwareVersion: stationInfo.firmwareVersion,
- }),
- ...(stationInfo.chargeBoxSerialNumber != null && {
- serialNumber: stationInfo.chargeBoxSerialNumber,
- }),
- ...((stationInfo.iccid != null || stationInfo.imsi != null) && {
- modem: {
- ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
- ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
- },
- }),
- },
- reason: bootReason,
- } satisfies BootNotificationRequest
- }
-}
-
export const warnTemplateKeysDeprecation = (
stationTemplate: ChargingStationTemplate,
logPrefix: string,
import { BaseError } from '../../../exception/index.js'
import {
ChargePointErrorCode,
+ type ChargingStationInfo,
type ConfigurationKey,
type GenericResponse,
type MeterValuesRequest,
type MeterValuesResponse,
OCPP16AuthorizationStatus,
type OCPP16AvailabilityType,
+ type OCPP16BootNotificationRequest,
type OCPP16ChangeAvailabilityResponse,
OCPP16ChargePointStatus,
type OCPP16ChargingProfile,
[OCPP16RequestCommand.STOP_TRANSACTION, 'StopTransaction'],
]
+ /**
+ * Builds an OCPP 1.6 BootNotification request payload from station info.
+ * @param stationInfo - Charging station information
+ * @returns Formatted OCPP 1.6 BootNotification request payload
+ */
+ public static buildBootNotificationRequest (
+ stationInfo: ChargingStationInfo
+ ): OCPP16BootNotificationRequest {
+ return {
+ chargePointModel: stationInfo.chargePointModel,
+ chargePointVendor: stationInfo.chargePointVendor,
+ ...(stationInfo.chargeBoxSerialNumber != null && {
+ chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
+ }),
+ ...(stationInfo.chargePointSerialNumber != null && {
+ chargePointSerialNumber: stationInfo.chargePointSerialNumber,
+ }),
+ ...(stationInfo.firmwareVersion != null && {
+ firmwareVersion: stationInfo.firmwareVersion,
+ }),
+ ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+ ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+ ...(stationInfo.meterSerialNumber != null && {
+ meterSerialNumber: stationInfo.meterSerialNumber,
+ }),
+ ...(stationInfo.meterType != null && {
+ meterType: stationInfo.meterType,
+ }),
+ } satisfies OCPP16BootNotificationRequest
+ }
+
/**
* @param commandParams - Status notification parameters
* @returns Formatted OCPP 1.6 StatusNotification request payload
import { type ChargingStation, resetConnectorStatus } from '../../../charging-station/index.js'
import { OCPPError } from '../../../exception/index.js'
import {
+ BootReasonEnumType,
+ type ChargingStationInfo,
type ConnectorStatus,
ConnectorStatusEnum,
ErrorType,
+ type OCPP20BootNotificationRequest,
OCPP20ChargingStateEnumType,
OCPP20ComponentName,
type OCPP20ConnectorStatusEnumType,
[OCPP20RequestCommand.TRANSACTION_EVENT, 'TransactionEvent'],
]
+ /**
+ * Builds an OCPP 2.0 BootNotification request payload from station info.
+ * @param stationInfo - Charging station information
+ * @param bootReason - Reason for the boot notification
+ * @returns Formatted OCPP 2.0 BootNotification request payload
+ */
+ public static buildBootNotificationRequest (
+ stationInfo: ChargingStationInfo,
+ bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
+ ): OCPP20BootNotificationRequest {
+ return {
+ chargingStation: {
+ model: stationInfo.chargePointModel,
+ vendorName: stationInfo.chargePointVendor,
+ ...(stationInfo.firmwareVersion != null && {
+ firmwareVersion: stationInfo.firmwareVersion,
+ }),
+ ...(stationInfo.chargeBoxSerialNumber != null && {
+ serialNumber: stationInfo.chargeBoxSerialNumber,
+ }),
+ ...((stationInfo.iccid != null || stationInfo.imsi != null) && {
+ modem: {
+ ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+ ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+ },
+ }),
+ },
+ reason: bootReason,
+ } satisfies OCPP20BootNotificationRequest
+ }
+
/**
* @param chargingStation - Target charging station for EVSE resolution
* @param commandParams - Status notification parameters
--- /dev/null
+import type { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
+import type { OCPPRequestService } from './OCPPRequestService.js'
+
+import { OCPPError } from '../../exception/index.js'
+import { ErrorType, OCPPVersion } from '../../types/index.js'
+import { OCPP16IncomingRequestService } from './1.6/OCPP16IncomingRequestService.js'
+import { OCPP16RequestService } from './1.6/OCPP16RequestService.js'
+import { OCPP16ResponseService } from './1.6/OCPP16ResponseService.js'
+import { OCPP20IncomingRequestService } from './2.0/OCPP20IncomingRequestService.js'
+import { OCPP20RequestService } from './2.0/OCPP20RequestService.js'
+import { OCPP20ResponseService } from './2.0/OCPP20ResponseService.js'
+
+/**
+ * Creates OCPP request and incoming request service instances for the given OCPP version.
+ * @param ocppVersion - OCPP protocol version to create services for.
+ * @returns An object containing the incoming request service and request service instances.
+ * @throws {OCPPError} If the OCPP version is not supported.
+ */
+export const createOCPPServices = (
+ ocppVersion: OCPPVersion
+): {
+ incomingRequestService: OCPPIncomingRequestService
+ requestService: OCPPRequestService
+} => {
+ switch (ocppVersion) {
+ case OCPPVersion.VERSION_16:
+ return {
+ incomingRequestService:
+ OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>(),
+ requestService: OCPP16RequestService.getInstance<OCPP16RequestService>(
+ OCPP16ResponseService.getInstance<OCPP16ResponseService>()
+ ),
+ }
+ case OCPPVersion.VERSION_20:
+ case OCPPVersion.VERSION_201:
+ return {
+ incomingRequestService:
+ OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>(),
+ requestService: OCPP20RequestService.getInstance<OCPP20RequestService>(
+ OCPP20ResponseService.getInstance<OCPP20ResponseService>()
+ ),
+ }
+ default:
+ throw new OCPPError(
+ ErrorType.INTERNAL_ERROR,
+ `Unsupported OCPP version '${ocppVersion as string}'`
+ )
+ }
+}
-import type { StopTransactionReason } from '../../types/index.js'
+import type { BootReasonEnumType, StopTransactionReason } from '../../types/index.js'
import { type ChargingStation } from '../../charging-station/index.js'
import { OCPPError } from '../../exception/index.js'
import {
AuthorizationStatus,
+ type BootNotificationRequest,
+ type ChargingStationInfo,
ErrorType,
OCPP20AuthorizationStatusEnumType,
OCPP20IdTokenEnumType,
break
}
}
+
+/**
+ * Builds an OCPP BootNotification request using the appropriate version-specific handler.
+ * @param stationInfo - Charging station information
+ * @param bootReason - Optional boot reason (OCPP 2.0 only)
+ * @returns The BootNotification request payload, or undefined if the OCPP version is unsupported
+ */
+export const buildBootNotificationRequest = (
+ stationInfo: ChargingStationInfo,
+ bootReason?: BootReasonEnumType
+): BootNotificationRequest | undefined => {
+ switch (stationInfo.ocppVersion) {
+ case OCPPVersion.VERSION_16:
+ return OCPP16ServiceUtils.buildBootNotificationRequest(stationInfo)
+ case OCPPVersion.VERSION_20:
+ case OCPPVersion.VERSION_201:
+ return OCPP20ServiceUtils.buildBootNotificationRequest(stationInfo, bootReason)
+ default:
+ return undefined
+ }
+}
export { isIdTagAuthorized } from './IdTagAuthorization.js'
export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
export { OCPPRequestService } from './OCPPRequestService.js'
+export { createOCPPServices } from './OCPPServiceFactory.js'
export {
+ buildBootNotificationRequest,
flushQueuedTransactionMessages,
startTransactionOnConnector,
stopRunningTransactions,
--- /dev/null
+/**
+ * @file Tests for OCPPServiceFactory
+ * @description Verifies that createOCPPServices returns correct service instances for each OCPP
+ * version and throws OCPPError for unsupported versions.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, describe, it } from 'node:test'
+
+import { OCPP16IncomingRequestService } from '../../../src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.js'
+import { OCPP16RequestService } from '../../../src/charging-station/ocpp/1.6/OCPP16RequestService.js'
+import { OCPP20IncomingRequestService } from '../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
+import { OCPP20RequestService } from '../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js'
+import { createOCPPServices } from '../../../src/charging-station/ocpp/OCPPServiceFactory.js'
+import { OCPPError } from '../../../src/exception/index.js'
+import { OCPPVersion } from '../../../src/types/index.js'
+import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
+
+await describe('OCPPServiceFactory', async () => {
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ await it('should return OCPP 1.6 services for VERSION_16', () => {
+ const { incomingRequestService, requestService } = createOCPPServices(OCPPVersion.VERSION_16)
+
+ assert.ok(incomingRequestService instanceof OCPP16IncomingRequestService)
+ assert.ok(requestService instanceof OCPP16RequestService)
+ })
+
+ await it('should return OCPP 2.0 services for VERSION_20', () => {
+ const { incomingRequestService, requestService } = createOCPPServices(OCPPVersion.VERSION_20)
+
+ assert.ok(incomingRequestService instanceof OCPP20IncomingRequestService)
+ assert.ok(requestService instanceof OCPP20RequestService)
+ })
+
+ await it('should return OCPP 2.0 services for VERSION_201', () => {
+ const { incomingRequestService, requestService } = createOCPPServices(OCPPVersion.VERSION_201)
+
+ assert.ok(incomingRequestService instanceof OCPP20IncomingRequestService)
+ assert.ok(requestService instanceof OCPP20RequestService)
+ })
+
+ await it('should throw OCPPError for unsupported version', () => {
+ assert.throws(
+ () => {
+ createOCPPServices('3.0' as unknown as OCPPVersion)
+ },
+ (error: unknown) => {
+ assert.ok(error instanceof OCPPError)
+ assert.ok(error.message.includes('Unsupported OCPP version'))
+ return true
+ }
+ )
+ })
+})
/**
* @file Tests for OCPPServiceOperations version-dispatching functions
* @description Verifies startTransactionOnConnector, stopTransactionOnConnector,
- * stopRunningTransactions, and flushQueuedTransactionMessages
- * cross-version dispatchers
+ * stopRunningTransactions, flushQueuedTransactionMessages, and
+ * buildBootNotificationRequest cross-version dispatchers
*/
import assert from 'node:assert/strict'
import { afterEach, describe, it, mock } from 'node:test'
import type { ChargingStation } from '../../../src/charging-station/index.js'
+import type { ChargingStationInfo } from '../../../src/types/index.js'
import type { MockChargingStationOptions } from '../helpers/StationHelpers.js'
import {
+ buildBootNotificationRequest,
flushQueuedTransactionMessages,
startTransactionOnConnector,
stopRunningTransactions,
stopTransactionOnConnector,
} from '../../../src/charging-station/ocpp/OCPPServiceOperations.js'
-import { type OCPP20TransactionEventRequest, OCPPVersion } from '../../../src/types/index.js'
+import {
+ BootReasonEnumType,
+ type OCPP20TransactionEventRequest,
+ OCPPVersion,
+} from '../../../src/types/index.js'
import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
import { createMockChargingStation } from '../ChargingStationTestUtils.js'
assert.strictEqual(connectorStatus.transactionEventQueue.length, 0)
})
})
+
+ await describe('buildBootNotificationRequest', async () => {
+ await describe('OCPP 1.6', async () => {
+ await it('should build OCPP 1.6 boot notification with required fields', () => {
+ const stationInfo = {
+ chargePointModel: 'TestModel',
+ chargePointVendor: 'TestVendor',
+ ocppVersion: OCPPVersion.VERSION_16,
+ } as unknown as ChargingStationInfo
+
+ const result = buildBootNotificationRequest(stationInfo)
+
+ assert.notStrictEqual(result, undefined)
+ assert.deepStrictEqual(result, {
+ chargePointModel: 'TestModel',
+ chargePointVendor: 'TestVendor',
+ })
+ })
+
+ await it('should build OCPP 1.6 boot notification with optional fields', () => {
+ // Arrange
+ const stationInfo = {
+ chargeBoxSerialNumber: 'CB-001',
+ chargePointModel: 'TestModel',
+ chargePointSerialNumber: 'CP-001',
+ chargePointVendor: 'TestVendor',
+ firmwareVersion: '1.0.0',
+ iccid: '8901234567890',
+ imsi: '310150123456789',
+ meterSerialNumber: 'M-001',
+ meterType: 'ACMeter',
+ ocppVersion: OCPPVersion.VERSION_16,
+ } as unknown as ChargingStationInfo
+
+ // Act
+ const result = buildBootNotificationRequest(stationInfo)
+
+ // Assert
+ assert.deepStrictEqual(result, {
+ chargeBoxSerialNumber: 'CB-001',
+ chargePointModel: 'TestModel',
+ chargePointSerialNumber: 'CP-001',
+ chargePointVendor: 'TestVendor',
+ firmwareVersion: '1.0.0',
+ iccid: '8901234567890',
+ imsi: '310150123456789',
+ meterSerialNumber: 'M-001',
+ meterType: 'ACMeter',
+ })
+ })
+ })
+
+ await describe('OCPP 2.0', async () => {
+ await it('should build OCPP 2.0 boot notification with required fields', () => {
+ const stationInfo = {
+ chargePointModel: 'TestModel',
+ chargePointVendor: 'TestVendor',
+ ocppVersion: OCPPVersion.VERSION_20,
+ } as unknown as ChargingStationInfo
+
+ const result = buildBootNotificationRequest(stationInfo)
+
+ assert.notStrictEqual(result, undefined)
+ assert.deepStrictEqual(result, {
+ chargingStation: {
+ model: 'TestModel',
+ vendorName: 'TestVendor',
+ },
+ reason: BootReasonEnumType.PowerUp,
+ })
+ })
+
+ await it('should build OCPP 2.0 boot notification with optional fields and modem', () => {
+ // Arrange
+ const stationInfo = {
+ chargeBoxSerialNumber: 'CB-001',
+ chargePointModel: 'TestModel',
+ chargePointVendor: 'TestVendor',
+ firmwareVersion: '2.0.0',
+ iccid: '8901234567890',
+ imsi: '310150123456789',
+ ocppVersion: OCPPVersion.VERSION_201,
+ } as unknown as ChargingStationInfo
+
+ // Act
+ const result = buildBootNotificationRequest(stationInfo)
+
+ // Assert
+ assert.deepStrictEqual(result, {
+ chargingStation: {
+ firmwareVersion: '2.0.0',
+ model: 'TestModel',
+ modem: {
+ iccid: '8901234567890',
+ imsi: '310150123456789',
+ },
+ serialNumber: 'CB-001',
+ vendorName: 'TestVendor',
+ },
+ reason: BootReasonEnumType.PowerUp,
+ })
+ })
+
+ await it('should build OCPP 2.0 boot notification with custom boot reason', () => {
+ const stationInfo = {
+ chargePointModel: 'TestModel',
+ chargePointVendor: 'TestVendor',
+ ocppVersion: OCPPVersion.VERSION_20,
+ } as unknown as ChargingStationInfo
+
+ const result = buildBootNotificationRequest(stationInfo, BootReasonEnumType.RemoteReset)
+
+ assert.notStrictEqual(result, undefined)
+ assert.deepStrictEqual(result, {
+ chargingStation: {
+ model: 'TestModel',
+ vendorName: 'TestVendor',
+ },
+ reason: BootReasonEnumType.RemoteReset,
+ })
+ })
+ })
+
+ await it('should return undefined for unsupported version', () => {
+ const stationInfo = {
+ chargePointModel: 'TestModel',
+ chargePointVendor: 'TestVendor',
+ ocppVersion: '3.0',
+ } as unknown as ChargingStationInfo
+
+ const result = buildBootNotificationRequest(stationInfo)
+
+ assert.strictEqual(result, undefined)
+ })
+ })
})