feat: add options to `addChargingStations` UI protocol command
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 9 Feb 2024 12:52:20 +0000 (13:52 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 9 Feb 2024 12:52:20 +0000 (13:52 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
README.md
src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json [moved from src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json with 99% similarity]
src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json [moved from src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocolCollection.json with 97% similarity]
src/charging-station/Bootstrap.ts
src/charging-station/ChargingStation.ts
src/charging-station/ChargingStationWorker.ts
src/charging-station/ui-server/UIHttpServer.ts
src/charging-station/ui-server/UIWebSocketServer.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/types/ChargingStationWorker.ts
src/types/index.ts

index 060d90bc53a822c6e8acdd65fab649cac639ed24..c537e47aef61a857085508ace385c0f2a1b75dbf 100644 (file)
--- 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:  
similarity index 99%
rename from src/assets/ui-protocol/Insomnia_CSSimulatorUIProtocol.json
rename to src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json
index 1a6269a56a0b7401f3a418dcc4c2d921c43e2528..7869b486ce102c746192a5d5cfcd47d896ce0c9d 100644 (file)
@@ -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",
     {
       "_id": "req_c43bfb67921546baa49b4ec96e571859",
       "parentId": "wrk_509d4a5094fa485ba93e53bc735e8ac3",
-      "modified": 1706781452580,
+      "modified": 1707482593708,
       "created": 1706781436512,
       "url": "{{baseUrl}}/{{protocol}}/{{version}}/addChargingStations",
       "name": "addChargingStations",
       "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": [
similarity index 97%
rename from src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocolCollection.json
rename to src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json
index 07bdc0c7e754e3f5d6e6689dddf22589c27aef33..f4c4687e2b9228411b1e09414b401f0dfe203f7a 100644 (file)
@@ -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",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
     {
       "_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]",
index 0e0a8607a07295c70bf2f5a9a1a442e351d006f8..acd8e73cb23ea1b7cf0b41e5e6cd3436cc4ee0f9 100644 (file)
@@ -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<void> {
+  public async addChargingStation (
+    index: number,
+    stationTemplateFile: string,
+    options?: ChargingStationOptions
+  ): Promise<void> {
     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(
index 0f4bce43184793c9d3f7c221cb152f26dac5d9eb..a086771934e14d564477f3dd80b6ed1b3854fea7 100644 (file)
@@ -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 {
index 4fde42c4d3908a368e2424d3a77cbb3180b0d34c..2623f1272d2f19142715f12bfaadaff469294ddb 100644 (file)
@@ -20,7 +20,7 @@ if (Configuration.workerPoolInUse()) {
   chargingStationWorker = new ThreadWorker<ChargingStationWorkerData>(
     (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,
index 27e8d7ba9051654d777f904b7e5ff16c884b0a47..b10d33363b4ae9cc2898eae9eb5efd426b317613 100644 (file)
@@ -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))
index 0dce52cace77c8cb27a67220f9bcb8788687fe55..16477146f0c638fbd1776213df0d336e3fa9f62d 100644 (file)
@@ -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(
index 8617c7ab4051f375d1a7717185b2e753bf7c9976..4055837fe18d89700e4f22f31bc01f0a1391fa1a 100644 (file)
@@ -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<ResponsePayload> {
-    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 {
index 5a24dc540ab8856500bbea01989fa47794e4e2a7..df4a789c55f69f2f26ff259d11b787c4b534935e 100644 (file)
@@ -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<EvseStatus, 'connectors'> & {
index 2b17daa55f639c13995c854d866084cd7dbd525d..d5f887f81c3014c4a874063969608f433965166a 100644 (file)
@@ -150,6 +150,7 @@ export {
   type ChargingStationWorkerMessage,
   type ChargingStationWorkerMessageData,
   ChargingStationWorkerMessageEvents,
+  type ChargingStationOptions,
   type EvseStatusWorkerType
 } from './ChargingStationWorker.js'
 export type { ChargingStationInfo } from './ChargingStationInfo.js'