From 71ac2bd72c0bd0f742e10dc135a619a5717e6a47 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 9 Feb 2024 13:52:20 +0100 Subject: [PATCH] feat: add options to `addChargingStations` UI protocol command MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- README.md | 5 ++- ...> Insomnia-CSSimulatorUIHTTPProtocol.json} | 8 ++-- ... => Insomnia-CSSimulatorUIWSProtocol.json} | 38 +++++++++---------- src/charging-station/Bootstrap.ts | 10 ++++- src/charging-station/ChargingStation.ts | 10 ++++- src/charging-station/ChargingStationWorker.ts | 5 ++- .../ui-server/UIHttpServer.ts | 14 ++++++- .../ui-server/UIWebSocketServer.ts | 16 +++++++- .../ui-services/AbstractUIService.ts | 7 +++- src/types/ChargingStationWorker.ts | 6 +-- src/types/index.ts | 1 + 11 files changed, 82 insertions(+), 38 deletions(-) rename src/assets/ui-protocol/{Insomnia_CSSimulatorUIProtocol.json => Insomnia-CSSimulatorUIHTTPProtocol.json} (99%) rename src/assets/ui-protocol/{Insomnia-CSSimulatorUIWSProtocolCollection.json => Insomnia-CSSimulatorUIWSProtocol.json} (97%) diff --git a/README.md b/README.md index 060d90bc..c537e47a 100644 --- a/README.md +++ b/README.md @@ -571,7 +571,10 @@ Set the Websocket header _Sec-Websocket-Protocol_ to `ui0.0.1`. `ProcedureName`: 'addChargingStations' `PDU`: { `template`: string, - `numberOfStations`: number + `numberOfStations`: number, + `options?`: { + `autoStart`: boolean + } } - Response: diff --git a/src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json b/src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json similarity index 99% rename from src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json rename to src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json index 1a6269a5..7869b486 100644 --- a/src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json +++ b/src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json @@ -1,8 +1,8 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2024-02-05T18:06:20.997Z", - "__export_source": "insomnia.desktop.app:v8.6.0", + "__export_date": "2024-02-09T12:44:04.862Z", + "__export_source": "insomnia.desktop.app:v8.6.1", "resources": [ { "_id": "req_09f5c772800b48d9aea7462de3379752", @@ -161,7 +161,7 @@ { "_id": "req_c43bfb67921546baa49b4ec96e571859", "parentId": "wrk_509d4a5094fa485ba93e53bc735e8ac3", - "modified": 1706781452580, + "modified": 1707482593708, "created": 1706781436512, "url": "{{baseUrl}}/{{protocol}}/{{version}}/addChargingStations", "name": "addChargingStations", @@ -169,7 +169,7 @@ "method": "POST", "body": { "mimeType": "application/json", - "text": "{\n\t\"template\": \"evlink.station-template\",\n\t\"numberOfStations\": 1\n}" + "text": "{\n\t\"template\": \"evlink.station-template\",\n\t\"numberOfStations\": 1,\n\t\"options\": {\n\t\t\"autoStart\": false\n\t}\n}" }, "parameters": [], "headers": [ diff --git a/src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocolCollection.json b/src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json similarity index 97% rename from src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocolCollection.json rename to src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json index 07bdc0c7..f4c4687e 100644 --- a/src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocolCollection.json +++ b/src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json @@ -1,8 +1,8 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2024-02-05T18:06:08.066Z", - "__export_source": "insomnia.desktop.app:v8.6.0", + "__export_date": "2024-02-09T12:41:23.711Z", + "__export_source": "insomnia.desktop.app:v8.6.1", "resources": [ { "_id": "ws-req_6815f92a40cf410383b99302180164f6", @@ -634,7 +634,7 @@ { "_id": "ws-payload_5a7ab577051646ff9975c34ccf900f18", "parentId": "ws-req_6154d7eed8ba498ca6da5245e205a329", - "modified": 1707156244270, + "modified": 1707475945762, "created": 1671192074985, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"stopSimulator\",\n\t{}\n]", @@ -644,7 +644,7 @@ { "_id": "ws-payload_b6275ce690b5411eb84265642cda2014", "parentId": "ws-req_6815f92a40cf410383b99302180164f6", - "modified": 1707156242627, + "modified": 1707164705938, "created": 1671297215182, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"startSimulator\",\n\t{}\n]", @@ -664,7 +664,7 @@ { "_id": "ws-payload_c61ab0e9f9ee43fe96782dfbeabf97d2", "parentId": "ws-req_8f579f886db842118bf0e1835e7aa750", - "modified": 1707156246428, + "modified": 1707475944960, "created": 1671297412505, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"closeConnection\",\n\t{}\n]", @@ -674,7 +674,7 @@ { "_id": "ws-payload_2872e2656c164769acf98cc7ba7ea028", "parentId": "ws-req_e5902850ac1d40369bd6e942a2755a9d", - "modified": 1707156248970, + "modified": 1707477801921, "created": 1671297544207, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"listChargingStations\",\n\t{}\n]", @@ -684,7 +684,7 @@ { "_id": "ws-payload_edebda0226aa43f88712d7feb60ac645", "parentId": "ws-req_ebe5a555a6344dfba7e29f857af11d08", - "modified": 1707156255026, + "modified": 1707475947625, "created": 1671297697172, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"startChargingStation\",\n\t{\n\t\t\"hashIds\": [\n\t\t\t\"5b82a559d2b453f6277e272e134ae824ae358cfb6ee2415af9f7c2f325ef8b3e930aeeadcd866df4b8aec58786e60ae7\"\n\t\t]\n\t}\n]", @@ -694,7 +694,7 @@ { "_id": "ws-payload_20cb03a0142d44a98ddb7bc59ccfea11", "parentId": "ws-req_720b5d562a0f42929ef9aa16019728fe", - "modified": 1707156256712, + "modified": 1707157215540, "created": 1671297731073, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"stopChargingStation\",\n\t{\n\t\t\"hashIds\": [\n\t\t\t\"5b82a559d2b453f6277e272e134ae824ae358cfb6ee2415af9f7c2f325ef8b3e930aeeadcd866df4b8aec58786e60ae7\"\n\t\t]\n\t}\n]", @@ -704,7 +704,7 @@ { "_id": "ws-payload_d8e66e0f933e4d74bb5fbff4d15a44bf", "parentId": "ws-req_23025e078480491daf01406b2b5e9cc2", - "modified": 1707156258087, + "modified": 1707157205853, "created": 1671298432039, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"startAutomaticTransactionGenerator\",\n\t{}\n]", @@ -724,7 +724,7 @@ { "_id": "ws-payload_1b02fae03f9c4f54af23911678519841", "parentId": "ws-req_f836c127aca54a909a110a16b791c29b", - "modified": 1707156269505, + "modified": 1707157211377, "created": 1673277254287, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"diagnosticsStatusNotification\",\n\t{\n\t\t\"status\": \"Uploaded\"\n\t}\n]", @@ -734,7 +734,7 @@ { "_id": "ws-payload_b563d5d8dc284ebb8f9dd2083734cc45", "parentId": "ws-req_c326f21a473c430081d7229a82c69b33", - "modified": 1707156270851, + "modified": 1707157212800, "created": 1673279189375, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"firmwareStatusNotification\",\n\t{\n\t\t\"status\": \"Downloading\"\n\t}\n]", @@ -744,7 +744,7 @@ { "_id": "ws-payload_e2e7b8a7d8694b94a16868fcd0b90916", "parentId": "ws-req_d88784511f704224999e41bd53ba71b8", - "modified": 1707156268568, + "modified": 1707157210547, "created": 1673728879079, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"statusNotification\",\n\t{\n\t\t\"connectorId\": 1,\n\t\t\"status\": \"Available\",\n\t\t\"errorCode\": \"NoError\"\n\t}\n]", @@ -754,7 +754,7 @@ { "_id": "ws-payload_bf36aa8e9c8646d6a37697c6496e257f", "parentId": "ws-req_fc239903df2d46bb998c16dbcb8cafea", - "modified": 1707156265844, + "modified": 1707157207882, "created": 1674411426307, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"startTransaction\",\n\t{\n\t\t\"hashIds\": [\n\t\t\t\"0058d8b50e422cce5bbd0c0a4ad13d5d657e8a88670dcf04c1b2b563fea3db5b96a3686278b374ed050e21baef89060e\"\n\t\t],\n\t\t\"connectorId\": 1,\n\t\t\"idTag\": \"test\"\n\t}\n]", @@ -764,7 +764,7 @@ { "_id": "ws-payload_43c713bdb0e64cbda34b3102f42da321", "parentId": "ws-req_e3db8f3f31c947c1969a6a257b65a2d5", - "modified": 1707156266777, + "modified": 1707157208836, "created": 1674411483206, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"stopTransaction\",\n\t{\n\t\t\"hashIds\": [\n\t\t\t\"0058d8b50e422cce5bbd0c0a4ad13d5d657e8a88670dcf04c1b2b563fea3db5b96a3686278b374ed050e21baef89060e\"\n\t\t],\n\t\t\"transactionId\": 235051179\n\t}\n]", @@ -774,7 +774,7 @@ { "_id": "ws-payload_95c28d71c8d940bb83ac514f8916a66d", "parentId": "ws-req_afbfa6e6824b427e99e735c0b1eabe3b", - "modified": 1707156252122, + "modified": 1707157216642, "created": 1678991663554, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"setSupervisionUrl\",\n\t{\n\t\t\"hashIds\": [\n\t\t\t\"5b82a559d2b453f6277e272e134ae824ae358cfb6ee2415af9f7c2f325ef8b3e930aeeadcd866df4b8aec58786e60ae7\"\n\t\t],\n\t\t\"url\": \"wss://domain.tld\"\n\t}\n]", @@ -784,7 +784,7 @@ { "_id": "ws-payload_3e1dffbcefcc481286b44c694b9e6496", "parentId": "ws-req_3a0ff14878b449f4be3dfbb7432b5f87", - "modified": 1707156247560, + "modified": 1707481292801, "created": 1706726300041, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"listTemplates\",\n\t{}\n]", @@ -794,17 +794,17 @@ { "_id": "ws-payload_5e2dfed34a104c28b887c885ada1b4af", "parentId": "ws-req_8777c5635dd64fccbc2b0f450be656c0", - "modified": 1707156250485, + "modified": 1707482458081, "created": 1706778795544, "name": "New Payload", - "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"addChargingStations\",\n\t{\n\t\t\"template\": \"evlink.station-template\",\n\t\t\"numberOfStations\": 1\n\t}\n]", + "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"addChargingStations\",\n\t{\n\t\t\"template\": \"evlink.station-template\",\n\t\t\"numberOfStations\": 1,\n\t\t\"options\": {\n\t\t\t\"autoStart\": false\n\t\t}\n\t}\n]", "mode": "application/json", "_type": "websocket_payload" }, { "_id": "ws-payload_494fba679fa644ccb318f092e780834f", "parentId": "ws-req_0bab7a97ceda4944976a463f616dec5c", - "modified": 1707156253509, + "modified": 1707475888681, "created": 1707143784130, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"performanceStatistics\",\n\t{}\n]", diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 0e0a8607..acd8e73c 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -18,6 +18,7 @@ import { BaseError } from '../exception/index.js' import { type Storage, StorageFactory } from '../performance/index.js' import { type ChargingStationData, + type ChargingStationOptions, type ChargingStationWorkerData, type ChargingStationWorkerEventError, type ChargingStationWorkerMessage, @@ -487,7 +488,11 @@ export class Bootstrap extends EventEmitter { } } - public async addChargingStation (index: number, stationTemplateFile: string): Promise { + public async addChargingStation ( + index: number, + stationTemplateFile: string, + options?: ChargingStationOptions + ): Promise { await this.workerImplementation?.addElement({ index, templateFile: join( @@ -495,7 +500,8 @@ export class Bootstrap extends EventEmitter { 'assets', 'station-templates', stationTemplateFile - ) + ), + options }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.chargingStationsByTemplate.get(parse(stationTemplateFile).name)!.lastIndex = max( diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 0f4bce43..a0867719 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -73,6 +73,7 @@ import { ChargingStationEvents, type ChargingStationInfo, type ChargingStationOcppConfiguration, + type ChargingStationOptions, type ChargingStationTemplate, type ConnectorStatus, ConnectorStatusEnum, @@ -189,7 +190,7 @@ export class ChargingStation extends EventEmitter { private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel private flushMessageBufferSetInterval?: NodeJS.Timeout - constructor (index: number, templateFile: string) { + constructor (index: number, templateFile: string, options?: ChargingStationOptions) { super() this.started = false this.starting = false @@ -247,7 +248,12 @@ export class ChargingStation extends EventEmitter { this.add() - this.stationInfo?.autoStart === true && this.start() + if ( + options?.autoStart === true || + (options?.autoStart !== false && this.stationInfo?.autoStart === true) + ) { + this.start() + } } public get hasEvses (): boolean { diff --git a/src/charging-station/ChargingStationWorker.ts b/src/charging-station/ChargingStationWorker.ts index 4fde42c4..2623f127 100644 --- a/src/charging-station/ChargingStationWorker.ts +++ b/src/charging-station/ChargingStationWorker.ts @@ -20,7 +20,7 @@ if (Configuration.workerPoolInUse()) { chargingStationWorker = new ThreadWorker( (data?: ChargingStationWorkerData): void => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, no-new - new ChargingStation(data!.index, data!.templateFile) + new ChargingStation(data!.index, data!.templateFile, data!.options) } ) } else { @@ -33,7 +33,8 @@ if (Configuration.workerPoolInUse()) { try { const chargingStation = new ChargingStation( message.data.index, - message.data.templateFile + message.data.templateFile, + message.data.options ) parentPort?.postMessage({ event: WorkerMessageEvents.addedWorkerElement, diff --git a/src/charging-station/ui-server/UIHttpServer.ts b/src/charging-station/ui-server/UIHttpServer.ts index 27e8d7ba..b10d3336 100644 --- a/src/charging-station/ui-server/UIHttpServer.ts +++ b/src/charging-station/ui-server/UIHttpServer.ts @@ -126,7 +126,19 @@ export class UIHttpServer extends AbstractUIServer { bodyBuffer.push(chunk) }) .on('end', () => { - const body = JSON.parse(Buffer.concat(bodyBuffer).toString()) as RequestPayload + let body: RequestPayload | undefined + try { + body = JSON.parse(Buffer.concat(bodyBuffer).toString()) as RequestPayload + } catch (error) { + this.sendResponse( + this.buildProtocolResponse(uuid, { + status: ResponseStatus.FAILURE, + errorMessage: (error as Error).message, + errorStack: (error as Error).stack + }) + ) + return + } this.uiServices .get(version) ?.requestHandler(this.buildProtocolRequest(uuid, procedureName, body)) diff --git a/src/charging-station/ui-server/UIWebSocketServer.ts b/src/charging-station/ui-server/UIWebSocketServer.ts index 0dce52ca..16477146 100644 --- a/src/charging-station/ui-server/UIWebSocketServer.ts +++ b/src/charging-station/ui-server/UIWebSocketServer.ts @@ -180,8 +180,20 @@ export class UIWebSocketServer extends AbstractUIServer { // )} Raw data received in string format: ${rawData.toString()}` // ) - // eslint-disable-next-line @typescript-eslint/no-base-to-string - const request = JSON.parse(rawData.toString()) as ProtocolRequest + let request: ProtocolRequest + try { + // eslint-disable-next-line @typescript-eslint/no-base-to-string + request = JSON.parse(rawData.toString()) as ProtocolRequest + } catch (error) { + logger.error( + `${this.logPrefix( + moduleName, + 'validateRawDataRequest' + // eslint-disable-next-line @typescript-eslint/no-base-to-string + )} UI protocol request is not valid JSON: ${rawData.toString()}` + ) + return false + } if (!Array.isArray(request)) { logger.error( diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index 8617c7ab..4055837f 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -2,6 +2,7 @@ import { BaseError, type OCPPError } from '../../../exception/index.js' import { BroadcastChannelProcedureName, type BroadcastChannelRequestPayload, + type ChargingStationOptions, ConfigurationSection, type JsonType, ProcedureName, @@ -226,9 +227,10 @@ export abstract class AbstractUIService { procedureName?: ProcedureName, requestPayload?: RequestPayload ): Promise { - const { template, numberOfStations } = requestPayload as { + const { template, numberOfStations, options } = requestPayload as { template: string numberOfStations: number + options?: ChargingStationOptions } if (!this.uiServer.chargingStationTemplates.has(template)) { return { @@ -240,7 +242,8 @@ export abstract class AbstractUIService { try { await Bootstrap.getInstance().addChargingStation( Bootstrap.getInstance().getLastIndex(template) + 1, - `${template}.json` + `${template}.json`, + options ) } catch (error) { return { diff --git a/src/types/ChargingStationWorker.ts b/src/types/ChargingStationWorker.ts index 5a24dc54..df4a789c 100644 --- a/src/types/ChargingStationWorker.ts +++ b/src/types/ChargingStationWorker.ts @@ -11,14 +11,14 @@ import type { BootNotificationResponse } from './ocpp/Responses.js' import type { Statistics } from './Statistics.js' import { type WorkerData, type WorkerMessage, WorkerMessageEvents } from '../worker/index.js' -interface ChargingStationWorkerOptions extends JsonObject { - elementStartDelay?: number +export interface ChargingStationOptions extends JsonObject { + autoStart?: boolean } export interface ChargingStationWorkerData extends WorkerData { index: number templateFile: string - chargingStationWorkerOptions?: ChargingStationWorkerOptions + options?: ChargingStationOptions } export type EvseStatusWorkerType = Omit & { diff --git a/src/types/index.ts b/src/types/index.ts index 2b17daa5..d5f887f8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -150,6 +150,7 @@ export { type ChargingStationWorkerMessage, type ChargingStationWorkerMessageData, ChargingStationWorkerMessageEvents, + type ChargingStationOptions, type EvseStatusWorkerType } from './ChargingStationWorker.js' export type { ChargingStationInfo } from './ChargingStationInfo.js' -- 2.34.1