Add BootNotification and ClearCache OCPP 2.0.1 commands support
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
index 56ad958658ed68c2249e8f9580317aa90af91254..7898ce6e1448d5ae4692839fb9488e737ba07516 100644 (file)
@@ -1,48 +1,49 @@
-// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
 import fs from 'fs';
 import path from 'path';
 import { fileURLToPath } from 'url';
 
-import { JSONSchemaType } from 'ajv';
+import type { JSONSchemaType } from 'ajv';
 
 import OCPPError from '../../../exception/OCPPError';
-import { JsonObject, JsonType } from '../../../types/JsonType';
+import type { JsonObject, JsonType } from '../../../types/JsonType';
 import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
 import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
-import {
+import type {
   OCPP16MeterValuesRequest,
   OCPP16MeterValuesResponse,
 } from '../../../types/ocpp/1.6/MeterValues';
 import {
-  OCPP16BootNotificationRequest,
+  type OCPP16BootNotificationRequest,
   OCPP16RequestCommand,
-  OCPP16StatusNotificationRequest,
+  type OCPP16StatusNotificationRequest,
 } from '../../../types/ocpp/1.6/Requests';
-import {
+import type {
   DiagnosticsStatusNotificationResponse,
   OCPP16BootNotificationResponse,
+  OCPP16DataTransferResponse,
   OCPP16HeartbeatResponse,
-  OCPP16RegistrationStatus,
   OCPP16StatusNotificationResponse,
 } from '../../../types/ocpp/1.6/Responses';
 import {
   OCPP16AuthorizationStatus,
-  OCPP16AuthorizeRequest,
-  OCPP16AuthorizeResponse,
-  OCPP16StartTransactionRequest,
-  OCPP16StartTransactionResponse,
-  OCPP16StopTransactionRequest,
-  OCPP16StopTransactionResponse,
+  type OCPP16AuthorizeRequest,
+  type OCPP16AuthorizeResponse,
+  type OCPP16StartTransactionRequest,
+  type OCPP16StartTransactionResponse,
+  type OCPP16StopTransactionRequest,
+  type OCPP16StopTransactionResponse,
 } from '../../../types/ocpp/1.6/Transaction';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
-import { ResponseHandler } from '../../../types/ocpp/Responses';
+import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
+import { RegistrationStatusEnumType, type ResponseHandler } from '../../../types/ocpp/Responses';
+import Constants from '../../../utils/Constants';
 import logger from '../../../utils/Logger';
 import Utils from '../../../utils/Utils';
 import type ChargingStation from '../../ChargingStation';
 import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils';
-import { ChargingStationUtils } from '../../ChargingStationUtils';
 import OCPPResponseService from '../OCPPResponseService';
 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
 
@@ -56,7 +57,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     if (new.target?.name === moduleName) {
       throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
     }
-    super();
+    super(OCPPVersion.VERSION_16);
     this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
       [OCPP16RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
       [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)],
@@ -66,6 +67,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       [OCPP16RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
       [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler.bind(this)],
       [OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
+      [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler.bind(this)],
     ]);
     this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>([
       [
@@ -164,7 +166,20 @@ export default class OCPP16ResponseService extends OCPPResponseService {
           )
         ) as JSONSchemaType<DiagnosticsStatusNotificationResponse>,
       ],
+      [
+        OCPP16RequestCommand.DATA_TRANSFER,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/DataTransferResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16DataTransferResponse>,
+      ],
     ]);
+    this.validatePayload.bind(this);
   }
 
   public async responseHandler(
@@ -173,23 +188,29 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     payload: JsonType,
     requestPayload: JsonType
   ): Promise<void> {
-    if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
+    if (
+      chargingStation.isRegistered() === true ||
+      commandName === OCPP16RequestCommand.BOOT_NOTIFICATION
+    ) {
       if (
-        this.responseHandlers.has(commandName) &&
-        ChargingStationUtils.isRequestCommandSupported(commandName, chargingStation)
+        this.responseHandlers.has(commandName) === true &&
+        OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true
       ) {
         try {
           this.validatePayload(chargingStation, commandName, payload);
           await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
         } catch (error) {
-          logger.error(chargingStation.logPrefix() + ' Handle request response error: %j', error);
+          logger.error(
+            `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
+            error
+          );
           throw error;
         }
       } else {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle request response PDU ${JSON.stringify(
+          `${commandName} is not implemented to handle response PDU ${JSON.stringify(
             payload,
             null,
             2
@@ -201,11 +222,11 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     } else {
       throw new OCPPError(
         ErrorType.SECURITY_ERROR,
-        `${commandName} cannot be issued to handle request response PDU ${JSON.stringify(
+        `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
           payload,
           null,
           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
       );
@@ -235,7 +256,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     chargingStation: ChargingStation,
     payload: OCPP16BootNotificationResponse
   ): void {
-    if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
+    if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
       ChargingStationConfigurationUtils.addConfigurationKey(
         chargingStation,
         OCPP16StandardParametersKey.HeartbeatInterval,
@@ -254,11 +275,11 @@ export default class OCPP16ResponseService extends OCPPResponseService {
         ? chargingStation.restartHeartbeat()
         : chargingStation.startHeartbeat();
     }
-    if (Object.values(OCPP16RegistrationStatus).includes(payload.status)) {
+    if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
       const logMsg = `${chargingStation.logPrefix()} Charging station in '${
         payload.status
       }' state on the central server`;
-      payload.status === OCPP16RegistrationStatus.REJECTED
+      payload.status === RegistrationStatusEnumType.REJECTED
         ? logger.warn(logMsg)
         : logger.info(logMsg);
     } else {
@@ -285,20 +306,24 @@ export default class OCPP16ResponseService extends OCPPResponseService {
         break;
       }
     }
+    const isAuthorizeConnectorIdDefined = authorizeConnectorId !== undefined;
     if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
-      chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true;
+      isAuthorizeConnectorIdDefined &&
+        (chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true);
       logger.debug(
-        `${chargingStation.logPrefix()} IdTag ${
-          requestPayload.idTag
-        } authorized on connector ${authorizeConnectorId}`
+        `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' accepted${
+          isAuthorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}` : ''
+        }`
       );
     } else {
-      chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false;
-      delete chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag;
+      if (isAuthorizeConnectorIdDefined) {
+        chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false;
+        delete chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag;
+      }
       logger.debug(
-        `${chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status '${
+        `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' rejected with status '${
           payload.idTagInfo.status
-        }' on connector ${authorizeConnectorId}`
+        }'${isAuthorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}` : ''}`
       );
     }
   }
@@ -326,11 +351,11 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       return;
     }
     if (
-      chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
-      chargingStation.getAuthorizeRemoteTxRequests() &&
-      chargingStation.getLocalAuthListEnabled() &&
+      chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
+      chargingStation.getAuthorizeRemoteTxRequests() === true &&
+      chargingStation.getLocalAuthListEnabled() === true &&
       chargingStation.hasAuthorizedTags() &&
-      !chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized
+      chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false
     ) {
       logger.error(
         chargingStation.logPrefix() +
@@ -343,11 +368,11 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       return;
     }
     if (
-      chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
-      chargingStation.getAuthorizeRemoteTxRequests() &&
-      chargingStation.getMayAuthorizeAtRemoteStart() &&
-      !chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
-      !chargingStation.getConnectorStatus(connectorId).idTagAuthorized
+      chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
+      chargingStation.getAuthorizeRemoteTxRequests() === true &&
+      chargingStation.getMustAuthorizeAtRemoteStart() === true &&
+      chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false &&
+      chargingStation.getConnectorStatus(connectorId).idTagAuthorized === false
     ) {
       logger.error(
         chargingStation.logPrefix() +
@@ -391,7 +416,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
       return;
     }
-    if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
+    if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
       logger.debug(
         chargingStation.logPrefix() +
           ' Trying to start a transaction on an already used connector ' +
@@ -413,14 +438,14 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       );
       return;
     }
-    if (!Number.isInteger(payload.transactionId)) {
-      logger.warn(
-        `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
-          payload.transactionId
-        }, converting to integer`
-      );
-      payload.transactionId = Utils.convertToInt(payload.transactionId);
-    }
+    // if (!Number.isInteger(payload.transactionId)) {
+    //   logger.warn(
+    //     `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
+    //       payload.transactionId
+    //     }, converting to integer`
+    //   );
+    //   payload.transactionId = Utils.convertToInt(payload.transactionId);
+    // }
 
     if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
       chargingStation.getConnectorStatus(connectorId).transactionStarted = true;
@@ -461,8 +486,9 @@ export default class OCPP16ResponseService extends OCPPResponseService {
           chargingStation.stationInfo.chargingStationId +
           '#' +
           connectorId.toString() +
-          ' for idTag ' +
-          requestPayload.idTag
+          " for idTag '" +
+          requestPayload.idTag +
+          "'"
       );
       if (chargingStation.stationInfo.powerSharedByConnectors) {
         chargingStation.powerDivider++;
@@ -476,7 +502,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
         connectorId,
         configuredMeterValueSampleInterval
           ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
-          : 60000
+          : Constants.DEFAULT_METER_VALUES_INTERVAL
       );
     } else {
       logger.warn(
@@ -485,8 +511,9 @@ export default class OCPP16ResponseService extends OCPPResponseService {
           payload.transactionId.toString() +
           " REJECTED with status '" +
           payload?.idTagInfo?.status +
-          "', idTag " +
-          requestPayload.idTag
+          "', idTag '" +
+          requestPayload.idTag +
+          "'"
       );
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
     }
@@ -529,9 +556,9 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       return;
     }
     if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
-      chargingStation.getBeginEndMeterValues() &&
-        !chargingStation.getOcppStrictCompliance() &&
-        chargingStation.getOutOfOrderEndMeterValues() &&
+      chargingStation.getBeginEndMeterValues() === true &&
+        chargingStation.getOcppStrictCompliance() === false &&
+        chargingStation.getOutOfOrderEndMeterValues() === true &&
         (await chargingStation.ocppRequestService.requestHandler<
           OCPP16MeterValuesRequest,
           OCPP16MeterValuesResponse
@@ -547,8 +574,8 @@ export default class OCPP16ResponseService extends OCPPResponseService {
           ],
         }));
       if (
-        !chargingStation.isChargingStationAvailable() ||
-        !chargingStation.isConnectorAvailable(transactionConnectorId)
+        chargingStation.isChargingStationAvailable() === false ||
+        chargingStation.isConnectorAvailable(transactionConnectorId) === false
       ) {
         await chargingStation.ocppRequestService.requestHandler<
           OCPP16StatusNotificationRequest,
@@ -575,6 +602,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       if (chargingStation.stationInfo.powerSharedByConnectors) {
         chargingStation.powerDivider--;
       }
+      chargingStation.resetConnectorStatus(transactionConnectorId);
       logger.info(
         chargingStation.logPrefix() +
           ' Transaction ' +
@@ -584,7 +612,6 @@ export default class OCPP16ResponseService extends OCPPResponseService {
           '#' +
           transactionConnectorId.toString()
       );
-      chargingStation.resetConnectorStatus(transactionConnectorId);
     } else {
       logger.warn(
         chargingStation.logPrefix() +