From 91a7d3eac062df6c73d8443a542568c1fe69ec1b Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 16 Nov 2022 17:51:10 +0100 Subject: [PATCH] Add OCPP DataTransfer request support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Ref #36 Signed-off-by: Jérôme Benoit --- README.md | 141 ++++++++++-------- .../json-schemas/ocpp/1.6/DataTransfer.json | 21 +++ .../ocpp/1.6/DataTransferResponse.json | 18 +++ .../ChargingStationWorkerBroadcastChannel.ts | 19 ++- .../ocpp/1.6/OCPP16RequestService.ts | 15 ++ .../ocpp/1.6/OCPP16ResponseService.ts | 14 ++ .../ui-services/AbstractUIService.ts | 1 + src/types/UIProtocol.ts | 1 + src/types/WorkerBroadcastChannel.ts | 1 + src/types/ocpp/1.6/Requests.ts | 7 + src/types/ocpp/1.6/Responses.ts | 12 ++ src/types/ocpp/Requests.ts | 3 + src/types/ocpp/Responses.ts | 10 ++ 13 files changed, 197 insertions(+), 66 deletions(-) create mode 100755 src/assets/json-schemas/ocpp/1.6/DataTransfer.json create mode 100755 src/assets/json-schemas/ocpp/1.6/DataTransferResponse.json diff --git a/README.md b/README.md index 562a1ba4..083d9c95 100644 --- 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 index 00000000..e91122c6 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/DataTransfer.json @@ -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 index 00000000..dec90865 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/DataTransferResponse.json @@ -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"] +} diff --git a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts index 0a0bd532..0eecb7b1 100644 --- a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts @@ -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) { diff --git a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts index afc4773d..a4b1275f 100644 --- a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts @@ -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, ], + [ + 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, + ], ]); 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( diff --git a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts index b4d8eea2..62c3a0d1 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts @@ -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>([ [ @@ -164,6 +166,18 @@ export default class OCPP16ResponseService extends OCPPResponseService { ) ) as JSONSchemaType, ], + [ + 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, + ], ]); this.validatePayload.bind(this); } diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index e26a69be..1d270b38 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -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; diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts index 174ae7dd..8c9379b4 100644 --- a/src/types/UIProtocol.ts +++ b/src/types/UIProtocol.ts @@ -44,6 +44,7 @@ export enum ProcedureName { STATUS_NOTIFICATION = 'statusNotification', HEARTBEAT = 'heartbeat', METER_VALUES = 'meterValues', + DATA_TRANSFER = 'dataTransfer', } export interface RequestPayload extends JsonObject { diff --git a/src/types/WorkerBroadcastChannel.ts b/src/types/WorkerBroadcastChannel.ts index 1756e496..0ca8eef8 100644 --- a/src/types/WorkerBroadcastChannel.ts +++ b/src/types/WorkerBroadcastChannel.ts @@ -21,6 +21,7 @@ export enum BroadcastChannelProcedureName { STATUS_NOTIFICATION = 'statusNotification', HEARTBEAT = 'heartbeat', METER_VALUES = 'meterValues', + DATA_TRANSFER = 'dataTransfer', } export interface BroadcastChannelRequestPayload extends RequestPayload { diff --git a/src/types/ocpp/1.6/Requests.ts b/src/types/ocpp/1.6/Requests.ts index b2961a91..cf810704 100644 --- a/src/types/ocpp/1.6/Requests.ts +++ b/src/types/ocpp/1.6/Requests.ts @@ -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; +} diff --git a/src/types/ocpp/1.6/Responses.ts b/src/types/ocpp/1.6/Responses.ts index 2acee1ea..0f65b7d1 100644 --- a/src/types/ocpp/1.6/Responses.ts +++ b/src/types/ocpp/1.6/Responses.ts @@ -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; +} diff --git a/src/types/ocpp/Requests.ts b/src/types/ocpp/Requests.ts index 9934f167..aeedae02 100644 --- a/src/types/ocpp/Requests.ts +++ b/src/types/ocpp/Requests.ts @@ -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 diff --git a/src/types/ocpp/Responses.ts b/src/types/ocpp/Responses.ts index c57510ae..2ddb98f2 100644 --- a/src/types/ocpp/Responses.ts +++ b/src/types/ocpp/Responses.ts @@ -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, +}; -- 2.34.1