From 09e5a7a8ed45886b6bf1434c4e49c945ded4c7d8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 11 Feb 2024 12:15:14 +0100 Subject: [PATCH] feat: add `deleteChargingStations` SRPC command to UI Services MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- README.md | 17 ++++++ pnpm-lock.yaml | 18 +++--- .../Insomnia-CSSimulatorUIHTTPProtocol.json | 41 ++++++++++++- .../Insomnia-CSSimulatorUIWSProtocol.json | 58 ++++++++++++++++--- .../AutomaticTransactionGenerator.ts | 5 ++ src/charging-station/Bootstrap.ts | 17 ++++++ src/charging-station/ChargingStation.ts | 34 ++++++++--- .../ChargingStationWorkerBroadcastChannel.ts | 6 ++ .../ui-server/AbstractUIServer.ts | 3 + .../ui-services/AbstractUIService.ts | 9 +++ src/performance/PerformanceStatistics.ts | 20 +++++-- src/types/ChargingStationEvents.ts | 1 + src/types/UIProtocol.ts | 1 + src/types/WorkerBroadcastChannel.ts | 1 + src/utils/Constants.ts | 2 +- src/utils/MessageChannelUtils.ts | 9 +++ src/utils/index.ts | 1 + ui/web/pnpm-lock.yaml | 18 +++--- 18 files changed, 222 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 2234043f..84347a67 100644 --- a/README.md +++ b/README.md @@ -587,6 +587,23 @@ Set the Websocket header _Sec-Websocket-Protocol_ to `ui0.0.1`. `status`: 'success' | 'failure' } +###### Delete Charging Stations + +- Request: + `ProcedureName`: 'deleteChargingStations' + `PDU`: { + `hashIds`: charging station unique identifier strings array (optional, default: all charging stations), + `deleteConfiguration?`: boolean + } + +- 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) + } + ###### Set Charging Station Supervision Url - Request: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e476785f..be83ed4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3834,7 +3834,7 @@ packages: has-property-descriptors: 1.0.1 has-proto: 1.0.1 has-symbols: 1.0.3 - hasown: 2.0.0 + hasown: 2.0.1 internal-slot: 1.0.7 is-array-buffer: 3.0.4 is-callable: 1.2.7 @@ -3890,13 +3890,13 @@ packages: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 - hasown: 2.0.0 + hasown: 2.0.1 dev: true /es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.1 dev: true /es-to-primitive@1.2.1: @@ -4245,7 +4245,7 @@ packages: eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - hasown: 2.0.0 + hasown: 2.0.1 is-core-module: 2.13.1 is-glob: 4.0.3 minimatch: 3.1.2 @@ -4858,7 +4858,7 @@ packages: function-bind: 1.1.2 has-proto: 1.0.1 has-symbols: 1.0.3 - hasown: 2.0.0 + hasown: 2.0.1 dev: true /get-package-type@0.1.0: @@ -5175,8 +5175,8 @@ packages: minimalistic-assert: 1.0.1 dev: true - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + /hasown@2.0.1: + resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 @@ -5522,7 +5522,7 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - hasown: 2.0.0 + hasown: 2.0.1 side-channel: 1.0.5 dev: true @@ -5642,7 +5642,7 @@ packages: /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.1 /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} diff --git a/src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json b/src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json index 759a2268..afb40778 100644 --- a/src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json +++ b/src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json @@ -1,7 +1,7 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2024-02-09T13:02:45.966Z", + "__export_date": "2024-02-11T11:11:14.532Z", "__export_source": "insomnia.desktop.app:v8.6.1", "resources": [ { @@ -197,6 +197,45 @@ "settingFollowRedirects": "global", "_type": "request" }, + { + "_id": "req_5db3c8b9a7094ec5806bcc804752f968", + "parentId": "wrk_509d4a5094fa485ba93e53bc735e8ac3", + "modified": 1707649833755, + "created": 1707649782517, + "url": "{{baseUrl}}/{{protocol}}/{{version}}/deleteChargingStations", + "name": "deleteChargingStations", + "description": "", + "method": "POST", + "body": { + "mimeType": "application/json", + "text": "{\n\t\"hashIds\": [\n\t\t\"0058d8b50e422cce5bbd0c0a4ad13d5d657e8a88670dcf04c1b2b563fea3db5b96a3686278b374ed050e21baef89060e\"\n\t]\n}" + }, + "parameters": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json", + "id": "pair_af9f914ca52f407488bc6df6c7db3a08" + } + ], + "authentication": { + "type": "basic", + "useISO88591": false, + "disabled": false, + "username": "{{username}}", + "password": "{{password}}" + }, + "metaSortKey": -999999971.875, + "isPrivate": false, + "pathParameters": [], + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, { "_id": "req_79422f6238204d9b8f832f2cb3ebfde3", "parentId": "wrk_509d4a5094fa485ba93e53bc735e8ac3", diff --git a/src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json b/src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json index cd50ed5e..888346bb 100644 --- a/src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json +++ b/src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json @@ -1,7 +1,7 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2024-02-09T13:03:00.609Z", + "__export_date": "2024-02-11T11:07:54.308Z", "__export_source": "insomnia.desktop.app:v8.6.1", "resources": [ { @@ -238,6 +238,38 @@ "description": "", "_type": "websocket_request" }, + { + "_id": "ws-req_c36eca7e4cdd453dac690a46cba1afdf", + "parentId": "wrk_64c9d5670f014930baf668326b95e601", + "modified": 1707645101146, + "created": 1707645101146, + "name": "deleteChargingStations", + "url": "{{ _.baseUrl }}", + "metaSortKey": -1671191988792.4688, + "headers": [ + { + "id": "pair_9a64d3b0bc654ab68710ef138f00d3f5", + "name": "Sec-WebSocket-Protocol", + "value": "{{ _.protocol }}{{ _.version }}", + "description": "" + } + ], + "authentication": { + "type": "basic", + "useISO88591": false, + "disabled": false, + "username": "{{ _.username }}", + "password": "{{ _.password }}" + }, + "parameters": [], + "pathParameters": [], + "settingEncodeUrl": true, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingFollowRedirects": "global", + "description": "", + "_type": "websocket_request" + }, { "_id": "ws-req_afbfa6e6824b427e99e735c0b1eabe3b", "parentId": "wrk_64c9d5670f014930baf668326b95e601", @@ -674,7 +706,7 @@ { "_id": "ws-payload_2872e2656c164769acf98cc7ba7ea028", "parentId": "ws-req_e5902850ac1d40369bd6e942a2755a9d", - "modified": 1707477801921, + "modified": 1707649312368, "created": 1671297544207, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"listChargingStations\",\n\t{}\n]", @@ -684,7 +716,7 @@ { "_id": "ws-payload_edebda0226aa43f88712d7feb60ac645", "parentId": "ws-req_ebe5a555a6344dfba7e29f857af11d08", - "modified": 1707475947625, + "modified": 1707649325381, "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]", @@ -704,7 +736,7 @@ { "_id": "ws-payload_d8e66e0f933e4d74bb5fbff4d15a44bf", "parentId": "ws-req_23025e078480491daf01406b2b5e9cc2", - "modified": 1707157205853, + "modified": 1707589443241, "created": 1671298432039, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"startAutomaticTransactionGenerator\",\n\t{}\n]", @@ -774,7 +806,7 @@ { "_id": "ws-payload_95c28d71c8d940bb83ac514f8916a66d", "parentId": "ws-req_afbfa6e6824b427e99e735c0b1eabe3b", - "modified": 1707157216642, + "modified": 1707649322410, "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 +816,7 @@ { "_id": "ws-payload_3e1dffbcefcc481286b44c694b9e6496", "parentId": "ws-req_3a0ff14878b449f4be3dfbb7432b5f87", - "modified": 1707481292801, + "modified": 1707645536699, "created": 1706726300041, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"listTemplates\",\n\t{}\n]", @@ -794,7 +826,7 @@ { "_id": "ws-payload_5e2dfed34a104c28b887c885ada1b4af", "parentId": "ws-req_8777c5635dd64fccbc2b0f450be656c0", - "modified": 1707482458081, + "modified": 1707649639026, "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\t\"options\": {\n\t\t\t\"autoStart\": false\n\t\t}\n\t}\n]", @@ -804,12 +836,22 @@ { "_id": "ws-payload_494fba679fa644ccb318f092e780834f", "parentId": "ws-req_0bab7a97ceda4944976a463f616dec5c", - "modified": 1707475888681, + "modified": 1707649324584, "created": 1707143784130, "name": "New Payload", "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"performanceStatistics\",\n\t{}\n]", "mode": "application/json", "_type": "websocket_payload" + }, + { + "_id": "ws-payload_c4a0dbcd684e430b9b27298fe2bd088a", + "parentId": "ws-req_c36eca7e4cdd453dac690a46cba1afdf", + "modified": 1707649358965, + "created": 1707645101151, + "name": "New Payload", + "value": "[\n\t\"{% uuid 'v4' %}\",\n\t\"deleteChargingStations\",\n\t{\n\t\t\"hashIds\": [\n\t\t\t\"5b82a559d2b453f6277e272e134ae824ae358cfb6ee2415af9f7c2f325ef8b3e930aeeadcd866df4b8aec58786e60ae7\"\n\t\t]\n\t}\n]", + "mode": "application/json", + "_type": "websocket_payload" } ] } diff --git a/src/charging-station/AutomaticTransactionGenerator.ts b/src/charging-station/AutomaticTransactionGenerator.ts index 37c9b631..95483b8e 100644 --- a/src/charging-station/AutomaticTransactionGenerator.ts +++ b/src/charging-station/AutomaticTransactionGenerator.ts @@ -66,6 +66,11 @@ export class AutomaticTransactionGenerator { return AutomaticTransactionGenerator.instances.get(chargingStation.stationInfo!.hashId) } + public static deleteInstance (chargingStation: ChargingStation): boolean { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return AutomaticTransactionGenerator.instances.delete(chargingStation.stationInfo!.hashId) + } + public start (stopAbsoluteDuration?: boolean): void { if (!checkChargingStation(this.chargingStation, this.logPrefix())) { return diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index ddf8ee70..5356a155 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -144,6 +144,7 @@ export class Bootstrap extends EventEmitter { if (!this.starting) { this.starting = true this.on(ChargingStationWorkerMessageEvents.added, this.workerEventAdded) + this.on(ChargingStationWorkerMessageEvents.deleted, this.workerEventDeleted) this.on(ChargingStationWorkerMessageEvents.started, this.workerEventStarted) this.on(ChargingStationWorkerMessageEvents.stopped, this.workerEventStopped) this.on(ChargingStationWorkerMessageEvents.updated, this.workerEventUpdated) @@ -351,6 +352,9 @@ export class Bootstrap extends EventEmitter { case ChargingStationWorkerMessageEvents.added: this.emit(ChargingStationWorkerMessageEvents.added, msg.data) break + case ChargingStationWorkerMessageEvents.deleted: + this.emit(ChargingStationWorkerMessageEvents.deleted, msg.data) + break case ChargingStationWorkerMessageEvents.started: this.emit(ChargingStationWorkerMessageEvents.started, msg.data) break @@ -399,6 +403,19 @@ export class Bootstrap extends EventEmitter { ) } + private readonly workerEventDeleted = (data: ChargingStationData): void => { + this.uiServer?.chargingStations.delete(data.stationInfo.hashId) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + --this.chargingStationsByTemplate.get(data.stationInfo.templateName)!.added + logger.info( + `${this.logPrefix()} ${moduleName}.workerEventDeleted: Charging station ${ + data.stationInfo.chargingStationId + } (hashId: ${data.stationInfo.hashId}) deleted (${ + this.numberOfAddedChargingStations + } added from ${this.numberOfConfiguredChargingStations} configured charging station(s))` + ) + } + private readonly workerEventStarted = (data: ChargingStationData): void => { this.uiServer?.chargingStations.set(data.stationInfo.hashId, data) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 4ef9ab5d..2b5b5f83 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -2,7 +2,7 @@ import { createHash } from 'node:crypto' import { EventEmitter } from 'node:events' -import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { type FSWatcher, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { dirname, join, parse } from 'node:path' import { URL } from 'node:url' import { parentPort } from 'node:worker_threads' @@ -127,6 +127,7 @@ import { buildAddedMessage, buildChargingStationAutomaticTransactionGeneratorConfiguration, buildConnectorsStatus, + buildDeletedMessage, buildEvsesStatus, buildStartedMessage, buildStoppedMessage, @@ -166,7 +167,7 @@ export class ChargingStation extends EventEmitter { public readonly connectors: Map public readonly evses: Map public readonly requests: Map - public performanceStatistics!: PerformanceStatistics | undefined + public performanceStatistics: PerformanceStatistics | undefined public heartbeatSetInterval?: NodeJS.Timeout public ocppRequestService!: OCPPRequestService public bootNotificationRequest?: BootNotificationRequest @@ -183,7 +184,7 @@ export class ChargingStation extends EventEmitter { private configuredSupervisionUrl!: URL private wsConnectionRetried: boolean private wsConnectionRetryCount: number - private templateFileWatcher!: FSWatcher | undefined + private templateFileWatcher: FSWatcher | undefined private templateFileHash!: string private readonly sharedLRUCache: SharedLRUCache private wsPingSetInterval?: NodeJS.Timeout @@ -211,6 +212,9 @@ export class ChargingStation extends EventEmitter { this.on(ChargingStationEvents.added, () => { parentPort?.postMessage(buildAddedMessage(this)) }) + this.on(ChargingStationEvents.deleted, () => { + parentPort?.postMessage(buildDeletedMessage(this)) + }) this.on(ChargingStationEvents.started, () => { parentPort?.postMessage(buildStartedMessage(this)) }) @@ -666,6 +670,23 @@ export class ChargingStation extends EventEmitter { this.emit(ChargingStationEvents.added) } + public async delete (deleteConfiguration = true): Promise { + if (this.started) { + await this.stop() + } + AutomaticTransactionGenerator.deleteInstance(this) + PerformanceStatistics.deleteInstance(this.stationInfo?.hashId) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!) + this.requests.clear() + this.connectors.clear() + this.evses.clear() + this.templateFileWatcher?.unref() + deleteConfiguration && rmSync(this.configurationFile, { force: true }) + this.chargingStationWorkerBroadcastChannel.unref() + this.emit(ChargingStationEvents.deleted) + } + public start (): void { if (!this.started) { if (!this.starting) { @@ -689,10 +710,10 @@ export class ChargingStation extends EventEmitter { } file have changed, reload` ) this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash) - // Initialize - this.initialize() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!) + // Initialize + this.initialize() // Restart the ATG const ATGStarted = this.automaticTransactionGenerator?.started if (ATGStarted === true) { @@ -743,12 +764,11 @@ export class ChargingStation extends EventEmitter { if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.stop() } - this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash) this.templateFileWatcher?.close() - this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash) delete this.bootNotificationResponse this.started = false this.saveConfiguration() + this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash) this.emit(ChargingStationEvents.stopped) this.stopping = false } else { diff --git a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts index 10d0c75c..26aa61d3 100644 --- a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts @@ -86,6 +86,12 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne await this.chargingStation.stop() } ], + [ + BroadcastChannelProcedureName.DELETE_CHARGING_STATIONS, + async (requestPayload?: BroadcastChannelRequestPayload) => { + await this.chargingStation.delete(requestPayload?.deleteConfiguration as boolean) + } + ], [ BroadcastChannelProcedureName.OPEN_CONNECTION, () => { diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index 99dbc657..a1375cea 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -59,6 +59,9 @@ export abstract class AbstractUIServer { public stop (): void { this.stopHttpServer() + for (const uiService of this.uiServices.values()) { + uiService.stop() + } this.chargingStations.clear() this.chargingStationTemplates.clear() } diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index 4055837f..f8a14789 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -29,6 +29,10 @@ export abstract class AbstractUIService { >([ [ProcedureName.START_CHARGING_STATION, BroadcastChannelProcedureName.START_CHARGING_STATION], [ProcedureName.STOP_CHARGING_STATION, BroadcastChannelProcedureName.STOP_CHARGING_STATION], + [ + ProcedureName.DELETE_CHARGING_STATIONS, + BroadcastChannelProcedureName.DELETE_CHARGING_STATIONS + ], [ProcedureName.CLOSE_CONNECTION, BroadcastChannelProcedureName.CLOSE_CONNECTION], [ProcedureName.OPEN_CONNECTION, BroadcastChannelProcedureName.OPEN_CONNECTION], [ @@ -79,6 +83,11 @@ export abstract class AbstractUIService { this.broadcastChannelRequests = new Map() } + public stop (): void { + this.broadcastChannelRequests.clear() + this.uiServiceWorkerBroadcastChannel.close() + } + public async requestHandler (request: ProtocolRequest): Promise { let messageId: string | undefined let command: ProcedureName | undefined diff --git a/src/performance/PerformanceStatistics.ts b/src/performance/PerformanceStatistics.ts index 3bb72ffa..e9059522 100644 --- a/src/performance/PerformanceStatistics.ts +++ b/src/performance/PerformanceStatistics.ts @@ -67,20 +67,19 @@ export class PerformanceStatistics { objName: string | undefined, uri: URL | undefined ): PerformanceStatistics | undefined { - const logPfx = logPrefix(' Performance statistics') if (objId == null) { const errMsg = 'Cannot get performance statistics instance without specifying object id' - logger.error(`${logPfx} ${errMsg}`) + logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`) throw new BaseError(errMsg) } if (objName == null) { const errMsg = 'Cannot get performance statistics instance without specifying object name' - logger.error(`${logPfx} ${errMsg}`) + logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`) throw new BaseError(errMsg) } if (uri == null) { const errMsg = 'Cannot get performance statistics instance without specifying object uri' - logger.error(`${logPfx} ${errMsg}`) + logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`) throw new BaseError(errMsg) } if (!PerformanceStatistics.instances.has(objId)) { @@ -89,6 +88,15 @@ export class PerformanceStatistics { return PerformanceStatistics.instances.get(objId) } + public static deleteInstance (objId: string | undefined): boolean { + if (objId == null) { + const errMsg = 'Cannot delete performance statistics instance without specifying object id' + logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`) + throw new BaseError(errMsg) + } + return PerformanceStatistics.instances.delete(objId) + } + public static beginMeasure (id: string): string { const markId = `${id.charAt(0).toUpperCase()}${id.slice(1)}~${generateUUID()}` performance.mark(markId) @@ -308,6 +316,10 @@ export class PerformanceStatistics { } } + private static readonly logPrefix = (): string => { + return logPrefix(' Performance statistics') + } + private readonly logPrefix = (): string => { return logPrefix(` ${this.objName} | Performance statistics`) } diff --git a/src/types/ChargingStationEvents.ts b/src/types/ChargingStationEvents.ts index bfdf7a00..b690bc91 100644 --- a/src/types/ChargingStationEvents.ts +++ b/src/types/ChargingStationEvents.ts @@ -1,5 +1,6 @@ export enum ChargingStationEvents { added = 'added', + deleted = 'deleted', started = 'started', stopped = 'stopped', updated = 'updated', diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts index 30fd26c3..e2d65804 100644 --- a/src/types/UIProtocol.ts +++ b/src/types/UIProtocol.ts @@ -33,6 +33,7 @@ export enum ProcedureName { LIST_TEMPLATES = 'listTemplates', LIST_CHARGING_STATIONS = 'listChargingStations', ADD_CHARGING_STATIONS = 'addChargingStations', + DELETE_CHARGING_STATIONS = 'deleteChargingStations', PERFORMANCE_STATISTICS = 'performanceStatistics', START_CHARGING_STATION = 'startChargingStation', STOP_CHARGING_STATION = 'stopChargingStation', diff --git a/src/types/WorkerBroadcastChannel.ts b/src/types/WorkerBroadcastChannel.ts index 902ba0af..caa56e9f 100644 --- a/src/types/WorkerBroadcastChannel.ts +++ b/src/types/WorkerBroadcastChannel.ts @@ -10,6 +10,7 @@ export type BroadcastChannelResponse = [string, BroadcastChannelResponsePayload] export enum BroadcastChannelProcedureName { START_CHARGING_STATION = 'startChargingStation', STOP_CHARGING_STATION = 'stopChargingStation', + DELETE_CHARGING_STATIONS = 'deleteChargingStations', OPEN_CONNECTION = 'openConnection', CLOSE_CONNECTION = 'closeConnection', START_AUTOMATIC_TRANSACTION_GENERATOR = 'startAutomaticTransactionGenerator', diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index c467df73..ea0ff8fc 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -14,7 +14,7 @@ export class Constants { private static readonly SEMVER_PATTERN = '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$' - private static readonly DEFAULT_CHARGING_STATION_RESET_TIME = 60000 // Ms + private static readonly DEFAULT_CHARGING_STATION_RESET_TIME = 30000 // Ms static readonly DEFAULT_STATION_INFO: Partial = Object.freeze({ enableStatistics: false, diff --git a/src/utils/MessageChannelUtils.ts b/src/utils/MessageChannelUtils.ts index a3b267ee..36f797f5 100644 --- a/src/utils/MessageChannelUtils.ts +++ b/src/utils/MessageChannelUtils.ts @@ -21,6 +21,15 @@ export const buildAddedMessage = ( } } +export const buildDeletedMessage = ( + chargingStation: ChargingStation +): ChargingStationWorkerMessage => { + return { + event: ChargingStationWorkerMessageEvents.deleted, + data: buildChargingStationDataPayload(chargingStation) + } +} + export const buildStartedMessage = ( chargingStation: ChargingStation ): ChargingStationWorkerMessage => { diff --git a/src/utils/index.ts b/src/utils/index.ts index f16b793d..21a650ef 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -20,6 +20,7 @@ export { watchJsonFile } from './FileUtils.js' export { buildAddedMessage, buildChargingStationDataPayload, + buildDeletedMessage, buildPerformanceStatisticsMessage, buildStartedMessage, buildStoppedMessage, diff --git a/ui/web/pnpm-lock.yaml b/ui/web/pnpm-lock.yaml index 07bfbb1a..0fc52763 100644 --- a/ui/web/pnpm-lock.yaml +++ b/ui/web/pnpm-lock.yaml @@ -1836,7 +1836,7 @@ packages: has-property-descriptors: 1.0.1 has-proto: 1.0.1 has-symbols: 1.0.3 - hasown: 2.0.0 + hasown: 2.0.1 internal-slot: 1.0.7 is-array-buffer: 3.0.4 is-callable: 1.2.7 @@ -1878,13 +1878,13 @@ packages: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 - hasown: 2.0.0 + hasown: 2.0.1 dev: true /es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.1 dev: true /es-to-primitive@1.2.1: @@ -2043,7 +2043,7 @@ packages: eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - hasown: 2.0.0 + hasown: 2.0.1 is-core-module: 2.13.1 is-glob: 4.0.3 minimatch: 3.1.2 @@ -2377,7 +2377,7 @@ packages: function-bind: 1.1.2 has-proto: 1.0.1 has-symbols: 1.0.3 - hasown: 2.0.0 + hasown: 2.0.1 dev: true /get-stream@8.0.1: @@ -2519,8 +2519,8 @@ packages: has-symbols: 1.0.3 dev: true - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + /hasown@2.0.1: + resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 @@ -2622,7 +2622,7 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - hasown: 2.0.0 + hasown: 2.0.1 side-channel: 1.0.5 dev: true @@ -2656,7 +2656,7 @@ packages: /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.1 dev: true /is-date-object@1.0.5: -- 2.34.1