]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp): dissolve OCPPServiceUtils class and eliminate dynamic imports
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 30 Mar 2026 21:45:32 +0000 (23:45 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 30 Mar 2026 21:45:32 +0000 (23:45 +0200)
Dissolve the OCPPServiceUtils class into standalone exported functions,
removing the class hierarchy (extends) from OCPP16/20ServiceUtils.

Extract 6 version-dispatching operations into OCPPServiceOperations.ts
with static top-level imports, eliminating all 11 dynamic await import()
calls that were used to work around circular dependencies.

The dependency graph is now a clean DAG:
  OCPPServiceUtils.ts (pure utilities, no version knowledge of subclasses)
  OCPP16/20ServiceUtils.ts (version-specific, import utils directly)
  OCPPServiceOperations.ts (dispatchers, static imports of both)

Cross-component imports now go through barrel files per project conventions.
JSDoc harmonized across all modified files.

22 files changed:
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20RequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPServiceOperations.ts [new file with mode: 0644]
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ocpp/auth/cache/index.ts [deleted file]
src/charging-station/ocpp/auth/factories/index.ts [deleted file]
src/charging-station/ocpp/auth/index.ts
src/charging-station/ocpp/auth/utils/index.ts [deleted file]
src/charging-station/ocpp/index.ts
tests/charging-station/ocpp/1.6/OCPP16ServiceUtils.test.ts
tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts
tests/charging-station/ocpp/OCPPServiceUtils-StopTransaction.test.ts
tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts
tests/charging-station/ocpp/OCPPServiceUtils-validation.test.ts

index 654e758392b3676f0ec32de769a37b33d65df0cf..0d630156df2d5c01afdb53d5082a7d7d1895f93e 100644 (file)
@@ -28,8 +28,11 @@ import {
 } from '../utils/index.js'
 import { checkChargingStationState } from './Helpers.js'
 import { IdTagsCache } from './IdTagsCache.js'
-import { isIdTagAuthorized } from './ocpp/index.js'
-import { startTransactionOnConnector, stopTransactionOnConnector } from './ocpp/OCPPServiceUtils.js'
+import {
+  isIdTagAuthorized,
+  startTransactionOnConnector,
+  stopTransactionOnConnector,
+} from './ocpp/index.js'
 
 export class AutomaticTransactionGenerator {
   private static readonly instances: Map<string, AutomaticTransactionGenerator> = new Map<
index 8f96d7b398090a612f80d9d97473adc9f9d79fdc..4dab126ac2f6c4800236a999242b98c269560f58 100644 (file)
@@ -146,6 +146,7 @@ import {
 } from './Helpers.js'
 import { IdTagsCache } from './IdTagsCache.js'
 import {
+  flushQueuedTransactionMessages,
   OCPP16IncomingRequestService,
   OCPP16RequestService,
   OCPP16ResponseService,
@@ -156,8 +157,8 @@ import {
   type OCPPIncomingRequestService,
   type OCPPRequestService,
   sendAndSetConnectorStatus,
+  stopRunningTransactions,
 } from './ocpp/index.js'
-import { flushQueuedTransactionMessages, stopRunningTransactions } from './ocpp/OCPPServiceUtils.js'
 import { SharedLRUCache } from './SharedLRUCache.js'
 
 export class ChargingStation extends EventEmitter {
index bbb9840ebf5a9fecb43fedba9956eac6a87174c5..02a33d5382675f8e5eeedd2acd7269afa8ec2dfa 100644 (file)
@@ -119,7 +119,14 @@ import { AuthContext } from '../auth/index.js'
 import { isIdTagAuthorized } from '../IdTagAuthorization.js'
 import { OCPPConstants } from '../OCPPConstants.js'
 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
-import { buildMeterValue, sendAndSetConnectorStatus } from '../OCPPServiceUtils.js'
+import {
+  buildMeterValue,
+  createPayloadValidatorMap,
+  isConnectorIdValid,
+  isIncomingRequestCommandSupported,
+  isMessageTriggerSupported,
+  sendAndSetConnectorStatus,
+} from '../OCPPServiceUtils.js'
 import { OCPP16Constants } from './OCPP16Constants.js'
 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
 
@@ -172,6 +179,9 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
   ]
 
+  /**
+   * Constructs an OCPP 1.6 Incoming Request Service with request handlers, validators, and event listeners.
+   */
   public constructor () {
     super(OCPPVersion.VERSION_16)
     this.incomingRequestHandlers = new Map<IncomingRequestCommand, IncomingRequestHandler>([
@@ -244,7 +254,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         this.toRequestHandler(this.handleRequestUpdateFirmware.bind(this)),
       ],
     ])
-    this.payloadValidatorFunctions = OCPP16ServiceUtils.createPayloadValidatorMap(
+    this.payloadValidatorFunctions = createPayloadValidatorMap(
       OCPP16ServiceUtils.createIncomingRequestPayloadConfigs(),
       OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
@@ -556,15 +566,25 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     )
   }
 
+  /**
+   * Stops the incoming request service for the given charging station.
+   * @param chargingStation - Target charging station
+   */
   public override stop (chargingStation: ChargingStation): void {
     /* no-op for OCPP 1.6 */
   }
 
+  /**
+   * Checks whether the given incoming request command is supported by the charging station.
+   * @param chargingStation - Target charging station
+   * @param commandName - Incoming request command to check
+   * @returns Whether the command is supported
+   */
   protected isIncomingRequestCommandSupported (
     chargingStation: ChargingStation,
     commandName: IncomingRequestCommand
   ): boolean {
-    return OCPP16ServiceUtils.isIncomingRequestCommandSupported(
+    return isIncomingRequestCommandSupported(
       chargingStation,
       commandName as OCPP16IncomingRequestCommand
     )
@@ -1494,13 +1514,13 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         OCPP16SupportedFeatureProfiles.RemoteTrigger,
         OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
       ) ||
-      !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
+      !isMessageTriggerSupported(chargingStation, requestedMessage)
     ) {
       return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
     }
     if (
       connectorId != null &&
-      !OCPP16ServiceUtils.isConnectorIdValid(
+      !isConnectorIdValid(
         chargingStation,
         OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
         connectorId
index f57d7d620d8a943b0f6d5c32ad84037aafab4764..63891195026850d993d24398e029adaebbd65770 100644 (file)
@@ -21,6 +21,8 @@ import { OCPPRequestService } from '../OCPPRequestService.js'
 import {
   buildStatusNotificationRequest,
   buildTransactionEndMeterValue,
+  createPayloadValidatorMap,
+  isRequestCommandSupported,
   sendAndSetConnectorStatus,
 } from '../OCPPServiceUtils.js'
 import { OCPP16Constants } from './OCPP16Constants.js'
@@ -59,7 +61,7 @@ export class OCPP16RequestService extends OCPPRequestService {
    */
   public constructor (ocppResponseService: OCPPResponseService) {
     super(OCPPVersion.VERSION_16, ocppResponseService)
-    this.payloadValidatorFunctions = OCPP16ServiceUtils.createPayloadValidatorMap(
+    this.payloadValidatorFunctions = createPayloadValidatorMap(
       OCPP16ServiceUtils.createRequestPayloadConfigs(),
       OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
@@ -98,7 +100,7 @@ export class OCPP16RequestService extends OCPPRequestService {
     logger.debug(
       `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Processing '${commandName}' request`
     )
-    if (OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
+    if (isRequestCommandSupported(chargingStation, commandName)) {
       try {
         logger.debug(
           `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Building request payload for '${commandName}'`
index 2b8eb41cf4b614059db9a62bb2eeb6282481e29a..32cc33db27e199d19aff108c9fc9d11afa18eea0 100644 (file)
@@ -39,6 +39,8 @@ import { Constants, convertToInt, logger, truncateId } from '../../../utils/inde
 import { OCPPResponseService } from '../OCPPResponseService.js'
 import {
   buildTransactionEndMeterValue,
+  createPayloadValidatorMap,
+  isRequestCommandSupported,
   restoreConnectorStatus,
   sendAndSetConnectorStatus,
 } from '../OCPPServiceUtils.js'
@@ -94,6 +96,9 @@ export class OCPP16ResponseService extends OCPPResponseService {
 
   protected readonly responseHandlers: Map<RequestCommand, ResponseHandler>
 
+  /**
+   * Constructs an OCPP 1.6 Response Service instance with response handlers and validators.
+   */
   public constructor () {
     super(OCPPVersion.VERSION_16)
     this.responseHandlers = new Map<RequestCommand, ResponseHandler>([
@@ -126,27 +131,29 @@ export class OCPP16ResponseService extends OCPPResponseService {
         this.toResponseHandler(this.handleResponseStopTransaction.bind(this)),
       ],
     ])
-    this.payloadValidatorFunctions = OCPP16ServiceUtils.createPayloadValidatorMap(
+    this.payloadValidatorFunctions = createPayloadValidatorMap(
       OCPP16ServiceUtils.createResponsePayloadConfigs(),
       OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
-    this.incomingRequestResponsePayloadValidateFunctions =
-      OCPP16ServiceUtils.createPayloadValidatorMap(
-        OCPP16ServiceUtils.createIncomingRequestResponsePayloadConfigs(),
-        OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
-        this.ajvIncomingRequest
-      )
+    this.incomingRequestResponsePayloadValidateFunctions = createPayloadValidatorMap(
+      OCPP16ServiceUtils.createIncomingRequestResponsePayloadConfigs(),
+      OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
+      this.ajvIncomingRequest
+    )
   }
 
+  /**
+   * Checks whether the given request command is supported by the charging station.
+   * @param chargingStation - Target charging station
+   * @param commandName - Request command to check
+   * @returns Whether the command is supported
+   */
   protected isRequestCommandSupported (
     chargingStation: ChargingStation,
     commandName: RequestCommand
   ): boolean {
-    return OCPP16ServiceUtils.isRequestCommandSupported(
-      chargingStation,
-      commandName as OCPP16RequestCommand
-    )
+    return isRequestCommandSupported(chargingStation, commandName as OCPP16RequestCommand)
   }
 
   private handleResponseAuthorize (
index 7807a54a94e337b797971efe88043b04af1a1857..026ba05e949463ba92f20f0255f827e64d380e0b 100644 (file)
@@ -57,14 +57,16 @@ import {
   buildSampledValue,
   buildTransactionEndMeterValue,
   getSampledValueTemplate,
-  OCPPServiceUtils,
+  PayloadValidatorConfig,
+  PayloadValidatorOptions,
   sendAndSetConnectorStatus,
 } from '../OCPPServiceUtils.js'
 import { OCPP16Constants } from './OCPP16Constants.js'
 
 const moduleName = 'OCPP16ServiceUtils'
 
-export class OCPP16ServiceUtils extends OCPPServiceUtils {
+// eslint-disable-next-line @typescript-eslint/no-extraneous-class
+export class OCPP16ServiceUtils {
   private static readonly incomingRequestSchemaNames: readonly [
     OCPP16IncomingRequestCommand,
     string
@@ -101,6 +103,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     [OCPP16RequestCommand.STOP_TRANSACTION, 'StopTransaction'],
   ]
 
+  /**
+   * Builds a meter value for the beginning of a transaction.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier
+   * @param meterStart - Initial meter reading in Wh
+   * @returns Meter value with the transaction begin context
+   */
   public static buildTransactionBeginMeterValue (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -124,6 +133,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return meterValue
   }
 
+  /**
+   * Builds an array of transaction data meter values from begin and end values.
+   * @param transactionBeginMeterValue - Meter value at transaction start
+   * @param transactionEndMeterValue - Meter value at transaction end
+   * @returns Array containing the begin and end meter values
+   */
   public static buildTransactionDataMeterValues (
     transactionBeginMeterValue: OCPP16MeterValue,
     transactionEndMeterValue: OCPP16MeterValue
@@ -134,6 +149,14 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return meterValues
   }
 
+  /**
+   * Changes the availability of connectors and updates their status.
+   * @param chargingStation - Target charging station
+   * @param connectorIds - Array of connector identifiers to update
+   * @param chargePointStatus - New charge point status to set
+   * @param availabilityType - Operative or inoperative availability type
+   * @returns Accepted or scheduled availability change response
+   */
   public static changeAvailability = async (
     chargingStation: ChargingStation,
     connectorIds: number[],
@@ -166,6 +189,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
   }
 
+  /**
+   * Checks whether a feature profile is enabled on the charging station.
+   * @param chargingStation - Target charging station
+   * @param featureProfile - Feature profile to check
+   * @param command - OCPP command requiring the feature profile
+   * @returns Whether the feature profile is enabled
+   */
   public static checkFeatureProfile (
     chargingStation: ChargingStation,
     featureProfile: OCPP16SupportedFeatureProfiles,
@@ -182,6 +212,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return true
   }
 
+  /**
+   * Clears charging profiles matching the given criteria from the profiles array.
+   * @param chargingStation - Target charging station
+   * @param commandPayload - Clear charging profile request with filter criteria
+   * @param chargingProfiles - Array of charging profiles to filter
+   * @returns Whether any charging profiles were cleared
+   */
   public static clearChargingProfiles = (
     chargingStation: ChargingStation,
     commandPayload: OCPP16ClearChargingProfileRequest,
@@ -223,6 +260,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return clearedCP
   }
 
+  /**
+   * Composes a composite charging schedule from higher and lower priority schedules.
+   * @param chargingScheduleHigher - Higher priority charging schedule
+   * @param chargingScheduleLower - Lower priority charging schedule
+   * @param compositeInterval - Time interval for the composite schedule
+   * @returns Composed charging schedule or undefined if both inputs are null
+   */
   public static composeChargingSchedules = (
     chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
     chargingScheduleLower: OCPP16ChargingSchedule | undefined,
@@ -435,7 +479,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   ][] =>
     OCPP16ServiceUtils.incomingRequestSchemaNames.map(([command, schemaBase]) => [
       command,
-      OCPP16ServiceUtils.PayloadValidatorConfig(`${schemaBase}.json`),
+      PayloadValidatorConfig(`${schemaBase}.json`),
     ])
 
   /**
@@ -448,7 +492,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   ][] =>
     OCPP16ServiceUtils.incomingRequestSchemaNames.map(([command, schemaBase]) => [
       command,
-      OCPP16ServiceUtils.PayloadValidatorConfig(`${schemaBase}Response.json`),
+      PayloadValidatorConfig(`${schemaBase}Response.json`),
     ])
 
   /**
@@ -458,7 +502,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
    * @returns Factory options object for OCPP 1.6 validators
    */
   public static createPayloadOptions = (moduleName: string, methodName: string) =>
-    OCPP16ServiceUtils.PayloadValidatorOptions(
+    PayloadValidatorOptions(
       OCPPVersion.VERSION_16,
       'assets/json-schemas/ocpp/1.6',
       moduleName,
@@ -475,7 +519,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   ][] =>
     OCPP16ServiceUtils.outgoingRequestSchemaNames.map(([command, schemaBase]) => [
       command,
-      OCPP16ServiceUtils.PayloadValidatorConfig(`${schemaBase}.json`),
+      PayloadValidatorConfig(`${schemaBase}.json`),
     ])
 
   /**
@@ -488,9 +532,16 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   ][] =>
     OCPP16ServiceUtils.outgoingRequestSchemaNames.map(([command, schemaBase]) => [
       command,
-      OCPP16ServiceUtils.PayloadValidatorConfig(`${schemaBase}Response.json`),
+      PayloadValidatorConfig(`${schemaBase}Response.json`),
     ])
 
+  /**
+   * Checks whether a connector or the charging station has a valid reservation for the given idTag.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier to check
+   * @param idTag - RFID tag to match against the reservation
+   * @returns Whether a valid reservation exists for the idTag
+   */
   public static hasReservation = (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -518,6 +569,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return false
   }
 
+  /**
+   * Determines whether a configuration key should be visible in GetConfiguration responses.
+   * @param key - Configuration key to check
+   * @returns Whether the key is visible
+   */
   public static isConfigurationKeyVisible (key: ConfigurationKey): boolean {
     if (key.visible == null) {
       return true
@@ -525,6 +581,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return key.visible
   }
 
+  /**
+   * Stops a transaction remotely on the given connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier with the active transaction
+   * @returns Accepted or rejected generic response
+   */
   public static remoteStopTransaction = async (
     chargingStation: ChargingStation,
     connectorId: number
@@ -544,6 +606,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return OCPP16Constants.OCPP_RESPONSE_REJECTED
   }
 
+  /**
+   * Sets or replaces a charging profile on a connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier to set the profile on
+   * @param cp - Charging profile to set
+   */
   public static setChargingProfile (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -589,6 +657,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
   }
 
+  /**
+   * Sends a StartTransaction request to the central system for the given connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier to start the transaction on
+   * @param idTag - Optional RFID tag for the transaction
+   * @returns Start transaction response from the central system
+   */
   public static async startTransactionOnConnector (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -603,6 +678,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     })
   }
 
+  /**
+   * Starts periodic meter value updates for an active transaction on a connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier with the active transaction
+   * @param interval - Meter value sample interval in milliseconds
+   */
   public static startUpdatedMeterValues (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -649,6 +730,13 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     }, clampToSafeTimerValue(interval))
   }
 
+  /**
+   * Sends a StopTransaction request to the central system for the given connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier with the active transaction
+   * @param reason - Optional stop transaction reason
+   * @returns Stop transaction response from the central system
+   */
   public static async stopTransactionOnConnector (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -688,6 +776,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     })
   }
 
+  /**
+   * Stops periodic meter value updates for a connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier to stop updates for
+   */
   public static stopUpdatedMeterValues (
     chargingStation: ChargingStation,
     connectorId: number
index fe02790e781c4ea697ab5f2be43c2544dc2384ff..aff6b6e0662bc3146271e24dfa07af574dfeea93 100644 (file)
@@ -150,6 +150,8 @@ import {
 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
 import {
   buildMeterValue,
+  createPayloadValidatorMap,
+  isIncomingRequestCommandSupported,
   restoreConnectorStatus,
   sendAndSetConnectorStatus,
 } from '../OCPPServiceUtils.js'
@@ -312,7 +314,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         this.toRequestHandler(this.handleRequestUpdateFirmware.bind(this)),
       ],
     ])
-    this.payloadValidatorFunctions = OCPP20ServiceUtils.createPayloadValidatorMap(
+    this.payloadValidatorFunctions = createPayloadValidatorMap(
       OCPP20ServiceUtils.createIncomingRequestPayloadConfigs(),
       OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
@@ -573,6 +575,12 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     )
   }
 
+  /**
+   * Handle OCPP 2.0.1 GetVariables request from the CSMS.
+   * @param chargingStation - Target charging station
+   * @param commandPayload - GetVariables request payload
+   * @returns GetVariables response with variable results
+   */
   public handleRequestGetVariables (
     chargingStation: ChargingStation,
     commandPayload: OCPP20GetVariablesRequest
@@ -641,6 +649,12 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     return getVariablesResponse
   }
 
+  /**
+   * Handle OCPP 2.0.1 SetVariables request from the CSMS.
+   * @param chargingStation - Target charging station
+   * @param commandPayload - SetVariables request payload
+   * @returns SetVariables response with variable results
+   */
   public handleRequestSetVariables (
     chargingStation: ChargingStation,
     commandPayload: OCPP20SetVariablesRequest
@@ -707,6 +721,10 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     return setVariablesResponse
   }
 
+  /**
+   * Stop the incoming request service and clean up per-station state.
+   * @param chargingStation - Target charging station to stop
+   */
   public override stop (chargingStation: ChargingStation): void {
     const stationState = this.stationsState.get(chargingStation)
     if (stationState != null) {
@@ -759,11 +777,17 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     }
   }
 
+  /**
+   * Check whether an incoming request command is supported by the charging station.
+   * @param chargingStation - Target charging station
+   * @param commandName - Incoming request command to check
+   * @returns Whether the command is supported
+   */
   protected isIncomingRequestCommandSupported (
     chargingStation: ChargingStation,
     commandName: IncomingRequestCommand
   ): boolean {
-    return OCPP20ServiceUtils.isIncomingRequestCommandSupported(
+    return isIncomingRequestCommandSupported(
       chargingStation,
       commandName as OCPP20IncomingRequestCommand
     )
index 0e5b8261d6dd529bb62a6f19a0e85d30cc9810dd..f5435a7b0dfe6adb5d771929ca7303b1e9666c20 100644 (file)
@@ -18,7 +18,11 @@ import {
 } from '../../../types/index.js'
 import { generateUUID, logger } from '../../../utils/index.js'
 import { OCPPRequestService } from '../OCPPRequestService.js'
-import { buildStatusNotificationRequest } from '../OCPPServiceUtils.js'
+import {
+  buildStatusNotificationRequest,
+  createPayloadValidatorMap,
+  isRequestCommandSupported,
+} from '../OCPPServiceUtils.js'
 import { generatePkcs10Csr } from './Asn1DerUtils.js'
 import { OCPP20Constants } from './OCPP20Constants.js'
 import { buildTransactionEvent, OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
@@ -60,7 +64,7 @@ export class OCPP20RequestService extends OCPPRequestService {
    */
   public constructor (ocppResponseService: OCPPResponseService) {
     super(OCPPVersion.VERSION_201, ocppResponseService)
-    this.payloadValidatorFunctions = OCPP20ServiceUtils.createPayloadValidatorMap(
+    this.payloadValidatorFunctions = createPayloadValidatorMap(
       OCPP20ServiceUtils.createRequestPayloadConfigs(),
       OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
@@ -101,7 +105,7 @@ export class OCPP20RequestService extends OCPPRequestService {
     logger.debug(
       `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Processing '${commandName}' request`
     )
-    if (OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
+    if (isRequestCommandSupported(chargingStation, commandName)) {
       try {
         logger.debug(
           `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Building request payload for '${commandName}'`
index 48f6f8df2be230eb66bded1c45714708936c1b20..1470cae66b393af7604a08ad37d2cd3ed8564d1b 100644 (file)
@@ -40,7 +40,11 @@ import {
 import { convertToDate, logger } from '../../../utils/index.js'
 import { mapOCPP20TokenType, OCPPAuthServiceFactory } from '../auth/index.js'
 import { OCPPResponseService } from '../OCPPResponseService.js'
-import { sendAndSetConnectorStatus } from '../OCPPServiceUtils.js'
+import {
+  createPayloadValidatorMap,
+  isRequestCommandSupported,
+  sendAndSetConnectorStatus,
+} from '../OCPPServiceUtils.js'
 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
 const moduleName = 'OCPP20ResponseService'
 
@@ -164,27 +168,29 @@ export class OCPP20ResponseService extends OCPPResponseService {
         this.toResponseHandler(this.handleResponseTransactionEvent.bind(this)),
       ],
     ])
-    this.payloadValidatorFunctions = OCPP20ServiceUtils.createPayloadValidatorMap(
+    this.payloadValidatorFunctions = createPayloadValidatorMap(
       OCPP20ServiceUtils.createResponsePayloadConfigs(),
       OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
-    this.incomingRequestResponsePayloadValidateFunctions =
-      OCPP20ServiceUtils.createPayloadValidatorMap(
-        OCPP20ServiceUtils.createIncomingRequestResponsePayloadConfigs(),
-        OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
-        this.ajvIncomingRequest
-      )
+    this.incomingRequestResponsePayloadValidateFunctions = createPayloadValidatorMap(
+      OCPP20ServiceUtils.createIncomingRequestResponsePayloadConfigs(),
+      OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
+      this.ajvIncomingRequest
+    )
   }
 
+  /**
+   * Check whether a request command is supported by the charging station.
+   * @param chargingStation - Target charging station
+   * @param commandName - Request command to check
+   * @returns Whether the command is supported
+   */
   protected isRequestCommandSupported (
     chargingStation: ChargingStation,
     commandName: RequestCommand
   ): boolean {
-    return OCPP20ServiceUtils.isRequestCommandSupported(
-      chargingStation,
-      commandName as OCPP20RequestCommand
-    )
+    return isRequestCommandSupported(chargingStation, commandName as OCPP20RequestCommand)
   }
 
   private handleResponseAuthorize (
index 34bbab148538d1f32016a3dd6d225f3b728aaffd..4f875bd690e05a2e50d48766db8db5c709cd0c1f 100644 (file)
@@ -41,7 +41,8 @@ import {
 import { buildConfigKey, getConfigurationKey } from '../../ConfigurationKeyUtils.js'
 import {
   buildMeterValue,
-  OCPPServiceUtils,
+  PayloadValidatorConfig,
+  PayloadValidatorOptions,
   sendAndSetConnectorStatus,
 } from '../OCPPServiceUtils.js'
 import { OCPP20VariableManager } from './OCPP20VariableManager.js'
@@ -53,7 +54,8 @@ export interface RejectionReason {
   reasonCode: ReasonCodeEnumType
 }
 
-export class OCPP20ServiceUtils extends OCPPServiceUtils {
+// eslint-disable-next-line @typescript-eslint/no-extraneous-class
+export class OCPP20ServiceUtils {
   private static readonly incomingRequestSchemaNames: readonly [
     OCPP20IncomingRequestCommand,
     string
@@ -98,6 +100,12 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     [OCPP20RequestCommand.TRANSACTION_EVENT, 'TransactionEvent'],
   ]
 
+  /**
+   * Build meter values for the start of a transaction.
+   * @param chargingStation - Target charging station
+   * @param transactionId - Transaction identifier
+   * @returns Array of OCPP 2.0 meter values at transaction begin
+   */
   static buildTransactionStartedMeterValues (
     chargingStation: ChargingStation,
     transactionId: number | string
@@ -123,6 +131,12 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
+  /**
+   * Clean up connector state after a transaction has ended.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier
+   * @param connectorStatus - Connector status to reset
+   */
   public static async cleanupEndedTransaction (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -147,7 +161,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
   ][] =>
     OCPP20ServiceUtils.incomingRequestSchemaNames.map(([command, schemaBase]) => [
       command,
-      OCPP20ServiceUtils.PayloadValidatorConfig(`${schemaBase}Request.json`),
+      PayloadValidatorConfig(`${schemaBase}Request.json`),
     ])
 
   /**
@@ -160,7 +174,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
   ][] =>
     OCPP20ServiceUtils.incomingRequestSchemaNames.map(([command, schemaBase]) => [
       command,
-      OCPP20ServiceUtils.PayloadValidatorConfig(`${schemaBase}Response.json`),
+      PayloadValidatorConfig(`${schemaBase}Response.json`),
     ])
 
   /**
@@ -170,7 +184,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
    * @returns Factory options object for OCPP 2.0 validators
    */
   public static createPayloadOptions = (moduleName: string, methodName: string) =>
-    OCPP20ServiceUtils.PayloadValidatorOptions(
+    PayloadValidatorOptions(
       OCPPVersion.VERSION_201,
       'assets/json-schemas/ocpp/2.0',
       moduleName,
@@ -187,7 +201,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
   ][] =>
     OCPP20ServiceUtils.outgoingRequestSchemaNames.map(([command, schemaBase]) => [
       command,
-      OCPP20ServiceUtils.PayloadValidatorConfig(`${schemaBase}Request.json`),
+      PayloadValidatorConfig(`${schemaBase}Request.json`),
     ])
 
   /**
@@ -200,9 +214,23 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
   ][] =>
     OCPP20ServiceUtils.outgoingRequestSchemaNames.map(([command, schemaBase]) => [
       command,
-      OCPP20ServiceUtils.PayloadValidatorConfig(`${schemaBase}Response.json`),
+      PayloadValidatorConfig(`${schemaBase}Response.json`),
     ])
 
+  /**
+   * Enforce ItemsPerMessage and BytesPerMessage limits on request data.
+   * @param chargingStation - Charging station providing log prefix
+   * @param chargingStation.logPrefix - Log prefix function
+   * @param moduleName - Module name for logging context
+   * @param context - Method name for logging context
+   * @param data - Array of variable data items to validate
+   * @param itemsLimit - Maximum allowed items per message (0 = unlimited)
+   * @param bytesLimit - Maximum allowed bytes per message (0 = unlimited)
+   * @param buildRejected - Factory function to build rejection results
+   * @param logger - Logger instance for debug output
+   * @param logger.debug - Debug logging function
+   * @returns Object indicating whether data was rejected and the rejection results
+   */
   public static enforceMessageLimits<
     T extends { attributeType?: unknown; component: unknown; variable: unknown },
     R
@@ -246,6 +274,20 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     return { rejected: false, results: [] }
   }
 
+  /**
+   * Enforce BytesPerMessage limit after results have been computed.
+   * @param chargingStation - Charging station providing log prefix
+   * @param chargingStation.logPrefix - Log prefix function
+   * @param moduleName - Module name for logging context
+   * @param context - Method name for logging context
+   * @param originalData - Original variable data items
+   * @param currentResults - Computed results to check against byte limit
+   * @param bytesLimit - Maximum allowed bytes per message (0 = unlimited)
+   * @param buildRejected - Factory function to build rejection results
+   * @param logger - Logger instance for debug output
+   * @param logger.debug - Debug logging function
+   * @returns Original results if within limit, or rejection results if exceeded
+   */
   public static enforcePostCalculationBytesLimit<
     T extends { attributeType?: unknown; component: unknown; variable: unknown },
     R
@@ -284,6 +326,11 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     return currentResults
   }
 
+  /**
+   * Retrieve the AlignedDataCtrlr interval in milliseconds.
+   * @param chargingStation - Target charging station
+   * @returns Aligned data interval in milliseconds
+   */
   public static getAlignedDataInterval (chargingStation: ChargingStation): number {
     return OCPP20ServiceUtils.readVariableAsIntervalMs(
       chargingStation,
@@ -293,6 +340,11 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     )
   }
 
+  /**
+   * Retrieve the SampledDataCtrlr TxEndedInterval in milliseconds.
+   * @param chargingStation - Target charging station
+   * @returns Transaction ended meter values interval in milliseconds
+   */
   public static getTxEndedInterval (chargingStation: ChargingStation): number {
     return OCPP20ServiceUtils.readVariableAsIntervalMs(
       chargingStation,
@@ -302,6 +354,11 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     )
   }
 
+  /**
+   * Retrieve the SampledDataCtrlr TxUpdatedInterval in milliseconds.
+   * @param chargingStation - Target charging station
+   * @returns Transaction updated meter values interval in milliseconds
+   */
   public static getTxUpdatedInterval (chargingStation: ChargingStation): number {
     return OCPP20ServiceUtils.readVariableAsIntervalMs(
       chargingStation,
@@ -354,6 +411,13 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     return { bytesLimit, itemsLimit }
   }
 
+  /**
+   * Deauthorize an active transaction per OCPP 2.0.1 E05 requirements.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier with the active transaction
+   * @param evseId - Optional EVSE identifier
+   * @returns Promise resolving to the TransactionEvent response
+   */
   public static async requestDeauthorizeTransaction (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -431,6 +495,15 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     )
   }
 
+  /**
+   * Stop an active transaction by sending a TransactionEvent(Ended).
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier with the active transaction
+   * @param evseId - Optional EVSE identifier
+   * @param triggerReason - Trigger reason for the stop event
+   * @param stoppedReason - Reason the transaction was stopped
+   * @returns Promise resolving to the TransactionEvent response
+   */
   public static async requestStopTransaction (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -476,6 +549,11 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
+  /**
+   * Send queued TransactionEvent requests accumulated while offline.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier whose queue to drain
+   */
   public static async sendQueuedTransactionEvents (
     chargingStation: ChargingStation,
     connectorId: number
@@ -523,6 +601,16 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
+  /**
+   * Send a TransactionEvent request to the CSMS, or queue it if offline.
+   * @param chargingStation - Target charging station
+   * @param eventType - Transaction event type (Started, Updated, Ended)
+   * @param triggerReason - Reason that triggered the event
+   * @param connectorId - Connector identifier
+   * @param transactionId - Transaction identifier
+   * @param options - Additional transaction event options
+   * @returns Promise resolving to the TransactionEvent response
+   */
   public static async sendTransactionEvent (
     chargingStation: ChargingStation,
     eventType: OCPP20TransactionEventEnumType,
@@ -590,6 +678,12 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
+  /**
+   * Start periodic collection of TxEnded meter values for a connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier
+   * @param interval - Collection interval in milliseconds
+   */
   public static startEndedMeterValues (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -629,6 +723,12 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     )
   }
 
+  /**
+   * Start periodic TransactionEvent(Updated) with meter values for a connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier
+   * @param interval - Sending interval in milliseconds
+   */
   public static startUpdatedMeterValues (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -712,6 +812,13 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     )
   }
 
+  /**
+   * Stop all active transactions on the charging station or a specific EVSE.
+   * @param chargingStation - Target charging station
+   * @param triggerReason - Trigger reason for stop events
+   * @param stoppedReason - Reason the transactions were stopped
+   * @param evseId - Optional EVSE identifier to limit scope
+   */
   public static async stopAllTransactions (
     chargingStation: ChargingStation,
     triggerReason: OCPP20TriggerReasonEnumType = OCPP20TriggerReasonEnumType.RemoteStop,
@@ -770,6 +877,11 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
+  /**
+   * Stop periodic TxEnded meter value collection for a connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier
+   */
   public static stopEndedMeterValues (chargingStation: ChargingStation, connectorId: number): void {
     const connectorStatus = chargingStation.getConnectorStatus(connectorId)
     if (connectorStatus?.transactionEndedMeterValuesSetInterval != null) {
@@ -781,6 +893,11 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
+  /**
+   * Stop periodic TransactionEvent(Updated) sending for a connector.
+   * @param chargingStation - Target charging station
+   * @param connectorId - Connector identifier
+   */
   public static stopUpdatedMeterValues (
     chargingStation: ChargingStation,
     connectorId: number
diff --git a/src/charging-station/ocpp/OCPPServiceOperations.ts b/src/charging-station/ocpp/OCPPServiceOperations.ts
new file mode 100644 (file)
index 0000000..074d5e8
--- /dev/null
@@ -0,0 +1,246 @@
+import type { StopTransactionReason } from '../../types/index.js'
+
+import { type ChargingStation } from '../../charging-station/index.js'
+import { OCPPError } from '../../exception/index.js'
+import {
+  AuthorizationStatus,
+  ErrorType,
+  OCPP20AuthorizationStatusEnumType,
+  OCPP20IdTokenEnumType,
+  OCPP20TransactionEventEnumType,
+  OCPP20TriggerReasonEnumType,
+  OCPPVersion,
+  type StartTransactionResult,
+  type StopTransactionResult,
+} from '../../types/index.js'
+import { generateUUID, logger } from '../../utils/index.js'
+import { OCPP16ServiceUtils } from './1.6/OCPP16ServiceUtils.js'
+import { OCPP20ServiceUtils } from './2.0/OCPP20ServiceUtils.js'
+import { mapStopReasonToOCPP20 } from './OCPPServiceUtils.js'
+
+/**
+ * Starts a transaction on a specific connector using the appropriate OCPP version handler.
+ * @param chargingStation - Target charging station
+ * @param connectorId - Connector ID to start the transaction on
+ * @param idTag - Optional RFID tag for authorization
+ * @returns Result indicating whether the transaction was accepted
+ */
+export const startTransactionOnConnector = async (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  idTag?: string
+): Promise<StartTransactionResult> => {
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16: {
+      const response = await OCPP16ServiceUtils.startTransactionOnConnector(
+        chargingStation,
+        connectorId,
+        idTag
+      )
+      return { accepted: response.idTagInfo.status === AuthorizationStatus.ACCEPTED }
+    }
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201: {
+      const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+      let transactionId = connectorStatus?.transactionId as string | undefined
+      if (transactionId == null) {
+        transactionId = generateUUID()
+        if (connectorStatus != null) {
+          connectorStatus.transactionId = transactionId
+        }
+        OCPP20ServiceUtils.resetTransactionSequenceNumber(chargingStation, connectorId)
+      }
+      const startedMeterValues = OCPP20ServiceUtils.buildTransactionStartedMeterValues(
+        chargingStation,
+        transactionId
+      )
+      const response = await OCPP20ServiceUtils.sendTransactionEvent(
+        chargingStation,
+        OCPP20TransactionEventEnumType.Started,
+        OCPP20TriggerReasonEnumType.Authorized,
+        connectorId,
+        transactionId,
+        {
+          idToken:
+            idTag != null ? { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 } : undefined,
+          ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }),
+        }
+      )
+      return {
+        accepted:
+          response.idTokenInfo == null ||
+          response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted,
+      }
+    }
+    default:
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `startTransactionOnConnector: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
+      )
+  }
+}
+
+/**
+ * Stops a transaction on a specific connector using the appropriate OCPP version handler.
+ * @param chargingStation - Target charging station
+ * @param connectorId - Connector ID to stop the transaction on
+ * @param reason - Optional reason for stopping the transaction
+ * @returns Result indicating whether the stop was accepted
+ */
+export const stopTransactionOnConnector = async (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  reason?: StopTransactionReason
+): Promise<StopTransactionResult> => {
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16: {
+      const response = await OCPP16ServiceUtils.stopTransactionOnConnector(
+        chargingStation,
+        connectorId,
+        reason
+      )
+      return { accepted: response.idTagInfo?.status === AuthorizationStatus.ACCEPTED }
+    }
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201: {
+      const evseId = chargingStation.getEvseIdByConnectorId(connectorId)
+      if (evseId == null) {
+        logger.warn(
+          `${chargingStation.logPrefix()} stopTransactionOnConnector: cannot resolve EVSE ID for connector ${connectorId.toString()}, skipping`
+        )
+        return { accepted: false }
+      }
+      const { stoppedReason, triggerReason } = mapStopReasonToOCPP20(reason)
+      const response = await OCPP20ServiceUtils.requestStopTransaction(
+        chargingStation,
+        connectorId,
+        evseId,
+        triggerReason,
+        stoppedReason
+      )
+      return {
+        accepted:
+          response.idTokenInfo == null ||
+          response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted,
+      }
+    }
+    default:
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `stopTransactionOnConnector: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
+      )
+  }
+}
+
+/**
+ * Stops all running transactions on all connectors of a charging station.
+ * @param chargingStation - Target charging station
+ * @param reason - Optional reason for stopping the transactions
+ */
+export const stopRunningTransactions = async (
+  chargingStation: ChargingStation,
+  reason?: StopTransactionReason
+): Promise<void> => {
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16: {
+      for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors(true)) {
+        if (connectorStatus.transactionStarted === true) {
+          await OCPP16ServiceUtils.stopTransactionOnConnector(chargingStation, connectorId, reason)
+        }
+      }
+      break
+    }
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201: {
+      const { stoppedReason, triggerReason } = mapStopReasonToOCPP20(reason)
+      await OCPP20ServiceUtils.stopAllTransactions(chargingStation, triggerReason, stoppedReason)
+      break
+    }
+    default:
+      logger.warn(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${chargingStation.logPrefix()} stopRunningTransactions: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}, no transactions stopped`
+      )
+  }
+}
+
+/**
+ * Starts periodic meter value updates for a connector during an active transaction.
+ * @param chargingStation - Target charging station
+ * @param connectorId - Connector ID to start meter value updates for
+ * @param interval - Meter value sampling interval in milliseconds
+ */
+export const startUpdatedMeterValues = (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  interval: number
+): void => {
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      OCPP16ServiceUtils.startUpdatedMeterValues(chargingStation, connectorId, interval)
+      break
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      OCPP20ServiceUtils.startUpdatedMeterValues(chargingStation, connectorId, interval)
+      break
+    default:
+      logger.error(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${chargingStation.logPrefix()} startUpdatedMeterValues: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
+      )
+  }
+}
+
+/**
+ * Stops periodic meter value updates for a connector.
+ * @param chargingStation - Target charging station
+ * @param connectorId - Connector ID to stop meter value updates for
+ */
+export const stopUpdatedMeterValues = (
+  chargingStation: ChargingStation,
+  connectorId: number
+): void => {
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
+      break
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
+      break
+    default:
+      break
+  }
+}
+
+/**
+ * Flushes queued transaction event messages for all connectors on an OCPP 2.0 charging station.
+ * @param chargingStation - Target charging station
+ */
+export const flushQueuedTransactionMessages = async (
+  chargingStation: ChargingStation
+): Promise<void> => {
+  switch (chargingStation.stationInfo?.ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      break
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors()) {
+        if ((connectorStatus.transactionEventQueue?.length ?? 0) > 0) {
+          await OCPP20ServiceUtils.sendQueuedTransactionEvents(chargingStation, connectorId).catch(
+            (error: unknown) => {
+              logger.error(
+                `${chargingStation.logPrefix()} flushQueuedTransactionMessages: Error flushing queued TransactionEvents:`,
+                error
+              )
+            }
+          )
+        }
+      }
+      break
+    default:
+      break
+  }
+}
index 5c3857fc8f5df68587127504ab983f0d43bd0f0f..37ac28980b712d1c370c0c316affb9bb398b13a3 100644 (file)
@@ -10,7 +10,6 @@ import type { StopTransactionReason } from '../../types/index.js'
 import { type ChargingStation, getConfigurationKey } from '../../charging-station/index.js'
 import { BaseError, OCPPError } from '../../exception/index.js'
 import {
-  AuthorizationStatus,
   ChargePointErrorCode,
   ChargingStationEvents,
   type ConfigurationKeyType,
@@ -34,24 +33,19 @@ import {
   type OCPP16SampledValue,
   type OCPP16StatusNotificationRequest,
   OCPP16StopTransactionReason,
-  OCPP20AuthorizationStatusEnumType,
   type OCPP20ConnectorStatusEnumType,
-  OCPP20IdTokenEnumType,
   type OCPP20MeterValue,
   OCPP20ReasonEnumType,
   type OCPP20SampledValue,
   type OCPP20StatusNotificationRequest,
-  OCPP20TransactionEventEnumType,
   OCPP20TriggerReasonEnumType,
   OCPPVersion,
   RequestCommand,
   type SampledValue,
   type SampledValueTemplate,
   StandardParametersKey,
-  type StartTransactionResult,
   type StatusNotificationRequest,
   type StatusNotificationResponse,
-  type StopTransactionResult,
 } from '../../types/index.js'
 import {
   ACElectricUtils,
@@ -59,7 +53,6 @@ import {
   convertToFloat,
   convertToInt,
   DCElectricUtils,
-  generateUUID,
   getRandomFloatFluctuatedRounded,
   getRandomFloatRounded,
   handleFileException,
@@ -92,6 +85,12 @@ interface SingleValueMeasurandData {
   value: number
 }
 
+/**
+ * Builds a StatusNotification request payload for the appropriate OCPP version.
+ * @param chargingStation - Target charging station
+ * @param commandParams - Status notification parameters including connector ID and status
+ * @returns Formatted StatusNotification request payload
+ */
 export const buildStatusNotificationRequest = (
   chargingStation: ChargingStation,
   commandParams: StatusNotificationRequest
@@ -136,6 +135,13 @@ export const buildStatusNotificationRequest = (
   }
 }
 
+/**
+ * Sends a StatusNotification request and updates the connector status locally.
+ * @param chargingStation - Target charging station
+ * @param commandParams - Status notification parameters including connector ID and status
+ * @param options - Optional settings to control whether the request is actually sent
+ * @param options.send - Whether to actually send the status notification
+ */
 export const sendAndSetConnectorStatus = async (
   chargingStation: ChargingStation,
   commandParams: StatusNotificationRequest,
@@ -163,6 +169,12 @@ export const sendAndSetConnectorStatus = async (
   })
 }
 
+/**
+ * Restores a connector status to Reserved or Available based on its current state.
+ * @param chargingStation - Target charging station
+ * @param connectorId - Connector ID to restore
+ * @param connectorStatus - Current connector status to evaluate
+ */
 export const restoreConnectorStatus = async (
   chargingStation: ChargingStation,
   connectorId: number,
@@ -184,6 +196,11 @@ export const restoreConnectorStatus = async (
   }
 }
 
+/**
+ * Maps an OCPP 1.6 or generic stop transaction reason to OCPP 2.0 stopped and trigger reasons.
+ * @param reason - Stop transaction reason to map
+ * @returns Object containing the OCPP 2.0 stoppedReason and triggerReason
+ */
 export const mapStopReasonToOCPP20 = (
   reason?: StopTransactionReason
 ): {
@@ -252,216 +269,6 @@ export const mapStopReasonToOCPP20 = (
   }
 }
 
-export const startTransactionOnConnector = async (
-  chargingStation: ChargingStation,
-  connectorId: number,
-  idTag?: string
-): Promise<StartTransactionResult> => {
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16: {
-      const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
-      const response = await OCPP16ServiceUtils.startTransactionOnConnector(
-        chargingStation,
-        connectorId,
-        idTag
-      )
-      return { accepted: response.idTagInfo.status === AuthorizationStatus.ACCEPTED }
-    }
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201: {
-      const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-      let transactionId = connectorStatus?.transactionId as string | undefined
-      if (transactionId == null) {
-        transactionId = generateUUID()
-        if (connectorStatus != null) {
-          connectorStatus.transactionId = transactionId
-        }
-        OCPP20ServiceUtils.resetTransactionSequenceNumber(chargingStation, connectorId)
-      }
-      const startedMeterValues = OCPP20ServiceUtils.buildTransactionStartedMeterValues(
-        chargingStation,
-        transactionId
-      )
-      const response = await OCPP20ServiceUtils.sendTransactionEvent(
-        chargingStation,
-        OCPP20TransactionEventEnumType.Started,
-        OCPP20TriggerReasonEnumType.Authorized,
-        connectorId,
-        transactionId,
-        {
-          idToken:
-            idTag != null ? { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 } : undefined,
-          ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }),
-        }
-      )
-      return {
-        accepted:
-          response.idTokenInfo == null ||
-          response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted,
-      }
-    }
-    default:
-      throw new OCPPError(
-        ErrorType.INTERNAL_ERROR,
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `startTransactionOnConnector: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
-      )
-  }
-}
-
-export const stopTransactionOnConnector = async (
-  chargingStation: ChargingStation,
-  connectorId: number,
-  reason?: StopTransactionReason
-): Promise<StopTransactionResult> => {
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16: {
-      const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
-      const response = await OCPP16ServiceUtils.stopTransactionOnConnector(
-        chargingStation,
-        connectorId,
-        reason
-      )
-      return { accepted: response.idTagInfo?.status === AuthorizationStatus.ACCEPTED }
-    }
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201: {
-      const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      const evseId = chargingStation.getEvseIdByConnectorId(connectorId)
-      if (evseId == null) {
-        logger.warn(
-          `${chargingStation.logPrefix()} stopTransactionOnConnector: cannot resolve EVSE ID for connector ${connectorId.toString()}, skipping`
-        )
-        return { accepted: false }
-      }
-      const { stoppedReason, triggerReason } = mapStopReasonToOCPP20(reason)
-      const response = await OCPP20ServiceUtils.requestStopTransaction(
-        chargingStation,
-        connectorId,
-        evseId,
-        triggerReason,
-        stoppedReason
-      )
-      return {
-        accepted:
-          response.idTokenInfo == null ||
-          response.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted,
-      }
-    }
-    default:
-      throw new OCPPError(
-        ErrorType.INTERNAL_ERROR,
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `stopTransactionOnConnector: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
-      )
-  }
-}
-
-export const stopRunningTransactions = async (
-  chargingStation: ChargingStation,
-  reason?: StopTransactionReason
-): Promise<void> => {
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16: {
-      const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
-      // Sequential — OCPP 1.6 behavior
-      for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors(true)) {
-        if (connectorStatus.transactionStarted === true) {
-          await OCPP16ServiceUtils.stopTransactionOnConnector(chargingStation, connectorId, reason)
-        }
-      }
-      break
-    }
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201: {
-      const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      const { stoppedReason, triggerReason } = mapStopReasonToOCPP20(reason)
-      await OCPP20ServiceUtils.stopAllTransactions(chargingStation, triggerReason, stoppedReason)
-      break
-    }
-    default:
-      logger.warn(
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `${chargingStation.logPrefix()} stopRunningTransactions: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}, no transactions stopped`
-      )
-  }
-}
-
-export const startUpdatedMeterValues = async (
-  chargingStation: ChargingStation,
-  connectorId: number,
-  interval: number
-): Promise<void> => {
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16: {
-      const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
-      OCPP16ServiceUtils.startUpdatedMeterValues(chargingStation, connectorId, interval)
-      break
-    }
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201: {
-      const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      OCPP20ServiceUtils.startUpdatedMeterValues(chargingStation, connectorId, interval)
-      break
-    }
-    default:
-      logger.error(
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `${chargingStation.logPrefix()} OCPPServiceUtils.startUpdatedMeterValues: unsupported OCPP version ${chargingStation.stationInfo?.ocppVersion}`
-      )
-  }
-}
-
-export const stopUpdatedMeterValues = async (
-  chargingStation: ChargingStation,
-  connectorId: number
-): Promise<void> => {
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16: {
-      const { OCPP16ServiceUtils } = await import('./1.6/OCPP16ServiceUtils.js')
-      OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
-      break
-    }
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201: {
-      const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
-      break
-    }
-    default:
-      break
-  }
-}
-
-export const flushQueuedTransactionMessages = async (
-  chargingStation: ChargingStation
-): Promise<void> => {
-  switch (chargingStation.stationInfo?.ocppVersion) {
-    case OCPPVersion.VERSION_16:
-      break
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201: {
-      const { OCPP20ServiceUtils } = await import('./2.0/OCPP20ServiceUtils.js')
-      for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors()) {
-        if ((connectorStatus.transactionEventQueue?.length ?? 0) > 0) {
-          await OCPP20ServiceUtils.sendQueuedTransactionEvents(chargingStation, connectorId).catch(
-            (error: unknown) => {
-              logger.error(
-                `${chargingStation.logPrefix()} OCPPServiceUtils.flushQueuedTransactionMessages: Error flushing queued TransactionEvents:`,
-                error
-              )
-            }
-          )
-        }
-      }
-      break
-    }
-    default:
-      break
-  }
-}
-
 const checkConnectorStatusTransition = (
   chargingStation: ChargingStation,
   connectorId: number,
@@ -522,6 +329,11 @@ const checkConnectorStatusTransition = (
   return transitionAllowed
 }
 
+/**
+ * Converts Ajv validation errors to the corresponding OCPP error type.
+ * @param errors - Array of Ajv validation error objects
+ * @returns OCPP ErrorType corresponding to the validation failure
+ */
 export const ajvErrorsToErrorType = (errors: ErrorObject[] | null | undefined): ErrorType => {
   if (isNotEmptyArray(errors)) {
     for (const error of errors) {
@@ -540,6 +352,10 @@ export const ajvErrorsToErrorType = (errors: ErrorObject[] | null | undefined):
   return ErrorType.FORMAT_VIOLATION
 }
 
+/**
+ * Recursively converts Date values to ISO 8601 strings within a JSON-compatible object.
+ * @param object - Object whose Date properties will be converted in-place
+ */
 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
 export const convertDateToISOString = <T extends JsonType>(object: T): void => {
   for (const [key, value] of Object.entries(object as Record<string, unknown>)) {
@@ -1367,11 +1183,25 @@ const buildCurrentMeasurandValue = (
   }
 }
 
+/**
+ * Builds an empty MeterValue with no sampled values and the current timestamp.
+ * @returns Empty MeterValue object
+ */
 export const buildEmptyMeterValue = (): MeterValue => ({
   sampledValue: [],
   timestamp: new Date(),
 })
 
+/**
+ * Builds a complete MeterValue with all configured measurands for a transaction.
+ * @param chargingStation - Target charging station
+ * @param transactionId - Active transaction identifier
+ * @param interval - Meter value sampling interval in milliseconds
+ * @param measurandsKey - Configuration key for the sampled measurands list
+ * @param context - Meter value reading context
+ * @param debug - Enable debug logging for measurand validation
+ * @returns Populated MeterValue object
+ */
 export const buildMeterValue = (
   chargingStation: ChargingStation,
   transactionId: number | string | undefined,
@@ -1795,6 +1625,13 @@ export const buildMeterValue = (
   }
 }
 
+/**
+ * Builds a MeterValue for the end of a transaction with the final energy register value.
+ * @param chargingStation - Target charging station
+ * @param connectorId - Connector ID associated with the transaction
+ * @param meterStop - Final meter reading in Wh at transaction end
+ * @returns MeterValue containing the transaction end energy reading
+ */
 export const buildTransactionEndMeterValue = (
   chargingStation: ChargingStation,
   connectorId: number,
@@ -1900,6 +1737,16 @@ const isMeasurandSupported = (measurand: MeterValueMeasurand): boolean => {
   return supportedMeasurands.includes(measurand as string)
 }
 
+/**
+ * Retrieves the sampled value template matching the given measurand and phase from configuration.
+ * @param chargingStation - Target charging station
+ * @param connectorId - Connector ID to look up templates for
+ * @param measurandsKey - Configuration key containing the list of sampled measurands
+ * @param measurand - Meter value measurand to match
+ * @param evseId - Optional EVSE ID for OCPP 2.0 template lookup
+ * @param phase - Optional phase to match in the template
+ * @returns Matching sampled value template, or undefined if not found
+ */
 export const getSampledValueTemplate = (
   chargingStation: ChargingStation,
   connectorId: number,
@@ -2210,228 +2057,213 @@ const getMeasurandDefaultUnit = (
 }
 
 /**
- * Utility class providing core OCPP (Open Charge Point Protocol) service functionality
- * and common operations across all OCPP versions and protocol implementations.
- *
- * This class serves as the foundation for OCPP protocol handling, providing:
- * - JSON schema-based payload validation using AJV (Another JSON Schema Validator)
- * - Common OCPP operations like connector status management and transaction handling
- * - Utility functions for meter value processing and ID tag authorization
- * - Shared functionality between OCPP 1.6 and OCPP 2.0+ implementations
- *
- * Key Features:
- * - **Schema Validation**: Centralized JSON schema loading and validation functions
- * - **Protocol Agnostic**: Provides utilities that work across OCPP versions
- * - **Transaction Management**: Utilities for transaction lifecycle and meter values
- * - **Status Management**: Connector and charging station status operations
- * - **Static Interface**: All functionality exposed as static methods for easy access
- *
- * Usage Pattern:
- * This class is typically used by other OCPP service classes (incoming request services,
- * response services) to perform common operations and validation. It acts as a shared
- * utility layer that prevents code duplication across OCPP version-specific implementations.
- * @see {@link parseJsonSchemaFile} Core JSON schema parsing functionality
- * @see {@link validateIncomingRequestPayload} Payload validation methods in service classes
- * @see {@link validateResponsePayload} Payload validation methods in service classes
+ * Creates a Map of compiled OCPP payload validators from configurations.
+ * Reduces code duplication across OCPP services.
+ * @param configs - Array of tuples containing command and validator configuration
+ * @param options - Factory options including OCPP version, schema directory, etc.
+ * @param options.ocppVersion - The OCPP version for schema validation
+ * @param options.schemaDir - Directory path containing JSON schemas
+ * @param options.moduleName - Name of the module for logging
+ * @param options.methodName - Name of the method for logging
+ * @param ajvInstance - Configured Ajv instance for validation
+ * @returns Map of commands to their compiled validation functions
  */
+export function createPayloadValidatorMap<Command extends JsonType> (
+  configs: [Command, { schemaPath: string }][],
+  options: {
+    methodName: string
+    moduleName: string
+    ocppVersion: OCPPVersion
+    schemaDir: string
+  },
+  ajvInstance: Ajv
+): Map<Command, ValidateFunction<JsonType>> {
+  return new Map<Command, ValidateFunction<JsonType>>(
+    configs.map(([command, config]) => {
+      const fullSchemaPath = `${options.schemaDir}/${config.schemaPath}`
+      const schema = parseJsonSchemaFile<JsonType>(
+        fullSchemaPath,
+        options.ocppVersion,
+        options.moduleName,
+        options.methodName
+      )
+      return [command, ajvInstance.compile(schema)]
+    })
+  )
+}
 
-// eslint-disable-next-line @typescript-eslint/no-extraneous-class
-export class OCPPServiceUtils {
-  protected constructor () {
-    // This is intentional
-  }
-
-  /**
-   * Creates a Map of compiled OCPP payload validators from configurations.
-   * Reduces code duplication across OCPP services.
-   * @param configs - Array of tuples containing command and validator configuration
-   * @param options - Factory options including OCPP version, schema directory, etc.
-   * @param options.ocppVersion - The OCPP version for schema validation
-   * @param options.schemaDir - Directory path containing JSON schemas
-   * @param options.moduleName - Name of the module for logging
-   * @param options.methodName - Name of the method for logging
-   * @param ajvInstance - Configured Ajv instance for validation
-   * @returns Map of commands to their compiled validation functions
-   */
-  public static createPayloadValidatorMap<Command extends JsonType>(
-    configs: [Command, { schemaPath: string }][],
-    options: {
-      methodName: string
-      moduleName: string
-      ocppVersion: OCPPVersion
-      schemaDir: string
-    },
-    ajvInstance: Ajv
-  ): Map<Command, ValidateFunction<JsonType>> {
-    return new Map<Command, ValidateFunction<JsonType>>(
-      configs.map(([command, config]) => {
-        const fullSchemaPath = `${options.schemaDir}/${config.schemaPath}`
-        const schema = OCPPServiceUtils.parseJsonSchemaFile<JsonType>(
-          fullSchemaPath,
-          options.ocppVersion,
-          options.moduleName,
-          options.methodName
-        )
-        return [command, ajvInstance.compile(schema)]
-      })
+/**
+ * @param chargingStation - Target charging station
+ * @param ocppCommand - OCPP command triggering the validation
+ * @param connectorId - Connector ID to validate
+ * @returns Whether the connector ID is valid (>= 0)
+ */
+export function isConnectorIdValid (
+  chargingStation: ChargingStation,
+  ocppCommand: IncomingRequestCommand,
+  connectorId: number
+): boolean {
+  if (connectorId < 0) {
+    logger.error(
+      `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId.toString()}`
     )
+    return false
   }
+  return true
+}
 
-  public static isConnectorIdValid (
-    chargingStation: ChargingStation,
-    ocppCommand: IncomingRequestCommand,
-    connectorId: number
-  ): boolean {
-    if (connectorId < 0) {
-      logger.error(
-        `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId.toString()}`
-      )
-      return false
-    }
+/**
+ * @param chargingStation - Target charging station
+ * @param command - Incoming request command to check
+ * @returns Whether the command is supported by the station configuration
+ */
+export function isIncomingRequestCommandSupported (
+  chargingStation: ChargingStation,
+  command: IncomingRequestCommand
+): boolean {
+  const isIncomingRequestCommand =
+    Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command)
+  if (
+    isIncomingRequestCommand &&
+    chargingStation.stationInfo?.commandsSupport?.incomingCommands == null
+  ) {
     return true
+  } else if (
+    isIncomingRequestCommand &&
+    chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] != null
+  ) {
+    return chargingStation.stationInfo.commandsSupport.incomingCommands[command]
   }
+  logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
+  return false
+}
 
-  public static isIncomingRequestCommandSupported (
-    chargingStation: ChargingStation,
-    command: IncomingRequestCommand
-  ): boolean {
-    const isIncomingRequestCommand =
-      Object.values<IncomingRequestCommand>(IncomingRequestCommand).includes(command)
-    if (
-      isIncomingRequestCommand &&
-      chargingStation.stationInfo?.commandsSupport?.incomingCommands == null
-    ) {
-      return true
-    } else if (
-      isIncomingRequestCommand &&
-      chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] != null
-    ) {
-      return chargingStation.stationInfo.commandsSupport.incomingCommands[command]
-    }
-    logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
-    return false
+/**
+ * @param chargingStation - Target charging station
+ * @param messageTrigger - Message trigger to check
+ * @returns Whether the trigger is supported by the station configuration
+ */
+export function isMessageTriggerSupported (
+  chargingStation: ChargingStation,
+  messageTrigger: MessageTrigger
+): boolean {
+  const isMessageTrigger = (Object.values(MessageTrigger) as MessageTrigger[]).includes(
+    messageTrigger
+  )
+  if (isMessageTrigger && chargingStation.stationInfo?.messageTriggerSupport == null) {
+    return true
+  } else if (
+    isMessageTrigger &&
+    chargingStation.stationInfo?.messageTriggerSupport?.[messageTrigger] != null
+  ) {
+    return chargingStation.stationInfo.messageTriggerSupport[messageTrigger]
   }
+  logger.error(
+    `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
+  )
+  return false
+}
 
-  public static isMessageTriggerSupported (
-    chargingStation: ChargingStation,
-    messageTrigger: MessageTrigger
-  ): boolean {
-    const isMessageTrigger = (Object.values(MessageTrigger) as MessageTrigger[]).includes(
-      messageTrigger
-    )
-    if (isMessageTrigger && chargingStation.stationInfo?.messageTriggerSupport == null) {
-      return true
-    } else if (
-      isMessageTrigger &&
-      chargingStation.stationInfo?.messageTriggerSupport?.[messageTrigger] != null
-    ) {
-      return chargingStation.stationInfo.messageTriggerSupport[messageTrigger]
-    }
-    logger.error(
-      `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
-    )
-    return false
+/**
+ * @param chargingStation - Target charging station
+ * @param command - Outgoing request command to check
+ * @returns Whether the command is supported by the station configuration
+ */
+export function isRequestCommandSupported (
+  chargingStation: ChargingStation,
+  command: RequestCommand
+): boolean {
+  const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command)
+  if (isRequestCommand && chargingStation.stationInfo?.commandsSupport?.outgoingCommands == null) {
+    return true
+  } else if (
+    isRequestCommand &&
+    chargingStation.stationInfo?.commandsSupport?.outgoingCommands?.[command] != null
+  ) {
+    return chargingStation.stationInfo.commandsSupport.outgoingCommands[command]
   }
+  logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
+  return false
+}
 
-  public static isRequestCommandSupported (
-    chargingStation: ChargingStation,
-    command: RequestCommand
-  ): boolean {
-    const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command)
-    if (
-      isRequestCommand &&
-      chargingStation.stationInfo?.commandsSupport?.outgoingCommands == null
-    ) {
-      return true
-    } else if (
-      isRequestCommand &&
-      chargingStation.stationInfo?.commandsSupport?.outgoingCommands?.[command] != null
-    ) {
-      return chargingStation.stationInfo.commandsSupport.outgoingCommands[command]
-    }
-    logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
-    return false
-  }
+/**
+ * Configuration for a single payload validator.
+ * @param schemaPath - Path to the JSON schema file
+ * @returns Configuration object for payload validator creation
+ */
+export const PayloadValidatorConfig = (schemaPath: string) =>
+  ({
+    schemaPath,
+  }) as const
 
-  /**
-   * Configuration for a single payload validator.
-   * @param schemaPath - Path to the JSON schema file
-   * @returns Configuration object for payload validator creation
-   */
-  public static readonly PayloadValidatorConfig = (schemaPath: string) =>
-    ({
-      schemaPath,
-    }) as const
-
-  /**
-   * Options for payload validator creation.
-   * @param ocppVersion - The OCPP version
-   * @param schemaDir - Directory containing JSON schemas
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Options object for payload validator creation
-   */
-  public static readonly PayloadValidatorOptions = (
-    ocppVersion: OCPPVersion,
-    schemaDir: string,
-    moduleName: string,
-    methodName: string
-  ) =>
-    ({
-      methodName,
-      moduleName,
-      ocppVersion,
-      schemaDir,
-    }) as const
-
-  /**
-   * Parses and loads a JSON schema file for OCPP payload validation.
-   * Handles file reading and JSON parsing for schema validation.
-   * @param relativePath - Path to the schema file relative to the OCPP utils directory
-   * @param ocppVersion - The OCPP version for error logging context
-   * @param moduleName - Optional module name for error logging
-   * @param methodName - Optional method name for error logging
-   * @returns Parsed JSON schema object
-   * @throws {NodeJS.ErrnoException} If the schema file cannot be read or parsed
-   */
-  protected static parseJsonSchemaFile<T extends JsonType>(
-    relativePath: string,
-    ocppVersion: OCPPVersion,
-    moduleName?: string,
-    methodName?: string
-  ): JSONSchemaType<T> {
-    const baseDir = dirname(fileURLToPath(import.meta.url))
-    // Primary: resolve from file directory (production esbuild bundle)
-    const primaryPath = join(baseDir, relativePath)
+/**
+ * Options for payload validator creation.
+ * @param ocppVersion - The OCPP version
+ * @param schemaDir - Directory containing JSON schemas
+ * @param moduleName - Name of the OCPP module
+ * @param methodName - Name of the method/command
+ * @returns Options object for payload validator creation
+ */
+export const PayloadValidatorOptions = (
+  ocppVersion: OCPPVersion,
+  schemaDir: string,
+  moduleName: string,
+  methodName: string
+) =>
+  ({
+    methodName,
+    moduleName,
+    ocppVersion,
+    schemaDir,
+  }) as const
+
+/**
+ * Parses and loads a JSON schema file for OCPP payload validation.
+ * Handles file reading and JSON parsing for schema validation.
+ * @param relativePath - Path to the schema file relative to the OCPP utils directory
+ * @param ocppVersion - The OCPP version for error logging context
+ * @param moduleName - Optional module name for error logging
+ * @param methodName - Optional method name for error logging
+ * @returns Parsed JSON schema object
+ * @throws {NodeJS.ErrnoException} If the schema file cannot be read or parsed
+ */
+export function parseJsonSchemaFile<T extends JsonType> (
+  relativePath: string,
+  ocppVersion: OCPPVersion,
+  moduleName?: string,
+  methodName?: string
+): JSONSchemaType<T> {
+  const baseDir = dirname(fileURLToPath(import.meta.url))
+  // Primary: resolve from file directory (production esbuild bundle)
+  const primaryPath = join(baseDir, relativePath)
+  try {
+    return JSON.parse(readFileSync(primaryPath, 'utf8')) as JSONSchemaType<T>
+  } catch (primaryError) {
+    // Fallback: resolve from source root (development/test with tsx)
+    const fallbackPath = join(baseDir, '..', '..', relativePath)
     try {
-      return JSON.parse(readFileSync(primaryPath, 'utf8')) as JSONSchemaType<T>
-    } catch (primaryError) {
-      // Fallback: resolve from source root (development/test with tsx)
-      const fallbackPath = join(baseDir, '..', '..', relativePath)
-      try {
-        return JSON.parse(readFileSync(fallbackPath, 'utf8')) as JSONSchemaType<T>
-      } catch {
-        handleFileException(
-          primaryPath,
-          FileType.JsonSchema,
-          primaryError as NodeJS.ErrnoException,
-          OCPPServiceUtils.logPrefix(ocppVersion, moduleName, methodName)
-        )
-        // handleFileException throws by default; this satisfies the compiler
-        throw primaryError
-      }
+      return JSON.parse(readFileSync(fallbackPath, 'utf8')) as JSONSchemaType<T>
+    } catch {
+      handleFileException(
+        primaryPath,
+        FileType.JsonSchema,
+        primaryError as NodeJS.ErrnoException,
+        ocppServiceUtilsLogPrefix(ocppVersion, moduleName, methodName)
+      )
+      // handleFileException throws by default; this satisfies the compiler
+      throw primaryError
     }
   }
+}
 
-  private static readonly logPrefix = (
-    ocppVersion: OCPPVersion,
-    moduleName?: string,
-    methodName?: string
-  ): string => {
-    const logMsg =
-      isNotEmptyString(moduleName) && isNotEmptyString(methodName)
-        ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
-        : ` OCPP ${ocppVersion} |`
-    return logPrefix(logMsg)
-  }
+const ocppServiceUtilsLogPrefix = (
+  ocppVersion: OCPPVersion,
+  moduleName?: string,
+  methodName?: string
+): string => {
+  const logMsg =
+    isNotEmptyString(moduleName) && isNotEmptyString(methodName)
+      ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
+      : ` OCPP ${ocppVersion} |`
+  return logPrefix(logMsg)
 }
diff --git a/src/charging-station/ocpp/auth/cache/index.ts b/src/charging-station/ocpp/auth/cache/index.ts
deleted file mode 100644 (file)
index 5161b80..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export { InMemoryAuthCache } from './InMemoryAuthCache.js'
diff --git a/src/charging-station/ocpp/auth/factories/index.ts b/src/charging-station/ocpp/auth/factories/index.ts
deleted file mode 100644 (file)
index 2df7e0b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export { AuthComponentFactory } from './AuthComponentFactory.js'
index b6aa93eefddb0cdccf5b32ab32be7574a782f63e..14cb45c764fdf93290ee1af269c81a7720527940 100644 (file)
@@ -80,4 +80,5 @@ export {
 // Utils
 // ============================================================================
 
-export * from './utils/index.js'
+export { AuthValidators } from './utils/AuthValidators.js'
+export { AuthConfigValidator } from './utils/ConfigValidator.js'
diff --git a/src/charging-station/ocpp/auth/utils/index.ts b/src/charging-station/ocpp/auth/utils/index.ts
deleted file mode 100644 (file)
index 5af98ac..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * Authentication utilities module
- *
- * Provides validation and helper functions for authentication operations
- */
-
-export { AuthValidators } from './AuthValidators.js'
-export { AuthConfigValidator } from './ConfigValidator.js'
index c1d717668241de47f07f2331c207cfad2829dcf5..ed86ff6329b01a688ea8ed6646fd8816c291a8b7 100644 (file)
@@ -11,6 +11,12 @@ export { OCPPAuthServiceFactory } from './auth/index.js'
 export { isIdTagAuthorized } from './IdTagAuthorization.js'
 export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
 export { OCPPRequestService } from './OCPPRequestService.js'
+export {
+  flushQueuedTransactionMessages,
+  startTransactionOnConnector,
+  stopRunningTransactions,
+  stopTransactionOnConnector,
+} from './OCPPServiceOperations.js'
 export {
   buildMeterValue,
   buildStatusNotificationRequest,
index 6036e5474438f37cc58f3e85149f7566a70ff409..40cb28d4a89cc4e70a1e2b6e49951a873fec60cb 100644 (file)
@@ -10,7 +10,11 @@ import assert from 'node:assert/strict'
 import { afterEach, describe, it } from 'node:test'
 
 import { OCPP16ServiceUtils } from '../../../../src/charging-station/ocpp/1.6/OCPP16ServiceUtils.js'
-import { buildTransactionEndMeterValue } from '../../../../src/charging-station/ocpp/OCPPServiceUtils.js'
+import {
+  buildTransactionEndMeterValue,
+  isIncomingRequestCommandSupported,
+  isRequestCommandSupported,
+} from '../../../../src/charging-station/ocpp/OCPPServiceUtils.js'
 import {
   type OCPP16ChargingProfile,
   OCPP16ChargingProfileKindType,
@@ -568,10 +572,7 @@ await describe('OCPP16ServiceUtils — pure functions', async () => {
       })
 
       // Act
-      const result = OCPP16ServiceUtils.isRequestCommandSupported(
-        station,
-        OCPP16RequestCommand.HEARTBEAT
-      )
+      const result = isRequestCommandSupported(station, OCPP16RequestCommand.HEARTBEAT)
 
       // Assert
       assert.strictEqual(result, true)
@@ -590,10 +591,7 @@ await describe('OCPP16ServiceUtils — pure functions', async () => {
         },
       })
 
-      const result = OCPP16ServiceUtils.isRequestCommandSupported(
-        station,
-        OCPP16RequestCommand.HEARTBEAT
-      )
+      const result = isRequestCommandSupported(station, OCPP16RequestCommand.HEARTBEAT)
 
       assert.strictEqual(result, true)
     })
@@ -611,10 +609,7 @@ await describe('OCPP16ServiceUtils — pure functions', async () => {
         },
       })
 
-      const result = OCPP16ServiceUtils.isRequestCommandSupported(
-        station,
-        OCPP16RequestCommand.HEARTBEAT
-      )
+      const result = isRequestCommandSupported(station, OCPP16RequestCommand.HEARTBEAT)
 
       assert.strictEqual(result, false)
     })
@@ -629,10 +624,7 @@ await describe('OCPP16ServiceUtils — pure functions', async () => {
         stationInfo: { commandsSupport: undefined },
       })
 
-      const result = OCPP16ServiceUtils.isIncomingRequestCommandSupported(
-        station,
-        OCPP16IncomingRequestCommand.RESET
-      )
+      const result = isIncomingRequestCommandSupported(station, OCPP16IncomingRequestCommand.RESET)
 
       assert.strictEqual(result, true)
     })
@@ -649,10 +641,7 @@ await describe('OCPP16ServiceUtils — pure functions', async () => {
         },
       })
 
-      const result = OCPP16ServiceUtils.isIncomingRequestCommandSupported(
-        station,
-        OCPP16IncomingRequestCommand.RESET
-      )
+      const result = isIncomingRequestCommandSupported(station, OCPP16IncomingRequestCommand.RESET)
 
       assert.strictEqual(result, true)
     })
@@ -669,7 +658,7 @@ await describe('OCPP16ServiceUtils — pure functions', async () => {
         },
       })
 
-      const result = OCPP16ServiceUtils.isIncomingRequestCommandSupported(
+      const result = isIncomingRequestCommandSupported(
         station,
         OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION
       )
index e5f95c39e08536b08154d110d189ba27518d434d..95c0a55890e598d9b5a431d7456819c3b24aaf3a 100644 (file)
@@ -23,7 +23,7 @@ import {
   OCPP20ServiceUtils,
 } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
 import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
-import { startUpdatedMeterValues } from '../../../../src/charging-station/ocpp/OCPPServiceUtils.js'
+import { startUpdatedMeterValues } from '../../../../src/charging-station/ocpp/OCPPServiceOperations.js'
 import { OCPPError } from '../../../../src/exception/index.js'
 import {
   AttributeEnumType,
@@ -2010,7 +2010,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
     await describe('startUpdatedMeterValues', async () => {
       await it('should not start OCPP 2.0 timer for OCPP 1.6 stations via dispatch', async t => {
-        await withMockTimers(t, ['setInterval'], async () => {
+        await withMockTimers(t, ['setInterval'], () => {
           const { station: ocpp16Station } = createMockChargingStation({
             baseName: TEST_CHARGING_STATION_BASE_NAME,
             connectorsCount: 1,
@@ -2019,7 +2019,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
             },
           })
 
-          await startUpdatedMeterValues(ocpp16Station, 1, 60000)
+          startUpdatedMeterValues(ocpp16Station, 1, 60000)
 
           const connectorStatus = ocpp16Station.getConnectorStatus(1)
           assert.strictEqual(connectorStatus?.transactionUpdatedMeterValuesSetInterval, undefined)
index ffa3dc926a8e72035a915a8217c89931bf0b02d8..73a6f368f7a303f0f2bd6ab8a11f71f7f79e16d8 100644 (file)
@@ -12,11 +12,11 @@ import type { MockChargingStationOptions } from '../helpers/StationHelpers.js'
 
 import {
   flushQueuedTransactionMessages,
-  mapStopReasonToOCPP20,
   startTransactionOnConnector,
   stopRunningTransactions,
   stopTransactionOnConnector,
-} from '../../../src/charging-station/ocpp/OCPPServiceUtils.js'
+} from '../../../src/charging-station/ocpp/OCPPServiceOperations.js'
+import { mapStopReasonToOCPP20 } from '../../../src/charging-station/ocpp/OCPPServiceUtils.js'
 import {
   type OCPP20TransactionEventRequest,
   OCPPVersion,
index dbb503447411c8ca922676d0ac559bb0d363a77c..236284dd148138359baa88a841bf8fcecab66f7b 100644 (file)
@@ -18,7 +18,7 @@ import type { ChargingStation } from '../../../src/charging-station/index.js'
 import {
   ajvErrorsToErrorType,
   convertDateToISOString,
-  OCPPServiceUtils,
+  isConnectorIdValid,
 } from '../../../src/charging-station/ocpp/OCPPServiceUtils.js'
 import { ErrorType, IncomingRequestCommand, type JsonType } from '../../../src/types/index.js'
 import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
@@ -136,7 +136,7 @@ await describe('OCPPServiceUtils — pure functions', async () => {
 
   await describe('OCPPServiceUtils.isConnectorIdValid', async () => {
     await it('should return true for connector ID greater than zero', () => {
-      const result = OCPPServiceUtils.isConnectorIdValid(
+      const result = isConnectorIdValid(
         makeStationMock(),
         IncomingRequestCommand.REMOTE_START_TRANSACTION,
         1
@@ -145,7 +145,7 @@ await describe('OCPPServiceUtils — pure functions', async () => {
     })
 
     await it('should return true for connector ID zero', () => {
-      const result = OCPPServiceUtils.isConnectorIdValid(
+      const result = isConnectorIdValid(
         makeStationMock(),
         IncomingRequestCommand.REMOTE_START_TRANSACTION,
         0
@@ -154,7 +154,7 @@ await describe('OCPPServiceUtils — pure functions', async () => {
     })
 
     await it('should return false for negative connector ID', () => {
-      const result = OCPPServiceUtils.isConnectorIdValid(
+      const result = isConnectorIdValid(
         makeStationMock(),
         IncomingRequestCommand.REMOTE_START_TRANSACTION,
         -1
index c2c155709463a9c4bf01ac74e79e5616edf066e5..af26b0d0302773fd15dc58538b7e60754f15948c 100644 (file)
@@ -14,7 +14,11 @@ import { afterEach, describe, it } from 'node:test'
 
 import type { ChargingStation } from '../../../src/charging-station/index.js'
 
-import { OCPPServiceUtils } from '../../../src/charging-station/ocpp/OCPPServiceUtils.js'
+import {
+  isIncomingRequestCommandSupported,
+  isMessageTriggerSupported,
+  isRequestCommandSupported,
+} from '../../../src/charging-station/ocpp/OCPPServiceUtils.js'
 import { IncomingRequestCommand, MessageTrigger, RequestCommand } from '../../../src/types/index.js'
 import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
 
@@ -45,10 +49,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         },
       })
 
-      const result = OCPPServiceUtils.isIncomingRequestCommandSupported(
-        station,
-        IncomingRequestCommand.RESET
-      )
+      const result = isIncomingRequestCommandSupported(station, IncomingRequestCommand.RESET)
 
       assert.strictEqual(result, true)
     })
@@ -62,10 +63,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         },
       })
 
-      const result = OCPPServiceUtils.isIncomingRequestCommandSupported(
-        station,
-        IncomingRequestCommand.RESET
-      )
+      const result = isIncomingRequestCommandSupported(station, IncomingRequestCommand.RESET)
 
       assert.strictEqual(result, false)
     })
@@ -73,10 +71,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
     await it('should return true when commandsSupport is undefined', () => {
       const station = makeStationMock({})
 
-      const result = OCPPServiceUtils.isIncomingRequestCommandSupported(
-        station,
-        IncomingRequestCommand.RESET
-      )
+      const result = isIncomingRequestCommandSupported(station, IncomingRequestCommand.RESET)
 
       assert.strictEqual(result, true)
     })
@@ -86,10 +81,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         commandsSupport: {},
       })
 
-      const result = OCPPServiceUtils.isIncomingRequestCommandSupported(
-        station,
-        IncomingRequestCommand.RESET
-      )
+      const result = isIncomingRequestCommandSupported(station, IncomingRequestCommand.RESET)
 
       assert.strictEqual(result, true)
     })
@@ -105,7 +97,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         },
       })
 
-      const result = OCPPServiceUtils.isRequestCommandSupported(station, RequestCommand.HEARTBEAT)
+      const result = isRequestCommandSupported(station, RequestCommand.HEARTBEAT)
 
       assert.strictEqual(result, true)
     })
@@ -119,7 +111,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         },
       })
 
-      const result = OCPPServiceUtils.isRequestCommandSupported(station, RequestCommand.HEARTBEAT)
+      const result = isRequestCommandSupported(station, RequestCommand.HEARTBEAT)
 
       assert.strictEqual(result, false)
     })
@@ -127,7 +119,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
     await it('should return true when commandsSupport is undefined', () => {
       const station = makeStationMock({})
 
-      const result = OCPPServiceUtils.isRequestCommandSupported(station, RequestCommand.HEARTBEAT)
+      const result = isRequestCommandSupported(station, RequestCommand.HEARTBEAT)
 
       assert.strictEqual(result, true)
     })
@@ -137,7 +129,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         commandsSupport: {},
       })
 
-      const result = OCPPServiceUtils.isRequestCommandSupported(station, RequestCommand.HEARTBEAT)
+      const result = isRequestCommandSupported(station, RequestCommand.HEARTBEAT)
 
       assert.strictEqual(result, true)
     })
@@ -151,7 +143,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         },
       })
 
-      const result = OCPPServiceUtils.isMessageTriggerSupported(station, MessageTrigger.Heartbeat)
+      const result = isMessageTriggerSupported(station, MessageTrigger.Heartbeat)
 
       assert.strictEqual(result, true)
     })
@@ -163,7 +155,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         },
       })
 
-      const result = OCPPServiceUtils.isMessageTriggerSupported(station, MessageTrigger.Heartbeat)
+      const result = isMessageTriggerSupported(station, MessageTrigger.Heartbeat)
 
       assert.strictEqual(result, false)
     })
@@ -171,7 +163,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
     await it('should return true when messageTriggerSupport is undefined', () => {
       const station = makeStationMock({})
 
-      const result = OCPPServiceUtils.isMessageTriggerSupported(station, MessageTrigger.Heartbeat)
+      const result = isMessageTriggerSupported(station, MessageTrigger.Heartbeat)
 
       assert.strictEqual(result, true)
     })
@@ -181,7 +173,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => {
         messageTriggerSupport: null,
       })
 
-      const result = OCPPServiceUtils.isMessageTriggerSupported(station, MessageTrigger.Heartbeat)
+      const result = isMessageTriggerSupported(station, MessageTrigger.Heartbeat)
 
       assert.strictEqual(result, true)
     })