From 10db00b2276f4cc7a88dd18e8f6f80593d6458b3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 5 Sep 2022 22:51:12 +0200 Subject: [PATCH] UI protocol: add OCPP heartbeat command support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- README.md | 20 +++++- .../Insomnia_CSSimulatorUIProtocol.json | 66 +++++++++++++++---- .../ChargingStationWorkerBroadcastChannel.ts | 54 +++++++++++---- .../ui-server/ui-services/UIService001.ts | 8 +++ src/types/UIProtocol.ts | 1 + src/types/WorkerBroadcastChannel.ts | 1 + 6 files changed, 124 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index eee5d343..0611fd3b 100644 --- a/README.md +++ b/README.md @@ -584,7 +584,25 @@ Set the Websocket header _Sec-Websocket-Protocol_ to `ui0.0.1`. `ProcedureName`: 'StatusNotification' `PDU`: { `hashIds`: charging station unique identifier strings array (optional, default: all charging stations), - `connectorId`: connector id integer + `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: diff --git a/src/assets/Insomnia_CSSimulatorUIProtocol.json b/src/assets/Insomnia_CSSimulatorUIProtocol.json index ddd79dca..61dded03 100644 --- a/src/assets/Insomnia_CSSimulatorUIProtocol.json +++ b/src/assets/Insomnia_CSSimulatorUIProtocol.json @@ -1,13 +1,13 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2022-09-05T08:32:56.599Z", + "__export_date": "2022-09-05T20:42:59.578Z", "__export_source": "insomnia.desktop.app:v2022.5.1", "resources": [ { "_id": "req_606dcee139984772877def40fcbb5c76", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662340741508, + "modified": 1662410526425, "created": 1661789624987, "url": "{{baseUrl}}/{{protocol}}/{{version}}/listChargingStations", "name": "listChargingStations", @@ -52,7 +52,7 @@ { "_id": "req_7d5f9506e7ac49208a4f960a7740663e", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662312719968, + "modified": 1662373634204, "created": 1661789624990, "url": "{{baseUrl}}/{{protocol}}/{{version}}/startSimulator", "name": "startSimulator", @@ -87,7 +87,7 @@ { "_id": "req_59056be11534481c80a0b0da32e2a06a", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662302216944, + "modified": 1662367707319, "created": 1661789624994, "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopSimulator", "name": "stopSimulator", @@ -122,7 +122,7 @@ { "_id": "req_aad7fd6db4c64869b60048b915010efc", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662340686080, + "modified": 1662372497848, "created": 1661789624998, "url": "{{baseUrl}}/{{protocol}}/{{version}}/startChargingStation", "name": "startChargingStation", @@ -160,7 +160,7 @@ { "_id": "req_d72d91cf3fb044179b8ae9d92a74f99c", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662308938677, + "modified": 1662372491437, "created": 1661789625002, "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopChargingStation", "name": "stopChargingStation", @@ -198,7 +198,7 @@ { "_id": "req_747f458d196f4681b5fe15204b0067aa", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662340696576, + "modified": 1662367716472, "created": 1661789625005, "url": "{{baseUrl}}/{{protocol}}/{{version}}/openConnection", "name": "openConnection", @@ -236,7 +236,7 @@ { "_id": "req_401e6a62a33c4b6c90aaa2e019daab6d", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662340695169, + "modified": 1662367720232, "created": 1661789625014, "url": "{{baseUrl}}/{{protocol}}/{{version}}/closeConnection", "name": "closeConnection", @@ -274,7 +274,7 @@ { "_id": "req_2f757efe92fb4936ad4fa4b6763f9293", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662340693347, + "modified": 1662367718288, "created": 1661789625017, "url": "{{baseUrl}}/{{protocol}}/{{version}}/startTransaction", "name": "startTransaction", @@ -312,7 +312,7 @@ { "_id": "req_7c285fb6cb6948a08235a6c73cbeb1f9", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662340705489, + "modified": 1662367694077, "created": 1661789625020, "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopTransaction", "name": "stopTransaction", @@ -350,7 +350,7 @@ { "_id": "req_b33c704fe3464dc5a5d3694abd9320d0", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662366450174, + "modified": 1662372502338, "created": 1661803778569, "url": "{{baseUrl}}/{{protocol}}/{{version}}/startAutomaticTransactionGenerator", "name": "startAutomaticTransactionGenerator", @@ -388,7 +388,7 @@ { "_id": "req_24c1c55fe3ba4ddb94702408f21a64df", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662366450665, + "modified": 1662372479288, "created": 1661803846882, "url": "{{baseUrl}}/{{protocol}}/{{version}}/stopAutomaticTransactionGenerator", "name": "stopAutomaticTransactionGenerator", @@ -426,7 +426,7 @@ { "_id": "req_6a78267706094fb59d85ed1531e07a55", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", - "modified": 1662366453452, + "modified": 1662410523458, "created": 1662330215407, "url": "{{baseUrl}}/{{protocol}}/{{version}}/statusNotification", "name": "statusNotification", @@ -434,7 +434,7 @@ "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"hashIds\": [\n\t\t\"0058d8b50e422cce5bbd0c0a4ad13d5d657e8a88670dcf04c1b2b563fea3db5b96a3686278b374ed050e21baef89060e\",\n\t\t\"331d024fea36e3e2483a0e5dc9376234241c8c099ad201a441437b23622c308555183f37cbc84a1818c1c45aaae50896\"\n\t],\n\t\"connectorId\": 1,\n\t\"errorCode\": \"NoError\",\n\t\"status\": \"Preparing\"\n}" + "text": "{\n\t\"hashIds\": [\n\t\t\"0058d8b50e422cce5bbd0c0a4ad13d5d657e8a88670dcf04c1b2b563fea3db5b96a3686278b374ed050e21baef89060e\",\n\t\t\"331d024fea36e3e2483a0e5dc9376234241c8c099ad201a441437b23622c308555183f37cbc84a1818c1c45aaae50896\"\n\t],\n\t\"connectorId\": 1,\n\t\"errorCode\": \"NoError\",\n\t\"status\": \"Available\"\n}" }, "parameters": [], "headers": [ @@ -461,6 +461,44 @@ "settingFollowRedirects": "global", "_type": "request" }, + { + "_id": "req_61efafe9f4a14c268b948b9f9c5c4195", + "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", + "modified": 1662410531123, + "created": 1662409405256, + "url": "{{baseUrl}}/{{protocol}}/{{version}}/heartbeat", + "name": "heartbeat", + "description": "", + "method": "POST", + "body": { + "mimeType": "application/json", + "text": "{\n\t\"hashIds\": [\n\t\t\"0058d8b50e422cce5bbd0c0a4ad13d5d657e8a88670dcf04c1b2b563fea3db5b96a3686278b374ed050e21baef89060e\",\n\t\t\"331d024fea36e3e2483a0e5dc9376234241c8c099ad201a441437b23622c308555183f37cbc84a1818c1c45aaae50896\"\n\t]\n}" + }, + "parameters": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json", + "id": "pair_3224616dd6604605a1e48b71f6e9f795" + } + ], + "authentication": { + "type": "basic", + "useISO88591": false, + "disabled": false, + "username": "{{username}}", + "password": "{{password}}" + }, + "metaSortKey": -999999500, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, { "_id": "env_74b29d59b9f04298b97fc9750476a4ca", "parentId": "wrk_d64b10b1e0c14563a80484ee684b5205", diff --git a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts index dfb50ab1..81996076 100644 --- a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts @@ -1,7 +1,11 @@ import BaseError from '../exception/BaseError'; import type OCPPError from '../exception/OCPPError'; -import { RequestCommand, type StatusNotificationRequest } from '../types/ocpp/Requests'; -import type { StatusNotificationResponse } from '../types/ocpp/Responses'; +import { + HeartbeatRequest, + RequestCommand, + type StatusNotificationRequest, +} from '../types/ocpp/Requests'; +import type { HeartbeatResponse, StatusNotificationResponse } from '../types/ocpp/Responses'; import { AuthorizationStatus, StartTransactionRequest, @@ -27,7 +31,8 @@ const moduleName = 'ChargingStationWorkerBroadcastChannel'; type CommandResponse = | StartTransactionResponse | StopTransactionResponse - | StatusNotificationResponse; + | StatusNotificationResponse + | HeartbeatResponse; export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel { private readonly chargingStation: ChargingStation; @@ -78,7 +83,7 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca } else { responsePayload = { hashId: this.chargingStation.stationInfo.hashId, - status: this.commandResponseToResponseStatus(commandResponse), + status: this.commandResponseToResponseStatus(command, commandResponse), }; } } catch (error) { @@ -166,19 +171,46 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca vendorErrorCode: requestPayload.vendorErrorCode, }), }); + case BroadcastChannelProcedureName.HEARTBEAT: + delete requestPayload.hashId; + delete requestPayload.hashIds; + delete requestPayload.connectorIds; + return this.chargingStation.ocppRequestService.requestHandler< + HeartbeatRequest, + HeartbeatResponse + >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload); default: // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new BaseError(`Unknown worker broadcast channel command: ${command}`); } } - private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus { - if ( - Utils.isEmptyObject(commandResponse) || - commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED - ) { - return ResponseStatus.SUCCESS; + private commandResponseToResponseStatus( + command: BroadcastChannelProcedureName, + commandResponse: CommandResponse + ): ResponseStatus { + switch (command) { + case BroadcastChannelProcedureName.START_TRANSACTION: + case BroadcastChannelProcedureName.STOP_TRANSACTION: + if ( + (commandResponse as StartTransactionResponse | StopTransactionResponse)?.idTagInfo + ?.status === AuthorizationStatus.ACCEPTED + ) { + return ResponseStatus.SUCCESS; + } + return ResponseStatus.FAILURE; + case BroadcastChannelProcedureName.STATUS_NOTIFICATION: + if (Utils.isEmptyObject(commandResponse) === true) { + return ResponseStatus.SUCCESS; + } + return ResponseStatus.FAILURE; + case BroadcastChannelProcedureName.HEARTBEAT: + if ('currentTime' in commandResponse) { + return ResponseStatus.SUCCESS; + } + return ResponseStatus.FAILURE; + default: + return ResponseStatus.FAILURE; } - return ResponseStatus.FAILURE; } } diff --git a/src/charging-station/ui-server/ui-services/UIService001.ts b/src/charging-station/ui-server/ui-services/UIService001.ts index 74403cea..57c34c22 100644 --- a/src/charging-station/ui-server/ui-services/UIService001.ts +++ b/src/charging-station/ui-server/ui-services/UIService001.ts @@ -47,6 +47,10 @@ export default class UIService001 extends AbstractUIService { ProcedureName.STATUS_NOTIFICATION, this.handleStatusNotification.bind(this) as ProtocolRequestHandler ); + this.requestHandlers.set( + ProcedureName.HEARTBEAT, + this.handleHeartbeat.bind(this) as ProtocolRequestHandler + ); } private handleStartChargingStation(uuid: string, payload: RequestPayload): void { @@ -108,4 +112,8 @@ export default class UIService001 extends AbstractUIService { payload ); } + + private handleHeartbeat(uuid: string, payload: RequestPayload): void { + this.sendBroadcastChannelRequest(uuid, BroadcastChannelProcedureName.HEARTBEAT, payload); + } } diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts index d995e096..4b96262b 100644 --- a/src/types/UIProtocol.ts +++ b/src/types/UIProtocol.ts @@ -39,6 +39,7 @@ export enum ProcedureName { START_AUTOMATIC_TRANSACTION_GENERATOR = 'startAutomaticTransactionGenerator', STOP_AUTOMATIC_TRANSACTION_GENERATOR = 'stopAutomaticTransactionGenerator', STATUS_NOTIFICATION = 'statusNotification', + HEARTBEAT = 'heartbeat', } export interface RequestPayload extends JsonObject { diff --git a/src/types/WorkerBroadcastChannel.ts b/src/types/WorkerBroadcastChannel.ts index f235200c..788f7d68 100644 --- a/src/types/WorkerBroadcastChannel.ts +++ b/src/types/WorkerBroadcastChannel.ts @@ -17,6 +17,7 @@ export enum BroadcastChannelProcedureName { START_AUTOMATIC_TRANSACTION_GENERATOR = 'startAutomaticTransactionGenerator', STOP_AUTOMATIC_TRANSACTION_GENERATOR = 'stopAutomaticTransactionGenerator', STATUS_NOTIFICATION = 'statusNotification', + HEARTBEAT = 'heartbeat', } export interface BroadcastChannelRequestPayload extends RequestPayload { -- 2.34.1