From 244c1396e337032577839fa13e9191d5e943864f Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 5 Feb 2024 11:18:40 +0100 Subject: [PATCH] feat: untangle add charging station op from start charging station op MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit closes #966 Signed-off-by: Jérôme Benoit --- README.md | 5 +- src/charging-station/Bootstrap.ts | 39 ++++++- src/charging-station/ChargingStation.ts | 14 +++ src/charging-station/ChargingStationWorker.ts | 106 +++++++++--------- src/types/ChargingStationEvents.ts | 1 + src/types/ChargingStationTemplate.ts | 1 + src/types/ChargingStationWorker.ts | 12 +- src/types/index.ts | 1 + src/utils/MessageChannelUtils.ts | 13 ++- src/utils/index.ts | 10 +- src/worker/WorkerSet.ts | 8 +- src/worker/WorkerTypes.ts | 12 +- 12 files changed, 146 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 963b273b..89ea7b6c 100644 --- a/README.md +++ b/README.md @@ -131,11 +131,12 @@ But the modifications to test have to be done to the files in the build target d | supervisionUrls | | [] | string \| string[] | string or strings array containing connection URIs to OCPP-J servers | | supervisionUser | | undefined | string | basic HTTP authentication user to OCPP-J server | | supervisionPassword | | undefined | string | basic HTTP authentication password to OCPP-J server | -| supervisionUrlOcppConfiguration | true/false | false | boolean | allow supervision URL configuration via a vendor OCPP parameter key | +| supervisionUrlOcppConfiguration | true/false | false | boolean | enable supervision URL configuration via a vendor OCPP parameter key | | supervisionUrlOcppKey | | 'ConnectionUrl' | string | the vendor string that will be used as a vendor OCPP parameter key to set the supervision URL | +| autoStart | true/false | true | boolean | enable automatic start of added charging station from template | | ocppVersion | 1.6/2.0/2.0.1 | 1.6 | string | OCPP version | | ocppProtocol | json | json | string | OCPP protocol | -| ocppStrictCompliance | true/false | true | boolean | strict adherence to the OCPP version and protocol specifications with OCPP commands PDU validation against [OCA](https://www.openchargealliance.org/) JSON schemas | +| ocppStrictCompliance | true/false | true | boolean | enable strict adherence to the OCPP version and protocol specifications with OCPP commands PDU validation against [OCA](https://www.openchargealliance.org/) JSON schemas | | ocppPersistentConfiguration | true/false | true | boolean | enable persistent OCPP parameters storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](dist/assets/configurations) | | stationInfoPersistentConfiguration | true/false | true | boolean | enable persistent station information and specifications storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](dist/assets/configurations) | | automaticTransactionGeneratorPersistentConfiguration | true/false | true | boolean | enable persistent automatic transaction generator configuration storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](dist/assets/configurations) | diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 72a76c4e..1dcad015 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -19,6 +19,7 @@ import { type Storage, StorageFactory } from '../performance/index.js' import { type ChargingStationData, type ChargingStationWorkerData, + type ChargingStationWorkerEventError, type ChargingStationWorkerMessage, type ChargingStationWorkerMessageData, ChargingStationWorkerMessageEvents, @@ -56,6 +57,7 @@ enum exitCodes { interface TemplateChargingStations { configured: number + added: number started: number lastIndex: number } @@ -118,6 +120,13 @@ export class Bootstrap extends EventEmitter { return this.chargingStationsByTemplate.get(templateName)?.lastIndex ?? 0 } + private get numberOfAddedChargingStations (): number { + return [...this.chargingStationsByTemplate.values()].reduce( + (accumulator, value) => accumulator + value.added, + 0 + ) + } + private get numberOfStartedChargingStations (): number { return [...this.chargingStationsByTemplate.values()].reduce( (accumulator, value) => accumulator + value.started, @@ -129,6 +138,7 @@ export class Bootstrap extends EventEmitter { if (!this.started) { if (!this.starting) { this.starting = true + this.on(ChargingStationWorkerMessageEvents.added, this.workerEventAdded) this.on(ChargingStationWorkerMessageEvents.started, this.workerEventStarted) this.on(ChargingStationWorkerMessageEvents.stopped, this.workerEventStopped) this.on(ChargingStationWorkerMessageEvents.updated, this.workerEventUpdated) @@ -323,6 +333,9 @@ export class Bootstrap extends EventEmitter { // ) try { switch (msg.event) { + case ChargingStationWorkerMessageEvents.added: + this.emit(ChargingStationWorkerMessageEvents.added, msg.data as ChargingStationData) + break case ChargingStationWorkerMessageEvents.started: this.emit(ChargingStationWorkerMessageEvents.started, msg.data as ChargingStationData) break @@ -338,14 +351,14 @@ export class Bootstrap extends EventEmitter { msg.data as Statistics ) break - case ChargingStationWorkerMessageEvents.startWorkerElementError: + case ChargingStationWorkerMessageEvents.workerElementError: logger.error( - `${this.logPrefix()} ${moduleName}.messageHandler: Error occurred while starting worker element:`, + `${this.logPrefix()} ${moduleName}.messageHandler: Error occurred while handling '${(msg.data as ChargingStationWorkerEventError).event}' event on worker:`, msg.data ) - this.emit(ChargingStationWorkerMessageEvents.startWorkerElementError, msg.data) + this.emit(ChargingStationWorkerMessageEvents.workerElementError, msg.data) break - case ChargingStationWorkerMessageEvents.startedWorkerElement: + case ChargingStationWorkerMessageEvents.addedWorkerElement: break default: throw new BaseError( @@ -364,6 +377,19 @@ export class Bootstrap extends EventEmitter { } } + private readonly workerEventAdded = (data: ChargingStationData): void => { + this.uiServer?.chargingStations.set(data.stationInfo.hashId, data) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ++this.chargingStationsByTemplate.get(data.stationInfo.templateName)!.added + logger.info( + `${this.logPrefix()} ${moduleName}.workerEventAdded: Charging station ${ + data.stationInfo.chargingStationId + } (hashId: ${data.stationInfo.hashId}) added (${ + 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 @@ -373,7 +399,7 @@ export class Bootstrap extends EventEmitter { data.stationInfo.chargingStationId } (hashId: ${data.stationInfo.hashId}) started (${ this.numberOfStartedChargingStations - } started from ${this.numberOfConfiguredChargingStations} configured charging station(s))` + } started from ${this.numberOfAddedChargingStations} added charging station(s))` ) } @@ -386,7 +412,7 @@ export class Bootstrap extends EventEmitter { data.stationInfo.chargingStationId } (hashId: ${data.stationInfo.hashId}) stopped (${ this.numberOfStartedChargingStations - } started from ${this.numberOfConfiguredChargingStations} configured charging station(s))` + } started from ${this.numberOfAddedChargingStations} added charging station(s))` ) } @@ -418,6 +444,7 @@ export class Bootstrap extends EventEmitter { const templateName = parse(stationTemplateUrl.file).name this.chargingStationsByTemplate.set(templateName, { configured: stationTemplateUrl.numberOfStations, + added: 0, started: 0, lastIndex: 0 }) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 36cd0958..c8eaf5e4 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -123,6 +123,7 @@ import { Configuration, Constants, DCElectricUtils, + buildAddedMessage, buildChargingStationAutomaticTransactionGeneratorConfiguration, buildConnectorsStatus, buildEvsesStatus, @@ -206,6 +207,9 @@ export class ChargingStation extends EventEmitter { this.idTagsCache = IdTagsCache.getInstance() this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this) + this.on(ChargingStationEvents.added, () => { + parentPort?.postMessage(buildAddedMessage(this)) + }) this.on(ChargingStationEvents.started, () => { parentPort?.postMessage(buildStartedMessage(this)) }) @@ -240,6 +244,8 @@ export class ChargingStation extends EventEmitter { }) this.initialize() + + this.stationInfo?.autoStart === true && this.start() } public get hasEvses (): boolean { @@ -646,6 +652,10 @@ export class ChargingStation extends EventEmitter { } } + public add (): void { + this.emit(ChargingStationEvents.added) + } + public start (): void { if (!this.started) { if (!this.starting) { @@ -1129,6 +1139,7 @@ export class ChargingStation extends EventEmitter { } const stationInfo = stationTemplateToStationInfo(stationTemplate) stationInfo.hashId = getHashId(this.index, stationTemplate) + stationInfo.autoStart = stationTemplate.autoStart ?? true stationInfo.templateName = parse(this.templateFile).name stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate) stationInfo.ocppVersion = stationTemplate.ocppVersion ?? OCPPVersion.VERSION_16 @@ -1188,6 +1199,9 @@ export class ChargingStation extends EventEmitter { if (stationInfo.templateName == null) { stationInfo.templateName = parse(this.templateFile).name } + if (stationInfo.autoStart == null) { + stationInfo.autoStart = true + } } } return stationInfo diff --git a/src/charging-station/ChargingStationWorker.ts b/src/charging-station/ChargingStationWorker.ts index 5bbfa6dd..4eb6e6a0 100644 --- a/src/charging-station/ChargingStationWorker.ts +++ b/src/charging-station/ChargingStationWorker.ts @@ -6,61 +6,63 @@ import { ThreadWorker } from 'poolifier' import { ChargingStation } from './ChargingStation.js' import { BaseError } from '../exception/index.js' -import type { ChargingStationWorkerData } from '../types/index.js' -import { Configuration } from '../utils/index.js' +import type { + ChargingStationData, + ChargingStationWorkerData, + ChargingStationWorkerEventError, + ChargingStationWorkerMessage +} from '../types/index.js' +import { Configuration, buildChargingStationDataPayload } from '../utils/index.js' import { type WorkerMessage, WorkerMessageEvents } from '../worker/index.js' -/** - * Adds and starts a charging station instance - * - * @param data - data sent to worker - */ -const addChargingStation = (data?: ChargingStationWorkerData): void => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - new ChargingStation(data!.index, data!.templateFile).start() -} - -// eslint-disable-next-line @typescript-eslint/no-extraneous-class -class ChargingStationWorker { - constructor () { - // Add message listener to create and start charging station from the main thread - parentPort?.on('message', (message: WorkerMessage) => { - switch (message.event) { - case WorkerMessageEvents.startWorkerElement: - try { - addChargingStation(message.data) - parentPort?.postMessage({ - event: WorkerMessageEvents.startedWorkerElement - }) - } catch (error) { - parentPort?.postMessage({ - event: WorkerMessageEvents.startWorkerElementError, - data: { - name: (error as Error).name, - message: (error as Error).message, - stack: (error as Error).stack - } - }) - } - break - default: - throw new BaseError( - `Unknown worker event: '${message.event}' received with data: '${JSON.stringify( - message.data, - undefined, - 2 - )}'` - ) - } - }) - } -} - -export let chargingStationWorker: -| ChargingStationWorker -| ThreadWorker +export let chargingStationWorker: object if (Configuration.workerPoolInUse()) { - chargingStationWorker = new ThreadWorker(addChargingStation) + chargingStationWorker = new ThreadWorker( + (data?: ChargingStationWorkerData): void => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + new ChargingStation(data!.index, data!.templateFile).add() + } + ) } else { + // eslint-disable-next-line @typescript-eslint/no-extraneous-class + class ChargingStationWorker { + constructor () { + parentPort?.on('message', (message: WorkerMessage) => { + switch (message.event) { + case WorkerMessageEvents.addWorkerElement: + try { + const chargingStation = new ChargingStation( + message.data.index, + message.data.templateFile + ) + chargingStation.add() + parentPort?.postMessage({ + event: WorkerMessageEvents.addedWorkerElement, + data: buildChargingStationDataPayload(chargingStation) + } satisfies ChargingStationWorkerMessage) + } catch (error) { + parentPort?.postMessage({ + event: WorkerMessageEvents.workerElementError, + data: { + event: WorkerMessageEvents.addWorkerElement, + name: (error as Error).name, + message: (error as Error).message, + stack: (error as Error).stack + } + } satisfies ChargingStationWorkerMessage) + } + break + default: + throw new BaseError( + `Unknown worker event: '${message.event}' received with data: '${JSON.stringify( + message.data, + undefined, + 2 + )}'` + ) + } + }) + } + } chargingStationWorker = new ChargingStationWorker() } diff --git a/src/types/ChargingStationEvents.ts b/src/types/ChargingStationEvents.ts index a24fd170..bfdf7a00 100644 --- a/src/types/ChargingStationEvents.ts +++ b/src/types/ChargingStationEvents.ts @@ -1,4 +1,5 @@ export enum ChargingStationEvents { + added = 'added', started = 'started', stopped = 'stopped', updated = 'updated', diff --git a/src/types/ChargingStationTemplate.ts b/src/types/ChargingStationTemplate.ts index cb358844..d17fd58f 100644 --- a/src/types/ChargingStationTemplate.ts +++ b/src/types/ChargingStationTemplate.ts @@ -71,6 +71,7 @@ export interface ChargingStationTemplate { supervisionUrlOcppKey?: string supervisionUser?: string supervisionPassword?: string + autoStart?: boolean ocppVersion?: OCPPVersion ocppProtocol?: OCPPProtocol ocppStrictCompliance?: boolean diff --git a/src/types/ChargingStationWorker.ts b/src/types/ChargingStationWorker.ts index 5b123079..5a24dc54 100644 --- a/src/types/ChargingStationWorker.ts +++ b/src/types/ChargingStationWorker.ts @@ -55,7 +55,17 @@ export type ChargingStationWorkerMessageEvents = | ChargingStationEvents | ChargingStationMessageEvents -export type ChargingStationWorkerMessageData = ChargingStationData | Statistics +export interface ChargingStationWorkerEventError extends WorkerData { + event: WorkerMessageEvents + name: string + message: string + stack?: string +} + +export type ChargingStationWorkerMessageData = + | ChargingStationData + | Statistics + | ChargingStationWorkerEventError export type ChargingStationWorkerMessage = Omit< WorkerMessage, diff --git a/src/types/index.ts b/src/types/index.ts index 0de12683..6cf5be57 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -146,6 +146,7 @@ export type { export { type ChargingStationData, type ChargingStationWorkerData, + type ChargingStationWorkerEventError, type ChargingStationWorkerMessage, type ChargingStationWorkerMessageData, ChargingStationWorkerMessageEvents, diff --git a/src/utils/MessageChannelUtils.ts b/src/utils/MessageChannelUtils.ts index a61bf94a..212ed5f2 100644 --- a/src/utils/MessageChannelUtils.ts +++ b/src/utils/MessageChannelUtils.ts @@ -12,6 +12,15 @@ import { type Statistics } from '../types/index.js' +export const buildAddedMessage = ( + chargingStation: ChargingStation +): ChargingStationWorkerMessage => { + return { + event: ChargingStationWorkerMessageEvents.added, + data: buildChargingStationDataPayload(chargingStation) + } +} + export const buildStartedMessage = ( chargingStation: ChargingStation ): ChargingStationWorkerMessage => { @@ -48,7 +57,9 @@ export const buildPerformanceStatisticsMessage = ( } } -const buildChargingStationDataPayload = (chargingStation: ChargingStation): ChargingStationData => { +export const buildChargingStationDataPayload = ( + chargingStation: ChargingStation +): ChargingStationData => { return { started: chargingStation.started, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/src/utils/index.ts b/src/utils/index.ts index b2022255..f16b793d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -11,20 +11,21 @@ export { Configuration } from './Configuration.js' export { Constants } from './Constants.js' export { handleFileException, + handleSendMessageError, handleUncaughtException, handleUnhandledRejection, - handleSendMessageError, setDefaultErrorParams } from './ErrorUtils.js' export { watchJsonFile } from './FileUtils.js' export { + buildAddedMessage, + buildChargingStationDataPayload, buildPerformanceStatisticsMessage, - buildUpdatedMessage, buildStartedMessage, - buildStoppedMessage + buildStoppedMessage, + buildUpdatedMessage } from './MessageChannelUtils.js' export { - isAsyncFunction, JSONStringifyWithMapSupport, clone, convertToBoolean, @@ -41,6 +42,7 @@ export { getRandomInteger, getWebSocketCloseEventStatusString, isArraySorted, + isAsyncFunction, isEmptyArray, isEmptyObject, isEmptyString, diff --git a/src/worker/WorkerSet.ts b/src/worker/WorkerSet.ts index 8b723dea..f1ab1cf5 100644 --- a/src/worker/WorkerSet.ts +++ b/src/worker/WorkerSet.ts @@ -107,7 +107,7 @@ export class WorkerSet extends WorkerAbstract { } const workerSetElement = await this.getWorkerSetElement() workerSetElement.worker.postMessage({ - event: WorkerMessageEvents.startWorkerElement, + event: WorkerMessageEvents.addWorkerElement, data: elementData }) ++workerSetElement.numberOfWorkerElements @@ -130,9 +130,9 @@ export class WorkerSet extends WorkerAbstract { }) worker.on('message', this.workerOptions.poolOptions?.messageHandler ?? EMPTY_FUNCTION) worker.on('message', (message: WorkerMessage) => { - if (message.event === WorkerMessageEvents.startedWorkerElement) { - this.emitter?.emit(WorkerSetEvents.elementStarted, this.info) - } else if (message.event === WorkerMessageEvents.startWorkerElementError) { + if (message.event === WorkerMessageEvents.addedWorkerElement) { + this.emitter?.emit(WorkerSetEvents.elementAdded, this.info) + } else if (message.event === WorkerMessageEvents.workerElementError) { this.emitter?.emit(WorkerSetEvents.elementError, message.data) } }) diff --git a/src/worker/WorkerTypes.ts b/src/worker/WorkerTypes.ts index faf327db..d879ce08 100644 --- a/src/worker/WorkerTypes.ts +++ b/src/worker/WorkerTypes.ts @@ -4,9 +4,9 @@ import { type PoolEvent, PoolEvents, type ThreadPoolOptions } from 'poolifier' export enum WorkerProcessType { workerSet = 'workerSet', + fixedPool = 'fixedPool', /** @experimental */ - dynamicPool = 'dynamicPool', - fixedPool = 'fixedPool' + dynamicPool = 'dynamicPool' } export interface SetInfo { @@ -22,7 +22,7 @@ export enum WorkerSetEvents { started = 'started', stopped = 'stopped', error = 'error', - elementStarted = 'elementStarted', + elementAdded = 'elementAdded', elementError = 'elementError' } @@ -55,7 +55,7 @@ export interface WorkerMessage { } export enum WorkerMessageEvents { - startWorkerElement = 'startWorkerElement', - startWorkerElementError = 'startWorkerElementError', - startedWorkerElement = 'startedWorkerElement' + addWorkerElement = 'addWorkerElement', + addedWorkerElement = 'addedWorkerElement', + workerElementError = 'workerElementError' } -- 2.34.1