Add StatusNotification command to OCPP 2.0.1 stack
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 9 Jan 2023 23:36:59 +0000 (00:36 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 9 Jan 2023 23:36:59 +0000 (00:36 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
12 files changed:
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/2.0/OCPP20RequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/types/ConnectorStatus.ts
src/types/ocpp/2.0/Requests.ts
src/types/ocpp/2.0/Responses.ts
src/types/ocpp/ChargePointStatus.ts [deleted file]
src/types/ocpp/ChargingProfile.ts
src/types/ocpp/ConnectorStatusEnum.ts [new file with mode: 0644]
src/types/ocpp/Requests.ts
src/types/ocpp/Responses.ts

index 84aa81ca649336eda64779744c87600ac5d9a098..997a8ebb161d95e67e5fa7a4b460ec0e6ff196d3 100644 (file)
@@ -26,7 +26,6 @@ import type { ConnectorStatus } from '../types/ConnectorStatus';
 import { FileType } from '../types/FileType';
 import type { JsonType } from '../types/JsonType';
 import { ChargePointErrorCode } from '../types/ocpp/ChargePointErrorCode';
-import { ChargePointStatus } from '../types/ocpp/ChargePointStatus';
 import { ChargingProfile, ChargingRateUnitType } from '../types/ocpp/ChargingProfile';
 import {
   ConnectorPhaseRotation,
@@ -34,6 +33,7 @@ import {
   SupportedFeatureProfiles,
   VendorDefaultParametersKey,
 } from '../types/ocpp/Configuration';
+import { ConnectorStatusEnum } from '../types/ocpp/ConnectorStatusEnum';
 import { ErrorType } from '../types/ocpp/ErrorType';
 import { MessageType } from '../types/ocpp/MessageType';
 import { MeterValue, MeterValueMeasurand } from '../types/ocpp/MeterValues';
@@ -90,6 +90,7 @@ import OCPP20RequestService from './ocpp/2.0/OCPP20RequestService';
 import OCPP20ResponseService from './ocpp/2.0/OCPP20ResponseService';
 import type OCPPIncomingRequestService from './ocpp/OCPPIncomingRequestService';
 import type OCPPRequestService from './ocpp/OCPPRequestService';
+import { OCPPServiceUtils } from './ocpp/OCPPServiceUtils';
 import SharedLRUCache from './SharedLRUCache';
 
 export default class ChargingStation {
@@ -1815,7 +1816,7 @@ export default class ChargingStation {
     this.startHeartbeat();
     // Initialize connectors status
     for (const connectorId of this.connectors.keys()) {
-      let chargePointStatus: ChargePointStatus;
+      let connectorStatus: ConnectorStatusEnum;
       if (connectorId === 0) {
         continue;
       } else if (
@@ -1823,29 +1824,29 @@ export default class ChargingStation {
         (this.isChargingStationAvailable() === false ||
           this.isConnectorAvailable(connectorId) === false)
       ) {
-        chargePointStatus = ChargePointStatus.UNAVAILABLE;
+        connectorStatus = ConnectorStatusEnum.UNAVAILABLE;
       } else if (
         !this.getConnectorStatus(connectorId)?.status &&
         this.getConnectorStatus(connectorId)?.bootStatus
       ) {
         // Set boot status in template at startup
-        chargePointStatus = this.getConnectorStatus(connectorId).bootStatus;
+        connectorStatus = this.getConnectorStatus(connectorId).bootStatus;
       } else if (this.getConnectorStatus(connectorId)?.status) {
         // Set previous status at startup
-        chargePointStatus = this.getConnectorStatus(connectorId).status;
+        connectorStatus = this.getConnectorStatus(connectorId).status;
       } else {
         // Set default status
-        chargePointStatus = ChargePointStatus.AVAILABLE;
+        connectorStatus = ConnectorStatusEnum.AVAILABLE;
       }
       await this.ocppRequestService.requestHandler<
         StatusNotificationRequest,
         StatusNotificationResponse
-      >(this, RequestCommand.STATUS_NOTIFICATION, {
-        connectorId,
-        status: chargePointStatus,
-        errorCode: ChargePointErrorCode.NO_ERROR,
-      });
-      this.getConnectorStatus(connectorId).status = chargePointStatus;
+      >(
+        this,
+        RequestCommand.STATUS_NOTIFICATION,
+        OCPPServiceUtils.buildStatusNotificationRequest(this, connectorId, connectorStatus)
+      );
+      this.getConnectorStatus(connectorId).status = connectorStatus;
     }
     if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
       await this.ocppRequestService.requestHandler<
@@ -1882,11 +1883,15 @@ export default class ChargingStation {
         await this.ocppRequestService.requestHandler<
           StatusNotificationRequest,
           StatusNotificationResponse
-        >(this, RequestCommand.STATUS_NOTIFICATION, {
-          connectorId,
-          status: ChargePointStatus.UNAVAILABLE,
-          errorCode: ChargePointErrorCode.NO_ERROR,
-        });
+        >(
+          this,
+          RequestCommand.STATUS_NOTIFICATION,
+          OCPPServiceUtils.buildStatusNotificationRequest(
+            this,
+            connectorId,
+            ConnectorStatusEnum.UNAVAILABLE
+          )
+        );
         this.getConnectorStatus(connectorId).status = null;
       }
     }
index 3e54ca50326c4bde179674662b50bfbe5ed6d6bb..6a7fe370cbf9103e5c98c7942299f248d601401e 100644 (file)
@@ -12,6 +12,7 @@ import {
   type OCPP20BootNotificationRequest,
   type OCPP20HeartbeatRequest,
   OCPP20RequestCommand,
+  type OCPP20StatusNotificationRequest,
 } from '../../../types/ocpp/2.0/Requests';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
 import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
@@ -57,6 +58,18 @@ export default class OCPP20RequestService extends OCPPRequestService {
           )
         ) as JSONSchemaType<OCPP20HeartbeatRequest>,
       ],
+      [
+        OCPP20RequestCommand.STATUS_NOTIFICATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/2.0/StatusNotificationRequest.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP20StatusNotificationRequest>,
+      ],
     ]);
     this.buildRequestPayload.bind(this);
   }
@@ -125,6 +138,13 @@ export default class OCPP20RequestService extends OCPPRequestService {
         } as unknown as Request;
       case OCPP20RequestCommand.HEARTBEAT:
         return {} as unknown as Request;
+      case OCPP20RequestCommand.STATUS_NOTIFICATION:
+        return {
+          timestamp: commandParams?.timestamp,
+          connectorStatus: commandParams?.connectorStatus,
+          evseId: commandParams?.evseId,
+          connectorId: commandParams?.connectorId,
+        } as unknown as Request;
       default:
         // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
         throw new OCPPError(
index 15b355a5b0862e1de4e851e84d36070c376663d4..add126e408933e9576c90ea59988057bbe73d2be 100644 (file)
@@ -16,6 +16,7 @@ import type {
   OCPP20BootNotificationResponse,
   OCPP20ClearCacheResponse,
   OCPP20HeartbeatResponse,
+  OCPP20StatusNotificationResponse,
 } from '../../../types/ocpp/2.0/Responses';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
 import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
@@ -44,6 +45,7 @@ export default class OCPP20ResponseService extends OCPPResponseService {
     this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
       [OCPP20RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
       [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)],
+      [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
     ]);
     this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>([
       [
@@ -70,6 +72,18 @@ export default class OCPP20ResponseService extends OCPPResponseService {
           )
         ) as JSONSchemaType<OCPP20HeartbeatResponse>,
       ],
+      [
+        OCPP20RequestCommand.STATUS_NOTIFICATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP20StatusNotificationResponse>,
+      ],
     ]);
     this.jsonIncomingRequestResponseSchemas = new Map([
       [
index 3c41525140774d82d9f459208ea15ee7941cf988..06c22367991bf5d6ac83d356b5aeeee7ec6218f2 100644 (file)
@@ -3,10 +3,20 @@ import type { DefinedError, ErrorObject } from 'ajv';
 import BaseError from '../../exception/BaseError';
 import type { JsonObject, JsonType } from '../../types/JsonType';
 import type { SampledValueTemplate } from '../../types/MeasurandPerPhaseSampledValueTemplates';
+import type { OCPP16StatusNotificationRequest } from '../../types/ocpp/1.6/Requests';
+import type { OCPP20StatusNotificationRequest } from '../../types/ocpp/2.0/Requests';
+import { ChargePointErrorCode } from '../../types/ocpp/ChargePointErrorCode';
 import { StandardParametersKey } from '../../types/ocpp/Configuration';
+import type { ConnectorStatusEnum } from '../../types/ocpp/ConnectorStatusEnum';
 import { ErrorType } from '../../types/ocpp/ErrorType';
 import { MeterValueMeasurand, type MeterValuePhase } from '../../types/ocpp/MeterValues';
-import { IncomingRequestCommand, MessageTrigger, RequestCommand } from '../../types/ocpp/Requests';
+import { OCPPVersion } from '../../types/ocpp/OCPPVersion';
+import {
+  IncomingRequestCommand,
+  MessageTrigger,
+  RequestCommand,
+  type StatusNotificationRequest,
+} from '../../types/ocpp/Requests';
 import Constants from '../../utils/Constants';
 import logger from '../../utils/Logger';
 import Utils from '../../utils/Utils';
@@ -115,6 +125,29 @@ export class OCPPServiceUtils {
     }
   }
 
+  public static buildStatusNotificationRequest(
+    chargingStation: ChargingStation,
+    connectorId: number,
+    status: ConnectorStatusEnum
+  ): StatusNotificationRequest {
+    switch (chargingStation.stationInfo.ocppVersion) {
+      case OCPPVersion.VERSION_16:
+        return {
+          connectorId,
+          status,
+          errorCode: ChargePointErrorCode.NO_ERROR,
+        } as OCPP16StatusNotificationRequest;
+      case OCPPVersion.VERSION_20:
+      case OCPPVersion.VERSION_201:
+        return {
+          timestamp: new Date(),
+          connectorStatus: status,
+          connectorId,
+          evseId: connectorId,
+        } as OCPP20StatusNotificationRequest;
+    }
+  }
+
   protected static getSampledValueTemplate(
     chargingStation: ChargingStation,
     connectorId: number,
index 209cd42e42be230f72c12985fed322087b70f162..d7698107b9cc2146677db9624f66ae174d8c9dd8 100644 (file)
@@ -1,13 +1,13 @@
 import type { SampledValueTemplate } from './MeasurandPerPhaseSampledValueTemplates';
-import type { ChargePointStatus } from './ocpp/ChargePointStatus';
 import type { ChargingProfile } from './ocpp/ChargingProfile';
+import type { ConnectorStatusEnum } from './ocpp/ConnectorStatusEnum';
 import type { MeterValue } from './ocpp/MeterValues';
 import type { AvailabilityType } from './ocpp/Requests';
 
 export type ConnectorStatus = {
   availability: AvailabilityType;
-  bootStatus?: ChargePointStatus;
-  status?: ChargePointStatus;
+  bootStatus?: ConnectorStatusEnum;
+  status?: ConnectorStatusEnum;
   MeterValues: SampledValueTemplate[];
   authorizeIdTag?: string;
   idTagAuthorized?: boolean;
index 72ad392eace7838e6e00295ee55189e4d6319345..1b30a976a7ee21f3b0d27876e5269ee0475e4d2a 100644 (file)
@@ -4,6 +4,7 @@ import type { JsonObject } from '../../JsonType';
 export enum OCPP20RequestCommand {
   BOOT_NOTIFICATION = 'BootNotification',
   HEARTBEAT = 'Heartbeat',
+  STATUS_NOTIFICATION = 'StatusNotification',
 }
 
 export enum OCPP20IncomingRequestCommand {
@@ -45,3 +46,18 @@ export type OCPP20BootNotificationRequest = {
 export type OCPP20HeartbeatRequest = EmptyObject;
 
 export type OCPP20ClearCacheRequest = EmptyObject;
+
+export enum OCPP20ConnectorStatusEnumType {
+  AVAILABLE = 'Available',
+  OCCUPIED = 'Occupied',
+  RESERVED = 'Reserved',
+  UNAVAILABLE = 'Unavailable',
+  FAULTED = 'Faulted',
+}
+
+export type OCPP20StatusNotificationRequest = {
+  timestamp: Date;
+  connectorStatus: OCPP20ConnectorStatusEnumType;
+  evseId: number;
+  connectorId: number;
+};
index b83f0c84369eefbcdced173c26941294691cdd1f..53bbb173d656327f72fc3abdda7100857ca40d7f 100644 (file)
@@ -1,3 +1,4 @@
+import type { EmptyObject } from '../../EmptyObject';
 import type { JsonObject } from '../../JsonType';
 import type { DefaultStatus, RegistrationStatusEnumType } from '../Responses';
 
@@ -21,3 +22,5 @@ export type OCPP20ClearCacheResponse = {
   status: DefaultStatus;
   statusInfo?: StatusInfoType;
 } & JsonObject;
+
+export type OCPP20StatusNotificationResponse = EmptyObject;
diff --git a/src/types/ocpp/ChargePointStatus.ts b/src/types/ocpp/ChargePointStatus.ts
deleted file mode 100644 (file)
index a8ae442..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-import { OCPP16ChargePointStatus } from './1.6/ChargePointStatus';
-
-export const ChargePointStatus = {
-  ...OCPP16ChargePointStatus,
-} as const;
-export type ChargePointStatus = OCPP16ChargePointStatus;
index a8b73a24175ffab4a824c585062210d4db189be3..837102728a81163abd1e7d3684f97c40ed04c1ec 100644 (file)
@@ -1,7 +1,7 @@
 import {
-  OCPP16ChargingProfile,
+  type OCPP16ChargingProfile,
   OCPP16ChargingRateUnitType,
-  OCPP16ChargingSchedulePeriod,
+  type OCPP16ChargingSchedulePeriod,
 } from './1.6/ChargingProfile';
 
 export type ChargingProfile = OCPP16ChargingProfile;
diff --git a/src/types/ocpp/ConnectorStatusEnum.ts b/src/types/ocpp/ConnectorStatusEnum.ts
new file mode 100644 (file)
index 0000000..929d59c
--- /dev/null
@@ -0,0 +1,8 @@
+import { OCPP16ChargePointStatus } from './1.6/ChargePointStatus';
+import { OCPP20ConnectorStatusEnumType } from './2.0/Requests';
+
+export const ConnectorStatusEnum = {
+  ...OCPP16ChargePointStatus,
+  ...OCPP20ConnectorStatusEnumType,
+} as const;
+export type ConnectorStatusEnum = OCPP16ChargePointStatus | OCPP20ConnectorStatusEnumType;
index 993ba086a2d62ffd5e1bf6728787548743eedf46..35933c834cfbd01c37faf353feac60d96589ec13 100644 (file)
@@ -20,6 +20,7 @@ import {
   type OCPP20BootNotificationRequest,
   OCPP20IncomingRequestCommand,
   OCPP20RequestCommand,
+  type OCPP20StatusNotificationRequest,
 } from './2.0/Requests';
 import type { MessageType } from './MessageType';
 
@@ -65,7 +66,9 @@ export type BootNotificationRequest = OCPP16BootNotificationRequest | OCPP20Boot
 
 export type HeartbeatRequest = OCPP16HeartbeatRequest;
 
-export type StatusNotificationRequest = OCPP16StatusNotificationRequest;
+export type StatusNotificationRequest =
+  | OCPP16StatusNotificationRequest
+  | OCPP20StatusNotificationRequest;
 
 export type MeterValuesRequest = OCPP16MeterValuesRequest;
 
index 90c5f1ea99272c09caefa093cd91f4bc036fe090..323674625e4200d4dcab0334259aae0637cc4664 100644 (file)
@@ -16,7 +16,11 @@ import {
   OCPP16TriggerMessageStatus,
   OCPP16UnlockStatus,
 } from './1.6/Responses';
-import type { OCPP20BootNotificationResponse, OCPP20ClearCacheResponse } from './2.0/Responses';
+import type {
+  OCPP20BootNotificationResponse,
+  OCPP20ClearCacheResponse,
+  OCPP20StatusNotificationResponse,
+} from './2.0/Responses';
 import type { ErrorType } from './ErrorType';
 import type { MessageType } from './MessageType';
 
@@ -38,7 +42,9 @@ export type HeartbeatResponse = OCPP16HeartbeatResponse;
 
 export type ClearCacheResponse = DefaultResponse | OCPP20ClearCacheResponse;
 
-export type StatusNotificationResponse = OCPP16StatusNotificationResponse;
+export type StatusNotificationResponse =
+  | OCPP16StatusNotificationResponse
+  | OCPP20StatusNotificationResponse;
 
 export type MeterValuesResponse = OCPP16MeterValuesResponse;