Add OCPP DataTransfer request support
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 16 Nov 2022 16:51:10 +0000 (17:51 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 16 Nov 2022 16:51:10 +0000 (17:51 +0100)
Ref #36

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
13 files changed:
README.md
src/assets/json-schemas/ocpp/1.6/DataTransfer.json [new file with mode: 0755]
src/assets/json-schemas/ocpp/1.6/DataTransferResponse.json [new file with mode: 0755]
src/charging-station/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/types/UIProtocol.ts
src/types/WorkerBroadcastChannel.ts
src/types/ocpp/1.6/Requests.ts
src/types/ocpp/1.6/Responses.ts
src/types/ocpp/Requests.ts
src/types/ocpp/Responses.ts

index 562a1ba493d13b1c4d4e5068756dd29c26efe79b..083d9c953e68e5f273a8d35b9601a4812f18f3e1 100644 (file)
--- a/README.md
+++ b/README.md
@@ -310,7 +310,7 @@ make SUBMODULES_INIT=true
 - :white_check_mark: ChangeAvailability
 - :white_check_mark: ChangeConfiguration
 - :white_check_mark: ClearCache
-- :x: DataTransfer
+- :white_check_mark: DataTransfer
 - :white_check_mark: GetConfiguration
 - :white_check_mark: Heartbeat
 - :white_check_mark: MeterValues
@@ -468,41 +468,6 @@ Set the Websocket header _Sec-Websocket-Protocol_ to `ui0.0.1`.
   `chargingStations`: ChargingStationData[]  
   }
 
-###### Start Transaction
-
-- Request:  
-  `ProcedureName`: 'startTransaction'  
-  `PDU`: {  
-  `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
-  `connectorId`: connector id integer,  
-  `idTag`: RFID tag string  
-  }
-
-- Response:  
-  `PDU`: {  
-  `status`: 'success' | 'failure',  
-  `hashIdsSucceeded`: charging station unique identifier strings array,  
-  `hashIdsFailed`: charging station unique identifier strings array (optional),  
-  `responsesFailed`: failed responses payload array (optional)  
-  }
-
-###### Stop Transaction
-
-- Request:  
-  `ProcedureName`: 'stopTransaction'  
-  `PDU`: {  
-  `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
-  `transactionId`: transaction id integer  
-  }
-
-- Response:  
-  `PDU`: {  
-  `status`: 'success' | 'failure',  
-  `hashIdsSucceeded`: charging station unique identifier strings array,  
-  `hashIdsFailed`: charging station unique identifier strings array (optional),  
-  `responsesFailed`: failed responses payload array (optional)  
-  }
-
 ###### Start Charging Station
 
 - Request:  
@@ -601,40 +566,86 @@ Set the Websocket header _Sec-Websocket-Protocol_ to `ui0.0.1`.
   `responsesFailed`: failed responses payload array (optional)  
   }
 
-###### Status Notification
+###### OCPP commands trigger
 
-- Request:  
-  `ProcedureName`: 'StatusNotification'  
-  `PDU`: {  
-  `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
-  `connectorId`: connector id integer,  
-  `errorCode`: connector error code,  
-  `status`: connector status  
-  }
+The request PDU is the same as the OCPP command payload with some optional fields added to target the simulated charging stations:
 
-- Response:  
-  `PDU`: {  
-  `status`: 'success' | 'failure',  
-  `hashIdsSucceeded`: charging station unique identifier strings array,  
-  `hashIdsFailed`: charging station unique identifier strings array (optional),  
-  `responsesFailed`: failed responses payload array (optional)  
-  }
+`PDU`: {  
+ `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
+ ...`payload`  
+ }
 
-###### Heartbeat
+Examples:
 
-- Request:  
-  `ProcedureName`: 'Heartbeat'  
-  `PDU`: {  
-  `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
-  }
+- **Start Transaction**
 
-- Response:  
-  `PDU`: {  
-  `status`: 'success' | 'failure',  
-  `hashIdsSucceeded`: charging station unique identifier strings array,  
-  `hashIdsFailed`: charging station unique identifier strings array (optional),  
-  `responsesFailed`: failed responses payload array (optional)  
-  }
+  - Request:  
+    `ProcedureName`: 'startTransaction'  
+    `PDU`: {  
+    `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
+    `connectorId`: connector id integer,  
+    `idTag`: RFID tag string  
+    }
+
+  - Response:  
+    `PDU`: {  
+    `status`: 'success' | 'failure',  
+    `hashIdsSucceeded`: charging station unique identifier strings array,  
+    `hashIdsFailed`: charging station unique identifier strings array (optional),  
+    `responsesFailed`: failed responses payload array (optional)  
+    }
+
+- **Stop Transaction**
+
+  - Request:  
+    `ProcedureName`: 'stopTransaction'  
+    `PDU`: {  
+    `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
+    `transactionId`: transaction id integer  
+    }
+
+  - Response:  
+    `PDU`: {  
+    `status`: 'success' | 'failure',  
+    `hashIdsSucceeded`: charging station unique identifier strings array,  
+    `hashIdsFailed`: charging station unique identifier strings array (optional),  
+    `responsesFailed`: failed responses payload array (optional)  
+    }
+
+- **Status Notification**
+
+  - Request:  
+    `ProcedureName`: 'StatusNotification'  
+    `PDU`: {  
+    `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
+    `connectorId`: connector id integer,  
+    `errorCode`: connector error code,  
+    `status`: connector status  
+    }
+
+  - Response:  
+    `PDU`: {  
+    `status`: 'success' | 'failure',  
+    `hashIdsSucceeded`: charging station unique identifier strings array,  
+    `hashIdsFailed`: charging station unique identifier strings array (optional),  
+    `responsesFailed`: failed responses payload array (optional)  
+    }
+
+- **Heartbeat**
+
+  - Request:  
+    `ProcedureName`: 'Heartbeat'  
+    `PDU`: {  
+    `hashIds`: charging station unique identifier strings array (optional, default: all charging stations),  
+    }
+
+  - Response:  
+    `PDU`: {  
+    `status`: 'success' | 'failure',  
+    `hashIdsSucceeded`: charging station unique identifier strings array,  
+    `hashIdsFailed`: charging station unique identifier strings array (optional),  
+    `responsesFailed`: failed responses payload array (optional)  
+    }
 
 ## Support, Feedback, Contributing
 
diff --git a/src/assets/json-schemas/ocpp/1.6/DataTransfer.json b/src/assets/json-schemas/ocpp/1.6/DataTransfer.json
new file mode 100755 (executable)
index 0000000..e91122c
--- /dev/null
@@ -0,0 +1,21 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:DataTransferRequest",
+  "title": "DataTransferRequest",
+  "type": "object",
+  "properties": {
+    "vendorId": {
+      "type": "string",
+      "maxLength": 255
+    },
+    "messageId": {
+      "type": "string",
+      "maxLength": 50
+    },
+    "data": {
+      "type": "string"
+    }
+  },
+  "additionalProperties": false,
+  "required": ["vendorId"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/DataTransferResponse.json b/src/assets/json-schemas/ocpp/1.6/DataTransferResponse.json
new file mode 100755 (executable)
index 0000000..dec9086
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:DataTransferResponse",
+  "title": "DataTransferResponse",
+  "type": "object",
+  "properties": {
+    "status": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": ["Accepted", "Rejected", "UnknownMessageId", "UnknownVendorId"]
+    },
+    "data": {
+      "type": "string"
+    }
+  },
+  "additionalProperties": false,
+  "required": ["status"]
+}
index 0a0bd532f1a5326c087a92cd2a530a0efe4581f1..0eecb7b1bf16838fc81c9eaed0bce0f0970e192c 100644 (file)
@@ -3,6 +3,7 @@ import type OCPPError from '../exception/OCPPError';
 import { StandardParametersKey } from '../types/ocpp/Configuration';
 import {
   type BootNotificationRequest,
+  type DataTransferRequest,
   type HeartbeatRequest,
   type MeterValuesRequest,
   RequestCommand,
@@ -10,6 +11,8 @@ import {
 } from '../types/ocpp/Requests';
 import {
   type BootNotificationResponse,
+  type DataTransferResponse,
+  DataTransferStatus,
   type HeartbeatResponse,
   type MeterValuesResponse,
   RegistrationStatus,
@@ -49,7 +52,8 @@ type CommandResponse =
   | BootNotificationResponse
   | StatusNotificationResponse
   | HeartbeatResponse
-  | MeterValuesResponse;
+  | MeterValuesResponse
+  | DataTransferResponse;
 
 type CommandHandler = (
   requestPayload?: BroadcastChannelRequestPayload
@@ -178,6 +182,14 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
           });
         },
       ],
+      [
+        BroadcastChannelProcedureName.DATA_TRANSFER,
+        async (requestPayload?: BroadcastChannelRequestPayload) =>
+          this.chargingStation.ocppRequestService.requestHandler<
+            DataTransferRequest,
+            DataTransferResponse
+          >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload),
+      ],
     ]);
     this.chargingStation = chargingStation;
     this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
@@ -316,6 +328,11 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca
           return ResponseStatus.SUCCESS;
         }
         return ResponseStatus.FAILURE;
+      case BroadcastChannelProcedureName.DATA_TRANSFER:
+        if (commandResponse?.status === DataTransferStatus.ACCEPTED) {
+          return ResponseStatus.SUCCESS;
+        }
+        return ResponseStatus.FAILURE;
       case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
       case BroadcastChannelProcedureName.METER_VALUES:
         if (Utils.isEmptyObject(commandResponse) === true) {
index afc4773d3cbc34914991faad8df89f6cdffbd4a9..a4b1275f5a4c1474ccb0ac2e447a354d77eb64a8 100644 (file)
@@ -12,6 +12,7 @@ import type { OCPP16MeterValuesRequest } from '../../../types/ocpp/1.6/MeterValu
 import {
   DiagnosticsStatusNotificationRequest,
   OCPP16BootNotificationRequest,
+  OCPP16DataTransferRequest,
   OCPP16HeartbeatRequest,
   OCPP16RequestCommand,
   OCPP16StatusNotificationRequest,
@@ -138,6 +139,18 @@ export default class OCPP16RequestService extends OCPPRequestService {
           )
         ) as JSONSchemaType<OCPP16StopTransactionRequest>,
       ],
+      [
+        OCPP16RequestCommand.DATA_TRANSFER,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/DataTransfer.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16DataTransferRequest>,
+      ],
     ]);
     this.buildRequestPayload.bind(this);
     this.validatePayload.bind(this);
@@ -268,6 +281,8 @@ export default class OCPP16RequestService extends OCPPRequestService {
             ),
           }),
         } as unknown as Request;
+      case OCPP16RequestCommand.DATA_TRANSFER:
+        return commandParams 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 b4d8eea2a3b79c615f048aee2938aa55bc419f13..62c3a0d1966d9cf71b82fde8a9053bfad330fe1c 100644 (file)
@@ -23,6 +23,7 @@ import {
 import {
   DiagnosticsStatusNotificationResponse,
   OCPP16BootNotificationResponse,
+  OCPP16DataTransferResponse,
   OCPP16HeartbeatResponse,
   OCPP16RegistrationStatus,
   OCPP16StatusNotificationResponse,
@@ -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,6 +166,18 @@ 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);
   }
index e26a69be201d50981b42fa0488d5edd375c371b6..1d270b38c6c1d5264d90a369a8ed8a74d799968f 100644 (file)
@@ -43,6 +43,7 @@ export default abstract class AbstractUIService {
     [ProcedureName.STATUS_NOTIFICATION]: BroadcastChannelProcedureName.STATUS_NOTIFICATION,
     [ProcedureName.HEARTBEAT]: BroadcastChannelProcedureName.HEARTBEAT,
     [ProcedureName.METER_VALUES]: BroadcastChannelProcedureName.METER_VALUES,
+    [ProcedureName.DATA_TRANSFER]: BroadcastChannelProcedureName.DATA_TRANSFER,
   };
 
   protected readonly requestHandlers: Map<ProcedureName, ProtocolRequestHandler>;
index 174ae7dd0cbccf00cab4f49c7fdced2098f9ab5b..8c9379b4570b6a5e6a7ddb643a51d5007e3a46b6 100644 (file)
@@ -44,6 +44,7 @@ export enum ProcedureName {
   STATUS_NOTIFICATION = 'statusNotification',
   HEARTBEAT = 'heartbeat',
   METER_VALUES = 'meterValues',
+  DATA_TRANSFER = 'dataTransfer',
 }
 
 export interface RequestPayload extends JsonObject {
index 1756e496a3be5f3edc1604b5e7293de0244893db..0ca8eef889c23d761e74a3d2bb851682c33f3691 100644 (file)
@@ -21,6 +21,7 @@ export enum BroadcastChannelProcedureName {
   STATUS_NOTIFICATION = 'statusNotification',
   HEARTBEAT = 'heartbeat',
   METER_VALUES = 'meterValues',
+  DATA_TRANSFER = 'dataTransfer',
 }
 
 export interface BroadcastChannelRequestPayload extends RequestPayload {
index b2961a916804cafeb62d545d95c277ce7ccd2512..cf810704edcc0436c90610b66b38316668d19793 100644 (file)
@@ -15,6 +15,7 @@ export enum OCPP16RequestCommand {
   STOP_TRANSACTION = 'StopTransaction',
   METER_VALUES = 'MeterValues',
   DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
+  DATA_TRANSFER = 'DataTransfer',
 }
 
 export type OCPP16HeartbeatRequest = EmptyObject;
@@ -137,3 +138,9 @@ export interface OCPP16TriggerMessageRequest extends JsonObject {
   requestedMessage: OCPP16MessageTrigger;
   connectorId?: number;
 }
+
+export interface OCPP16DataTransferRequest extends JsonObject {
+  vendorId: string;
+  messageId?: string;
+  data?: string;
+}
index 2acee1ea7f03e7bb8d3b9359da7874c7dd20ae5b..0f65b7d199ea7f9a9f0066b73510358731871085 100644 (file)
@@ -90,3 +90,15 @@ export enum OCPP16TriggerMessageStatus {
 export interface OCPP16TriggerMessageResponse extends JsonObject {
   status: OCPP16TriggerMessageStatus;
 }
+
+export enum OCPP16DataTransferStatus {
+  ACCEPTED = 'Accepted',
+  REJECTED = 'Rejected',
+  UNKNOWN_MESSAGE_ID = 'UnknownMessageId',
+  UNKNOWN_VENDOR_ID = 'UnknownVendorId',
+}
+
+export interface OCPP16DataTransferResponse extends JsonObject {
+  status: OCPP16DataTransferStatus;
+  data?: string;
+}
index 9934f1671292158bb5d4f3d47d03f95f3e7c361a..aeedae02f3a3325c777ccfc7e8a05b6668ebce4d 100644 (file)
@@ -6,6 +6,7 @@ import type { OCPP16MeterValuesRequest } from './1.6/MeterValues';
 import {
   OCPP16AvailabilityType,
   OCPP16BootNotificationRequest,
+  OCPP16DataTransferRequest,
   OCPP16HeartbeatRequest,
   OCPP16IncomingRequestCommand,
   OCPP16MessageTrigger,
@@ -56,6 +57,8 @@ export type StatusNotificationRequest = OCPP16StatusNotificationRequest;
 
 export type MeterValuesRequest = OCPP16MeterValuesRequest;
 
+export type DataTransferRequest = OCPP16DataTransferRequest;
+
 export type IncomingRequestHandler = (
   chargingStation: ChargingStation,
   commandPayload: JsonType
index c57510ae7eaf04ebb71d9c407e9a9007b574a9c3..2ddb98f2d107ad57ff6b0fcd87798254d2e9ae13 100644 (file)
@@ -7,6 +7,8 @@ import {
   OCPP16ChargingProfileStatus,
   OCPP16ClearChargingProfileStatus,
   OCPP16ConfigurationStatus,
+  OCPP16DataTransferResponse,
+  OCPP16DataTransferStatus,
   OCPP16HeartbeatResponse,
   OCPP16RegistrationStatus,
   OCPP16StatusNotificationResponse,
@@ -34,6 +36,8 @@ export type StatusNotificationResponse = OCPP16StatusNotificationResponse;
 
 export type MeterValuesResponse = OCPP16MeterValuesResponse;
 
+export type DataTransferResponse = OCPP16DataTransferResponse;
+
 export enum DefaultStatus {
   ACCEPTED = 'Accepted',
   REJECTED = 'Rejected',
@@ -84,3 +88,9 @@ export type TriggerMessageStatus = OCPP16TriggerMessageStatus;
 export const TriggerMessageStatus = {
   ...OCPP16TriggerMessageStatus,
 };
+
+export type DataTransferStatus = OCPP16DataTransferStatus;
+
+export const DataTransferStatus = {
+  ...OCPP16DataTransferStatus,
+};