feat: untangle add charging station op from start charging station op
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 5 Feb 2024 10:18:40 +0000 (11:18 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 5 Feb 2024 10:18:40 +0000 (11:18 +0100)
closes #966

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
12 files changed:
README.md
src/charging-station/Bootstrap.ts
src/charging-station/ChargingStation.ts
src/charging-station/ChargingStationWorker.ts
src/types/ChargingStationEvents.ts
src/types/ChargingStationTemplate.ts
src/types/ChargingStationWorker.ts
src/types/index.ts
src/utils/MessageChannelUtils.ts
src/utils/index.ts
src/worker/WorkerSet.ts
src/worker/WorkerTypes.ts

index 963b273bf8372ac73d78864ae4ba5d2d72532dfb..89ea7b6c10d3498dcd0d26bae966c40d6f6b2e5a 100644 (file)
--- 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) |
index 72a76c4e370418d4dc0e9569bf0cb52e13c53175..1dcad015822788a32d534cbd7574822b1f077170 100644 (file)
@@ -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
           })
index 36cd0958fffe778f3cb68c0402be8b3285f58f4f..c8eaf5e44bc70e18844a37ac79390db7a8b845bc 100644 (file)
@@ -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
index 5bbfa6dd092ec485d670b02c1b639a1539d2542b..4eb6e6a0aa8d8a13ed7091e8255f35029ed62fce 100644 (file)
@@ -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<Data extends ChargingStationWorkerData> {
-  constructor () {
-    // Add message listener to create and start charging station from the main thread
-    parentPort?.on('message', (message: WorkerMessage<Data>) => {
-      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<ChargingStationWorkerData>
-| ThreadWorker<ChargingStationWorkerData>
+export let chargingStationWorker: object
 if (Configuration.workerPoolInUse()) {
-  chargingStationWorker = new ThreadWorker<ChargingStationWorkerData>(addChargingStation)
+  chargingStationWorker = new ThreadWorker<ChargingStationWorkerData>(
+    (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<Data extends ChargingStationWorkerData> {
+    constructor () {
+      parentPort?.on('message', (message: WorkerMessage<Data>) => {
+        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<ChargingStationData>)
+            } 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<ChargingStationWorkerEventError>)
+            }
+            break
+          default:
+            throw new BaseError(
+              `Unknown worker event: '${message.event}' received with data: '${JSON.stringify(
+                message.data,
+                undefined,
+                2
+              )}'`
+            )
+        }
+      })
+    }
+  }
   chargingStationWorker = new ChargingStationWorker<ChargingStationWorkerData>()
 }
index a24fd17095b91c25ff2f839ff4ff584f10f07833..bfdf7a00b4a3a557da6bd09ac0d6c2d142de7afa 100644 (file)
@@ -1,4 +1,5 @@
 export enum ChargingStationEvents {
+  added = 'added',
   started = 'started',
   stopped = 'stopped',
   updated = 'updated',
index cb358844a98e4968b832eeddc59476397fd2c02e..d17fd58f3a3f2ad998dbb5583b808d0289458061 100644 (file)
@@ -71,6 +71,7 @@ export interface ChargingStationTemplate {
   supervisionUrlOcppKey?: string
   supervisionUser?: string
   supervisionPassword?: string
+  autoStart?: boolean
   ocppVersion?: OCPPVersion
   ocppProtocol?: OCPPProtocol
   ocppStrictCompliance?: boolean
index 5b12307973d495118e65b273bc00190d32809b20..5a24dc540ab8856500bbea01989fa47794e4e2a7 100644 (file)
@@ -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<T extends ChargingStationWorkerMessageData> = Omit<
 WorkerMessage<T>,
index 0de12683d952000179e7dedc58b1a944a8411fe5..6cf5be57f5a658488da3c305a754f8f76920b07d 100644 (file)
@@ -146,6 +146,7 @@ export type {
 export {
   type ChargingStationData,
   type ChargingStationWorkerData,
+  type ChargingStationWorkerEventError,
   type ChargingStationWorkerMessage,
   type ChargingStationWorkerMessageData,
   ChargingStationWorkerMessageEvents,
index a61bf94ab29d34879805982692a867b2e7285f4b..212ed5f20124c419fbf3933b6e2855dfcb482a07 100644 (file)
@@ -12,6 +12,15 @@ import {
   type Statistics
 } from '../types/index.js'
 
+export const buildAddedMessage = (
+  chargingStation: ChargingStation
+): ChargingStationWorkerMessage<ChargingStationData> => {
+  return {
+    event: ChargingStationWorkerMessageEvents.added,
+    data: buildChargingStationDataPayload(chargingStation)
+  }
+}
+
 export const buildStartedMessage = (
   chargingStation: ChargingStation
 ): ChargingStationWorkerMessage<ChargingStationData> => {
@@ -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
index b202225586685306df379e6050cfa6b0a4d58b5f..f16b793d55297b31d2c6661a01670167442c93c7 100644 (file)
@@ -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,
index 8b723deaeb3d33d750965c17a45dffc7a931844a..f1ab1cf5482efaadd25398aa7dfe0ae521d68912 100644 (file)
@@ -107,7 +107,7 @@ export class WorkerSet extends WorkerAbstract<WorkerData> {
     }
     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<WorkerData> {
     })
     worker.on('message', this.workerOptions.poolOptions?.messageHandler ?? EMPTY_FUNCTION)
     worker.on('message', (message: WorkerMessage<WorkerData>) => {
-      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)
       }
     })
index faf327dbede39d111c139d32b8dcd313d2981381..d879ce0859c45bb0886dc93791887d99005cd440 100644 (file)
@@ -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<T extends WorkerData> {
 }
 
 export enum WorkerMessageEvents {
-  startWorkerElement = 'startWorkerElement',
-  startWorkerElementError = 'startWorkerElementError',
-  startedWorkerElement = 'startedWorkerElement'
+  addWorkerElement = 'addWorkerElement',
+  addedWorkerElement = 'addedWorkerElement',
+  workerElementError = 'workerElementError'
 }