fix: ensure inflight requests id cannot be duplicated
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
index e65f8a8cb3f7316a6415d8697e1bb527d1a5c20e..aec5d65f31ba851e199aa865b8b759694b81faea 100644 (file)
@@ -3,10 +3,9 @@
 import type { ValidateFunction } from 'ajv'
 import { secondsToMilliseconds } from 'date-fns'
 
-import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
 import {
-  type ChargingStation,
   addConfigurationKey,
+  type ChargingStation,
   getConfigurationKey,
   hasReservationExpired,
   resetConnectorStatus
@@ -14,6 +13,7 @@ import {
 import { OCPPError } from '../../../exception/index.js'
 import {
   type ChangeConfigurationResponse,
+  ChargingStationEvents,
   ErrorType,
   type GenericResponse,
   type GetConfigurationResponse,
@@ -51,18 +51,19 @@ import {
   type SetChargingProfileResponse,
   type UnlockConnectorResponse
 } from '../../../types/index.js'
-import { Constants, convertToInt, logger } from '../../../utils/index.js'
+import { Constants, convertToInt, isAsyncFunction, logger } from '../../../utils/index.js'
 import { OCPPResponseService } from '../OCPPResponseService.js'
+import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
 
 const moduleName = 'OCPP16ResponseService'
 
 export class OCPP16ResponseService extends OCPPResponseService {
-  public jsonSchemasIncomingRequestResponseValidateFunction: Map<
+  public incomingRequestResponsePayloadValidateFunctions: Map<
   OCPP16IncomingRequestCommand,
   ValidateFunction<JsonType>
   >
 
-  protected jsonSchemasValidateFunction: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
+  protected payloadValidateFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
   private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>
 
   public constructor () {
@@ -97,7 +98,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler],
       [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, this.emptyResponseHandler]
     ])
-    this.jsonSchemasValidateFunction = new Map<OCPP16RequestCommand, ValidateFunction<JsonType>>([
+    this.payloadValidateFunctions = new Map<OCPP16RequestCommand, ValidateFunction<JsonType>>([
       [
         OCPP16RequestCommand.BOOT_NOTIFICATION,
         this.ajv
@@ -219,13 +220,13 @@ export class OCPP16ResponseService extends OCPPResponseService {
           .bind(this)
       ]
     ])
-    this.jsonSchemasIncomingRequestResponseValidateFunction = new Map<
+    this.incomingRequestResponsePayloadValidateFunctions = new Map<
     OCPP16IncomingRequestCommand,
     ValidateFunction<JsonType>
     >([
       [
         OCPP16IncomingRequestCommand.RESET,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
               'assets/json-schemas/ocpp/1.6/ResetResponse.json',
@@ -237,7 +238,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.CLEAR_CACHE,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
               'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
@@ -249,7 +250,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>(
               'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
@@ -261,7 +262,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>(
               'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
@@ -273,7 +274,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.GET_CONFIGURATION,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>(
               'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
@@ -285,7 +286,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>(
               'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
@@ -297,7 +298,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>(
               'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
@@ -309,7 +310,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>(
               'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
@@ -321,7 +322,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>(
               'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
@@ -333,7 +334,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
               'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
@@ -345,7 +346,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
               'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
@@ -357,7 +358,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>(
               'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
@@ -369,7 +370,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>(
               'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
@@ -381,7 +382,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.DATA_TRANSFER,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
               'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
@@ -393,7 +394,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>(
               'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
@@ -405,7 +406,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.RESERVE_NOW,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>(
               'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json',
@@ -417,7 +418,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       ],
       [
         OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
-        this.ajv
+        this.ajvIncomingRequest
           .compile(
             OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
               'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json',
@@ -445,7 +446,18 @@ export class OCPP16ResponseService extends OCPPResponseService {
         try {
           this.validatePayload(chargingStation, commandName, payload)
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload)
+          const responseHandler = this.responseHandlers.get(commandName)!
+          if (isAsyncFunction(responseHandler)) {
+            await responseHandler(chargingStation, payload, requestPayload)
+          } else {
+            (
+              responseHandler as (
+                chargingStation: ChargingStation,
+                payload: JsonType,
+                requestPayload?: JsonType
+              ) => void
+            )(chargingStation, payload, requestPayload)
+          }
         } catch (error) {
           logger.error(
             `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
@@ -473,7 +485,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
           payload,
           undefined,
           2
-        )} while the charging station is not registered on the central server.`,
+        )} while the charging station is not registered on the central server`,
         commandName,
         payload
       )
@@ -485,7 +497,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
     commandName: OCPP16RequestCommand,
     payload: JsonType
   ): boolean {
-    if (this.jsonSchemasValidateFunction.has(commandName)) {
+    if (this.payloadValidateFunctions.has(commandName)) {
       return this.validateResponsePayload(chargingStation, commandName, payload)
     }
     logger.warn(
@@ -498,24 +510,30 @@ export class OCPP16ResponseService extends OCPPResponseService {
     chargingStation: ChargingStation,
     payload: OCPP16BootNotificationResponse
   ): void {
-    if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
-      addConfigurationKey(
-        chargingStation,
-        OCPP16StandardParametersKey.HeartbeatInterval,
-        payload.interval.toString(),
-        {},
-        { overwrite: true, save: true }
-      )
-      addConfigurationKey(
-        chargingStation,
-        OCPP16StandardParametersKey.HeartBeatInterval,
-        payload.interval.toString(),
-        { visible: false },
-        { overwrite: true, save: true }
-      )
-      OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
-    }
     if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
+      chargingStation.bootNotificationResponse = payload
+      if (chargingStation.isRegistered()) {
+        chargingStation.emit(ChargingStationEvents.registered)
+        if (chargingStation.inAcceptedState()) {
+          addConfigurationKey(
+            chargingStation,
+            OCPP16StandardParametersKey.HeartbeatInterval,
+            payload.interval.toString(),
+            {},
+            { overwrite: true, save: true }
+          )
+          addConfigurationKey(
+            chargingStation,
+            OCPP16StandardParametersKey.HeartBeatInterval,
+            payload.interval.toString(),
+            { visible: false },
+            { overwrite: true, save: true }
+          )
+          chargingStation.emit(ChargingStationEvents.accepted)
+        }
+      } else if (chargingStation.inRejectedState()) {
+        chargingStation.emit(ChargingStationEvents.rejected)
+      }
       const logMsg = `${chargingStation.logPrefix()} Charging station in '${
         payload.status
       }' state on the central server`
@@ -523,6 +541,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
         ? logger.warn(logMsg)
         : logger.info(logMsg)
     } else {
+      delete chargingStation.bootNotificationResponse
       logger.error(
         `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
         payload
@@ -572,9 +591,9 @@ export class OCPP16ResponseService extends OCPPResponseService {
         authorizeConnectorStatus.idTagAuthorized = false
         delete authorizeConnectorStatus.authorizeIdTag
         logger.debug(
-          `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
-            payload.idTagInfo.status
-          }`
+          `${chargingStation.logPrefix()} idTag '${
+            requestPayload.idTag
+          }' rejected with status '${payload.idTagInfo.status}'`
         )
       }
     } else {
@@ -687,7 +706,9 @@ export class OCPP16ResponseService extends OCPPResponseService {
       connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
     ) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
+        `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${
+          connectorStatus?.status
+        }`
       )
       return
     }
@@ -722,9 +743,9 @@ export class OCPP16ResponseService extends OCPPResponseService {
             logger.warn(
               `${chargingStation.logPrefix()} Reserved transaction ${
                 payload.transactionId
-              } started with a different idTag ${requestPayload.idTag} than the reservation one ${
-                reservation.idTag
-              }`
+              } started with a different idTag ${
+                requestPayload.idTag
+              } than the reservation one ${reservation.idTag}`
             )
           }
           if (hasReservationExpired(reservation)) {
@@ -763,11 +784,9 @@ export class OCPP16ResponseService extends OCPPResponseService {
         OCPP16ChargePointStatus.Charging
       )
       logger.info(
-        `${chargingStation.logPrefix()} Transaction with id ${
-          payload.transactionId
-        } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
-          requestPayload.idTag
-        }'`
+        `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
+          chargingStation.stationInfo?.chargingStationId
+        }#${connectorId} for idTag '${requestPayload.idTag}'`
       )
       if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -805,16 +824,10 @@ export class OCPP16ResponseService extends OCPPResponseService {
     chargingStation: ChargingStation,
     connectorId: number
   ): Promise<void> {
+    chargingStation.stopMeterValues(connectorId)
     const connectorStatus = chargingStation.getConnectorStatus(connectorId)
     resetConnectorStatus(connectorStatus)
-    chargingStation.stopMeterValues(connectorId)
-    if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
-      await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-        chargingStation,
-        connectorId,
-        OCPP16ChargePointStatus.Available
-      )
-    }
+    await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
   }
 
   private async handleResponseStopTransaction (