chore(deps-dev): apply updates
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 20 Nov 2024 12:05:33 +0000 (13:05 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 20 Nov 2024 12:05:33 +0000 (13:05 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
81 files changed:
package.json
pnpm-lock.yaml
scripts/build-requirements.js
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/Bootstrap.ts
src/charging-station/ChargingStation.ts
src/charging-station/ConfigurationKeyUtils.ts
src/charging-station/IdTagsCache.ts
src/charging-station/SharedLRUCache.ts
src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/broadcast-channel/WorkerBroadcastChannel.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20RequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ocpp/OCPPIncomingRequestService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ocpp/OCPPResponseService.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/UIHttpServer.ts
src/charging-station/ui-server/UIServerFactory.ts
src/charging-station/ui-server/UIWebSocketServer.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/performance/PerformanceStatistics.ts
src/performance/storage/JsonFileStorage.ts
src/performance/storage/MikroOrmStorage.ts
src/performance/storage/MongoDBStorage.ts
src/performance/storage/Storage.ts
src/types/AutomaticTransactionGenerator.ts
src/types/ChargingStationConfiguration.ts
src/types/ChargingStationInfo.ts
src/types/ChargingStationOcppConfiguration.ts
src/types/ChargingStationTemplate.ts
src/types/ChargingStationWorker.ts
src/types/ConfigurationData.ts
src/types/Evse.ts
src/types/JsonType.ts
src/types/MeasurandPerPhaseSampledValueTemplates.ts
src/types/Statistics.ts
src/types/Storage.ts
src/types/UIProtocol.ts
src/types/WebSocket.ts
src/types/WorkerBroadcastChannel.ts
src/types/ocpp/1.6/ChargingProfile.ts
src/types/ocpp/1.6/Configuration.ts
src/types/ocpp/1.6/MeterValues.ts
src/types/ocpp/1.6/Requests.ts
src/types/ocpp/1.6/Responses.ts
src/types/ocpp/1.6/Transaction.ts
src/types/ocpp/2.0/Common.ts
src/types/ocpp/2.0/Requests.ts
src/types/ocpp/2.0/Responses.ts
src/types/ocpp/2.0/Variables.ts
src/types/ocpp/Common.ts
src/types/ocpp/Configuration.ts
src/types/ocpp/MessageType.ts
src/types/ocpp/MeterValues.ts
src/types/ocpp/Requests.ts
src/types/ocpp/Reservation.ts
src/types/ocpp/Responses.ts
src/types/ocpp/Transaction.ts
src/utils/AsyncLock.ts
src/utils/Configuration.ts
src/utils/Logger.ts
src/utils/Utils.ts
src/worker/WorkerAbstract.ts
src/worker/WorkerConstants.ts
src/worker/WorkerDynamicPool.ts
src/worker/WorkerFixedPool.ts
src/worker/WorkerSet.ts
src/worker/WorkerTypes.ts
tests/utils/Utils.test.ts
ui/web/package.json
ui/web/src/composables/UIClient.ts
ui/web/src/types/ChargingStationType.ts
ui/web/src/types/JsonType.ts
ui/web/src/types/UIProtocol.ts

index 93c468746f9eb5069d1c870de29d364884ad3ccd..0d9222d263e99b47689c2b7f3108d6ab2b807360 100644 (file)
     "utf-8-validate": "^6.0.5"
   },
   "devDependencies": {
-    "@commitlint/cli": "^19.5.0",
-    "@commitlint/config-conventional": "^19.5.0",
+    "@commitlint/cli": "^19.6.0",
+    "@commitlint/config-conventional": "^19.6.0",
     "@cspell/eslint-plugin": "^8.16.0",
     "@eslint/js": "^9.15.0",
     "@mikro-orm/cli": "^6.4.0",
     "@std/expect": "npm:@jsr/std__expect@^1.0.8",
-    "@types/node": "^22.9.0",
+    "@types/node": "^22.9.1",
     "@types/semver": "^7.5.8",
     "@types/ws": "^8.5.13",
     "c8": "^10.1.2",
     "eslint": "^9.15.0",
     "eslint-define-config": "^2.1.0",
     "eslint-plugin-jsdoc": "^50.5.0",
-    "eslint-plugin-perfectionist": "^3.9.1",
+    "eslint-plugin-perfectionist": "^4.0.3",
     "eslint-plugin-vue": "^9.31.0",
     "glob": "^11.0.0",
     "husky": "^9.1.7",
index 6453166116d1f17fe10eabd90a6fc1d9fd2942f1..07337066e9de1f28049442aa6125ff225d276533 100644 (file)
@@ -83,11 +83,11 @@ importers:
         version: 6.0.5
     devDependencies:
       '@commitlint/cli':
-        specifier: ^19.5.0
-        version: 19.5.0(@types/node@22.9.0)(typescript@5.6.3)
+        specifier: ^19.6.0
+        version: 19.6.0(@types/node@22.9.1)(typescript@5.6.3)
       '@commitlint/config-conventional':
-        specifier: ^19.5.0
-        version: 19.5.0
+        specifier: ^19.6.0
+        version: 19.6.0
       '@cspell/eslint-plugin':
         specifier: ^8.16.0
         version: 8.16.0(eslint@9.15.0(jiti@1.21.6))
@@ -101,8 +101,8 @@ importers:
         specifier: npm:@jsr/std__expect@^1.0.8
         version: '@jsr/std__expect@1.0.8'
       '@types/node':
-        specifier: ^22.9.0
-        version: 22.9.0
+        specifier: ^22.9.1
+        version: 22.9.1
       '@types/semver':
         specifier: ^7.5.8
         version: 7.5.8
@@ -137,8 +137,8 @@ importers:
         specifier: ^50.5.0
         version: 50.5.0(eslint@9.15.0(jiti@1.21.6))
       eslint-plugin-perfectionist:
-        specifier: ^3.9.1
-        version: 3.9.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vue-eslint-parser@9.4.3(eslint@9.15.0(jiti@1.21.6)))
+        specifier: ^4.0.3
+        version: 4.0.3(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
       eslint-plugin-vue:
         specifier: ^9.31.0
         version: 9.31.0(eslint@9.15.0(jiti@1.21.6))
@@ -165,7 +165,7 @@ importers:
         version: 7.6.3
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.9.0)(typescript@5.6.3)
+        version: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
       tsx:
         specifier: ^4.19.2
         version: 4.19.2
@@ -198,17 +198,17 @@ importers:
         specifier: ^21.1.7
         version: 21.1.7
       '@types/node':
-        specifier: ^22.9.0
-        version: 22.9.0
+        specifier: ^22.9.1
+        version: 22.9.1
       '@vitejs/plugin-vue':
         specifier: ^5.2.0
-        version: 5.2.0(vite@5.4.11(@types/node@22.9.0))(vue@3.5.13(typescript@5.6.3))
+        version: 5.2.0(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))
       '@vitejs/plugin-vue-jsx':
         specifier: ^4.1.0
-        version: 4.1.0(vite@5.4.11(@types/node@22.9.0))(vue@3.5.13(typescript@5.6.3))
+        version: 4.1.0(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))
       '@vitest/coverage-v8':
         specifier: ^2.1.5
-        version: 2.1.5(vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5)))
+        version: 2.1.5(vitest@2.1.5(@types/node@22.9.1)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5)))
       '@vue/test-utils':
         specifier: ^2.4.6
         version: 2.4.6
@@ -232,10 +232,10 @@ importers:
         version: 5.6.3
       vite:
         specifier: ^5.4.11
-        version: 5.4.11(@types/node@22.9.0)
+        version: 5.4.11(@types/node@22.9.1)
       vitest:
         specifier: ^2.1.5
-        version: 2.1.5(@types/node@22.9.0)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5))
+        version: 2.1.5(@types/node@22.9.1)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5))
 
 packages:
 
@@ -398,13 +398,13 @@ packages:
     resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
     engines: {node: '>=0.1.90'}
 
-  '@commitlint/cli@19.5.0':
-    resolution: {integrity: sha512-gaGqSliGwB86MDmAAKAtV9SV1SHdmN8pnGq4EJU4+hLisQ7IFfx4jvU4s+pk6tl0+9bv6yT+CaZkufOinkSJIQ==}
+  '@commitlint/cli@19.6.0':
+    resolution: {integrity: sha512-v17BgGD9w5KnthaKxXnEg6KLq6DYiAxyiN44TpiRtqyW8NSq+Kx99mkEG8Qo6uu6cI5eMzMojW2muJxjmPnF8w==}
     engines: {node: '>=v18'}
     hasBin: true
 
-  '@commitlint/config-conventional@19.5.0':
-    resolution: {integrity: sha512-OBhdtJyHNPryZKg0fFpZNOBM1ZDbntMvqMuSmpfyP86XSfwzGw4CaoYRG4RutUPg0BTK07VMRIkNJT6wi2zthg==}
+  '@commitlint/config-conventional@19.6.0':
+    resolution: {integrity: sha512-DJT40iMnTYtBtUfw9ApbsLZFke1zKh6llITVJ+x9mtpHD08gsNXaIRqHTmwTZL3dNX5+WoyK7pCN/5zswvkBCQ==}
     engines: {node: '>=v18'}
 
   '@commitlint/config-validator@19.5.0':
@@ -423,12 +423,12 @@ packages:
     resolution: {integrity: sha512-yNy088miE52stCI3dhG/vvxFo9e4jFkU1Mj3xECfzp/bIS/JUay4491huAlVcffOoMK1cd296q0W92NlER6r3A==}
     engines: {node: '>=v18'}
 
-  '@commitlint/is-ignored@19.5.0':
-    resolution: {integrity: sha512-0XQ7Llsf9iL/ANtwyZ6G0NGp5Y3EQ8eDQSxv/SRcfJ0awlBY4tHFAvwWbw66FVUaWICH7iE5en+FD9TQsokZ5w==}
+  '@commitlint/is-ignored@19.6.0':
+    resolution: {integrity: sha512-Ov6iBgxJQFR9koOupDPHvcHU9keFupDgtB3lObdEZDroiG4jj1rzky60fbQozFKVYRTUdrBGICHG0YVmRuAJmw==}
     engines: {node: '>=v18'}
 
-  '@commitlint/lint@19.5.0':
-    resolution: {integrity: sha512-cAAQwJcRtiBxQWO0eprrAbOurtJz8U6MgYqLz+p9kLElirzSCc0vGMcyCaA1O7AqBuxo11l1XsY3FhOFowLAAg==}
+  '@commitlint/lint@19.6.0':
+    resolution: {integrity: sha512-LRo7zDkXtcIrpco9RnfhOKeg8PAnE3oDDoalnrVU/EVaKHYBWYL1DlRR7+3AWn0JiBqD8yKOfetVxJGdEtZ0tg==}
     engines: {node: '>=v18'}
 
   '@commitlint/load@19.5.0':
@@ -451,8 +451,8 @@ packages:
     resolution: {integrity: sha512-CU/GscZhCUsJwcKTJS9Ndh3AKGZTNFIOoQB2n8CmFnizE0VnEuJoum+COW+C1lNABEeqk6ssfc1Kkalm4bDklA==}
     engines: {node: '>=v18'}
 
-  '@commitlint/rules@19.5.0':
-    resolution: {integrity: sha512-hDW5TPyf/h1/EufSHEKSp6Hs+YVsDMHazfJ2azIk9tHPXS6UqSz1dIRs1gpqS3eMXgtkT7JH6TW4IShdqOwhAw==}
+  '@commitlint/rules@19.6.0':
+    resolution: {integrity: sha512-1f2reW7lbrI0X0ozZMesS/WZxgPa4/wi56vFuJENBmed6mWq5KsheN/nxqnl/C23ioxpPO/PL6tXpiiFy5Bhjw==}
     engines: {node: '>=v18'}
 
   '@commitlint/to-lines@19.5.0':
@@ -1399,8 +1399,8 @@ packages:
     resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
     engines: {node: '>=14.16'}
 
-  '@stylistic/eslint-plugin@2.10.1':
-    resolution: {integrity: sha512-U+4yzNXElTf9q0kEfnloI9XbOyD4cnEQCxjUI94q0+W++0GAEQvJ/slwEj9lwjDHfGADRSr+Tco/z0XJvmDfCQ==}
+  '@stylistic/eslint-plugin@2.11.0':
+    resolution: {integrity: sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: '>=8.40.0'
@@ -1465,8 +1465,8 @@ packages:
   '@types/long@4.0.2':
     resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==}
 
-  '@types/node@22.9.0':
-    resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==}
+  '@types/node@22.9.1':
+    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
 
   '@types/offscreencanvas@2019.3.0':
     resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
@@ -2016,8 +2016,8 @@ packages:
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
 
-  bson@6.9.0:
-    resolution: {integrity: sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==}
+  bson@6.10.0:
+    resolution: {integrity: sha512-ROchNosXMJD2cbQGm84KoP7vOGPO6/bOAW0veMMbzhXLqoZptcaYRVLitwvuhwhjjpU1qP4YZRWLhgETdgqUQw==}
     engines: {node: '>=16.20.1'}
 
   buffer-equal@0.0.1:
@@ -2913,24 +2913,11 @@ packages:
     peerDependencies:
       eslint: '>=8.23.0'
 
-  eslint-plugin-perfectionist@3.9.1:
-    resolution: {integrity: sha512-9WRzf6XaAxF4Oi5t/3TqKP5zUjERhasHmLFHin2Yw6ZAp/EP/EVA2dr3BhQrrHWCm5SzTMZf0FcjDnBkO2xFkA==}
+  eslint-plugin-perfectionist@4.0.3:
+    resolution: {integrity: sha512-CyafnreF6boy4lf1XaF72U8NbkwrfjU/mOf1y6doaDMS9zGXhUU1DSk+ZPf/rVwCf1PL1m+rhHqFs+IcB8kDmA==}
     engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
-      astro-eslint-parser: ^1.0.2
       eslint: '>=8.0.0'
-      svelte: '>=3.0.0'
-      svelte-eslint-parser: ^0.41.1
-      vue-eslint-parser: '>=9.0.0'
-    peerDependenciesMeta:
-      astro-eslint-parser:
-        optional: true
-      svelte:
-        optional: true
-      svelte-eslint-parser:
-        optional: true
-      vue-eslint-parser:
-        optional: true
 
   eslint-plugin-promise@7.1.0:
     resolution: {integrity: sha512-8trNmPxdAy3W620WKDpaS65NlM5yAumod6XeC4LOb+jxlkG4IVcp68c6dXY2ev+uT4U1PtG57YDV6EGAXN0GbQ==}
@@ -4450,12 +4437,13 @@ packages:
   napi-build-utils@1.0.2:
     resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
 
-  natural-compare-lite@1.4.0:
-    resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
-
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
+  natural-orderby@5.0.0:
+    resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==}
+    engines: {node: '>=18'}
+
   ndarray-blas-level1@1.1.3:
     resolution: {integrity: sha512-g0Qzf+W0J2S/w1GeYlGuFjGRGGE+f+u8x4O8lhBtsrCaf++n/+YLTPKk1tovYmciL3zUePmwi/szoP5oj8zQvg==}
 
@@ -4516,8 +4504,8 @@ packages:
       encoding:
         optional: true
 
-  node-gyp-build@4.8.3:
-    resolution: {integrity: sha512-EMS95CMJzdoSKoIiXo8pxKoL8DYxwIZXYlLmgPb8KUv794abpnLK6ynsCAWNliOjREKruYKdzbh76HHYUHX7nw==}
+  node-gyp-build@4.8.4:
+    resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
     hasBin: true
 
   node-gyp@8.4.1:
@@ -6149,8 +6137,8 @@ packages:
     engines: {node: '>= 14'}
     hasBin: true
 
-  yaml@2.6.0:
-    resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==}
+  yaml@2.6.1:
+    resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==}
     engines: {node: '>= 14'}
     hasBin: true
 
@@ -6511,11 +6499,11 @@ snapshots:
 
   '@colors/colors@1.6.0': {}
 
-  '@commitlint/cli@19.5.0(@types/node@22.9.0)(typescript@5.6.3)':
+  '@commitlint/cli@19.6.0(@types/node@22.9.1)(typescript@5.6.3)':
     dependencies:
       '@commitlint/format': 19.5.0
-      '@commitlint/lint': 19.5.0
-      '@commitlint/load': 19.5.0(@types/node@22.9.0)(typescript@5.6.3)
+      '@commitlint/lint': 19.6.0
+      '@commitlint/load': 19.5.0(@types/node@22.9.1)(typescript@5.6.3)
       '@commitlint/read': 19.5.0
       '@commitlint/types': 19.5.0
       tinyexec: 0.3.1
@@ -6524,7 +6512,7 @@ snapshots:
       - '@types/node'
       - typescript
 
-  '@commitlint/config-conventional@19.5.0':
+  '@commitlint/config-conventional@19.6.0':
     dependencies:
       '@commitlint/types': 19.5.0
       conventional-changelog-conventionalcommits: 7.0.2
@@ -6550,19 +6538,19 @@ snapshots:
       '@commitlint/types': 19.5.0
       chalk: 5.3.0
 
-  '@commitlint/is-ignored@19.5.0':
+  '@commitlint/is-ignored@19.6.0':
     dependencies:
       '@commitlint/types': 19.5.0
       semver: 7.6.3
 
-  '@commitlint/lint@19.5.0':
+  '@commitlint/lint@19.6.0':
     dependencies:
-      '@commitlint/is-ignored': 19.5.0
+      '@commitlint/is-ignored': 19.6.0
       '@commitlint/parse': 19.5.0
-      '@commitlint/rules': 19.5.0
+      '@commitlint/rules': 19.6.0
       '@commitlint/types': 19.5.0
 
-  '@commitlint/load@19.5.0(@types/node@22.9.0)(typescript@5.6.3)':
+  '@commitlint/load@19.5.0(@types/node@22.9.1)(typescript@5.6.3)':
     dependencies:
       '@commitlint/config-validator': 19.5.0
       '@commitlint/execute-rule': 19.5.0
@@ -6570,7 +6558,7 @@ snapshots:
       '@commitlint/types': 19.5.0
       chalk: 5.3.0
       cosmiconfig: 9.0.0(typescript@5.6.3)
-      cosmiconfig-typescript-loader: 5.1.0(@types/node@22.9.0)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3)
+      cosmiconfig-typescript-loader: 5.1.0(@types/node@22.9.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
@@ -6603,7 +6591,7 @@ snapshots:
       lodash.mergewith: 4.6.2
       resolve-from: 5.0.0
 
-  '@commitlint/rules@19.5.0':
+  '@commitlint/rules@19.6.0':
     dependencies:
       '@commitlint/ensure': 19.5.0
       '@commitlint/message': 19.5.0
@@ -7350,7 +7338,7 @@ snapshots:
 
   '@sindresorhus/is@5.6.0': {}
 
-  '@stylistic/eslint-plugin@2.10.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
+  '@stylistic/eslint-plugin@2.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
     dependencies:
       '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
       eslint: 9.15.0(jiti@1.21.6)
@@ -7406,7 +7394,7 @@ snapshots:
 
   '@types/conventional-commits-parser@5.0.0':
     dependencies:
-      '@types/node': 22.9.0
+      '@types/node': 22.9.1
 
   '@types/estree@1.0.6': {}
 
@@ -7418,7 +7406,7 @@ snapshots:
 
   '@types/jsdom@21.1.7':
     dependencies:
-      '@types/node': 22.9.0
+      '@types/node': 22.9.1
       '@types/tough-cookie': 4.0.5
       parse5: 7.2.1
 
@@ -7426,7 +7414,7 @@ snapshots:
 
   '@types/long@4.0.2': {}
 
-  '@types/node@22.9.0':
+  '@types/node@22.9.1':
     dependencies:
       undici-types: 6.19.8
 
@@ -7450,7 +7438,7 @@ snapshots:
 
   '@types/ws@8.5.13':
     dependencies:
-      '@types/node': 22.9.0
+      '@types/node': 22.9.1
 
   '@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
     dependencies:
@@ -7534,22 +7522,22 @@ snapshots:
       '@typescript-eslint/types': 8.15.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-vue-jsx@4.1.0(vite@5.4.11(@types/node@22.9.0))(vue@3.5.13(typescript@5.6.3))':
+  '@vitejs/plugin-vue-jsx@4.1.0(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.0)
       '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0)
-      vite: 5.4.11(@types/node@22.9.0)
+      vite: 5.4.11(@types/node@22.9.1)
       vue: 3.5.13(typescript@5.6.3)
     transitivePeerDependencies:
       - supports-color
 
-  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.0))(vue@3.5.13(typescript@5.6.3))':
+  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
-      vite: 5.4.11(@types/node@22.9.0)
+      vite: 5.4.11(@types/node@22.9.1)
       vue: 3.5.13(typescript@5.6.3)
 
-  '@vitest/coverage-v8@2.1.5(vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5)))':
+  '@vitest/coverage-v8@2.1.5(vitest@2.1.5(@types/node@22.9.1)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5)))':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@bcoe/v8-coverage': 0.2.3
@@ -7563,7 +7551,7 @@ snapshots:
       std-env: 3.8.0
       test-exclude: 7.0.1
       tinyrainbow: 1.2.0
-      vitest: 2.1.5(@types/node@22.9.0)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5))
+      vitest: 2.1.5(@types/node@22.9.1)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5))
     transitivePeerDependencies:
       - supports-color
 
@@ -7574,13 +7562,13 @@ snapshots:
       chai: 5.1.2
       tinyrainbow: 1.2.0
 
-  '@vitest/mocker@2.1.5(vite@5.4.11(@types/node@22.9.0))':
+  '@vitest/mocker@2.1.5(vite@5.4.11(@types/node@22.9.1))':
     dependencies:
       '@vitest/spy': 2.1.5
       estree-walker: 3.0.3
       magic-string: 0.30.13
     optionalDependencies:
-      vite: 5.4.11(@types/node@22.9.0)
+      vite: 5.4.11(@types/node@22.9.1)
 
   '@vitest/pretty-format@2.1.5':
     dependencies:
@@ -8152,7 +8140,7 @@ snapshots:
       node-releases: 2.0.18
       update-browserslist-db: 1.1.1(browserslist@4.24.2)
 
-  bson@6.9.0: {}
+  bson@6.10.0: {}
 
   buffer-equal@0.0.1: {}
 
@@ -8172,7 +8160,7 @@ snapshots:
 
   bufferutil@4.0.8:
     dependencies:
-      node-gyp-build: 4.8.3
+      node-gyp-build: 4.8.4
     optional: true
 
   builtin-status-codes@3.0.0: {}
@@ -8547,9 +8535,9 @@ snapshots:
 
   core-util-is@1.0.3: {}
 
-  cosmiconfig-typescript-loader@5.1.0(@types/node@22.9.0)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3):
+  cosmiconfig-typescript-loader@5.1.0(@types/node@22.9.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3):
     dependencies:
-      '@types/node': 22.9.0
+      '@types/node': 22.9.1
       cosmiconfig: 9.0.0(typescript@5.6.3)
       jiti: 1.21.6
       typescript: 5.6.3
@@ -8622,7 +8610,7 @@ snapshots:
     dependencies:
       '@cspell/cspell-types': 8.16.0
       comment-json: 4.2.5
-      yaml: 2.6.0
+      yaml: 2.6.1
 
   cspell-dictionary@8.16.0:
     dependencies:
@@ -9328,15 +9316,12 @@ snapshots:
       minimatch: 9.0.5
       semver: 7.6.3
 
-  eslint-plugin-perfectionist@3.9.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vue-eslint-parser@9.4.3(eslint@9.15.0(jiti@1.21.6))):
+  eslint-plugin-perfectionist@4.0.3(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3):
     dependencies:
       '@typescript-eslint/types': 8.15.0
       '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
       eslint: 9.15.0(jiti@1.21.6)
-      minimatch: 9.0.5
-      natural-compare-lite: 1.4.0
-    optionalDependencies:
-      vue-eslint-parser: 9.4.3(eslint@9.15.0(jiti@1.21.6))
+      natural-orderby: 5.0.0
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -10692,7 +10677,7 @@ snapshots:
   mariadb@3.4.0:
     dependencies:
       '@types/geojson': 7946.0.14
-      '@types/node': 22.9.0
+      '@types/node': 22.9.1
       denque: 2.1.0
       iconv-lite: 0.6.3
       lru-cache: 10.4.3
@@ -10903,7 +10888,7 @@ snapshots:
   mongodb@6.10.0(socks@2.8.3):
     dependencies:
       '@mongodb-js/saslprep': 1.1.9
-      bson: 6.9.0
+      bson: 6.10.0
       mongodb-connection-string-url: 3.0.1
     optionalDependencies:
       socks: 2.8.3
@@ -10954,10 +10939,10 @@ snapshots:
 
   napi-build-utils@1.0.2: {}
 
-  natural-compare-lite@1.4.0: {}
-
   natural-compare@1.4.0: {}
 
+  natural-orderby@5.0.0: {}
+
   ndarray-blas-level1@1.1.3: {}
 
   ndarray-cholesky-factorization@1.0.2:
@@ -11005,7 +10990,7 @@ snapshots:
   neostandard@0.11.8(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3):
     dependencies:
       '@humanwhocodes/gitignore-to-minimatch': 1.0.2
-      '@stylistic/eslint-plugin': 2.10.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
+      '@stylistic/eslint-plugin': 2.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
       eslint: 9.15.0(jiti@1.21.6)
       eslint-plugin-n: 17.13.2(eslint@9.15.0(jiti@1.21.6))
       eslint-plugin-promise: 7.1.0(eslint@9.15.0(jiti@1.21.6))
@@ -11036,7 +11021,7 @@ snapshots:
     optionalDependencies:
       encoding: 0.1.13
 
-  node-gyp-build@4.8.3:
+  node-gyp-build@4.8.4:
     optional: true
 
   node-gyp@8.4.1:
@@ -12326,14 +12311,14 @@ snapshots:
       '@ts-morph/common': 0.25.0
       code-block-writer: 13.0.3
 
-  ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3):
+  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 22.9.0
+      '@types/node': 22.9.1
       acorn: 8.14.0
       acorn-walk: 8.3.4
       arg: 4.1.3
@@ -12532,7 +12517,7 @@ snapshots:
 
   utf-8-validate@6.0.5:
     dependencies:
-      node-gyp-build: 4.8.3
+      node-gyp-build: 4.8.4
     optional: true
 
   util-deprecate@1.0.2: {}
@@ -12573,13 +12558,13 @@ snapshots:
       core-util-is: 1.0.2
       extsprintf: 1.3.0
 
-  vite-node@2.1.5(@types/node@22.9.0):
+  vite-node@2.1.5(@types/node@22.9.1):
     dependencies:
       cac: 6.7.14
       debug: 4.3.7
       es-module-lexer: 1.5.4
       pathe: 1.1.2
-      vite: 5.4.11(@types/node@22.9.0)
+      vite: 5.4.11(@types/node@22.9.1)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -12591,19 +12576,19 @@ snapshots:
       - supports-color
       - terser
 
-  vite@5.4.11(@types/node@22.9.0):
+  vite@5.4.11(@types/node@22.9.1):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.49
       rollup: 4.27.3
     optionalDependencies:
-      '@types/node': 22.9.0
+      '@types/node': 22.9.1
       fsevents: 2.3.3
 
-  vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5)):
+  vitest@2.1.5(@types/node@22.9.1)(jsdom@25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5)):
     dependencies:
       '@vitest/expect': 2.1.5
-      '@vitest/mocker': 2.1.5(vite@5.4.11(@types/node@22.9.0))
+      '@vitest/mocker': 2.1.5(vite@5.4.11(@types/node@22.9.1))
       '@vitest/pretty-format': 2.1.5
       '@vitest/runner': 2.1.5
       '@vitest/snapshot': 2.1.5
@@ -12619,11 +12604,11 @@ snapshots:
       tinyexec: 0.3.1
       tinypool: 1.0.2
       tinyrainbow: 1.2.0
-      vite: 5.4.11(@types/node@22.9.0)
-      vite-node: 2.1.5(@types/node@22.9.0)
+      vite: 5.4.11(@types/node@22.9.1)
+      vite-node: 2.1.5(@types/node@22.9.1)
       why-is-node-running: 2.3.0
     optionalDependencies:
-      '@types/node': 22.9.0
+      '@types/node': 22.9.1
       jsdom: 25.0.1(bufferutil@4.0.8)(utf-8-validate@6.0.5)
     transitivePeerDependencies:
       - less
@@ -12865,7 +12850,7 @@ snapshots:
 
   yaml@2.5.1: {}
 
-  yaml@2.6.0: {}
+  yaml@2.6.1: {}
 
   yargs-parser@15.0.3:
     dependencies:
index 495edb9e6a978c1c316a06362d359667b34f96c2..ed5459f15b084315c60871a14915ca3dc6fa31b4 100644 (file)
@@ -27,10 +27,10 @@ switch (runtime) {
   case JSRuntime.node:
     checkNodeVersion()
     break
+  case JSRuntime.browser:
   case JSRuntime.bun:
   case JSRuntime.deno:
   case JSRuntime.workerd:
-  case JSRuntime.browser:
   default:
     console.warn(chalk.yellow(`Unsupported '${runtime}' runtime detected`))
     break
index ece8e243b89c4312f4788b516e39549ca233bc3d..61be7d7650688a6869420460f9e56e1302a9b204 100644 (file)
@@ -38,20 +38,12 @@ export class AutomaticTransactionGenerator {
     AutomaticTransactionGenerator
   >()
 
-  private readonly chargingStation: ChargingStation
-  private readonly logPrefix = (connectorId?: number): string => {
-    return logPrefix(
-      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-      ` ${this.chargingStation.stationInfo?.chargingStationId} | ATG${
-        connectorId != null ? ` on connector #${connectorId.toString()}` : ''
-      }:`
-    )
-  }
+  public readonly connectorsStatus: Map<number, Status>
+  public started: boolean
 
+  private readonly chargingStation: ChargingStation
   private starting: boolean
   private stopping: boolean
-  public readonly connectorsStatus: Map<number, Status>
-  public started: boolean
 
   private constructor (chargingStation: ChargingStation) {
     this.started = false
@@ -82,6 +74,67 @@ export class AutomaticTransactionGenerator {
     return AutomaticTransactionGenerator.instances.get(chargingStation.stationInfo!.hashId)
   }
 
+  public start (stopAbsoluteDuration?: boolean): void {
+    if (!checkChargingStationState(this.chargingStation, this.logPrefix())) {
+      return
+    }
+    if (this.started) {
+      logger.warn(`${this.logPrefix()} is already started`)
+      return
+    }
+    if (this.starting) {
+      logger.warn(`${this.logPrefix()} is already starting`)
+      return
+    }
+    this.starting = true
+    this.startConnectors(stopAbsoluteDuration)
+    this.started = true
+    this.starting = false
+  }
+
+  public startConnector (connectorId: number, stopAbsoluteDuration?: boolean): void {
+    if (!checkChargingStationState(this.chargingStation, this.logPrefix(connectorId))) {
+      return
+    }
+    if (!this.connectorsStatus.has(connectorId)) {
+      logger.error(`${this.logPrefix(connectorId)} starting on non existing connector`)
+      throw new BaseError(`Connector ${connectorId.toString()} does not exist`)
+    }
+    if (this.connectorsStatus.get(connectorId)?.start === false) {
+      this.internalStartConnector(connectorId, stopAbsoluteDuration).catch(Constants.EMPTY_FUNCTION)
+    } else if (this.connectorsStatus.get(connectorId)?.start === true) {
+      logger.warn(`${this.logPrefix(connectorId)} is already started on connector`)
+    }
+  }
+
+  public stop (): void {
+    if (!this.started) {
+      logger.warn(`${this.logPrefix()} is already stopped`)
+      return
+    }
+    if (this.stopping) {
+      logger.warn(`${this.logPrefix()} is already stopping`)
+      return
+    }
+    this.stopping = true
+    this.stopConnectors()
+    this.started = false
+    this.stopping = false
+  }
+
+  public stopConnector (connectorId: number): void {
+    if (!this.connectorsStatus.has(connectorId)) {
+      logger.error(`${this.logPrefix(connectorId)} stopping on non existing connector`)
+      throw new BaseError(`Connector ${connectorId.toString()} does not exist`)
+    }
+    if (this.connectorsStatus.get(connectorId)?.start === true) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.connectorsStatus.get(connectorId)!.start = false
+    } else if (this.connectorsStatus.get(connectorId)?.start === false) {
+      logger.warn(`${this.logPrefix(connectorId)} is already stopped on connector`)
+    }
+  }
+
   private canStartConnector (connectorId: number): boolean {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     if (new Date() > this.connectorsStatus.get(connectorId)!.stopDate!) {
@@ -317,6 +370,15 @@ export class AutomaticTransactionGenerator {
     this.chargingStation.emit(ChargingStationEvents.updated)
   }
 
+  private readonly logPrefix = (connectorId?: number): string => {
+    return logPrefix(
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      ` ${this.chargingStation.stationInfo?.chargingStationId} | ATG${
+        connectorId != null ? ` on connector #${connectorId.toString()}` : ''
+      }:`
+    )
+  }
+
   private setStartConnectorStatus (
     connectorId: number,
     stopAbsoluteDuration = this.chargingStation.getAutomaticTransactionGeneratorConfiguration()
@@ -535,65 +597,4 @@ export class AutomaticTransactionGenerator {
       await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
     }
   }
-
-  public start (stopAbsoluteDuration?: boolean): void {
-    if (!checkChargingStationState(this.chargingStation, this.logPrefix())) {
-      return
-    }
-    if (this.started) {
-      logger.warn(`${this.logPrefix()} is already started`)
-      return
-    }
-    if (this.starting) {
-      logger.warn(`${this.logPrefix()} is already starting`)
-      return
-    }
-    this.starting = true
-    this.startConnectors(stopAbsoluteDuration)
-    this.started = true
-    this.starting = false
-  }
-
-  public startConnector (connectorId: number, stopAbsoluteDuration?: boolean): void {
-    if (!checkChargingStationState(this.chargingStation, this.logPrefix(connectorId))) {
-      return
-    }
-    if (!this.connectorsStatus.has(connectorId)) {
-      logger.error(`${this.logPrefix(connectorId)} starting on non existing connector`)
-      throw new BaseError(`Connector ${connectorId.toString()} does not exist`)
-    }
-    if (this.connectorsStatus.get(connectorId)?.start === false) {
-      this.internalStartConnector(connectorId, stopAbsoluteDuration).catch(Constants.EMPTY_FUNCTION)
-    } else if (this.connectorsStatus.get(connectorId)?.start === true) {
-      logger.warn(`${this.logPrefix(connectorId)} is already started on connector`)
-    }
-  }
-
-  public stop (): void {
-    if (!this.started) {
-      logger.warn(`${this.logPrefix()} is already stopped`)
-      return
-    }
-    if (this.stopping) {
-      logger.warn(`${this.logPrefix()} is already stopping`)
-      return
-    }
-    this.stopping = true
-    this.stopConnectors()
-    this.started = false
-    this.stopping = false
-  }
-
-  public stopConnector (connectorId: number): void {
-    if (!this.connectorsStatus.has(connectorId)) {
-      logger.error(`${this.logPrefix(connectorId)} stopping on non existing connector`)
-      throw new BaseError(`Connector ${connectorId.toString()} does not exist`)
-    }
-    if (this.connectorsStatus.get(connectorId)?.start === true) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.connectorsStatus.get(connectorId)!.start = false
-    } else if (this.connectorsStatus.get(connectorId)?.start === false) {
-      logger.warn(`${this.logPrefix(connectorId)} is already stopped on connector`)
-    }
-  }
 }
index e791ed01828d82f83e7d1dafff31c61213ee2be6..1a016b6931399686ca6d6bca9f69fb3ce91cb331 100644 (file)
@@ -50,21 +50,34 @@ import { UIServerFactory } from './ui-server/UIServerFactory.js'
 
 const moduleName = 'Bootstrap'
 
+/* eslint-disable perfectionist/sort-enums */
 enum exitCodes {
   succeeded = 0,
-  // eslint-disable-next-line perfectionist/sort-enums
   missingChargingStationsConfiguration = 1,
-  // eslint-disable-next-line perfectionist/sort-enums
   duplicateChargingStationTemplateUrls = 2,
   noChargingStationTemplates = 3,
-  // eslint-disable-next-line perfectionist/sort-enums
   gracefulShutdownError = 4,
 }
+/* eslint-enable perfectionist/sort-enums */
 
 export class Bootstrap extends EventEmitter {
   private static instance: Bootstrap | null = null
-  private readonly logPrefix = (): string => {
-    return logPrefix(' Bootstrap |')
+  public get numberOfChargingStationTemplates (): number {
+    return this.templateStatistics.size
+  }
+
+  public get numberOfConfiguredChargingStations (): number {
+    return [...this.templateStatistics.values()].reduce(
+      (accumulator, value) => accumulator + value.configured,
+      0
+    )
+  }
+
+  public get numberOfProvisionedChargingStations (): number {
+    return [...this.templateStatistics.values()].reduce(
+      (accumulator, value) => accumulator + value.provisioned,
+      0
+    )
   }
 
   private started: boolean
@@ -75,76 +88,21 @@ export class Bootstrap extends EventEmitter {
   private readonly uiServer: AbstractUIServer
   private uiServerStarted: boolean
   private readonly version: string = version
-
-  private readonly workerEventAdded = (data: ChargingStationData): void => {
-    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
-    logger.info(
-      `${this.logPrefix()} ${moduleName}.workerEventAdded: Charging station ${
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        data.stationInfo.chargingStationId
-      } (hashId: ${data.stationInfo.hashId}) added (${this.numberOfAddedChargingStations.toString()} added from ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s))`
-    )
-  }
-
-  private readonly workerEventDeleted = (data: ChargingStationData): void => {
-    this.uiServer.chargingStations.delete(data.stationInfo.hashId)
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const templateStatistics = this.templateStatistics.get(data.stationInfo.templateName)!
-    --templateStatistics.added
-    templateStatistics.indexes.delete(data.stationInfo.templateIndex)
-    logger.info(
-      `${this.logPrefix()} ${moduleName}.workerEventDeleted: Charging station ${
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        data.stationInfo.chargingStationId
-      } (hashId: ${data.stationInfo.hashId}) deleted (${this.numberOfAddedChargingStations.toString()} added from ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s))`
-    )
-  }
-
-  private readonly workerEventPerformanceStatistics = (data: Statistics): void => {
-    // eslint-disable-next-line @typescript-eslint/unbound-method
-    if (isAsyncFunction(this.storage?.storePerformanceStatistics)) {
-      ;(
-        this.storage.storePerformanceStatistics as (
-          performanceStatistics: Statistics
-        ) => Promise<void>
-      )(data).catch(Constants.EMPTY_FUNCTION)
-    } else {
-      ;(this.storage?.storePerformanceStatistics as (performanceStatistics: Statistics) => void)(
-        data
-      )
-    }
-  }
-
-  private readonly workerEventStarted = (data: ChargingStationData): void => {
-    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    ++this.templateStatistics.get(data.stationInfo.templateName)!.started
-    logger.info(
-      `${this.logPrefix()} ${moduleName}.workerEventStarted: Charging station ${
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        data.stationInfo.chargingStationId
-      } (hashId: ${data.stationInfo.hashId}) started (${this.numberOfStartedChargingStations.toString()} started from ${this.numberOfAddedChargingStations.toString()} added charging station(s))`
+  private workerImplementation?: WorkerAbstract<ChargingStationWorkerData, ChargingStationInfo>
+  private get numberOfAddedChargingStations (): number {
+    return [...this.templateStatistics.values()].reduce(
+      (accumulator, value) => accumulator + value.added,
+      0
     )
   }
 
-  private readonly workerEventStopped = (data: ChargingStationData): void => {
-    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    --this.templateStatistics.get(data.stationInfo.templateName)!.started
-    logger.info(
-      `${this.logPrefix()} ${moduleName}.workerEventStopped: Charging station ${
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        data.stationInfo.chargingStationId
-      } (hashId: ${data.stationInfo.hashId}) stopped (${this.numberOfStartedChargingStations.toString()} started from ${this.numberOfAddedChargingStations.toString()} added charging station(s))`
+  private get numberOfStartedChargingStations (): number {
+    return [...this.templateStatistics.values()].reduce(
+      (accumulator, value) => accumulator + value.started,
+      0
     )
   }
 
-  private readonly workerEventUpdated = (data: ChargingStationData): void => {
-    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
-  }
-
-  private workerImplementation?: WorkerAbstract<ChargingStationWorkerData, ChargingStationInfo>
-
   private constructor () {
     super()
     for (const signal of ['SIGINT', 'SIGQUIT', 'SIGTERM']) {
@@ -176,7 +134,191 @@ export class Bootstrap extends EventEmitter {
     if (Bootstrap.instance === null) {
       Bootstrap.instance = new Bootstrap()
     }
-    return Bootstrap.instance
+    return Bootstrap.instance
+  }
+
+  public async addChargingStation (
+    index: number,
+    templateFile: string,
+    options?: ChargingStationOptions
+  ): Promise<ChargingStationInfo | undefined> {
+    if (!this.started && !this.starting) {
+      throw new BaseError(
+        'Cannot add charging station while the charging stations simulator is not started'
+      )
+    }
+    const stationInfo = await this.workerImplementation?.addElement({
+      index,
+      options,
+      templateFile: join(
+        dirname(fileURLToPath(import.meta.url)),
+        'assets',
+        'station-templates',
+        templateFile
+      ),
+    })
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const templateStatistics = this.templateStatistics.get(buildTemplateName(templateFile))!
+    ++templateStatistics.added
+    templateStatistics.indexes.add(index)
+    return stationInfo
+  }
+
+  public getLastIndex (templateName: string): number {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const indexes = [...this.templateStatistics.get(templateName)!.indexes]
+      .concat(0)
+      .sort((a, b) => a - b)
+    for (let i = 0; i < indexes.length - 1; i++) {
+      if (indexes[i + 1] - indexes[i] !== 1) {
+        return indexes[i]
+      }
+    }
+    return indexes[indexes.length - 1]
+  }
+
+  public getPerformanceStatistics (): IterableIterator<Statistics> | undefined {
+    return this.storage?.getPerformanceStatistics()
+  }
+
+  public getState (): SimulatorState {
+    return {
+      configuration: Configuration.getConfigurationData(),
+      started: this.started,
+      templateStatistics: this.templateStatistics,
+      version: this.version,
+    }
+  }
+
+  public async start (): Promise<void> {
+    if (!this.started) {
+      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)
+        this.on(
+          ChargingStationWorkerMessageEvents.performanceStatistics,
+          this.workerEventPerformanceStatistics
+        )
+        // eslint-disable-next-line @typescript-eslint/unbound-method
+        if (isAsyncFunction(this.workerImplementation?.start)) {
+          await this.workerImplementation.start()
+        } else {
+          ;(this.workerImplementation?.start as () => void)()
+        }
+        const performanceStorageConfiguration =
+          Configuration.getConfigurationSection<StorageConfiguration>(
+            ConfigurationSection.performanceStorage
+          )
+        if (performanceStorageConfiguration.enabled === true) {
+          this.storage = StorageFactory.getStorage(
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            performanceStorageConfiguration.type!,
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            performanceStorageConfiguration.uri!,
+            this.logPrefix()
+          )
+          await this.storage?.open()
+        }
+        if (
+          !this.uiServerStarted &&
+          Configuration.getConfigurationSection<UIServerConfiguration>(
+            ConfigurationSection.uiServer
+          ).enabled === true
+        ) {
+          this.uiServer.start()
+          this.uiServerStarted = true
+        }
+        // Start ChargingStation object instance in worker thread
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        for (const stationTemplateUrl of Configuration.getStationTemplateUrls()!) {
+          try {
+            const nbStations = stationTemplateUrl.numberOfStations
+            for (let index = 1; index <= nbStations; index++) {
+              await this.addChargingStation(index, stationTemplateUrl.file)
+            }
+          } catch (error) {
+            console.error(
+              chalk.red(
+                `Error at starting charging station with template file ${stationTemplateUrl.file}: `
+              ),
+              error
+            )
+          }
+        }
+        const workerConfiguration = Configuration.getConfigurationSection<WorkerConfiguration>(
+          ConfigurationSection.worker
+        )
+        console.info(
+          chalk.green(
+            `Charging stations simulator ${this.version} started with ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s) from ${this.numberOfChargingStationTemplates.toString()} charging station template(s) and ${
+              Configuration.workerDynamicPoolInUse()
+                ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                  `${workerConfiguration.poolMinSize?.toString()}/`
+                : ''
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            }${this.workerImplementation?.size.toString()}${
+              Configuration.workerPoolInUse()
+                ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                  `/${workerConfiguration.poolMaxSize?.toString()}`
+                : ''
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            } worker(s) concurrently running in '${workerConfiguration.processType}' mode${
+              this.workerImplementation?.maxElementsPerWorker != null
+                ? ` (${this.workerImplementation.maxElementsPerWorker.toString()} charging station(s) per worker)`
+                : ''
+            }`
+          )
+        )
+        Configuration.workerDynamicPoolInUse() &&
+          console.warn(
+            chalk.yellow(
+              'Charging stations simulator is using dynamic pool mode. This is an experimental feature with known issues.\nPlease consider using fixed pool or worker set mode instead'
+            )
+          )
+        console.info(chalk.green('Worker set/pool information:'), this.workerImplementation?.info)
+        this.started = true
+        this.starting = false
+      } else {
+        console.error(chalk.red('Cannot start an already starting charging stations simulator'))
+      }
+    } else {
+      console.error(chalk.red('Cannot start an already started charging stations simulator'))
+    }
+  }
+
+  public async stop (): Promise<void> {
+    if (this.started) {
+      if (!this.stopping) {
+        this.stopping = true
+        await this.uiServer.sendInternalRequest(
+          this.uiServer.buildProtocolRequest(
+            generateUUID(),
+            ProcedureName.STOP_CHARGING_STATION,
+            Constants.EMPTY_FROZEN_OBJECT
+          )
+        )
+        try {
+          await this.waitChargingStationsStopped()
+        } catch (error) {
+          console.error(chalk.red('Error while waiting for charging stations to stop: '), error)
+        }
+        await this.workerImplementation?.stop()
+        this.removeAllListeners()
+        this.uiServer.clearCaches()
+        await this.storage?.close()
+        delete this.storage
+        this.started = false
+        this.stopping = false
+      } else {
+        console.error(chalk.red('Cannot stop an already stopping charging stations simulator'))
+      }
+    } else {
+      console.error(chalk.red('Cannot stop an already stopped charging stations simulator'))
+    }
   }
 
   private gracefulShutdown (): void {
@@ -299,6 +441,10 @@ export class Bootstrap extends EventEmitter {
     )
   }
 
+  private readonly logPrefix = (): string => {
+    return logPrefix(' Bootstrap |')
+  }
+
   private messageHandler (
     msg: ChargingStationWorkerMessage<ChargingStationWorkerMessageData>
   ): void {
@@ -395,219 +541,70 @@ export class Bootstrap extends EventEmitter {
     })
   }
 
-  public async addChargingStation (
-    index: number,
-    templateFile: string,
-    options?: ChargingStationOptions
-  ): Promise<ChargingStationInfo | undefined> {
-    if (!this.started && !this.starting) {
-      throw new BaseError(
-        'Cannot add charging station while the charging stations simulator is not started'
-      )
-    }
-    const stationInfo = await this.workerImplementation?.addElement({
-      index,
-      options,
-      templateFile: join(
-        dirname(fileURLToPath(import.meta.url)),
-        'assets',
-        'station-templates',
-        templateFile
-      ),
-    })
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const templateStatistics = this.templateStatistics.get(buildTemplateName(templateFile))!
-    ++templateStatistics.added
-    templateStatistics.indexes.add(index)
-    return stationInfo
+  private readonly workerEventAdded = (data: ChargingStationData): void => {
+    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
+    logger.info(
+      `${this.logPrefix()} ${moduleName}.workerEventAdded: Charging station ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        data.stationInfo.chargingStationId
+      } (hashId: ${data.stationInfo.hashId}) added (${this.numberOfAddedChargingStations.toString()} added from ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s))`
+    )
   }
 
-  public getLastIndex (templateName: string): number {
+  private readonly workerEventDeleted = (data: ChargingStationData): void => {
+    this.uiServer.chargingStations.delete(data.stationInfo.hashId)
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const indexes = [...this.templateStatistics.get(templateName)!.indexes]
-      .concat(0)
-      .sort((a, b) => a - b)
-    for (let i = 0; i < indexes.length - 1; i++) {
-      if (indexes[i + 1] - indexes[i] !== 1) {
-        return indexes[i]
-      }
-    }
-    return indexes[indexes.length - 1]
-  }
-
-  public getPerformanceStatistics (): IterableIterator<Statistics> | undefined {
-    return this.storage?.getPerformanceStatistics()
-  }
-
-  public getState (): SimulatorState {
-    return {
-      configuration: Configuration.getConfigurationData(),
-      started: this.started,
-      templateStatistics: this.templateStatistics,
-      version: this.version,
-    }
-  }
-
-  public async start (): Promise<void> {
-    if (!this.started) {
-      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)
-        this.on(
-          ChargingStationWorkerMessageEvents.performanceStatistics,
-          this.workerEventPerformanceStatistics
-        )
-        // eslint-disable-next-line @typescript-eslint/unbound-method
-        if (isAsyncFunction(this.workerImplementation?.start)) {
-          await this.workerImplementation.start()
-        } else {
-          ;(this.workerImplementation?.start as () => void)()
-        }
-        const performanceStorageConfiguration =
-          Configuration.getConfigurationSection<StorageConfiguration>(
-            ConfigurationSection.performanceStorage
-          )
-        if (performanceStorageConfiguration.enabled === true) {
-          this.storage = StorageFactory.getStorage(
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            performanceStorageConfiguration.type!,
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            performanceStorageConfiguration.uri!,
-            this.logPrefix()
-          )
-          await this.storage?.open()
-        }
-        if (
-          !this.uiServerStarted &&
-          Configuration.getConfigurationSection<UIServerConfiguration>(
-            ConfigurationSection.uiServer
-          ).enabled === true
-        ) {
-          this.uiServer.start()
-          this.uiServerStarted = true
-        }
-        // Start ChargingStation object instance in worker thread
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        for (const stationTemplateUrl of Configuration.getStationTemplateUrls()!) {
-          try {
-            const nbStations = stationTemplateUrl.numberOfStations
-            for (let index = 1; index <= nbStations; index++) {
-              await this.addChargingStation(index, stationTemplateUrl.file)
-            }
-          } catch (error) {
-            console.error(
-              chalk.red(
-                `Error at starting charging station with template file ${stationTemplateUrl.file}: `
-              ),
-              error
-            )
-          }
-        }
-        const workerConfiguration = Configuration.getConfigurationSection<WorkerConfiguration>(
-          ConfigurationSection.worker
-        )
-        console.info(
-          chalk.green(
-            `Charging stations simulator ${this.version} started with ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s) from ${this.numberOfChargingStationTemplates.toString()} charging station template(s) and ${
-              Configuration.workerDynamicPoolInUse()
-                ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-                  `${workerConfiguration.poolMinSize?.toString()}/`
-                : ''
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            }${this.workerImplementation?.size.toString()}${
-              Configuration.workerPoolInUse()
-                ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-                  `/${workerConfiguration.poolMaxSize?.toString()}`
-                : ''
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            } worker(s) concurrently running in '${workerConfiguration.processType}' mode${
-              this.workerImplementation?.maxElementsPerWorker != null
-                ? ` (${this.workerImplementation.maxElementsPerWorker.toString()} charging station(s) per worker)`
-                : ''
-            }`
-          )
-        )
-        Configuration.workerDynamicPoolInUse() &&
-          console.warn(
-            chalk.yellow(
-              'Charging stations simulator is using dynamic pool mode. This is an experimental feature with known issues.\nPlease consider using fixed pool or worker set mode instead'
-            )
-          )
-        console.info(chalk.green('Worker set/pool information:'), this.workerImplementation?.info)
-        this.started = true
-        this.starting = false
-      } else {
-        console.error(chalk.red('Cannot start an already starting charging stations simulator'))
-      }
-    } else {
-      console.error(chalk.red('Cannot start an already started charging stations simulator'))
-    }
+    const templateStatistics = this.templateStatistics.get(data.stationInfo.templateName)!
+    --templateStatistics.added
+    templateStatistics.indexes.delete(data.stationInfo.templateIndex)
+    logger.info(
+      `${this.logPrefix()} ${moduleName}.workerEventDeleted: Charging station ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        data.stationInfo.chargingStationId
+      } (hashId: ${data.stationInfo.hashId}) deleted (${this.numberOfAddedChargingStations.toString()} added from ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s))`
+    )
   }
 
-  public async stop (): Promise<void> {
-    if (this.started) {
-      if (!this.stopping) {
-        this.stopping = true
-        await this.uiServer.sendInternalRequest(
-          this.uiServer.buildProtocolRequest(
-            generateUUID(),
-            ProcedureName.STOP_CHARGING_STATION,
-            Constants.EMPTY_FROZEN_OBJECT
-          )
-        )
-        try {
-          await this.waitChargingStationsStopped()
-        } catch (error) {
-          console.error(chalk.red('Error while waiting for charging stations to stop: '), error)
-        }
-        await this.workerImplementation?.stop()
-        this.removeAllListeners()
-        this.uiServer.clearCaches()
-        await this.storage?.close()
-        delete this.storage
-        this.started = false
-        this.stopping = false
-      } else {
-        console.error(chalk.red('Cannot stop an already stopping charging stations simulator'))
-      }
+  private readonly workerEventPerformanceStatistics = (data: Statistics): void => {
+    // eslint-disable-next-line @typescript-eslint/unbound-method
+    if (isAsyncFunction(this.storage?.storePerformanceStatistics)) {
+      ;(
+        this.storage.storePerformanceStatistics as (
+          performanceStatistics: Statistics
+        ) => Promise<void>
+      )(data).catch(Constants.EMPTY_FUNCTION)
     } else {
-      console.error(chalk.red('Cannot stop an already stopped charging stations simulator'))
+      ;(this.storage?.storePerformanceStatistics as (performanceStatistics: Statistics) => void)(
+        data
+      )
     }
   }
 
-  private get numberOfAddedChargingStations (): number {
-    return [...this.templateStatistics.values()].reduce(
-      (accumulator, value) => accumulator + value.added,
-      0
-    )
-  }
-
-  public get numberOfChargingStationTemplates (): number {
-    return this.templateStatistics.size
-  }
-
-  public get numberOfConfiguredChargingStations (): number {
-    return [...this.templateStatistics.values()].reduce(
-      (accumulator, value) => accumulator + value.configured,
-      0
+  private readonly workerEventStarted = (data: ChargingStationData): void => {
+    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    ++this.templateStatistics.get(data.stationInfo.templateName)!.started
+    logger.info(
+      `${this.logPrefix()} ${moduleName}.workerEventStarted: Charging station ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        data.stationInfo.chargingStationId
+      } (hashId: ${data.stationInfo.hashId}) started (${this.numberOfStartedChargingStations.toString()} started from ${this.numberOfAddedChargingStations.toString()} added charging station(s))`
     )
   }
 
-  public get numberOfProvisionedChargingStations (): number {
-    return [...this.templateStatistics.values()].reduce(
-      (accumulator, value) => accumulator + value.provisioned,
-      0
+  private readonly workerEventStopped = (data: ChargingStationData): void => {
+    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    --this.templateStatistics.get(data.stationInfo.templateName)!.started
+    logger.info(
+      `${this.logPrefix()} ${moduleName}.workerEventStopped: Charging station ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        data.stationInfo.chargingStationId
+      } (hashId: ${data.stationInfo.hashId}) stopped (${this.numberOfStartedChargingStations.toString()} started from ${this.numberOfAddedChargingStations.toString()} added charging station(s))`
     )
   }
 
-  private get numberOfStartedChargingStations (): number {
-    return [...this.templateStatistics.values()].reduce(
-      (accumulator, value) => accumulator + value.started,
-      0
-    )
+  private readonly workerEventUpdated = (data: ChargingStationData): void => {
+    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
   }
 }
index 5757e47d6663baf62b98c4aebc29725d820dc5f0..45a2f94edc15e053d8156af70581cb1ac7fe60ba 100644 (file)
@@ -156,22 +156,6 @@ import {
 import { SharedLRUCache } from './SharedLRUCache.js'
 
 export class ChargingStation extends EventEmitter {
-  private automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration
-  private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel
-  private configurationFile!: string
-  private configurationFileHash!: string
-  private configuredSupervisionUrl!: URL
-  private connectorsConfigurationHash!: string
-  private evsesConfigurationHash!: string
-  private flushMessageBufferSetInterval?: NodeJS.Timeout
-  private readonly messageBuffer: Set<string>
-  private ocppIncomingRequestService!: OCPPIncomingRequestService
-  private readonly sharedLRUCache: SharedLRUCache
-  private stopping: boolean
-  private templateFileHash!: string
-  private templateFileWatcher?: FSWatcher
-  private wsConnectionRetryCount: number
-  private wsPingSetInterval?: NodeJS.Timeout
   public automaticTransactionGenerator?: AutomaticTransactionGenerator
   public bootNotificationRequest?: BootNotificationRequest
   public bootNotificationResponse?: BootNotificationResponse
@@ -180,25 +164,6 @@ export class ChargingStation extends EventEmitter {
   public heartbeatSetInterval?: NodeJS.Timeout
   public idTagsCache: IdTagsCache
   public readonly index: number
-  public logPrefix = (): string => {
-    if (
-      this instanceof ChargingStation &&
-      this.stationInfo != null &&
-      isNotEmptyString(this.stationInfo.chargingStationId)
-    ) {
-      return logPrefix(` ${this.stationInfo.chargingStationId} |`)
-    }
-    let stationTemplate: ChargingStationTemplate | undefined
-    try {
-      stationTemplate = JSON.parse(
-        readFileSync(this.templateFile, 'utf8')
-      ) as ChargingStationTemplate
-    } catch {
-      // Ignore
-    }
-    return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`)
-  }
-
   public ocppConfiguration?: ChargingStationOcppConfiguration
   public ocppRequestService!: OCPPRequestService
   public performanceStatistics?: PerformanceStatistics
@@ -209,6 +174,43 @@ export class ChargingStation extends EventEmitter {
   public stationInfo?: ChargingStationInfo
   public readonly templateFile: string
   public wsConnection: null | WebSocket
+  public get hasEvses (): boolean {
+    return this.connectors.size === 0 && this.evses.size > 0
+  }
+
+  public get wsConnectionUrl (): URL {
+    const wsConnectionBaseUrlStr = `${
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
+      isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value)
+        ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value
+        : this.configuredSupervisionUrl.href
+    }`
+    return new URL(
+      `${wsConnectionBaseUrlStr}${
+        !wsConnectionBaseUrlStr.endsWith('/') ? '/' : ''
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }${this.stationInfo?.chargingStationId}`
+    )
+  }
+
+  private automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration
+  private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel
+  private configurationFile!: string
+  private configurationFileHash!: string
+  private configuredSupervisionUrl!: URL
+  private connectorsConfigurationHash!: string
+  private evsesConfigurationHash!: string
+  private flushMessageBufferSetInterval?: NodeJS.Timeout
+  private readonly messageBuffer: Set<string>
+  private ocppIncomingRequestService!: OCPPIncomingRequestService
+  private readonly sharedLRUCache: SharedLRUCache
+  private stopping: boolean
+  private templateFileHash!: string
+  private templateFileWatcher?: FSWatcher
+  private wsConnectionRetryCount: number
+  private wsPingSetInterval?: NodeJS.Timeout
 
   constructor (index: number, templateFile: string, options?: ChargingStationOptions) {
     super()
@@ -280,2204 +282,2202 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  private add (): void {
-    this.emit(ChargingStationEvents.added)
+  public async addReservation (reservation: Reservation): Promise<void> {
+    const reservationFound = this.getReservationBy('reservationId', reservation.reservationId)
+    if (reservationFound != null) {
+      await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING)
+    }
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.getConnectorStatus(reservation.connectorId)!.reservation = reservation
+    await sendAndSetConnectorStatus(
+      this,
+      reservation.connectorId,
+      ConnectorStatusEnum.Reserved,
+      undefined,
+      { send: reservation.connectorId !== 0 }
+    )
   }
 
-  private clearIntervalFlushMessageBuffer (): void {
-    if (this.flushMessageBufferSetInterval != null) {
-      clearInterval(this.flushMessageBufferSetInterval)
-      delete this.flushMessageBufferSetInterval
-    }
+  public bufferMessage (message: string): void {
+    this.messageBuffer.add(message)
+    this.setIntervalFlushMessageBuffer()
   }
 
-  private flushMessageBuffer (): void {
-    if (this.messageBuffer.size > 0) {
-      for (const message of this.messageBuffer.values()) {
-        let beginId: string | undefined
-        let commandName: RequestCommand | undefined
-        const [messageType] = JSON.parse(message) as ErrorResponse | OutgoingRequest | Response
-        const isRequest = messageType === MessageType.CALL_MESSAGE
-        if (isRequest) {
-          ;[, , commandName] = JSON.parse(message) as OutgoingRequest
-          beginId = PerformanceStatistics.beginMeasure(commandName)
-        }
-        this.wsConnection?.send(message, (error?: Error) => {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!)
-          if (error == null) {
-            logger.debug(
-              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
-                messageType
-              )} OCPP message sent '${JSON.stringify(message)}'`
-            )
-            this.messageBuffer.delete(message)
-          } else {
-            logger.debug(
-              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
-                messageType
-              )} OCPP message '${JSON.stringify(message)}' send failed:`,
-              error
-            )
-          }
-        })
-      }
+  public closeWSConnection (): void {
+    if (this.isWebSocketConnectionOpened()) {
+      this.wsConnection?.close()
+      this.wsConnection = null
     }
   }
 
-  private getAmperageLimitation (): number | undefined {
-    if (
-      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) != null
-    ) {
-      return (
-        convertToInt(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey)?.value) /
-        getAmperageLimitationUnitDivider(this.stationInfo)
-      )
+  public async delete (deleteConfiguration = true): Promise<void> {
+    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)
+    this.removeAllListeners()
   }
 
-  private getCachedRequest (
-    messageType: MessageType | undefined,
-    messageId: string
-  ): CachedRequest | undefined {
-    const cachedRequest = this.requests.get(messageId)
-    if (Array.isArray(cachedRequest)) {
-      return cachedRequest
-    }
-    throw new OCPPError(
-      ErrorType.PROTOCOL_ERROR,
-      `Cached request for message id '${messageId}' ${getMessageTypeString(
-        messageType
-      )} is not an array`,
-      undefined,
-      cachedRequest
+  public getAuthorizeRemoteTxRequests (): boolean {
+    const authorizeRemoteTxRequests = getConfigurationKey(
+      this,
+      StandardParametersKey.AuthorizeRemoteTxRequests
     )
+    return authorizeRemoteTxRequests != null
+      ? convertToBoolean(authorizeRemoteTxRequests.value)
+      : false
   }
 
-  private getConfigurationFromFile (): ChargingStationConfiguration | undefined {
-    let configuration: ChargingStationConfiguration | undefined
-    if (isNotEmptyString(this.configurationFile) && existsSync(this.configurationFile)) {
-      try {
-        if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
-          configuration = this.sharedLRUCache.getChargingStationConfiguration(
-            this.configurationFileHash
-          )
-        } else {
-          const measureId = `${FileType.ChargingStationConfiguration} read`
-          const beginId = PerformanceStatistics.beginMeasure(measureId)
-          configuration = JSON.parse(
-            readFileSync(this.configurationFile, 'utf8')
-          ) as ChargingStationConfiguration
-          PerformanceStatistics.endMeasure(measureId, beginId)
-          this.sharedLRUCache.setChargingStationConfiguration(configuration)
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.configurationFileHash = configuration.configurationHash!
+  public getAutomaticTransactionGeneratorConfiguration ():
+    | AutomaticTransactionGeneratorConfiguration
+    | undefined {
+    if (this.automaticTransactionGeneratorConfiguration == null) {
+      let automaticTransactionGeneratorConfiguration:
+        | AutomaticTransactionGeneratorConfiguration
+        | undefined
+      const stationTemplate = this.getTemplateFromFile()
+      const stationConfiguration = this.getConfigurationFromFile()
+      if (
+        this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true &&
+        stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+        stationConfiguration?.automaticTransactionGenerator != null
+      ) {
+        automaticTransactionGeneratorConfiguration =
+          stationConfiguration.automaticTransactionGenerator
+      } else {
+        automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator
+      }
+      this.automaticTransactionGeneratorConfiguration = {
+        ...Constants.DEFAULT_ATG_CONFIGURATION,
+        ...automaticTransactionGeneratorConfiguration,
+      }
+    }
+    return this.automaticTransactionGeneratorConfiguration
+  }
+
+  public getAutomaticTransactionGeneratorStatuses (): Status[] | undefined {
+    return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses
+  }
+
+  public getConnectorIdByTransactionId (transactionId: number | undefined): number | undefined {
+    if (transactionId == null) {
+      return undefined
+    } else if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (connectorStatus.transactionId === transactionId) {
+            return connectorId
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
+          return connectorId
         }
-      } catch (error) {
-        handleFileException(
-          this.configurationFile,
-          FileType.ChargingStationConfiguration,
-          error as NodeJS.ErrnoException,
-          this.logPrefix()
-        )
       }
     }
-    return configuration
   }
 
-  private getConfiguredSupervisionUrl (): URL {
-    let configuredSupervisionUrl: string | undefined
-    const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls()
-    if (isNotEmptyArray(supervisionUrls)) {
-      let configuredSupervisionUrlIndex: number
-      switch (Configuration.getSupervisionUrlDistribution()) {
-        case SupervisionUrlDistribution.RANDOM:
-          configuredSupervisionUrlIndex = Math.floor(secureRandom() * supervisionUrls.length)
-          break
-        case SupervisionUrlDistribution.ROUND_ROBIN:
-        case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
-        default:
-          !Object.values(SupervisionUrlDistribution).includes(
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            Configuration.getSupervisionUrlDistribution()!
-          ) &&
-            logger.warn(
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
-              `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' in configuration from values '${SupervisionUrlDistribution.toString()}', defaulting to '${
-                SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
-              }'`
-            )
-          configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length
-          break
-      }
-      configuredSupervisionUrl = supervisionUrls[configuredSupervisionUrlIndex]
-    } else if (typeof supervisionUrls === 'string') {
+  public getConnectorMaximumAvailablePower (connectorId: number): number {
+    let connectorAmperageLimitationLimit: number | undefined
+    const amperageLimitation = this.getAmperageLimitation()
+    if (
+      amperageLimitation != null &&
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      configuredSupervisionUrl = supervisionUrls!
-    }
-    if (isNotEmptyString(configuredSupervisionUrl)) {
-      return new URL(configuredSupervisionUrl)
+      amperageLimitation < this.stationInfo!.maximumAmperage!
+    ) {
+      connectorAmperageLimitationLimit =
+        (this.stationInfo?.currentOutType === CurrentType.AC
+          ? ACElectricUtils.powerTotal(
+            this.getNumberOfPhases(),
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            this.stationInfo.voltageOut!,
+            amperageLimitation *
+                (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors())
+          )
+          : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          DCElectricUtils.power(this.stationInfo!.voltageOut!, amperageLimitation)) /
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.powerDivider!
     }
-    const errorMsg = 'No supervision url(s) configured'
-    logger.error(`${this.logPrefix()} ${errorMsg}`)
-    throw new BaseError(errorMsg)
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const connectorMaximumPower = this.stationInfo!.maximumPower! / this.powerDivider!
+    const chargingStationChargingProfilesLimit =
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      getChargingStationChargingProfilesLimit(this)! / this.powerDivider!
+    const connectorChargingProfilesLimit = getConnectorChargingProfilesLimit(this, connectorId)
+    return min(
+      Number.isNaN(connectorMaximumPower) ? Number.POSITIVE_INFINITY : connectorMaximumPower,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      Number.isNaN(connectorAmperageLimitationLimit!)
+        ? Number.POSITIVE_INFINITY
+        : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        connectorAmperageLimitationLimit!,
+      Number.isNaN(chargingStationChargingProfilesLimit)
+        ? Number.POSITIVE_INFINITY
+        : chargingStationChargingProfilesLimit,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      Number.isNaN(connectorChargingProfilesLimit!)
+        ? Number.POSITIVE_INFINITY
+        : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        connectorChargingProfilesLimit!
+    )
   }
 
-  // 0 for disabling
-  private getConnectionTimeout (): number {
-    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) != null) {
-      return convertToInt(
-        getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)?.value ??
-          Constants.DEFAULT_CONNECTION_TIMEOUT
-      )
+  public getConnectorStatus (connectorId: number): ConnectorStatus | undefined {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        if (evseStatus.connectors.has(connectorId)) {
+          return evseStatus.connectors.get(connectorId)
+        }
+      }
+      return undefined
     }
-    return Constants.DEFAULT_CONNECTION_TIMEOUT
+    return this.connectors.get(connectorId)
   }
 
-  private getCurrentOutType (stationInfo?: ChargingStationInfo): CurrentType {
-    return (
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      (stationInfo ?? this.stationInfo!).currentOutType ??
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      Constants.DEFAULT_STATION_INFO.currentOutType!
-    )
+  public getEnergyActiveImportRegisterByConnectorId (connectorId: number, rounded = false): number {
+    return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId), rounded)
   }
 
-  private getEnergyActiveImportRegister (
-    connectorStatus: ConnectorStatus | undefined,
+  public getEnergyActiveImportRegisterByTransactionId (
+    transactionId: number | undefined,
     rounded = false
   ): number {
-    if (this.stationInfo?.meteringPerTransaction === true) {
-      return (
-        (rounded
-          ? connectorStatus?.transactionEnergyActiveImportRegisterValue != null
-            ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue)
-            : undefined
-          : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0
-      )
+    return this.getEnergyActiveImportRegister(
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)!),
+      rounded
+    )
+  }
+
+  public getHeartbeatInterval (): number {
+    const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)
+    if (HeartbeatInterval != null) {
+      return secondsToMilliseconds(convertToInt(HeartbeatInterval.value))
     }
-    return (
-      (rounded
-        ? connectorStatus?.energyActiveImportRegisterValue != null
-          ? Math.round(connectorStatus.energyActiveImportRegisterValue)
-          : undefined
-        : connectorStatus?.energyActiveImportRegisterValue) ?? 0
+    const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval)
+    if (HeartBeatInterval != null) {
+      return secondsToMilliseconds(convertToInt(HeartBeatInterval.value))
+    }
+    this.stationInfo?.autoRegister === false &&
+      logger.warn(
+        `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${Constants.DEFAULT_HEARTBEAT_INTERVAL.toString()}`
+      )
+    return Constants.DEFAULT_HEARTBEAT_INTERVAL
+  }
+
+  public getLocalAuthListEnabled (): boolean {
+    const localAuthListEnabled = getConfigurationKey(
+      this,
+      StandardParametersKey.LocalAuthListEnabled
     )
+    return localAuthListEnabled != null ? convertToBoolean(localAuthListEnabled.value) : false
   }
 
-  private getMaximumAmperage (stationInfo?: ChargingStationInfo): number | undefined {
+  public getNumberOfConnectors (): number {
+    if (this.hasEvses) {
+      let numberOfConnectors = 0
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          numberOfConnectors += evseStatus.connectors.size
+        }
+      }
+      return numberOfConnectors
+    }
+    return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size
+  }
+
+  public getNumberOfEvses (): number {
+    return this.evses.has(0) ? this.evses.size - 1 : this.evses.size
+  }
+
+  public getNumberOfPhases (stationInfo?: ChargingStationInfo): number {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const maximumPower = (stationInfo ?? this.stationInfo!).maximumPower!
+    const localStationInfo = stationInfo ?? this.stationInfo!
     switch (this.getCurrentOutType(stationInfo)) {
       case CurrentType.AC:
-        return ACElectricUtils.amperagePerPhaseFromPower(
-          this.getNumberOfPhases(stationInfo),
-          maximumPower / (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
-          this.getVoltageOut(stationInfo)
-        )
+        return localStationInfo.numberOfPhases ?? 3
       case CurrentType.DC:
-        return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo))
+        return 0
     }
   }
 
-  private getNumberOfReservableConnectors (): number {
-    let numberOfReservableConnectors = 0
+  public getNumberOfRunningTransactions (): number {
+    let numberOfRunningTransactions = 0
     if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        numberOfReservableConnectors += getNumberOfReservableConnectors(evseStatus.connectors)
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId === 0) {
+          continue
+        }
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.transactionStarted === true) {
+            ++numberOfRunningTransactions
+          }
+        }
       }
     } else {
-      numberOfReservableConnectors = getNumberOfReservableConnectors(this.connectors)
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+          ++numberOfRunningTransactions
+        }
+      }
     }
-    return numberOfReservableConnectors - this.getNumberOfReservationsOnConnectorZero()
+    return numberOfRunningTransactions
   }
 
-  private getNumberOfReservationsOnConnectorZero (): number {
-    if (
-      (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation != null) ||
-      (!this.hasEvses && this.connectors.get(0)?.reservation != null)
-    ) {
-      return 1
+  public getReservationBy (
+    filterKey: ReservationKey,
+    value: number | string
+  ): Reservation | undefined {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.reservation?.[filterKey] === value) {
+            return connectorStatus.reservation
+          }
+        }
+      }
+    } else {
+      for (const connectorStatus of this.connectors.values()) {
+        if (connectorStatus.reservation?.[filterKey] === value) {
+          return connectorStatus.reservation
+        }
+      }
     }
-    return 0
   }
 
-  private getOcppConfiguration (
-    ocppPersistentConfiguration: boolean | undefined = this.stationInfo?.ocppPersistentConfiguration
-  ): ChargingStationOcppConfiguration | undefined {
-    let ocppConfiguration: ChargingStationOcppConfiguration | undefined =
-      this.getOcppConfigurationFromFile(ocppPersistentConfiguration)
-    if (ocppConfiguration == null) {
-      ocppConfiguration = this.getOcppConfigurationFromTemplate()
-    }
-    return ocppConfiguration
+  public getReserveConnectorZeroSupported (): boolean {
+    return convertToBoolean(
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value
+    )
   }
 
-  private getOcppConfigurationFromFile (
-    ocppPersistentConfiguration?: boolean
-  ): ChargingStationOcppConfiguration | undefined {
-    const configurationKey = this.getConfigurationFromFile()?.configurationKey
-    if (ocppPersistentConfiguration && Array.isArray(configurationKey)) {
-      return { configurationKey }
+  public getTransactionIdTag (transactionId: number): string | undefined {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.transactionId === transactionId) {
+            return connectorStatus.transactionIdTag
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
+          return this.getConnectorStatus(connectorId)?.transactionIdTag
+        }
+      }
     }
-    return undefined
   }
 
-  private getOcppConfigurationFromTemplate (): ChargingStationOcppConfiguration | undefined {
-    return this.getTemplateFromFile()?.Configuration
-  }
-
-  private getPowerDivider (): number {
-    let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()
-    if (this.stationInfo?.powerSharedByConnectors === true) {
-      powerDivider = this.getNumberOfRunningTransactions()
-    }
-    return powerDivider
-  }
-
-  private getStationInfo (options?: ChargingStationOptions): ChargingStationInfo {
-    const stationInfoFromTemplate = this.getStationInfoFromTemplate()
-    options?.persistentConfiguration != null &&
-      (stationInfoFromTemplate.stationInfoPersistentConfiguration = options.persistentConfiguration)
-    const stationInfoFromFile = this.getStationInfoFromFile(
-      stationInfoFromTemplate.stationInfoPersistentConfiguration
-    )
-    let stationInfo: ChargingStationInfo
-    // Priority:
-    // 1. charging station info from template
-    // 2. charging station info from configuration file
-    if (
-      stationInfoFromFile != null &&
-      stationInfoFromFile.templateHash === stationInfoFromTemplate.templateHash
-    ) {
-      stationInfo = stationInfoFromFile
-    } else {
-      stationInfo = stationInfoFromTemplate
-      stationInfoFromFile != null &&
-        propagateSerialNumber(this.getTemplateFromFile(), stationInfoFromFile, stationInfo)
-    }
-    return setChargingStationOptions(
-      mergeDeepRight(Constants.DEFAULT_STATION_INFO, stationInfo),
-      options
-    )
-  }
-
-  private getStationInfoFromFile (
-    stationInfoPersistentConfiguration: boolean | undefined = Constants.DEFAULT_STATION_INFO
-      .stationInfoPersistentConfiguration
-  ): ChargingStationInfo | undefined {
-    let stationInfo: ChargingStationInfo | undefined
-    if (stationInfoPersistentConfiguration) {
-      stationInfo = this.getConfigurationFromFile()?.stationInfo
-      if (stationInfo != null) {
-        // eslint-disable-next-line @typescript-eslint/no-deprecated
-        delete stationInfo.infoHash
-        delete (stationInfo as ChargingStationTemplate).numberOfConnectors
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-        if (stationInfo.templateIndex == null) {
-          stationInfo.templateIndex = this.index
-        }
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-        if (stationInfo.templateName == null) {
-          stationInfo.templateName = buildTemplateName(this.templateFile)
+  public hasConnector (connectorId: number): boolean {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        if (evseStatus.connectors.has(connectorId)) {
+          return true
         }
       }
+      return false
     }
-    return stationInfo
+    return this.connectors.has(connectorId)
   }
 
-  private getStationInfoFromTemplate (): ChargingStationInfo {
+  public hasIdTags (): boolean {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const stationTemplate = this.getTemplateFromFile()!
-    checkTemplate(stationTemplate, this.logPrefix(), this.templateFile)
-    const warnTemplateKeysDeprecationOnce = once(warnTemplateKeysDeprecation)
-    warnTemplateKeysDeprecationOnce(stationTemplate, this.logPrefix(), this.templateFile)
-    if (stationTemplate.Connectors != null) {
-      checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile)
-    }
-    const stationInfo = stationTemplateToStationInfo(stationTemplate)
-    stationInfo.hashId = getHashId(this.index, stationTemplate)
-    stationInfo.templateIndex = this.index
-    stationInfo.templateName = buildTemplateName(this.templateFile)
-    stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate)
-    createSerialNumber(stationTemplate, stationInfo)
-    stationInfo.voltageOut = this.getVoltageOut(stationInfo)
-    if (isNotEmptyArray<number>(stationTemplate.power)) {
-      const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length)
-      stationInfo.maximumPower =
-        stationTemplate.powerUnit === PowerUnits.KILO_WATT
-          ? stationTemplate.power[powerArrayRandomIndex] * 1000
-          : stationTemplate.power[powerArrayRandomIndex]
-    } else if (typeof stationTemplate.power === 'number') {
-      stationInfo.maximumPower =
-        stationTemplate.powerUnit === PowerUnits.KILO_WATT
-          ? stationTemplate.power * 1000
-          : stationTemplate.power
-    }
-    stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo)
-    if (
-      isNotEmptyString(stationInfo.firmwareVersionPattern) &&
-      isNotEmptyString(stationInfo.firmwareVersion) &&
-      !new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion)
-    ) {
-      logger.warn(
-        `${this.logPrefix()} Firmware version '${stationInfo.firmwareVersion}' in template file ${
-          this.templateFile
-        } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
-      )
-    }
-    if (stationTemplate.resetTime != null) {
-      stationInfo.resetTime = secondsToMilliseconds(stationTemplate.resetTime)
-    }
-    return stationInfo
+    return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo!)!))
   }
 
-  private getTemplateFromFile (): ChargingStationTemplate | undefined {
-    let template: ChargingStationTemplate | undefined
-    try {
-      if (this.sharedLRUCache.hasChargingStationTemplate(this.templateFileHash)) {
-        template = this.sharedLRUCache.getChargingStationTemplate(this.templateFileHash)
-      } else {
-        const measureId = `${FileType.ChargingStationTemplate} read`
-        const beginId = PerformanceStatistics.beginMeasure(measureId)
-        template = JSON.parse(readFileSync(this.templateFile, 'utf8')) as ChargingStationTemplate
-        PerformanceStatistics.endMeasure(measureId, beginId)
-        template.templateHash = hash(
-          Constants.DEFAULT_HASH_ALGORITHM,
-          JSON.stringify(template),
-          'hex'
-        )
-        this.sharedLRUCache.setChargingStationTemplate(template)
-        this.templateFileHash = template.templateHash
-      }
-    } catch (error) {
-      handleFileException(
-        this.templateFile,
-        FileType.ChargingStationTemplate,
-        error as NodeJS.ErrnoException,
-        this.logPrefix()
-      )
-    }
-    return template
+  public inAcceptedState (): boolean {
+    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED
   }
 
-  private getUseConnectorId0 (stationTemplate?: ChargingStationTemplate): boolean {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return stationTemplate?.useConnectorId0 ?? Constants.DEFAULT_STATION_INFO.useConnectorId0!
+  public inPendingState (): boolean {
+    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING
   }
 
-  private getVoltageOut (stationInfo?: ChargingStationInfo): Voltage {
-    return (
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      (stationInfo ?? this.stationInfo!).voltageOut ??
-      getDefaultVoltageOut(this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile)
-    )
+  public inRejectedState (): boolean {
+    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED
   }
 
-  private getWebSocketPingInterval (): number {
-    return getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) != null
-      ? convertToInt(getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value)
-      : 0
+  public inUnknownState (): boolean {
+    return this.bootNotificationResponse?.status == null
   }
 
-  private handleErrorMessage (errorResponse: ErrorResponse): void {
-    const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse
-    if (!this.requests.has(messageId)) {
-      // Error
-      throw new OCPPError(
-        ErrorType.INTERNAL_ERROR,
-        `Error response for unknown message id '${messageId}'`,
-        undefined,
-        { errorDetails, errorMessage, errorType }
-      )
-    }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!
-    logger.debug(
-      `${this.logPrefix()} << Command '${requestCommandName}' received error response payload: ${JSON.stringify(
-        errorResponse
-      )}`
-    )
-    errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails))
+  public isChargingStationAvailable (): boolean {
+    return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative
   }
 
-  private async handleIncomingMessage (request: IncomingRequest): Promise<void> {
-    const [messageType, messageId, commandName, commandPayload] = request
-    if (this.requests.has(messageId)) {
-      throw new OCPPError(
-        ErrorType.SECURITY_ERROR,
-        `Received message with duplicate message id '${messageId}'`,
-        commandName,
-        commandPayload
-      )
-    }
-    if (this.stationInfo?.enableStatistics === true) {
-      this.performanceStatistics?.addRequestStatistic(commandName, messageType)
-    }
-    logger.debug(
-      `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
-        request
-      )}`
-    )
-    // Process the message
-    await this.ocppIncomingRequestService.incomingRequestHandler(
-      this,
-      messageId,
-      commandName,
-      commandPayload
+  public isConnectorAvailable (connectorId: number): boolean {
+    return (
+      connectorId > 0 &&
+      this.getConnectorStatus(connectorId)?.availability === AvailabilityType.Operative
     )
-    this.emit(ChargingStationEvents.updated)
   }
 
-  private handleResponseMessage (response: Response): void {
-    const [messageType, messageId, commandPayload] = response
-    if (!this.requests.has(messageId)) {
-      // Error
-      throw new OCPPError(
-        ErrorType.INTERNAL_ERROR,
-        `Response for unknown message id '${messageId}'`,
-        undefined,
-        commandPayload
+  public isConnectorReservable (
+    reservationId: number,
+    idTag?: string,
+    connectorId?: number
+  ): boolean {
+    const reservation = this.getReservationBy('reservationId', reservationId)
+    const reservationExists = reservation != null && !hasReservationExpired(reservation)
+    if (arguments.length === 1) {
+      return !reservationExists
+    } else if (arguments.length > 1) {
+      const userReservation = idTag != null ? this.getReservationBy('idTag', idTag) : undefined
+      const userReservationExists =
+        userReservation != null && !hasReservationExpired(userReservation)
+      const notConnectorZero = connectorId == null ? true : connectorId > 0
+      const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0
+      return (
+        !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable
       )
     }
-    // Respond
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest(
-      messageType,
-      messageId
-    )!
-    logger.debug(
-      `${this.logPrefix()} << Command '${requestCommandName}' received response payload: ${JSON.stringify(
-        response
-      )}`
-    )
-    responseCallback(commandPayload, requestPayload)
+    return false
   }
 
-  private handleUnsupportedVersion (version: OCPPVersion | undefined): void {
-    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-    const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`
-    logger.error(`${this.logPrefix()} ${errorMsg}`)
-    throw new BaseError(errorMsg)
+  public isRegistered (): boolean {
+    return !this.inUnknownState() && (this.inAcceptedState() || this.inPendingState())
   }
 
-  private initialize (options?: ChargingStationOptions): void {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const stationTemplate = this.getTemplateFromFile()!
-    checkTemplate(stationTemplate, this.logPrefix(), this.templateFile)
-    this.configurationFile = join(
-      dirname(this.templateFile.replace('station-templates', 'configurations')),
-      `${getHashId(this.index, stationTemplate)}.json`
-    )
-    const stationConfiguration = this.getConfigurationFromFile()
+  public isWebSocketConnectionOpened (): boolean {
+    return this.wsConnection?.readyState === WebSocket.OPEN
+  }
+
+  public logPrefix = (): string => {
     if (
-      stationConfiguration?.stationInfo?.templateHash === stationTemplate.templateHash &&
-      (stationConfiguration?.connectorsStatus != null || stationConfiguration?.evsesStatus != null)
+      this instanceof ChargingStation &&
+      this.stationInfo != null &&
+      isNotEmptyString(this.stationInfo.chargingStationId)
     ) {
-      checkConfiguration(stationConfiguration, this.logPrefix(), this.configurationFile)
-      this.initializeConnectorsOrEvsesFromFile(stationConfiguration)
-    } else {
-      this.initializeConnectorsOrEvsesFromTemplate(stationTemplate)
+      return logPrefix(` ${this.stationInfo.chargingStationId} |`)
     }
-    this.stationInfo = this.getStationInfo(options)
-    validateStationInfo(this)
-    if (
-      this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
-      isNotEmptyString(this.stationInfo.firmwareVersionPattern) &&
-      isNotEmptyString(this.stationInfo.firmwareVersion)
-    ) {
-      const patternGroup =
-        this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
-        this.stationInfo.firmwareVersion.split('.').length
-      const match = new RegExp(this.stationInfo.firmwareVersionPattern)
-        .exec(this.stationInfo.firmwareVersion)
-        ?.slice(1, patternGroup + 1)
-      if (match != null) {
-        const patchLevelIndex = match.length - 1
-        match[patchLevelIndex] = (
-          convertToInt(match[patchLevelIndex]) +
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.stationInfo.firmwareUpgrade!.versionUpgrade!.step!
-        ).toString()
-        this.stationInfo.firmwareVersion = match.join('.')
-      }
+    let stationTemplate: ChargingStationTemplate | undefined
+    try {
+      stationTemplate = JSON.parse(
+        readFileSync(this.templateFile, 'utf8')
+      ) as ChargingStationTemplate
+    } catch {
+      // Ignore
     }
-    this.saveStationInfo()
-    this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
-    if (this.stationInfo.enableStatistics === true) {
-      this.performanceStatistics = PerformanceStatistics.getInstance(
-        this.stationInfo.hashId,
-        this.stationInfo.chargingStationId,
-        this.configuredSupervisionUrl
-      )
+    return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`)
+  }
+
+  public openWSConnection (
+    options?: WsOptions,
+    params?: { closeOpened?: boolean; terminateOpened?: boolean }
+  ): void {
+    options = {
+      handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()),
+      ...this.stationInfo?.wsOptions,
+      ...options,
     }
-    const bootNotificationRequest = createBootNotificationRequest(this.stationInfo)
-    if (bootNotificationRequest == null) {
-      const errorMsg = 'Error while creating boot notification request'
-      logger.error(`${this.logPrefix()} ${errorMsg}`)
-      throw new BaseError(errorMsg)
+    params = { ...{ closeOpened: false, terminateOpened: false }, ...params }
+    if (!checkChargingStationState(this, this.logPrefix())) {
+      return
     }
-    this.bootNotificationRequest = bootNotificationRequest
-    this.powerDivider = this.getPowerDivider()
-    // OCPP configuration
-    this.ocppConfiguration = this.getOcppConfiguration(options?.persistentConfiguration)
-    this.initializeOcppConfiguration()
-    this.initializeOcppServices()
-    if (this.stationInfo.autoRegister === true) {
-      this.bootNotificationResponse = {
-        currentTime: new Date(),
-        interval: millisecondsToSeconds(this.getHeartbeatInterval()),
-        status: RegistrationStatusEnumType.ACCEPTED,
-      }
+    if (this.stationInfo?.supervisionUser != null && this.stationInfo.supervisionPassword != null) {
+      options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`
     }
-  }
-
-  private initializeConnectorsFromTemplate (stationTemplate: ChargingStationTemplate): void {
-    if (stationTemplate.Connectors == null && this.connectors.size === 0) {
-      const errorMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`
-      logger.error(`${this.logPrefix()} ${errorMsg}`)
-      throw new BaseError(errorMsg)
+    if (params.closeOpened) {
+      this.closeWSConnection()
     }
-    if (stationTemplate.Connectors?.[0] == null) {
-      logger.warn(
-        `${this.logPrefix()} Charging station information from template ${
-          this.templateFile
-        } with no connector id 0 configuration`
-      )
+    if (params.terminateOpened) {
+      this.terminateWSConnection()
     }
-    if (stationTemplate.Connectors != null) {
-      const { configuredMaxConnectors, templateMaxAvailableConnectors, templateMaxConnectors } =
-        checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile)
-      const connectorsConfigHash = hash(
-        Constants.DEFAULT_HASH_ALGORITHM,
-        `${JSON.stringify(stationTemplate.Connectors)}${configuredMaxConnectors.toString()}`,
-        'hex'
-      )
-      const connectorsConfigChanged =
-        this.connectors.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash
-      if (this.connectors.size === 0 || connectorsConfigChanged) {
-        connectorsConfigChanged && this.connectors.clear()
-        this.connectorsConfigurationHash = connectorsConfigHash
-        if (templateMaxConnectors > 0) {
-          for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
-            if (
-              connectorId === 0 &&
-              // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-              (stationTemplate.Connectors[connectorId] == null ||
-                !this.getUseConnectorId0(stationTemplate))
-            ) {
-              continue
-            }
-            const templateConnectorId =
-              connectorId > 0 && stationTemplate.randomConnectors === true
-                ? randomInt(1, templateMaxAvailableConnectors)
-                : connectorId
-            const connectorStatus = stationTemplate.Connectors[templateConnectorId]
-            checkStationInfoConnectorStatus(
-              templateConnectorId,
-              connectorStatus,
-              this.logPrefix(),
-              this.templateFile
-            )
-            this.connectors.set(connectorId, clone<ConnectorStatus>(connectorStatus))
-          }
-          initializeConnectorsMapStatus(this.connectors, this.logPrefix())
-          this.saveConnectorsStatus()
-        } else {
-          logger.warn(
-            `${this.logPrefix()} Charging station information from template ${
-              this.templateFile
-            } with no connectors configuration defined, cannot create connectors`
-          )
-        }
-      }
-    } else {
+
+    if (this.isWebSocketConnectionOpened()) {
       logger.warn(
-        `${this.logPrefix()} Charging station information from template ${
-          this.templateFile
-        } with no connectors configuration defined, using already defined connectors`
+        `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.href} is already opened`
       )
+      return
     }
+
+    logger.info(`${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.href}`)
+
+    this.wsConnection = new WebSocket(
+      this.wsConnectionUrl,
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      `ocpp${this.stationInfo?.ocppVersion}`,
+      options
+    )
+
+    // Handle WebSocket message
+    this.wsConnection.on('message', data => {
+      this.onMessage(data).catch(Constants.EMPTY_FUNCTION)
+    })
+    // Handle WebSocket error
+    this.wsConnection.on('error', this.onError.bind(this))
+    // Handle WebSocket close
+    this.wsConnection.on('close', this.onClose.bind(this))
+    // Handle WebSocket open
+    this.wsConnection.on('open', () => {
+      this.onOpen().catch((error: unknown) =>
+        logger.error(`${this.logPrefix()} Error while opening WebSocket connection:`, error)
+      )
+    })
+    // Handle WebSocket ping
+    this.wsConnection.on('ping', this.onPing.bind(this))
+    // Handle WebSocket pong
+    this.wsConnection.on('pong', this.onPong.bind(this))
   }
 
-  private initializeConnectorsOrEvsesFromFile (configuration: ChargingStationConfiguration): void {
-    if (configuration.connectorsStatus != null && configuration.evsesStatus == null) {
-      for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) {
-        this.connectors.set(
-          connectorId,
-          prepareConnectorStatus(clone<ConnectorStatus>(connectorStatus))
+  public async removeReservation (
+    reservation: Reservation,
+    reason: ReservationTerminationReason
+  ): Promise<void> {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const connector = this.getConnectorStatus(reservation.connectorId)!
+    switch (reason) {
+      case ReservationTerminationReason.CONNECTOR_STATE_CHANGED:
+      case ReservationTerminationReason.TRANSACTION_STARTED:
+        delete connector.reservation
+        break
+      case ReservationTerminationReason.EXPIRED:
+      case ReservationTerminationReason.REPLACE_EXISTING:
+      case ReservationTerminationReason.RESERVATION_CANCELED:
+        await sendAndSetConnectorStatus(
+          this,
+          reservation.connectorId,
+          ConnectorStatusEnum.Available,
+          undefined,
+          { send: reservation.connectorId !== 0 }
         )
-      }
-    } else if (configuration.evsesStatus != null && configuration.connectorsStatus == null) {
-      for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) {
-        const evseStatus = clone<EvseStatusConfiguration>(evseStatusConfiguration)
-        delete evseStatus.connectorsStatus
-        this.evses.set(evseId, {
-          ...(evseStatus as EvseStatus),
-          connectors: new Map<number, ConnectorStatus>(
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            evseStatusConfiguration.connectorsStatus!.map((connectorStatus, connectorId) => [
-              connectorId,
-              prepareConnectorStatus(connectorStatus),
-            ])
-          ),
-        })
-      }
-    } else if (configuration.evsesStatus != null && configuration.connectorsStatus != null) {
-      const errorMsg = `Connectors and evses defined at the same time in configuration file ${this.configurationFile}`
-      logger.error(`${this.logPrefix()} ${errorMsg}`)
-      throw new BaseError(errorMsg)
-    } else {
-      const errorMsg = `No connectors or evses defined in configuration file ${this.configurationFile}`
-      logger.error(`${this.logPrefix()} ${errorMsg}`)
-      throw new BaseError(errorMsg)
+        delete connector.reservation
+        break
+      default:
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        throw new BaseError(`Unknown reservation termination reason '${reason}'`)
     }
   }
 
-  private initializeConnectorsOrEvsesFromTemplate (stationTemplate: ChargingStationTemplate): void {
-    if (stationTemplate.Connectors != null && stationTemplate.Evses == null) {
-      this.initializeConnectorsFromTemplate(stationTemplate)
-    } else if (stationTemplate.Evses != null && stationTemplate.Connectors == null) {
-      this.initializeEvsesFromTemplate(stationTemplate)
-    } else if (stationTemplate.Evses != null && stationTemplate.Connectors != null) {
-      const errorMsg = `Connectors and evses defined at the same time in template file ${this.templateFile}`
-      logger.error(`${this.logPrefix()} ${errorMsg}`)
-      throw new BaseError(errorMsg)
-    } else {
-      const errorMsg = `No connectors or evses defined in template file ${this.templateFile}`
-      logger.error(`${this.logPrefix()} ${errorMsg}`)
-      throw new BaseError(errorMsg)
+  public async reset (reason?: StopTransactionReason): Promise<void> {
+    await this.stop(reason)
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    await sleep(this.stationInfo!.resetTime!)
+    this.initialize()
+    this.start()
+  }
+
+  public restartHeartbeat (): void {
+    // Stop heartbeat
+    this.stopHeartbeat()
+    // Start heartbeat
+    this.startHeartbeat()
+  }
+
+  public restartMeterValues (connectorId: number, interval: number): void {
+    this.stopMeterValues(connectorId)
+    this.startMeterValues(connectorId, interval)
+  }
+
+  public restartWebSocketPing (): void {
+    // Stop WebSocket ping
+    this.stopWebSocketPing()
+    // Start WebSocket ping
+    this.startWebSocketPing()
+  }
+
+  public saveOcppConfiguration (): void {
+    if (this.stationInfo?.ocppPersistentConfiguration === true) {
+      this.saveConfiguration()
     }
   }
 
-  private initializeEvsesFromTemplate (stationTemplate: ChargingStationTemplate): void {
-    if (stationTemplate.Evses == null && this.evses.size === 0) {
-      const errorMsg = `No already defined evses and charging station information from template ${this.templateFile} with no evses configuration defined`
-      logger.error(`${this.logPrefix()} ${errorMsg}`)
-      throw new BaseError(errorMsg)
+  public setSupervisionUrl (url: string): void {
+    if (
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey)
+    ) {
+      setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey, url)
+    } else {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.stationInfo!.supervisionUrls = url
+      this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
+      this.saveStationInfo()
     }
-    if (stationTemplate.Evses?.[0] == null) {
-      logger.warn(
-        `${this.logPrefix()} Charging station information from template ${
-          this.templateFile
-        } with no evse id 0 configuration`
-      )
+  }
+
+  public start (): void {
+    if (!this.started) {
+      if (!this.starting) {
+        this.starting = true
+        if (this.stationInfo?.enableStatistics === true) {
+          this.performanceStatistics?.start()
+        }
+        this.openWSConnection()
+        // Monitor charging station template file
+        this.templateFileWatcher = watchJsonFile(
+          this.templateFile,
+          FileType.ChargingStationTemplate,
+          this.logPrefix(),
+          undefined,
+          (event, filename): void => {
+            if (isNotEmptyString(filename) && event === 'change') {
+              try {
+                logger.debug(
+                  `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
+                    this.templateFile
+                  } file have changed, reload`
+                )
+                this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash)
+                // 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) {
+                  this.stopAutomaticTransactionGenerator()
+                }
+                delete this.automaticTransactionGeneratorConfiguration
+                if (
+                  this.getAutomaticTransactionGeneratorConfiguration()?.enable === true &&
+                  ATGStarted === true
+                ) {
+                  this.startAutomaticTransactionGenerator(undefined, true)
+                }
+                if (this.stationInfo?.enableStatistics === true) {
+                  this.performanceStatistics?.restart()
+                } else {
+                  this.performanceStatistics?.stop()
+                }
+                // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
+              } catch (error) {
+                logger.error(
+                  `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
+                  error
+                )
+              }
+            }
+          }
+        )
+        this.started = true
+        this.emit(ChargingStationEvents.started)
+        this.starting = false
+      } else {
+        logger.warn(`${this.logPrefix()} Charging station is already starting...`)
+      }
+    } else {
+      logger.warn(`${this.logPrefix()} Charging station is already started...`)
     }
-    if (stationTemplate.Evses?.[0]?.Connectors[0] == null) {
-      logger.warn(
-        `${this.logPrefix()} Charging station information from template ${
-          this.templateFile
-        } with evse id 0 with no connector id 0 configuration`
-      )
+  }
+
+  public startAutomaticTransactionGenerator (
+    connectorIds?: number[],
+    stopAbsoluteDuration?: boolean
+  ): void {
+    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this)
+    if (isNotEmptyArray(connectorIds)) {
+      for (const connectorId of connectorIds) {
+        this.automaticTransactionGenerator?.startConnector(connectorId, stopAbsoluteDuration)
+      }
+    } else {
+      this.automaticTransactionGenerator?.start(stopAbsoluteDuration)
     }
-    if (Object.keys(stationTemplate.Evses?.[0]?.Connectors as object).length > 1) {
-      logger.warn(
-        `${this.logPrefix()} Charging station information from template ${
-          this.templateFile
-        } with evse id 0 with more than one connector configuration, only connector id 0 configuration will be used`
+    this.saveAutomaticTransactionGeneratorConfiguration()
+    this.emit(ChargingStationEvents.updated)
+  }
+
+  public startHeartbeat (): void {
+    const heartbeatInterval = this.getHeartbeatInterval()
+    if (heartbeatInterval > 0 && this.heartbeatSetInterval == null) {
+      this.heartbeatSetInterval = setInterval(() => {
+        this.ocppRequestService
+          .requestHandler<HeartbeatRequest, HeartbeatResponse>(this, RequestCommand.HEARTBEAT)
+          .catch((error: unknown) => {
+            logger.error(
+              `${this.logPrefix()} Error while sending '${RequestCommand.HEARTBEAT}':`,
+              error
+            )
+          })
+      }, heartbeatInterval)
+      logger.info(
+        `${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds(
+          heartbeatInterval
+        )}`
       )
-    }
-    if (stationTemplate.Evses != null) {
-      const evsesConfigHash = hash(
-        Constants.DEFAULT_HASH_ALGORITHM,
-        JSON.stringify(stationTemplate.Evses),
-        'hex'
+    } else if (this.heartbeatSetInterval != null) {
+      logger.info(
+        `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds(
+          heartbeatInterval
+        )}`
       )
-      const evsesConfigChanged =
-        this.evses.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash
-      if (this.evses.size === 0 || evsesConfigChanged) {
-        evsesConfigChanged && this.evses.clear()
-        this.evsesConfigurationHash = evsesConfigHash
-        const templateMaxEvses = getMaxNumberOfEvses(stationTemplate.Evses)
-        if (templateMaxEvses > 0) {
-          for (const evseKey in stationTemplate.Evses) {
-            const evseId = convertToInt(evseKey)
-            this.evses.set(evseId, {
-              availability: AvailabilityType.Operative,
-              connectors: buildConnectorsMap(
-                stationTemplate.Evses[evseKey].Connectors,
-                this.logPrefix(),
-                this.templateFile
-              ),
-            })
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            initializeConnectorsMapStatus(this.evses.get(evseId)!.connectors, this.logPrefix())
-          }
-          this.saveEvsesStatus()
-        } else {
-          logger.warn(
-            `${this.logPrefix()} Charging station information from template ${
-              this.templateFile
-            } with no evses configuration defined, cannot create evses`
-          )
-        }
-      }
     } else {
-      logger.warn(
-        `${this.logPrefix()} Charging station information from template ${
-          this.templateFile
-        } with no evses configuration defined, using already defined evses`
+      logger.error(
+        `${this.logPrefix()} Heartbeat interval set to ${heartbeatInterval.toString()}, not starting the heartbeat`
       )
     }
   }
 
-  private initializeOcppConfiguration (): void {
-    if (getConfigurationKey(this, StandardParametersKey.HeartbeatInterval) == null) {
-      addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0')
+  public startMeterValues (connectorId: number, interval: number): void {
+    if (connectorId === 0) {
+      logger.error(
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()}`
+      )
+      return
     }
-    if (getConfigurationKey(this, StandardParametersKey.HeartBeatInterval) == null) {
-      addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', {
-        visible: false,
-      })
+    const connectorStatus = this.getConnectorStatus(connectorId)
+    if (connectorStatus == null) {
+      logger.error(
+        `${this.logPrefix()} Trying to start MeterValues on non existing connector id
+          ${connectorId.toString()}`
+      )
+      return
     }
-    if (
-      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
-      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) == null
-    ) {
-      addConfigurationKey(
-        this,
-        this.stationInfo.supervisionUrlOcppKey,
-        this.configuredSupervisionUrl.href,
-        { reboot: true }
+    if (connectorStatus.transactionStarted === false) {
+      logger.error(
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()} with no transaction started`
       )
+      return
     } else if (
-      this.stationInfo?.supervisionUrlOcppConfiguration === false &&
-      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) != null
+      connectorStatus.transactionStarted === true &&
+      connectorStatus.transactionId == null
     ) {
-      deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey, {
-        save: false,
-      })
+      logger.error(
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()} with no transaction id`
+      )
+      return
     }
-    if (
-      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) == null
-    ) {
-      addConfigurationKey(
-        this,
-        this.stationInfo.amperageLimitationOcppKey,
-        // prettier-ignore
-        (
+    if (interval > 0) {
+      connectorStatus.transactionSetInterval = setInterval(() => {
+        const meterValue = buildMeterValue(
+          this,
+          connectorId,
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.stationInfo.maximumAmperage! * getAmperageLimitationUnitDivider(this.stationInfo)
-        ).toString()
-      )
-    }
-    if (getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles) == null) {
-      addConfigurationKey(
-        this,
-        StandardParametersKey.SupportedFeatureProfiles,
-        `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`
-      )
-    }
-    addConfigurationKey(
-      this,
-      StandardParametersKey.NumberOfConnectors,
-      this.getNumberOfConnectors().toString(),
-      { readonly: true },
-      { overwrite: true }
-    )
-    if (getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData) == null) {
-      addConfigurationKey(
-        this,
-        StandardParametersKey.MeterValuesSampledData,
-        MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+          connectorStatus.transactionId!,
+          interval
+        )
+        this.ocppRequestService
+          .requestHandler<MeterValuesRequest, MeterValuesResponse>(
+            this,
+            RequestCommand.METER_VALUES,
+            {
+              connectorId,
+              meterValue: [meterValue],
+              transactionId: connectorStatus.transactionId,
+            }
+          )
+          .catch((error: unknown) => {
+            logger.error(
+              `${this.logPrefix()} Error while sending '${RequestCommand.METER_VALUES}':`,
+              error
+            )
+          })
+      }, interval)
+    } else {
+      logger.error(
+        `${this.logPrefix()} Charging station ${
+          StandardParametersKey.MeterValueSampleInterval
+        } configuration set to ${interval.toString()}, not sending MeterValues`
       )
     }
-    if (getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation) == null) {
-      const connectorsPhaseRotation: string[] = []
-      if (this.hasEvses) {
-        for (const evseStatus of this.evses.values()) {
-          for (const connectorId of evseStatus.connectors.keys()) {
-            connectorsPhaseRotation.push(
-              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              getPhaseRotationValue(connectorId, this.getNumberOfPhases())!
-            )
-          }
+  }
+
+  public async stop (
+    reason?: StopTransactionReason,
+    stopTransactions = this.stationInfo?.stopTransactionsOnStopped
+  ): Promise<void> {
+    if (this.started) {
+      if (!this.stopping) {
+        this.stopping = true
+        await this.stopMessageSequence(reason, stopTransactions)
+        this.closeWSConnection()
+        if (this.stationInfo?.enableStatistics === true) {
+          this.performanceStatistics?.stop()
         }
+        this.templateFileWatcher?.close()
+        delete this.bootNotificationResponse
+        this.started = false
+        this.saveConfiguration()
+        this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
+        this.emit(ChargingStationEvents.stopped)
+        this.stopping = false
       } else {
-        for (const connectorId of this.connectors.keys()) {
-          connectorsPhaseRotation.push(
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            getPhaseRotationValue(connectorId, this.getNumberOfPhases())!
-          )
-        }
+        logger.warn(`${this.logPrefix()} Charging station is already stopping...`)
       }
-      addConfigurationKey(
-        this,
-        StandardParametersKey.ConnectorPhaseRotation,
-        connectorsPhaseRotation.toString()
-      )
+    } else {
+      logger.warn(`${this.logPrefix()} Charging station is already stopped...`)
     }
-    if (getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests) == null) {
-      addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true')
+  }
+
+  public stopAutomaticTransactionGenerator (connectorIds?: number[]): void {
+    if (isNotEmptyArray(connectorIds)) {
+      for (const connectorId of connectorIds) {
+        this.automaticTransactionGenerator?.stopConnector(connectorId)
+      }
+    } else {
+      this.automaticTransactionGenerator?.stop()
+    }
+    this.saveAutomaticTransactionGeneratorConfiguration()
+    this.emit(ChargingStationEvents.updated)
+  }
+
+  public stopMeterValues (connectorId: number): void {
+    const connectorStatus = this.getConnectorStatus(connectorId)
+    if (connectorStatus?.transactionSetInterval != null) {
+      clearInterval(connectorStatus.transactionSetInterval)
     }
+  }
+
+  public async stopTransactionOnConnector (
+    connectorId: number,
+    reason?: StopTransactionReason
+  ): Promise<StopTransactionResponse> {
+    const transactionId = this.getConnectorStatus(connectorId)?.transactionId
     if (
-      getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) == null &&
-      hasFeatureProfile(this, SupportedFeatureProfiles.LocalAuthListManagement) === true
+      this.stationInfo?.beginEndMeterValues === true &&
+      this.stationInfo.ocppStrictCompliance === true &&
+      this.stationInfo.outOfOrderEndMeterValues === false
     ) {
-      addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false')
-    }
-    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) == null) {
-      addConfigurationKey(
+      const transactionEndMeterValue = buildTransactionEndMeterValue(
         this,
-        StandardParametersKey.ConnectionTimeOut,
-        Constants.DEFAULT_CONNECTION_TIMEOUT.toString()
+        connectorId,
+        this.getEnergyActiveImportRegisterByTransactionId(transactionId)
+      )
+      await this.ocppRequestService.requestHandler<MeterValuesRequest, MeterValuesResponse>(
+        this,
+        RequestCommand.METER_VALUES,
+        {
+          connectorId,
+          meterValue: [transactionEndMeterValue],
+          transactionId,
+        }
       )
     }
-    this.saveOcppConfiguration()
+    return await this.ocppRequestService.requestHandler<
+      Partial<StopTransactionRequest>,
+      StopTransactionResponse
+    >(this, RequestCommand.STOP_TRANSACTION, {
+      meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true),
+      transactionId,
+      ...(reason != null && { reason }),
+    })
   }
 
-  private initializeOcppServices (): void {
-    const ocppVersion = this.stationInfo?.ocppVersion
-    switch (ocppVersion) {
-      case OCPPVersion.VERSION_16:
-        this.ocppIncomingRequestService =
-          OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>()
-        this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
-          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
-        )
-        break
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        this.ocppIncomingRequestService =
-          OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>()
-        this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
-          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
-        )
-        break
-      default:
-        this.handleUnsupportedVersion(ocppVersion)
-        break
-    }
+  private add (): void {
+    this.emit(ChargingStationEvents.added)
   }
 
-  private internalStopMessageSequence (): void {
-    // Stop WebSocket ping
-    this.stopWebSocketPing()
-    // Stop heartbeat
-    this.stopHeartbeat()
-    // Stop the ATG
-    if (this.automaticTransactionGenerator?.started === true) {
-      this.stopAutomaticTransactionGenerator()
+  private clearIntervalFlushMessageBuffer (): void {
+    if (this.flushMessageBufferSetInterval != null) {
+      clearInterval(this.flushMessageBufferSetInterval)
+      delete this.flushMessageBufferSetInterval
     }
   }
 
-  private onClose (code: WebSocketCloseEventStatusCode, reason: Buffer): void {
-    this.emit(ChargingStationEvents.disconnected)
-    this.emit(ChargingStationEvents.updated)
-    switch (code) {
-      // Normal close
-      case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
-      case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
-        logger.info(
-          `${this.logPrefix()} WebSocket normally closed with status '${getWebSocketCloseEventStatusString(
-            code
-          )}' and reason '${reason.toString()}'`
-        )
-        this.wsConnectionRetryCount = 0
-        break
-      // Abnormal close
-      default:
-        logger.error(
-          `${this.logPrefix()} WebSocket abnormally closed with status '${getWebSocketCloseEventStatusString(
-            code
-          )}' and reason '${reason.toString()}'`
-        )
-        this.started &&
-          this.reconnect()
-            .then(() => {
-              this.emit(ChargingStationEvents.updated)
-              return undefined
-            })
-            .catch((error: unknown) =>
-              logger.error(`${this.logPrefix()} Error while reconnecting:`, error)
+  private flushMessageBuffer (): void {
+    if (this.messageBuffer.size > 0) {
+      for (const message of this.messageBuffer.values()) {
+        let beginId: string | undefined
+        let commandName: RequestCommand | undefined
+        const [messageType] = JSON.parse(message) as ErrorResponse | OutgoingRequest | Response
+        const isRequest = messageType === MessageType.CALL_MESSAGE
+        if (isRequest) {
+          ;[, , commandName] = JSON.parse(message) as OutgoingRequest
+          beginId = PerformanceStatistics.beginMeasure(commandName)
+        }
+        this.wsConnection?.send(message, (error?: Error) => {
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!)
+          if (error == null) {
+            logger.debug(
+              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
+                messageType
+              )} OCPP message sent '${JSON.stringify(message)}'`
             )
-        break
-    }
-  }
-
-  private onError (error: WSError): void {
-    this.closeWSConnection()
-    logger.error(`${this.logPrefix()} WebSocket error:`, error)
-  }
-
-  private async onMessage (data: RawData): Promise<void> {
-    let request: ErrorResponse | IncomingRequest | Response | undefined
-    let messageType: MessageType | undefined
-    let errorMsg: string
-    try {
-      // eslint-disable-next-line @typescript-eslint/no-base-to-string
-      request = JSON.parse(data.toString()) as ErrorResponse | IncomingRequest | Response
-      if (Array.isArray(request)) {
-        ;[messageType] = request
-        // Check the type of message
-        switch (messageType) {
-          // Error Message
-          case MessageType.CALL_ERROR_MESSAGE:
-            this.handleErrorMessage(request as ErrorResponse)
-            break
-          // Incoming Message
-          case MessageType.CALL_MESSAGE:
-            await this.handleIncomingMessage(request as IncomingRequest)
-            break
-          // Response Message
-          case MessageType.CALL_RESULT_MESSAGE:
-            this.handleResponseMessage(request as Response)
-            break
-          // Unknown Message
-          default:
-            // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-            errorMsg = `Wrong message type ${messageType}`
-            logger.error(`${this.logPrefix()} ${errorMsg}`)
-            throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg)
-        }
-      } else {
-        throw new OCPPError(
-          ErrorType.PROTOCOL_ERROR,
-          'Incoming message is not an array',
-          undefined,
-          {
-            request,
-          }
-        )
-      }
-    } catch (error) {
-      if (!Array.isArray(request)) {
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        logger.error(`${this.logPrefix()} Incoming message '${request}' parsing error:`, error)
-        return
-      }
-      let commandName: IncomingRequestCommand | undefined
-      let requestCommandName: IncomingRequestCommand | RequestCommand | undefined
-      let errorCallback: ErrorCallback
-      const [, messageId] = request
-      switch (messageType) {
-        case MessageType.CALL_ERROR_MESSAGE:
-        case MessageType.CALL_RESULT_MESSAGE:
-          if (this.requests.has(messageId)) {
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            ;[, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!
-            // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
-            errorCallback(error as OCPPError, false)
+            this.messageBuffer.delete(message)
           } else {
-            // Remove the request from the cache in case of error at response handling
-            this.requests.delete(messageId)
-          }
-          break
-        case MessageType.CALL_MESSAGE:
-          ;[, , commandName] = request as IncomingRequest
-          // Send error
-          await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName)
-          break
-      }
-      if (!(error instanceof OCPPError)) {
-        logger.warn(
-          `${this.logPrefix()} Error thrown at incoming OCPP command ${
-            commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
-            // eslint-disable-next-line @typescript-eslint/no-base-to-string
-          } message '${data.toString()}' handling is not an OCPPError:`,
-          error
-        )
-      }
-      logger.error(
-        `${this.logPrefix()} Incoming OCPP command '${
-          commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
-          // eslint-disable-next-line @typescript-eslint/no-base-to-string
-        }' message '${data.toString()}'${
-          this.requests.has(messageId)
-            ? ` matching cached request '${JSON.stringify(
-                this.getCachedRequest(messageType, messageId)
-              )}'`
-            : ''
-        } processing error:`,
-        error
-      )
-    }
-  }
-
-  private async onOpen (): Promise<void> {
-    if (this.isWebSocketConnectionOpened()) {
-      this.emit(ChargingStationEvents.connected)
-      this.emit(ChargingStationEvents.updated)
-      logger.info(
-        `${this.logPrefix()} Connection to OCPP server through ${
-          this.wsConnectionUrl.href
-        } succeeded`
-      )
-      let registrationRetryCount = 0
-      if (!this.isRegistered()) {
-        // Send BootNotification
-        do {
-          await this.ocppRequestService.requestHandler<
-            BootNotificationRequest,
-            BootNotificationResponse
-          >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
-            skipBufferingOnError: true,
-          })
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.bootNotificationResponse!.currentTime = convertToDate(
-            this.bootNotificationResponse?.currentTime
-          )!
-          if (!this.isRegistered()) {
-            this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount
-            await sleep(
-              this.bootNotificationResponse?.interval != null
-                ? secondsToMilliseconds(this.bootNotificationResponse.interval)
-                : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL
+            logger.debug(
+              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
+                messageType
+              )} OCPP message '${JSON.stringify(message)}' send failed:`,
+              error
             )
           }
-        } while (
-          !this.isRegistered() &&
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          (registrationRetryCount <= this.stationInfo!.registrationMaxRetries! ||
-            this.stationInfo?.registrationMaxRetries === -1)
-        )
-      }
-      if (!this.isRegistered()) {
-        logger.error(
-          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-          `${this.logPrefix()} Registration failure: maximum retries reached (${registrationRetryCount.toString()}) or retry disabled (${this.stationInfo?.registrationMaxRetries?.toString()})`
-        )
+        })
       }
-      this.emit(ChargingStationEvents.updated)
-    } else {
-      logger.warn(
-        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} failed`
-      )
     }
   }
 
-  private onPing (): void {
-    logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`)
-  }
-
-  private onPong (): void {
-    logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`)
-  }
-
-  private async reconnect (): Promise<void> {
+  private getAmperageLimitation (): number | undefined {
     if (
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.wsConnectionRetryCount < this.stationInfo!.autoReconnectMaxRetries! ||
-      this.stationInfo?.autoReconnectMaxRetries === -1
+      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) != null
     ) {
-      ++this.wsConnectionRetryCount
-      const reconnectDelay =
-        this.stationInfo?.reconnectExponentialDelay === true
-          ? exponentialDelay(this.wsConnectionRetryCount)
-          : secondsToMilliseconds(this.getConnectionTimeout())
-      const reconnectDelayWithdraw = 1000
-      const reconnectTimeout =
-        reconnectDelay - reconnectDelayWithdraw > 0 ? reconnectDelay - reconnectDelayWithdraw : 0
-      logger.error(
-        `${this.logPrefix()} WebSocket connection retry in ${roundTo(
-          reconnectDelay,
-          2
-        ).toString()}ms, timeout ${reconnectTimeout.toString()}ms`
-      )
-      await sleep(reconnectDelay)
-      logger.error(
-        `${this.logPrefix()} WebSocket connection retry #${this.wsConnectionRetryCount.toString()}`
-      )
-      this.openWSConnection(
-        {
-          handshakeTimeout: reconnectTimeout,
-        },
-        { closeOpened: true }
-      )
-    } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) {
-      logger.error(
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${this.wsConnectionRetryCount.toString()}) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries?.toString()})`
+      return (
+        convertToInt(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey)?.value) /
+        getAmperageLimitationUnitDivider(this.stationInfo)
       )
     }
   }
 
-  private saveAutomaticTransactionGeneratorConfiguration (): void {
-    if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true) {
-      this.saveConfiguration()
+  private getCachedRequest (
+    messageType: MessageType | undefined,
+    messageId: string
+  ): CachedRequest | undefined {
+    const cachedRequest = this.requests.get(messageId)
+    if (Array.isArray(cachedRequest)) {
+      return cachedRequest
     }
+    throw new OCPPError(
+      ErrorType.PROTOCOL_ERROR,
+      `Cached request for message id '${messageId}' ${getMessageTypeString(
+        messageType
+      )} is not an array`,
+      undefined,
+      cachedRequest
+    )
   }
 
-  private saveConfiguration (): void {
-    if (isNotEmptyString(this.configurationFile)) {
+  private getConfigurationFromFile (): ChargingStationConfiguration | undefined {
+    let configuration: ChargingStationConfiguration | undefined
+    if (isNotEmptyString(this.configurationFile) && existsSync(this.configurationFile)) {
       try {
-        if (!existsSync(dirname(this.configurationFile))) {
-          mkdirSync(dirname(this.configurationFile), { recursive: true })
-        }
-        const configurationFromFile = this.getConfigurationFromFile()
-        let configurationData: ChargingStationConfiguration =
-          configurationFromFile != null
-            ? clone<ChargingStationConfiguration>(configurationFromFile)
-            : {}
-        if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
-          configurationData.stationInfo = this.stationInfo
-        } else {
-          delete configurationData.stationInfo
-        }
-        if (
-          this.stationInfo?.ocppPersistentConfiguration === true &&
-          Array.isArray(this.ocppConfiguration?.configurationKey)
-        ) {
-          configurationData.configurationKey = this.ocppConfiguration.configurationKey
+        if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
+          configuration = this.sharedLRUCache.getChargingStationConfiguration(
+            this.configurationFileHash
+          )
         } else {
-          delete configurationData.configurationKey
+          const measureId = `${FileType.ChargingStationConfiguration} read`
+          const beginId = PerformanceStatistics.beginMeasure(measureId)
+          configuration = JSON.parse(
+            readFileSync(this.configurationFile, 'utf8')
+          ) as ChargingStationConfiguration
+          PerformanceStatistics.endMeasure(measureId, beginId)
+          this.sharedLRUCache.setChargingStationConfiguration(configuration)
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.configurationFileHash = configuration.configurationHash!
         }
-        configurationData = mergeDeepRight(
-          configurationData,
-          buildChargingStationAutomaticTransactionGeneratorConfiguration(this)
+      } catch (error) {
+        handleFileException(
+          this.configurationFile,
+          FileType.ChargingStationConfiguration,
+          error as NodeJS.ErrnoException,
+          this.logPrefix()
         )
-        if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration !== true) {
-          delete configurationData.automaticTransactionGenerator
-        }
-        if (this.connectors.size > 0) {
-          configurationData.connectorsStatus = buildConnectorsStatus(this)
-        } else {
-          delete configurationData.connectorsStatus
-        }
-        if (this.evses.size > 0) {
-          configurationData.evsesStatus = buildEvsesStatus(this)
-        } else {
-          delete configurationData.evsesStatus
-        }
-        delete configurationData.configurationHash
-        const configurationHash = hash(
-          Constants.DEFAULT_HASH_ALGORITHM,
-          JSON.stringify({
-            automaticTransactionGenerator: configurationData.automaticTransactionGenerator,
-            configurationKey: configurationData.configurationKey,
-            stationInfo: configurationData.stationInfo,
-            ...(this.connectors.size > 0 && {
-              connectorsStatus: configurationData.connectorsStatus,
-            }),
-            ...(this.evses.size > 0 && {
-              evsesStatus: configurationData.evsesStatus,
-            }),
-          } satisfies ChargingStationConfiguration),
-          'hex'
-        )
-        if (this.configurationFileHash !== configurationHash) {
-          AsyncLock.runExclusive(AsyncLockType.configuration, () => {
-            configurationData.configurationHash = configurationHash
-            const measureId = `${FileType.ChargingStationConfiguration} write`
-            const beginId = PerformanceStatistics.beginMeasure(measureId)
-            writeFileSync(
-              this.configurationFile,
-              JSON.stringify(configurationData, undefined, 2),
-              'utf8'
-            )
-            PerformanceStatistics.endMeasure(measureId, beginId)
-            this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
-            this.sharedLRUCache.setChargingStationConfiguration(configurationData)
-            this.configurationFileHash = configurationHash
-          }).catch((error: unknown) => {
-            handleFileException(
-              this.configurationFile,
-              FileType.ChargingStationConfiguration,
-              error as NodeJS.ErrnoException,
-              this.logPrefix()
+      }
+    }
+    return configuration
+  }
+
+  private getConfiguredSupervisionUrl (): URL {
+    let configuredSupervisionUrl: string | undefined
+    const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls()
+    if (isNotEmptyArray(supervisionUrls)) {
+      let configuredSupervisionUrlIndex: number
+      switch (Configuration.getSupervisionUrlDistribution()) {
+        case SupervisionUrlDistribution.RANDOM:
+          configuredSupervisionUrlIndex = Math.floor(secureRandom() * supervisionUrls.length)
+          break
+        case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
+        case SupervisionUrlDistribution.ROUND_ROBIN:
+        default:
+          !Object.values(SupervisionUrlDistribution).includes(
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            Configuration.getSupervisionUrlDistribution()!
+          ) &&
+            logger.warn(
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
+              `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' in configuration from values '${SupervisionUrlDistribution.toString()}', defaulting to '${
+                SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
+              }'`
             )
-          })
-        } else {
-          logger.debug(
-            `${this.logPrefix()} Not saving unchanged charging station configuration file ${
-              this.configurationFile
-            }`
-          )
-        }
-      } catch (error) {
-        handleFileException(
-          this.configurationFile,
-          FileType.ChargingStationConfiguration,
-          error as NodeJS.ErrnoException,
-          this.logPrefix()
-        )
+          configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length
+          break
       }
-    } else {
-      logger.error(
-        `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`
-      )
+      configuredSupervisionUrl = supervisionUrls[configuredSupervisionUrlIndex]
+    } else if (typeof supervisionUrls === 'string') {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      configuredSupervisionUrl = supervisionUrls!
+    }
+    if (isNotEmptyString(configuredSupervisionUrl)) {
+      return new URL(configuredSupervisionUrl)
     }
+    const errorMsg = 'No supervision url(s) configured'
+    logger.error(`${this.logPrefix()} ${errorMsg}`)
+    throw new BaseError(errorMsg)
   }
 
-  private saveConnectorsStatus (): void {
-    this.saveConfiguration()
+  // 0 for disabling
+  private getConnectionTimeout (): number {
+    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) != null) {
+      return convertToInt(
+        getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)?.value ??
+          Constants.DEFAULT_CONNECTION_TIMEOUT
+      )
+    }
+    return Constants.DEFAULT_CONNECTION_TIMEOUT
   }
 
-  private saveEvsesStatus (): void {
-    this.saveConfiguration()
+  private getCurrentOutType (stationInfo?: ChargingStationInfo): CurrentType {
+    return (
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      (stationInfo ?? this.stationInfo!).currentOutType ??
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      Constants.DEFAULT_STATION_INFO.currentOutType!
+    )
   }
 
-  private saveStationInfo (): void {
-    if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
-      this.saveConfiguration()
+  private getEnergyActiveImportRegister (
+    connectorStatus: ConnectorStatus | undefined,
+    rounded = false
+  ): number {
+    if (this.stationInfo?.meteringPerTransaction === true) {
+      return (
+        (rounded
+          ? connectorStatus?.transactionEnergyActiveImportRegisterValue != null
+            ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue)
+            : undefined
+          : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0
+      )
     }
+    return (
+      (rounded
+        ? connectorStatus?.energyActiveImportRegisterValue != null
+          ? Math.round(connectorStatus.energyActiveImportRegisterValue)
+          : undefined
+        : connectorStatus?.energyActiveImportRegisterValue) ?? 0
+    )
   }
 
-  private setIntervalFlushMessageBuffer (): void {
-    if (this.flushMessageBufferSetInterval == null) {
-      this.flushMessageBufferSetInterval = setInterval(() => {
-        if (this.isWebSocketConnectionOpened() && this.inAcceptedState()) {
-          this.flushMessageBuffer()
-        }
-        if (this.messageBuffer.size === 0) {
-          this.clearIntervalFlushMessageBuffer()
-        }
-      }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL)
+  private getMaximumAmperage (stationInfo?: ChargingStationInfo): number | undefined {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const maximumPower = (stationInfo ?? this.stationInfo!).maximumPower!
+    switch (this.getCurrentOutType(stationInfo)) {
+      case CurrentType.AC:
+        return ACElectricUtils.amperagePerPhaseFromPower(
+          this.getNumberOfPhases(stationInfo),
+          maximumPower / (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
+          this.getVoltageOut(stationInfo)
+        )
+      case CurrentType.DC:
+        return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo))
     }
   }
 
-  private async startMessageSequence (ATGStopAbsoluteDuration?: boolean): Promise<void> {
-    if (this.stationInfo?.autoRegister === true) {
-      await this.ocppRequestService.requestHandler<
-        BootNotificationRequest,
-        BootNotificationResponse
-      >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
-        skipBufferingOnError: true,
-      })
-    }
-    // Start WebSocket ping
-    if (this.wsPingSetInterval == null) {
-      this.startWebSocketPing()
-    }
-    // Start heartbeat
-    if (this.heartbeatSetInterval == null) {
-      this.startHeartbeat()
-    }
-    // Initialize connectors status
+  private getNumberOfReservableConnectors (): number {
+    let numberOfReservableConnectors = 0
     if (this.hasEvses) {
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId > 0) {
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            await sendAndSetConnectorStatus(
-              this,
-              connectorId,
-              getBootConnectorStatus(this, connectorId, connectorStatus),
-              evseId
-            )
-          }
-        }
+      for (const evseStatus of this.evses.values()) {
+        numberOfReservableConnectors += getNumberOfReservableConnectors(evseStatus.connectors)
       }
     } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0) {
-          await sendAndSetConnectorStatus(
-            this,
-            connectorId,
-            getBootConnectorStatus(
-              this,
-              connectorId,
-              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              this.getConnectorStatus(connectorId)!
-            )
-          )
-        }
-      }
+      numberOfReservableConnectors = getNumberOfReservableConnectors(this.connectors)
     }
-    if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
-      await this.ocppRequestService.requestHandler<
-        FirmwareStatusNotificationRequest,
-        FirmwareStatusNotificationResponse
-      >(this, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
-        status: FirmwareStatus.Installed,
-      })
-      this.stationInfo.firmwareStatus = FirmwareStatus.Installed
+    return numberOfReservableConnectors - this.getNumberOfReservationsOnConnectorZero()
+  }
+
+  private getNumberOfReservationsOnConnectorZero (): number {
+    if (
+      (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation != null) ||
+      (!this.hasEvses && this.connectors.get(0)?.reservation != null)
+    ) {
+      return 1
     }
+    return 0
+  }
 
-    // Start the ATG
-    if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
-      this.startAutomaticTransactionGenerator(undefined, ATGStopAbsoluteDuration)
+  private getOcppConfiguration (
+    ocppPersistentConfiguration: boolean | undefined = this.stationInfo?.ocppPersistentConfiguration
+  ): ChargingStationOcppConfiguration | undefined {
+    let ocppConfiguration: ChargingStationOcppConfiguration | undefined =
+      this.getOcppConfigurationFromFile(ocppPersistentConfiguration)
+    if (ocppConfiguration == null) {
+      ocppConfiguration = this.getOcppConfigurationFromTemplate()
     }
-    this.flushMessageBuffer()
+    return ocppConfiguration
   }
 
-  private startWebSocketPing (): void {
-    const webSocketPingInterval = this.getWebSocketPingInterval()
-    if (webSocketPingInterval > 0 && this.wsPingSetInterval == null) {
-      this.wsPingSetInterval = setInterval(() => {
-        if (this.isWebSocketConnectionOpened()) {
-          this.wsConnection?.ping()
-        }
-      }, secondsToMilliseconds(webSocketPingInterval))
-      logger.info(
-        `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
-          webSocketPingInterval
-        )}`
-      )
-    } else if (this.wsPingSetInterval != null) {
-      logger.info(
-        `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds(
-          webSocketPingInterval
-        )}`
-      )
-    } else {
-      logger.error(
-        `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval.toString()}, not starting the WebSocket ping`
-      )
+  private getOcppConfigurationFromFile (
+    ocppPersistentConfiguration?: boolean
+  ): ChargingStationOcppConfiguration | undefined {
+    const configurationKey = this.getConfigurationFromFile()?.configurationKey
+    if (ocppPersistentConfiguration && Array.isArray(configurationKey)) {
+      return { configurationKey }
     }
+    return undefined
   }
 
-  private stopHeartbeat (): void {
-    if (this.heartbeatSetInterval != null) {
-      clearInterval(this.heartbeatSetInterval)
-      delete this.heartbeatSetInterval
+  private getOcppConfigurationFromTemplate (): ChargingStationOcppConfiguration | undefined {
+    return this.getTemplateFromFile()?.Configuration
+  }
+
+  private getPowerDivider (): number {
+    let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()
+    if (this.stationInfo?.powerSharedByConnectors === true) {
+      powerDivider = this.getNumberOfRunningTransactions()
     }
+    return powerDivider
   }
 
-  private async stopMessageSequence (
-    reason?: StopTransactionReason,
-    stopTransactions?: boolean
-  ): Promise<void> {
-    this.internalStopMessageSequence()
-    // Stop ongoing transactions
-    stopTransactions && (await this.stopRunningTransactions(reason))
-    if (this.hasEvses) {
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId > 0) {
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            await sendAndSetConnectorStatus(
-              this,
-              connectorId,
-              ConnectorStatusEnum.Unavailable,
-              evseId
-            )
-            delete connectorStatus.status
-          }
-        }
-      }
+  private getStationInfo (options?: ChargingStationOptions): ChargingStationInfo {
+    const stationInfoFromTemplate = this.getStationInfoFromTemplate()
+    options?.persistentConfiguration != null &&
+      (stationInfoFromTemplate.stationInfoPersistentConfiguration = options.persistentConfiguration)
+    const stationInfoFromFile = this.getStationInfoFromFile(
+      stationInfoFromTemplate.stationInfoPersistentConfiguration
+    )
+    let stationInfo: ChargingStationInfo
+    // Priority:
+    // 1. charging station info from template
+    // 2. charging station info from configuration file
+    if (
+      stationInfoFromFile != null &&
+      stationInfoFromFile.templateHash === stationInfoFromTemplate.templateHash
+    ) {
+      stationInfo = stationInfoFromFile
     } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0) {
-          await sendAndSetConnectorStatus(this, connectorId, ConnectorStatusEnum.Unavailable)
-          delete this.getConnectorStatus(connectorId)?.status
-        }
-      }
+      stationInfo = stationInfoFromTemplate
+      stationInfoFromFile != null &&
+        propagateSerialNumber(this.getTemplateFromFile(), stationInfoFromFile, stationInfo)
     }
+    return setChargingStationOptions(
+      mergeDeepRight(Constants.DEFAULT_STATION_INFO, stationInfo),
+      options
+    )
   }
 
-  private async stopRunningTransactions (reason?: StopTransactionReason): Promise<void> {
-    if (this.hasEvses) {
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId === 0) {
-          continue
-        }
-        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-          if (connectorStatus.transactionStarted === true) {
-            await this.stopTransactionOnConnector(connectorId, reason)
-          }
+  private getStationInfoFromFile (
+    stationInfoPersistentConfiguration: boolean | undefined = Constants.DEFAULT_STATION_INFO
+      .stationInfoPersistentConfiguration
+  ): ChargingStationInfo | undefined {
+    let stationInfo: ChargingStationInfo | undefined
+    if (stationInfoPersistentConfiguration) {
+      stationInfo = this.getConfigurationFromFile()?.stationInfo
+      if (stationInfo != null) {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        delete stationInfo.infoHash
+        delete (stationInfo as ChargingStationTemplate).numberOfConnectors
+        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+        if (stationInfo.templateIndex == null) {
+          stationInfo.templateIndex = this.index
         }
-      }
-    } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-          await this.stopTransactionOnConnector(connectorId, reason)
+        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+        if (stationInfo.templateName == null) {
+          stationInfo.templateName = buildTemplateName(this.templateFile)
         }
       }
     }
+    return stationInfo
   }
 
-  private stopWebSocketPing (): void {
-    if (this.wsPingSetInterval != null) {
-      clearInterval(this.wsPingSetInterval)
-      delete this.wsPingSetInterval
+  private getStationInfoFromTemplate (): ChargingStationInfo {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const stationTemplate = this.getTemplateFromFile()!
+    checkTemplate(stationTemplate, this.logPrefix(), this.templateFile)
+    const warnTemplateKeysDeprecationOnce = once(warnTemplateKeysDeprecation)
+    warnTemplateKeysDeprecationOnce(stationTemplate, this.logPrefix(), this.templateFile)
+    if (stationTemplate.Connectors != null) {
+      checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile)
+    }
+    const stationInfo = stationTemplateToStationInfo(stationTemplate)
+    stationInfo.hashId = getHashId(this.index, stationTemplate)
+    stationInfo.templateIndex = this.index
+    stationInfo.templateName = buildTemplateName(this.templateFile)
+    stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate)
+    createSerialNumber(stationTemplate, stationInfo)
+    stationInfo.voltageOut = this.getVoltageOut(stationInfo)
+    if (isNotEmptyArray<number>(stationTemplate.power)) {
+      const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length)
+      stationInfo.maximumPower =
+        stationTemplate.powerUnit === PowerUnits.KILO_WATT
+          ? stationTemplate.power[powerArrayRandomIndex] * 1000
+          : stationTemplate.power[powerArrayRandomIndex]
+    } else if (typeof stationTemplate.power === 'number') {
+      stationInfo.maximumPower =
+        stationTemplate.powerUnit === PowerUnits.KILO_WATT
+          ? stationTemplate.power * 1000
+          : stationTemplate.power
+    }
+    stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo)
+    if (
+      isNotEmptyString(stationInfo.firmwareVersionPattern) &&
+      isNotEmptyString(stationInfo.firmwareVersion) &&
+      !new RegExp(stationInfo.firmwareVersionPattern).test(stationInfo.firmwareVersion)
+    ) {
+      logger.warn(
+        `${this.logPrefix()} Firmware version '${stationInfo.firmwareVersion}' in template file ${
+          this.templateFile
+        } does not match firmware version pattern '${stationInfo.firmwareVersionPattern}'`
+      )
+    }
+    if (stationTemplate.resetTime != null) {
+      stationInfo.resetTime = secondsToMilliseconds(stationTemplate.resetTime)
     }
+    return stationInfo
   }
 
-  private terminateWSConnection (): void {
-    if (this.isWebSocketConnectionOpened()) {
-      this.wsConnection?.terminate()
-      this.wsConnection = null
+  private getTemplateFromFile (): ChargingStationTemplate | undefined {
+    let template: ChargingStationTemplate | undefined
+    try {
+      if (this.sharedLRUCache.hasChargingStationTemplate(this.templateFileHash)) {
+        template = this.sharedLRUCache.getChargingStationTemplate(this.templateFileHash)
+      } else {
+        const measureId = `${FileType.ChargingStationTemplate} read`
+        const beginId = PerformanceStatistics.beginMeasure(measureId)
+        template = JSON.parse(readFileSync(this.templateFile, 'utf8')) as ChargingStationTemplate
+        PerformanceStatistics.endMeasure(measureId, beginId)
+        template.templateHash = hash(
+          Constants.DEFAULT_HASH_ALGORITHM,
+          JSON.stringify(template),
+          'hex'
+        )
+        this.sharedLRUCache.setChargingStationTemplate(template)
+        this.templateFileHash = template.templateHash
+      }
+    } catch (error) {
+      handleFileException(
+        this.templateFile,
+        FileType.ChargingStationTemplate,
+        error as NodeJS.ErrnoException,
+        this.logPrefix()
+      )
     }
+    return template
   }
 
-  public async addReservation (reservation: Reservation): Promise<void> {
-    const reservationFound = this.getReservationBy('reservationId', reservation.reservationId)
-    if (reservationFound != null) {
-      await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING)
-    }
+  private getUseConnectorId0 (stationTemplate?: ChargingStationTemplate): boolean {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.getConnectorStatus(reservation.connectorId)!.reservation = reservation
-    await sendAndSetConnectorStatus(
-      this,
-      reservation.connectorId,
-      ConnectorStatusEnum.Reserved,
-      undefined,
-      { send: reservation.connectorId !== 0 }
-    )
+    return stationTemplate?.useConnectorId0 ?? Constants.DEFAULT_STATION_INFO.useConnectorId0!
   }
 
-  public bufferMessage (message: string): void {
-    this.messageBuffer.add(message)
-    this.setIntervalFlushMessageBuffer()
+  private getVoltageOut (stationInfo?: ChargingStationInfo): Voltage {
+    return (
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      (stationInfo ?? this.stationInfo!).voltageOut ??
+      getDefaultVoltageOut(this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile)
+    )
   }
 
-  public closeWSConnection (): void {
-    if (this.isWebSocketConnectionOpened()) {
-      this.wsConnection?.close()
-      this.wsConnection = null
-    }
+  private getWebSocketPingInterval (): number {
+    return getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) != null
+      ? convertToInt(getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value)
+      : 0
   }
 
-  public async delete (deleteConfiguration = true): Promise<void> {
-    if (this.started) {
-      await this.stop()
+  private handleErrorMessage (errorResponse: ErrorResponse): void {
+    const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse
+    if (!this.requests.has(messageId)) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Error response for unknown message id '${messageId}'`,
+        undefined,
+        { errorDetails, errorMessage, errorType }
+      )
     }
-    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)
-    this.removeAllListeners()
-  }
-
-  public getAuthorizeRemoteTxRequests (): boolean {
-    const authorizeRemoteTxRequests = getConfigurationKey(
+    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!
+    logger.debug(
+      `${this.logPrefix()} << Command '${requestCommandName}' received error response payload: ${JSON.stringify(
+        errorResponse
+      )}`
+    )
+    errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails))
+  }
+
+  private async handleIncomingMessage (request: IncomingRequest): Promise<void> {
+    const [messageType, messageId, commandName, commandPayload] = request
+    if (this.requests.has(messageId)) {
+      throw new OCPPError(
+        ErrorType.SECURITY_ERROR,
+        `Received message with duplicate message id '${messageId}'`,
+        commandName,
+        commandPayload
+      )
+    }
+    if (this.stationInfo?.enableStatistics === true) {
+      this.performanceStatistics?.addRequestStatistic(commandName, messageType)
+    }
+    logger.debug(
+      `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
+        request
+      )}`
+    )
+    // Process the message
+    await this.ocppIncomingRequestService.incomingRequestHandler(
       this,
-      StandardParametersKey.AuthorizeRemoteTxRequests
+      messageId,
+      commandName,
+      commandPayload
     )
-    return authorizeRemoteTxRequests != null
-      ? convertToBoolean(authorizeRemoteTxRequests.value)
-      : false
+    this.emit(ChargingStationEvents.updated)
   }
 
-  public getAutomaticTransactionGeneratorConfiguration ():
-    | AutomaticTransactionGeneratorConfiguration
-    | undefined {
-    if (this.automaticTransactionGeneratorConfiguration == null) {
-      let automaticTransactionGeneratorConfiguration:
-        | AutomaticTransactionGeneratorConfiguration
-        | undefined
-      const stationTemplate = this.getTemplateFromFile()
-      const stationConfiguration = this.getConfigurationFromFile()
-      if (
-        this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true &&
-        stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
-        stationConfiguration?.automaticTransactionGenerator != null
-      ) {
-        automaticTransactionGeneratorConfiguration =
-          stationConfiguration.automaticTransactionGenerator
-      } else {
-        automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator
-      }
-      this.automaticTransactionGeneratorConfiguration = {
-        ...Constants.DEFAULT_ATG_CONFIGURATION,
-        ...automaticTransactionGeneratorConfiguration,
-      }
+  private handleResponseMessage (response: Response): void {
+    const [messageType, messageId, commandPayload] = response
+    if (!this.requests.has(messageId)) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Response for unknown message id '${messageId}'`,
+        undefined,
+        commandPayload
+      )
     }
-    return this.automaticTransactionGeneratorConfiguration
+    // Respond
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest(
+      messageType,
+      messageId
+    )!
+    logger.debug(
+      `${this.logPrefix()} << Command '${requestCommandName}' received response payload: ${JSON.stringify(
+        response
+      )}`
+    )
+    responseCallback(commandPayload, requestPayload)
   }
 
-  public getAutomaticTransactionGeneratorStatuses (): Status[] | undefined {
-    return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses
+  private handleUnsupportedVersion (version: OCPPVersion | undefined): void {
+    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+    const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`
+    logger.error(`${this.logPrefix()} ${errorMsg}`)
+    throw new BaseError(errorMsg)
   }
 
-  public getConnectorIdByTransactionId (transactionId: number | undefined): number | undefined {
-    if (transactionId == null) {
-      return undefined
-    } else if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-          if (connectorStatus.transactionId === transactionId) {
-            return connectorId
-          }
-        }
-      }
+  private initialize (options?: ChargingStationOptions): void {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const stationTemplate = this.getTemplateFromFile()!
+    checkTemplate(stationTemplate, this.logPrefix(), this.templateFile)
+    this.configurationFile = join(
+      dirname(this.templateFile.replace('station-templates', 'configurations')),
+      `${getHashId(this.index, stationTemplate)}.json`
+    )
+    const stationConfiguration = this.getConfigurationFromFile()
+    if (
+      stationConfiguration?.stationInfo?.templateHash === stationTemplate.templateHash &&
+      (stationConfiguration?.connectorsStatus != null || stationConfiguration?.evsesStatus != null)
+    ) {
+      checkConfiguration(stationConfiguration, this.logPrefix(), this.configurationFile)
+      this.initializeConnectorsOrEvsesFromFile(stationConfiguration)
     } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
-          return connectorId
-        }
-      }
+      this.initializeConnectorsOrEvsesFromTemplate(stationTemplate)
     }
-  }
-
-  public getConnectorMaximumAvailablePower (connectorId: number): number {
-    let connectorAmperageLimitationLimit: number | undefined
-    const amperageLimitation = this.getAmperageLimitation()
+    this.stationInfo = this.getStationInfo(options)
+    validateStationInfo(this)
     if (
-      amperageLimitation != null &&
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      amperageLimitation < this.stationInfo!.maximumAmperage!
+      this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
+      isNotEmptyString(this.stationInfo.firmwareVersionPattern) &&
+      isNotEmptyString(this.stationInfo.firmwareVersion)
     ) {
-      connectorAmperageLimitationLimit =
-        (this.stationInfo?.currentOutType === CurrentType.AC
-          ? ACElectricUtils.powerTotal(
-            this.getNumberOfPhases(),
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            this.stationInfo.voltageOut!,
-            amperageLimitation *
-                (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors())
-          )
-          : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          DCElectricUtils.power(this.stationInfo!.voltageOut!, amperageLimitation)) /
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.powerDivider!
+      const patternGroup =
+        this.stationInfo.firmwareUpgrade?.versionUpgrade?.patternGroup ??
+        this.stationInfo.firmwareVersion.split('.').length
+      const match = new RegExp(this.stationInfo.firmwareVersionPattern)
+        .exec(this.stationInfo.firmwareVersion)
+        ?.slice(1, patternGroup + 1)
+      if (match != null) {
+        const patchLevelIndex = match.length - 1
+        match[patchLevelIndex] = (
+          convertToInt(match[patchLevelIndex]) +
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.stationInfo.firmwareUpgrade!.versionUpgrade!.step!
+        ).toString()
+        this.stationInfo.firmwareVersion = match.join('.')
+      }
     }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const connectorMaximumPower = this.stationInfo!.maximumPower! / this.powerDivider!
-    const chargingStationChargingProfilesLimit =
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      getChargingStationChargingProfilesLimit(this)! / this.powerDivider!
-    const connectorChargingProfilesLimit = getConnectorChargingProfilesLimit(this, connectorId)
-    return min(
-      Number.isNaN(connectorMaximumPower) ? Number.POSITIVE_INFINITY : connectorMaximumPower,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      Number.isNaN(connectorAmperageLimitationLimit!)
-        ? Number.POSITIVE_INFINITY
-        : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        connectorAmperageLimitationLimit!,
-      Number.isNaN(chargingStationChargingProfilesLimit)
-        ? Number.POSITIVE_INFINITY
-        : chargingStationChargingProfilesLimit,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      Number.isNaN(connectorChargingProfilesLimit!)
-        ? Number.POSITIVE_INFINITY
-        : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        connectorChargingProfilesLimit!
-    )
-  }
-
-  public getConnectorStatus (connectorId: number): ConnectorStatus | undefined {
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        if (evseStatus.connectors.has(connectorId)) {
-          return evseStatus.connectors.get(connectorId)
-        }
+    this.saveStationInfo()
+    this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
+    if (this.stationInfo.enableStatistics === true) {
+      this.performanceStatistics = PerformanceStatistics.getInstance(
+        this.stationInfo.hashId,
+        this.stationInfo.chargingStationId,
+        this.configuredSupervisionUrl
+      )
+    }
+    const bootNotificationRequest = createBootNotificationRequest(this.stationInfo)
+    if (bootNotificationRequest == null) {
+      const errorMsg = 'Error while creating boot notification request'
+      logger.error(`${this.logPrefix()} ${errorMsg}`)
+      throw new BaseError(errorMsg)
+    }
+    this.bootNotificationRequest = bootNotificationRequest
+    this.powerDivider = this.getPowerDivider()
+    // OCPP configuration
+    this.ocppConfiguration = this.getOcppConfiguration(options?.persistentConfiguration)
+    this.initializeOcppConfiguration()
+    this.initializeOcppServices()
+    if (this.stationInfo.autoRegister === true) {
+      this.bootNotificationResponse = {
+        currentTime: new Date(),
+        interval: millisecondsToSeconds(this.getHeartbeatInterval()),
+        status: RegistrationStatusEnumType.ACCEPTED,
       }
-      return undefined
     }
-    return this.connectors.get(connectorId)
-  }
-
-  public getEnergyActiveImportRegisterByConnectorId (connectorId: number, rounded = false): number {
-    return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId), rounded)
-  }
-
-  public getEnergyActiveImportRegisterByTransactionId (
-    transactionId: number | undefined,
-    rounded = false
-  ): number {
-    return this.getEnergyActiveImportRegister(
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)!),
-      rounded
-    )
   }
 
-  public getHeartbeatInterval (): number {
-    const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)
-    if (HeartbeatInterval != null) {
-      return secondsToMilliseconds(convertToInt(HeartbeatInterval.value))
-    }
-    const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval)
-    if (HeartBeatInterval != null) {
-      return secondsToMilliseconds(convertToInt(HeartBeatInterval.value))
+  private initializeConnectorsFromTemplate (stationTemplate: ChargingStationTemplate): void {
+    if (stationTemplate.Connectors == null && this.connectors.size === 0) {
+      const errorMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`
+      logger.error(`${this.logPrefix()} ${errorMsg}`)
+      throw new BaseError(errorMsg)
     }
-    this.stationInfo?.autoRegister === false &&
+    if (stationTemplate.Connectors?.[0] == null) {
       logger.warn(
-        `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${Constants.DEFAULT_HEARTBEAT_INTERVAL.toString()}`
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with no connector id 0 configuration`
       )
-    return Constants.DEFAULT_HEARTBEAT_INTERVAL
-  }
-
-  public getLocalAuthListEnabled (): boolean {
-    const localAuthListEnabled = getConfigurationKey(
-      this,
-      StandardParametersKey.LocalAuthListEnabled
-    )
-    return localAuthListEnabled != null ? convertToBoolean(localAuthListEnabled.value) : false
-  }
-
-  public getNumberOfConnectors (): number {
-    if (this.hasEvses) {
-      let numberOfConnectors = 0
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId > 0) {
-          numberOfConnectors += evseStatus.connectors.size
-        }
-      }
-      return numberOfConnectors
-    }
-    return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size
-  }
-
-  public getNumberOfEvses (): number {
-    return this.evses.has(0) ? this.evses.size - 1 : this.evses.size
-  }
-
-  public getNumberOfPhases (stationInfo?: ChargingStationInfo): number {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const localStationInfo = stationInfo ?? this.stationInfo!
-    switch (this.getCurrentOutType(stationInfo)) {
-      case CurrentType.AC:
-        return localStationInfo.numberOfPhases ?? 3
-      case CurrentType.DC:
-        return 0
     }
-  }
-
-  public getNumberOfRunningTransactions (): number {
-    let numberOfRunningTransactions = 0
-    if (this.hasEvses) {
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId === 0) {
-          continue
-        }
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.transactionStarted === true) {
-            ++numberOfRunningTransactions
+    if (stationTemplate.Connectors != null) {
+      const { configuredMaxConnectors, templateMaxAvailableConnectors, templateMaxConnectors } =
+        checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile)
+      const connectorsConfigHash = hash(
+        Constants.DEFAULT_HASH_ALGORITHM,
+        `${JSON.stringify(stationTemplate.Connectors)}${configuredMaxConnectors.toString()}`,
+        'hex'
+      )
+      const connectorsConfigChanged =
+        this.connectors.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash
+      if (this.connectors.size === 0 || connectorsConfigChanged) {
+        connectorsConfigChanged && this.connectors.clear()
+        this.connectorsConfigurationHash = connectorsConfigHash
+        if (templateMaxConnectors > 0) {
+          for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
+            if (
+              connectorId === 0 &&
+              // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+              (stationTemplate.Connectors[connectorId] == null ||
+                !this.getUseConnectorId0(stationTemplate))
+            ) {
+              continue
+            }
+            const templateConnectorId =
+              connectorId > 0 && stationTemplate.randomConnectors === true
+                ? randomInt(1, templateMaxAvailableConnectors)
+                : connectorId
+            const connectorStatus = stationTemplate.Connectors[templateConnectorId]
+            checkStationInfoConnectorStatus(
+              templateConnectorId,
+              connectorStatus,
+              this.logPrefix(),
+              this.templateFile
+            )
+            this.connectors.set(connectorId, clone<ConnectorStatus>(connectorStatus))
           }
+          initializeConnectorsMapStatus(this.connectors, this.logPrefix())
+          this.saveConnectorsStatus()
+        } else {
+          logger.warn(
+            `${this.logPrefix()} Charging station information from template ${
+              this.templateFile
+            } with no connectors configuration defined, cannot create connectors`
+          )
         }
       }
     } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-          ++numberOfRunningTransactions
-        }
-      }
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with no connectors configuration defined, using already defined connectors`
+      )
     }
-    return numberOfRunningTransactions
   }
 
-  public getReservationBy (
-    filterKey: ReservationKey,
-    value: number | string
-  ): Reservation | undefined {
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.reservation?.[filterKey] === value) {
-            return connectorStatus.reservation
-          }
-        }
+  private initializeConnectorsOrEvsesFromFile (configuration: ChargingStationConfiguration): void {
+    if (configuration.connectorsStatus != null && configuration.evsesStatus == null) {
+      for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) {
+        this.connectors.set(
+          connectorId,
+          prepareConnectorStatus(clone<ConnectorStatus>(connectorStatus))
+        )
       }
-    } else {
-      for (const connectorStatus of this.connectors.values()) {
-        if (connectorStatus.reservation?.[filterKey] === value) {
-          return connectorStatus.reservation
-        }
+    } else if (configuration.evsesStatus != null && configuration.connectorsStatus == null) {
+      for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) {
+        const evseStatus = clone<EvseStatusConfiguration>(evseStatusConfiguration)
+        delete evseStatus.connectorsStatus
+        this.evses.set(evseId, {
+          ...(evseStatus as EvseStatus),
+          connectors: new Map<number, ConnectorStatus>(
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            evseStatusConfiguration.connectorsStatus!.map((connectorStatus, connectorId) => [
+              connectorId,
+              prepareConnectorStatus(connectorStatus),
+            ])
+          ),
+        })
       }
+    } else if (configuration.evsesStatus != null && configuration.connectorsStatus != null) {
+      const errorMsg = `Connectors and evses defined at the same time in configuration file ${this.configurationFile}`
+      logger.error(`${this.logPrefix()} ${errorMsg}`)
+      throw new BaseError(errorMsg)
+    } else {
+      const errorMsg = `No connectors or evses defined in configuration file ${this.configurationFile}`
+      logger.error(`${this.logPrefix()} ${errorMsg}`)
+      throw new BaseError(errorMsg)
     }
   }
 
-  public getReserveConnectorZeroSupported (): boolean {
-    return convertToBoolean(
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value
-    )
+  private initializeConnectorsOrEvsesFromTemplate (stationTemplate: ChargingStationTemplate): void {
+    if (stationTemplate.Connectors != null && stationTemplate.Evses == null) {
+      this.initializeConnectorsFromTemplate(stationTemplate)
+    } else if (stationTemplate.Evses != null && stationTemplate.Connectors == null) {
+      this.initializeEvsesFromTemplate(stationTemplate)
+    } else if (stationTemplate.Evses != null && stationTemplate.Connectors != null) {
+      const errorMsg = `Connectors and evses defined at the same time in template file ${this.templateFile}`
+      logger.error(`${this.logPrefix()} ${errorMsg}`)
+      throw new BaseError(errorMsg)
+    } else {
+      const errorMsg = `No connectors or evses defined in template file ${this.templateFile}`
+      logger.error(`${this.logPrefix()} ${errorMsg}`)
+      throw new BaseError(errorMsg)
+    }
   }
 
-  public getTransactionIdTag (transactionId: number): string | undefined {
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.transactionId === transactionId) {
-            return connectorStatus.transactionIdTag
+  private initializeEvsesFromTemplate (stationTemplate: ChargingStationTemplate): void {
+    if (stationTemplate.Evses == null && this.evses.size === 0) {
+      const errorMsg = `No already defined evses and charging station information from template ${this.templateFile} with no evses configuration defined`
+      logger.error(`${this.logPrefix()} ${errorMsg}`)
+      throw new BaseError(errorMsg)
+    }
+    if (stationTemplate.Evses?.[0] == null) {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with no evse id 0 configuration`
+      )
+    }
+    if (stationTemplate.Evses?.[0]?.Connectors[0] == null) {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with evse id 0 with no connector id 0 configuration`
+      )
+    }
+    if (Object.keys(stationTemplate.Evses?.[0]?.Connectors as object).length > 1) {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with evse id 0 with more than one connector configuration, only connector id 0 configuration will be used`
+      )
+    }
+    if (stationTemplate.Evses != null) {
+      const evsesConfigHash = hash(
+        Constants.DEFAULT_HASH_ALGORITHM,
+        JSON.stringify(stationTemplate.Evses),
+        'hex'
+      )
+      const evsesConfigChanged =
+        this.evses.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash
+      if (this.evses.size === 0 || evsesConfigChanged) {
+        evsesConfigChanged && this.evses.clear()
+        this.evsesConfigurationHash = evsesConfigHash
+        const templateMaxEvses = getMaxNumberOfEvses(stationTemplate.Evses)
+        if (templateMaxEvses > 0) {
+          for (const evseKey in stationTemplate.Evses) {
+            const evseId = convertToInt(evseKey)
+            this.evses.set(evseId, {
+              availability: AvailabilityType.Operative,
+              connectors: buildConnectorsMap(
+                stationTemplate.Evses[evseKey].Connectors,
+                this.logPrefix(),
+                this.templateFile
+              ),
+            })
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            initializeConnectorsMapStatus(this.evses.get(evseId)!.connectors, this.logPrefix())
           }
+          this.saveEvsesStatus()
+        } else {
+          logger.warn(
+            `${this.logPrefix()} Charging station information from template ${
+              this.templateFile
+            } with no evses configuration defined, cannot create evses`
+          )
         }
       }
     } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
-          return this.getConnectorStatus(connectorId)?.transactionIdTag
-        }
-      }
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with no evses configuration defined, using already defined evses`
+      )
     }
   }
 
-  public hasConnector (connectorId: number): boolean {
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        if (evseStatus.connectors.has(connectorId)) {
-          return true
-        }
-      }
-      return false
+  private initializeOcppConfiguration (): void {
+    if (getConfigurationKey(this, StandardParametersKey.HeartbeatInterval) == null) {
+      addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0')
     }
-    return this.connectors.has(connectorId)
-  }
-
-  public hasIdTags (): boolean {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo!)!))
-  }
-
-  public inAcceptedState (): boolean {
-    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED
-  }
-
-  public inPendingState (): boolean {
-    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING
+    if (getConfigurationKey(this, StandardParametersKey.HeartBeatInterval) == null) {
+      addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', {
+        visible: false,
+      })
+    }
+    if (
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) == null
+    ) {
+      addConfigurationKey(
+        this,
+        this.stationInfo.supervisionUrlOcppKey,
+        this.configuredSupervisionUrl.href,
+        { reboot: true }
+      )
+    } else if (
+      this.stationInfo?.supervisionUrlOcppConfiguration === false &&
+      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) != null
+    ) {
+      deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey, {
+        save: false,
+      })
+    }
+    if (
+      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) == null
+    ) {
+      addConfigurationKey(
+        this,
+        this.stationInfo.amperageLimitationOcppKey,
+        // prettier-ignore
+        (
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.stationInfo.maximumAmperage! * getAmperageLimitationUnitDivider(this.stationInfo)
+        ).toString()
+      )
+    }
+    if (getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles) == null) {
+      addConfigurationKey(
+        this,
+        StandardParametersKey.SupportedFeatureProfiles,
+        `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`
+      )
+    }
+    addConfigurationKey(
+      this,
+      StandardParametersKey.NumberOfConnectors,
+      this.getNumberOfConnectors().toString(),
+      { readonly: true },
+      { overwrite: true }
+    )
+    if (getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData) == null) {
+      addConfigurationKey(
+        this,
+        StandardParametersKey.MeterValuesSampledData,
+        MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+      )
+    }
+    if (getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation) == null) {
+      const connectorsPhaseRotation: string[] = []
+      if (this.hasEvses) {
+        for (const evseStatus of this.evses.values()) {
+          for (const connectorId of evseStatus.connectors.keys()) {
+            connectorsPhaseRotation.push(
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              getPhaseRotationValue(connectorId, this.getNumberOfPhases())!
+            )
+          }
+        }
+      } else {
+        for (const connectorId of this.connectors.keys()) {
+          connectorsPhaseRotation.push(
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            getPhaseRotationValue(connectorId, this.getNumberOfPhases())!
+          )
+        }
+      }
+      addConfigurationKey(
+        this,
+        StandardParametersKey.ConnectorPhaseRotation,
+        connectorsPhaseRotation.toString()
+      )
+    }
+    if (getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests) == null) {
+      addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true')
+    }
+    if (
+      getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) == null &&
+      hasFeatureProfile(this, SupportedFeatureProfiles.LocalAuthListManagement) === true
+    ) {
+      addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false')
+    }
+    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) == null) {
+      addConfigurationKey(
+        this,
+        StandardParametersKey.ConnectionTimeOut,
+        Constants.DEFAULT_CONNECTION_TIMEOUT.toString()
+      )
+    }
+    this.saveOcppConfiguration()
   }
 
-  public inRejectedState (): boolean {
-    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED
+  private initializeOcppServices (): void {
+    const ocppVersion = this.stationInfo?.ocppVersion
+    switch (ocppVersion) {
+      case OCPPVersion.VERSION_16:
+        this.ocppIncomingRequestService =
+          OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>()
+        this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
+          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
+        )
+        break
+      case OCPPVersion.VERSION_20:
+      case OCPPVersion.VERSION_201:
+        this.ocppIncomingRequestService =
+          OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>()
+        this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
+          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
+        )
+        break
+      default:
+        this.handleUnsupportedVersion(ocppVersion)
+        break
+    }
   }
 
-  public inUnknownState (): boolean {
-    return this.bootNotificationResponse?.status == null
+  private internalStopMessageSequence (): void {
+    // Stop WebSocket ping
+    this.stopWebSocketPing()
+    // Stop heartbeat
+    this.stopHeartbeat()
+    // Stop the ATG
+    if (this.automaticTransactionGenerator?.started === true) {
+      this.stopAutomaticTransactionGenerator()
+    }
   }
 
-  public isChargingStationAvailable (): boolean {
-    return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative
+  private onClose (code: WebSocketCloseEventStatusCode, reason: Buffer): void {
+    this.emit(ChargingStationEvents.disconnected)
+    this.emit(ChargingStationEvents.updated)
+    switch (code) {
+      // Normal close
+      case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
+      case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
+        logger.info(
+          `${this.logPrefix()} WebSocket normally closed with status '${getWebSocketCloseEventStatusString(
+            code
+          )}' and reason '${reason.toString()}'`
+        )
+        this.wsConnectionRetryCount = 0
+        break
+      // Abnormal close
+      default:
+        logger.error(
+          `${this.logPrefix()} WebSocket abnormally closed with status '${getWebSocketCloseEventStatusString(
+            code
+          )}' and reason '${reason.toString()}'`
+        )
+        this.started &&
+          this.reconnect()
+            .then(() => {
+              this.emit(ChargingStationEvents.updated)
+              return undefined
+            })
+            .catch((error: unknown) =>
+              logger.error(`${this.logPrefix()} Error while reconnecting:`, error)
+            )
+        break
+    }
   }
 
-  public isConnectorAvailable (connectorId: number): boolean {
-    return (
-      connectorId > 0 &&
-      this.getConnectorStatus(connectorId)?.availability === AvailabilityType.Operative
-    )
+  private onError (error: WSError): void {
+    this.closeWSConnection()
+    logger.error(`${this.logPrefix()} WebSocket error:`, error)
   }
 
-  public isConnectorReservable (
-    reservationId: number,
-    idTag?: string,
-    connectorId?: number
-  ): boolean {
-    const reservation = this.getReservationBy('reservationId', reservationId)
-    const reservationExists = reservation != null && !hasReservationExpired(reservation)
-    if (arguments.length === 1) {
-      return !reservationExists
-    } else if (arguments.length > 1) {
-      const userReservation = idTag != null ? this.getReservationBy('idTag', idTag) : undefined
-      const userReservationExists =
-        userReservation != null && !hasReservationExpired(userReservation)
-      const notConnectorZero = connectorId == null ? true : connectorId > 0
-      const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0
-      return (
-        !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable
+  private async onMessage (data: RawData): Promise<void> {
+    let request: ErrorResponse | IncomingRequest | Response | undefined
+    let messageType: MessageType | undefined
+    let errorMsg: string
+    try {
+      // eslint-disable-next-line @typescript-eslint/no-base-to-string
+      request = JSON.parse(data.toString()) as ErrorResponse | IncomingRequest | Response
+      if (Array.isArray(request)) {
+        ;[messageType] = request
+        // Check the type of message
+        switch (messageType) {
+          // Error Message
+          case MessageType.CALL_ERROR_MESSAGE:
+            this.handleErrorMessage(request as ErrorResponse)
+            break
+          // Incoming Message
+          case MessageType.CALL_MESSAGE:
+            await this.handleIncomingMessage(request as IncomingRequest)
+            break
+          // Response Message
+          case MessageType.CALL_RESULT_MESSAGE:
+            this.handleResponseMessage(request as Response)
+            break
+          // Unknown Message
+          default:
+            // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            errorMsg = `Wrong message type ${messageType}`
+            logger.error(`${this.logPrefix()} ${errorMsg}`)
+            throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg)
+        }
+      } else {
+        throw new OCPPError(
+          ErrorType.PROTOCOL_ERROR,
+          'Incoming message is not an array',
+          undefined,
+          {
+            request,
+          }
+        )
+      }
+    } catch (error) {
+      if (!Array.isArray(request)) {
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        logger.error(`${this.logPrefix()} Incoming message '${request}' parsing error:`, error)
+        return
+      }
+      let commandName: IncomingRequestCommand | undefined
+      let requestCommandName: IncomingRequestCommand | RequestCommand | undefined
+      let errorCallback: ErrorCallback
+      const [, messageId] = request
+      switch (messageType) {
+        case MessageType.CALL_ERROR_MESSAGE:
+        case MessageType.CALL_RESULT_MESSAGE:
+          if (this.requests.has(messageId)) {
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            ;[, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!
+            // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
+            errorCallback(error as OCPPError, false)
+          } else {
+            // Remove the request from the cache in case of error at response handling
+            this.requests.delete(messageId)
+          }
+          break
+        case MessageType.CALL_MESSAGE:
+          ;[, , commandName] = request as IncomingRequest
+          // Send error
+          await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName)
+          break
+      }
+      if (!(error instanceof OCPPError)) {
+        logger.warn(
+          `${this.logPrefix()} Error thrown at incoming OCPP command ${
+            commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
+            // eslint-disable-next-line @typescript-eslint/no-base-to-string
+          } message '${data.toString()}' handling is not an OCPPError:`,
+          error
+        )
+      }
+      logger.error(
+        `${this.logPrefix()} Incoming OCPP command '${
+          commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
+          // eslint-disable-next-line @typescript-eslint/no-base-to-string
+        }' message '${data.toString()}'${
+          this.requests.has(messageId)
+            ? ` matching cached request '${JSON.stringify(
+                this.getCachedRequest(messageType, messageId)
+              )}'`
+            : ''
+        } processing error:`,
+        error
       )
     }
-    return false
-  }
-
-  public isRegistered (): boolean {
-    return !this.inUnknownState() && (this.inAcceptedState() || this.inPendingState())
-  }
-
-  public isWebSocketConnectionOpened (): boolean {
-    return this.wsConnection?.readyState === WebSocket.OPEN
   }
 
-  public openWSConnection (
-    options?: WsOptions,
-    params?: { closeOpened?: boolean; terminateOpened?: boolean }
-  ): void {
-    options = {
-      handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()),
-      ...this.stationInfo?.wsOptions,
-      ...options,
-    }
-    params = { ...{ closeOpened: false, terminateOpened: false }, ...params }
-    if (!checkChargingStationState(this, this.logPrefix())) {
-      return
-    }
-    if (this.stationInfo?.supervisionUser != null && this.stationInfo.supervisionPassword != null) {
-      options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`
-    }
-    if (params.closeOpened) {
-      this.closeWSConnection()
-    }
-    if (params.terminateOpened) {
-      this.terminateWSConnection()
-    }
-
+  private async onOpen (): Promise<void> {
     if (this.isWebSocketConnectionOpened()) {
+      this.emit(ChargingStationEvents.connected)
+      this.emit(ChargingStationEvents.updated)
+      logger.info(
+        `${this.logPrefix()} Connection to OCPP server through ${
+          this.wsConnectionUrl.href
+        } succeeded`
+      )
+      let registrationRetryCount = 0
+      if (!this.isRegistered()) {
+        // Send BootNotification
+        do {
+          await this.ocppRequestService.requestHandler<
+            BootNotificationRequest,
+            BootNotificationResponse
+          >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
+            skipBufferingOnError: true,
+          })
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.bootNotificationResponse!.currentTime = convertToDate(
+            this.bootNotificationResponse?.currentTime
+          )!
+          if (!this.isRegistered()) {
+            this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount
+            await sleep(
+              this.bootNotificationResponse?.interval != null
+                ? secondsToMilliseconds(this.bootNotificationResponse.interval)
+                : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL
+            )
+          }
+        } while (
+          !this.isRegistered() &&
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          (registrationRetryCount <= this.stationInfo!.registrationMaxRetries! ||
+            this.stationInfo?.registrationMaxRetries === -1)
+        )
+      }
+      if (!this.isRegistered()) {
+        logger.error(
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          `${this.logPrefix()} Registration failure: maximum retries reached (${registrationRetryCount.toString()}) or retry disabled (${this.stationInfo?.registrationMaxRetries?.toString()})`
+        )
+      }
+      this.emit(ChargingStationEvents.updated)
+    } else {
       logger.warn(
-        `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.href} is already opened`
+        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} failed`
       )
-      return
     }
+  }
 
-    logger.info(`${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.href}`)
-
-    this.wsConnection = new WebSocket(
-      this.wsConnectionUrl,
-      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-      `ocpp${this.stationInfo?.ocppVersion}`,
-      options
-    )
+  private onPing (): void {
+    logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`)
+  }
 
-    // Handle WebSocket message
-    this.wsConnection.on('message', data => {
-      this.onMessage(data).catch(Constants.EMPTY_FUNCTION)
-    })
-    // Handle WebSocket error
-    this.wsConnection.on('error', this.onError.bind(this))
-    // Handle WebSocket close
-    this.wsConnection.on('close', this.onClose.bind(this))
-    // Handle WebSocket open
-    this.wsConnection.on('open', () => {
-      this.onOpen().catch((error: unknown) =>
-        logger.error(`${this.logPrefix()} Error while opening WebSocket connection:`, error)
-      )
-    })
-    // Handle WebSocket ping
-    this.wsConnection.on('ping', this.onPing.bind(this))
-    // Handle WebSocket pong
-    this.wsConnection.on('pong', this.onPong.bind(this))
+  private onPong (): void {
+    logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`)
   }
 
-  public async removeReservation (
-    reservation: Reservation,
-    reason: ReservationTerminationReason
-  ): Promise<void> {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const connector = this.getConnectorStatus(reservation.connectorId)!
-    switch (reason) {
-      case ReservationTerminationReason.CONNECTOR_STATE_CHANGED:
-      case ReservationTerminationReason.TRANSACTION_STARTED:
-        delete connector.reservation
-        break
-      case ReservationTerminationReason.EXPIRED:
-      case ReservationTerminationReason.REPLACE_EXISTING:
-      case ReservationTerminationReason.RESERVATION_CANCELED:
-        await sendAndSetConnectorStatus(
-          this,
-          reservation.connectorId,
-          ConnectorStatusEnum.Available,
-          undefined,
-          { send: reservation.connectorId !== 0 }
-        )
-        delete connector.reservation
-        break
-      default:
+  private async reconnect (): Promise<void> {
+    if (
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.wsConnectionRetryCount < this.stationInfo!.autoReconnectMaxRetries! ||
+      this.stationInfo?.autoReconnectMaxRetries === -1
+    ) {
+      ++this.wsConnectionRetryCount
+      const reconnectDelay =
+        this.stationInfo?.reconnectExponentialDelay === true
+          ? exponentialDelay(this.wsConnectionRetryCount)
+          : secondsToMilliseconds(this.getConnectionTimeout())
+      const reconnectDelayWithdraw = 1000
+      const reconnectTimeout =
+        reconnectDelay - reconnectDelayWithdraw > 0 ? reconnectDelay - reconnectDelayWithdraw : 0
+      logger.error(
+        `${this.logPrefix()} WebSocket connection retry in ${roundTo(
+          reconnectDelay,
+          2
+        ).toString()}ms, timeout ${reconnectTimeout.toString()}ms`
+      )
+      await sleep(reconnectDelay)
+      logger.error(
+        `${this.logPrefix()} WebSocket connection retry #${this.wsConnectionRetryCount.toString()}`
+      )
+      this.openWSConnection(
+        {
+          handshakeTimeout: reconnectTimeout,
+        },
+        { closeOpened: true }
+      )
+    } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) {
+      logger.error(
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        throw new BaseError(`Unknown reservation termination reason '${reason}'`)
+        `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${this.wsConnectionRetryCount.toString()}) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries?.toString()})`
+      )
     }
   }
 
-  public async reset (reason?: StopTransactionReason): Promise<void> {
-    await this.stop(reason)
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    await sleep(this.stationInfo!.resetTime!)
-    this.initialize()
-    this.start()
+  private saveAutomaticTransactionGeneratorConfiguration (): void {
+    if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true) {
+      this.saveConfiguration()
+    }
   }
 
-  public restartHeartbeat (): void {
-    // Stop heartbeat
-    this.stopHeartbeat()
-    // Start heartbeat
-    this.startHeartbeat()
+  private saveConfiguration (): void {
+    if (isNotEmptyString(this.configurationFile)) {
+      try {
+        if (!existsSync(dirname(this.configurationFile))) {
+          mkdirSync(dirname(this.configurationFile), { recursive: true })
+        }
+        const configurationFromFile = this.getConfigurationFromFile()
+        let configurationData: ChargingStationConfiguration =
+          configurationFromFile != null
+            ? clone<ChargingStationConfiguration>(configurationFromFile)
+            : {}
+        if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
+          configurationData.stationInfo = this.stationInfo
+        } else {
+          delete configurationData.stationInfo
+        }
+        if (
+          this.stationInfo?.ocppPersistentConfiguration === true &&
+          Array.isArray(this.ocppConfiguration?.configurationKey)
+        ) {
+          configurationData.configurationKey = this.ocppConfiguration.configurationKey
+        } else {
+          delete configurationData.configurationKey
+        }
+        configurationData = mergeDeepRight(
+          configurationData,
+          buildChargingStationAutomaticTransactionGeneratorConfiguration(this)
+        )
+        if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration !== true) {
+          delete configurationData.automaticTransactionGenerator
+        }
+        if (this.connectors.size > 0) {
+          configurationData.connectorsStatus = buildConnectorsStatus(this)
+        } else {
+          delete configurationData.connectorsStatus
+        }
+        if (this.evses.size > 0) {
+          configurationData.evsesStatus = buildEvsesStatus(this)
+        } else {
+          delete configurationData.evsesStatus
+        }
+        delete configurationData.configurationHash
+        const configurationHash = hash(
+          Constants.DEFAULT_HASH_ALGORITHM,
+          JSON.stringify({
+            automaticTransactionGenerator: configurationData.automaticTransactionGenerator,
+            configurationKey: configurationData.configurationKey,
+            stationInfo: configurationData.stationInfo,
+            ...(this.connectors.size > 0 && {
+              connectorsStatus: configurationData.connectorsStatus,
+            }),
+            ...(this.evses.size > 0 && {
+              evsesStatus: configurationData.evsesStatus,
+            }),
+          } satisfies ChargingStationConfiguration),
+          'hex'
+        )
+        if (this.configurationFileHash !== configurationHash) {
+          AsyncLock.runExclusive(AsyncLockType.configuration, () => {
+            configurationData.configurationHash = configurationHash
+            const measureId = `${FileType.ChargingStationConfiguration} write`
+            const beginId = PerformanceStatistics.beginMeasure(measureId)
+            writeFileSync(
+              this.configurationFile,
+              JSON.stringify(configurationData, undefined, 2),
+              'utf8'
+            )
+            PerformanceStatistics.endMeasure(measureId, beginId)
+            this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
+            this.sharedLRUCache.setChargingStationConfiguration(configurationData)
+            this.configurationFileHash = configurationHash
+          }).catch((error: unknown) => {
+            handleFileException(
+              this.configurationFile,
+              FileType.ChargingStationConfiguration,
+              error as NodeJS.ErrnoException,
+              this.logPrefix()
+            )
+          })
+        } else {
+          logger.debug(
+            `${this.logPrefix()} Not saving unchanged charging station configuration file ${
+              this.configurationFile
+            }`
+          )
+        }
+      } catch (error) {
+        handleFileException(
+          this.configurationFile,
+          FileType.ChargingStationConfiguration,
+          error as NodeJS.ErrnoException,
+          this.logPrefix()
+        )
+      }
+    } else {
+      logger.error(
+        `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`
+      )
+    }
   }
 
-  public restartMeterValues (connectorId: number, interval: number): void {
-    this.stopMeterValues(connectorId)
-    this.startMeterValues(connectorId, interval)
+  private saveConnectorsStatus (): void {
+    this.saveConfiguration()
   }
 
-  public restartWebSocketPing (): void {
-    // Stop WebSocket ping
-    this.stopWebSocketPing()
-    // Start WebSocket ping
-    this.startWebSocketPing()
+  private saveEvsesStatus (): void {
+    this.saveConfiguration()
   }
 
-  public saveOcppConfiguration (): void {
-    if (this.stationInfo?.ocppPersistentConfiguration === true) {
+  private saveStationInfo (): void {
+    if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
       this.saveConfiguration()
     }
   }
 
-  public setSupervisionUrl (url: string): void {
-    if (
-      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
-      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey)
-    ) {
-      setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey, url)
-    } else {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.stationInfo!.supervisionUrls = url
-      this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
-      this.saveStationInfo()
+  private setIntervalFlushMessageBuffer (): void {
+    if (this.flushMessageBufferSetInterval == null) {
+      this.flushMessageBufferSetInterval = setInterval(() => {
+        if (this.isWebSocketConnectionOpened() && this.inAcceptedState()) {
+          this.flushMessageBuffer()
+        }
+        if (this.messageBuffer.size === 0) {
+          this.clearIntervalFlushMessageBuffer()
+        }
+      }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL)
     }
   }
 
-  public start (): void {
-    if (!this.started) {
-      if (!this.starting) {
-        this.starting = true
-        if (this.stationInfo?.enableStatistics === true) {
-          this.performanceStatistics?.start()
-        }
-        this.openWSConnection()
-        // Monitor charging station template file
-        this.templateFileWatcher = watchJsonFile(
-          this.templateFile,
-          FileType.ChargingStationTemplate,
-          this.logPrefix(),
-          undefined,
-          (event, filename): void => {
-            if (isNotEmptyString(filename) && event === 'change') {
-              try {
-                logger.debug(
-                  `${this.logPrefix()} ${FileType.ChargingStationTemplate} ${
-                    this.templateFile
-                  } file have changed, reload`
-                )
-                this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash)
-                // 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) {
-                  this.stopAutomaticTransactionGenerator()
-                }
-                delete this.automaticTransactionGeneratorConfiguration
-                if (
-                  this.getAutomaticTransactionGeneratorConfiguration()?.enable === true &&
-                  ATGStarted === true
-                ) {
-                  this.startAutomaticTransactionGenerator(undefined, true)
-                }
-                if (this.stationInfo?.enableStatistics === true) {
-                  this.performanceStatistics?.restart()
-                } else {
-                  this.performanceStatistics?.stop()
-                }
-                // FIXME?: restart heartbeat and WebSocket ping when their interval values have changed
-              } catch (error) {
-                logger.error(
-                  `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`,
-                  error
-                )
-              }
-            }
+  private async startMessageSequence (ATGStopAbsoluteDuration?: boolean): Promise<void> {
+    if (this.stationInfo?.autoRegister === true) {
+      await this.ocppRequestService.requestHandler<
+        BootNotificationRequest,
+        BootNotificationResponse
+      >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
+        skipBufferingOnError: true,
+      })
+    }
+    // Start WebSocket ping
+    if (this.wsPingSetInterval == null) {
+      this.startWebSocketPing()
+    }
+    // Start heartbeat
+    if (this.heartbeatSetInterval == null) {
+      this.startHeartbeat()
+    }
+    // Initialize connectors status
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+            await sendAndSetConnectorStatus(
+              this,
+              connectorId,
+              getBootConnectorStatus(this, connectorId, connectorStatus),
+              evseId
+            )
           }
-        )
-        this.started = true
-        this.emit(ChargingStationEvents.started)
-        this.starting = false
-      } else {
-        logger.warn(`${this.logPrefix()} Charging station is already starting...`)
+        }
       }
     } else {
-      logger.warn(`${this.logPrefix()} Charging station is already started...`)
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0) {
+          await sendAndSetConnectorStatus(
+            this,
+            connectorId,
+            getBootConnectorStatus(
+              this,
+              connectorId,
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              this.getConnectorStatus(connectorId)!
+            )
+          )
+        }
+      }
+    }
+    if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
+      await this.ocppRequestService.requestHandler<
+        FirmwareStatusNotificationRequest,
+        FirmwareStatusNotificationResponse
+      >(this, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+        status: FirmwareStatus.Installed,
+      })
+      this.stationInfo.firmwareStatus = FirmwareStatus.Installed
     }
-  }
 
-  public startAutomaticTransactionGenerator (
-    connectorIds?: number[],
-    stopAbsoluteDuration?: boolean
-  ): void {
-    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this)
-    if (isNotEmptyArray(connectorIds)) {
-      for (const connectorId of connectorIds) {
-        this.automaticTransactionGenerator?.startConnector(connectorId, stopAbsoluteDuration)
-      }
-    } else {
-      this.automaticTransactionGenerator?.start(stopAbsoluteDuration)
+    // Start the ATG
+    if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
+      this.startAutomaticTransactionGenerator(undefined, ATGStopAbsoluteDuration)
     }
-    this.saveAutomaticTransactionGeneratorConfiguration()
-    this.emit(ChargingStationEvents.updated)
+    this.flushMessageBuffer()
   }
 
-  public startHeartbeat (): void {
-    const heartbeatInterval = this.getHeartbeatInterval()
-    if (heartbeatInterval > 0 && this.heartbeatSetInterval == null) {
-      this.heartbeatSetInterval = setInterval(() => {
-        this.ocppRequestService
-          .requestHandler<HeartbeatRequest, HeartbeatResponse>(this, RequestCommand.HEARTBEAT)
-          .catch((error: unknown) => {
-            logger.error(
-              `${this.logPrefix()} Error while sending '${RequestCommand.HEARTBEAT}':`,
-              error
-            )
-          })
-      }, heartbeatInterval)
+  private startWebSocketPing (): void {
+    const webSocketPingInterval = this.getWebSocketPingInterval()
+    if (webSocketPingInterval > 0 && this.wsPingSetInterval == null) {
+      this.wsPingSetInterval = setInterval(() => {
+        if (this.isWebSocketConnectionOpened()) {
+          this.wsConnection?.ping()
+        }
+      }, secondsToMilliseconds(webSocketPingInterval))
       logger.info(
-        `${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds(
-          heartbeatInterval
+        `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
+          webSocketPingInterval
         )}`
       )
-    } else if (this.heartbeatSetInterval != null) {
+    } else if (this.wsPingSetInterval != null) {
       logger.info(
-        `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds(
-          heartbeatInterval
+        `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds(
+          webSocketPingInterval
         )}`
       )
     } else {
       logger.error(
-        `${this.logPrefix()} Heartbeat interval set to ${heartbeatInterval.toString()}, not starting the heartbeat`
+        `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval.toString()}, not starting the WebSocket ping`
       )
     }
   }
 
-  public startMeterValues (connectorId: number, interval: number): void {
-    if (connectorId === 0) {
-      logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()}`
-      )
-      return
-    }
-    const connectorStatus = this.getConnectorStatus(connectorId)
-    if (connectorStatus == null) {
-      logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on non existing connector id
-          ${connectorId.toString()}`
-      )
-      return
-    }
-    if (connectorStatus.transactionStarted === false) {
-      logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()} with no transaction started`
-      )
-      return
-    } else if (
-      connectorStatus.transactionStarted === true &&
-      connectorStatus.transactionId == null
-    ) {
-      logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()} with no transaction id`
-      )
-      return
-    }
-    if (interval > 0) {
-      connectorStatus.transactionSetInterval = setInterval(() => {
-        const meterValue = buildMeterValue(
-          this,
-          connectorId,
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          connectorStatus.transactionId!,
-          interval
-        )
-        this.ocppRequestService
-          .requestHandler<MeterValuesRequest, MeterValuesResponse>(
-            this,
-            RequestCommand.METER_VALUES,
-            {
-              connectorId,
-              meterValue: [meterValue],
-              transactionId: connectorStatus.transactionId,
-            }
-          )
-          .catch((error: unknown) => {
-            logger.error(
-              `${this.logPrefix()} Error while sending '${RequestCommand.METER_VALUES}':`,
-              error
-            )
-          })
-      }, interval)
-    } else {
-      logger.error(
-        `${this.logPrefix()} Charging station ${
-          StandardParametersKey.MeterValueSampleInterval
-        } configuration set to ${interval.toString()}, not sending MeterValues`
-      )
+  private stopHeartbeat (): void {
+    if (this.heartbeatSetInterval != null) {
+      clearInterval(this.heartbeatSetInterval)
+      delete this.heartbeatSetInterval
     }
   }
 
-  public async stop (
+  private async stopMessageSequence (
     reason?: StopTransactionReason,
-    stopTransactions = this.stationInfo?.stopTransactionsOnStopped
+    stopTransactions?: boolean
   ): Promise<void> {
-    if (this.started) {
-      if (!this.stopping) {
-        this.stopping = true
-        await this.stopMessageSequence(reason, stopTransactions)
-        this.closeWSConnection()
-        if (this.stationInfo?.enableStatistics === true) {
-          this.performanceStatistics?.stop()
+    this.internalStopMessageSequence()
+    // Stop ongoing transactions
+    stopTransactions && (await this.stopRunningTransactions(reason))
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+            await sendAndSetConnectorStatus(
+              this,
+              connectorId,
+              ConnectorStatusEnum.Unavailable,
+              evseId
+            )
+            delete connectorStatus.status
+          }
         }
-        this.templateFileWatcher?.close()
-        delete this.bootNotificationResponse
-        this.started = false
-        this.saveConfiguration()
-        this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
-        this.emit(ChargingStationEvents.stopped)
-        this.stopping = false
-      } else {
-        logger.warn(`${this.logPrefix()} Charging station is already stopping...`)
       }
     } else {
-      logger.warn(`${this.logPrefix()} Charging station is already stopped...`)
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0) {
+          await sendAndSetConnectorStatus(this, connectorId, ConnectorStatusEnum.Unavailable)
+          delete this.getConnectorStatus(connectorId)?.status
+        }
+      }
     }
   }
 
-  public stopAutomaticTransactionGenerator (connectorIds?: number[]): void {
-    if (isNotEmptyArray(connectorIds)) {
-      for (const connectorId of connectorIds) {
-        this.automaticTransactionGenerator?.stopConnector(connectorId)
+  private async stopRunningTransactions (reason?: StopTransactionReason): Promise<void> {
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId === 0) {
+          continue
+        }
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (connectorStatus.transactionStarted === true) {
+            await this.stopTransactionOnConnector(connectorId, reason)
+          }
+        }
       }
     } else {
-      this.automaticTransactionGenerator?.stop()
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+          await this.stopTransactionOnConnector(connectorId, reason)
+        }
+      }
     }
-    this.saveAutomaticTransactionGeneratorConfiguration()
-    this.emit(ChargingStationEvents.updated)
   }
 
-  public stopMeterValues (connectorId: number): void {
-    const connectorStatus = this.getConnectorStatus(connectorId)
-    if (connectorStatus?.transactionSetInterval != null) {
-      clearInterval(connectorStatus.transactionSetInterval)
+  private stopWebSocketPing (): void {
+    if (this.wsPingSetInterval != null) {
+      clearInterval(this.wsPingSetInterval)
+      delete this.wsPingSetInterval
     }
   }
 
-  public async stopTransactionOnConnector (
-    connectorId: number,
-    reason?: StopTransactionReason
-  ): Promise<StopTransactionResponse> {
-    const transactionId = this.getConnectorStatus(connectorId)?.transactionId
-    if (
-      this.stationInfo?.beginEndMeterValues === true &&
-      this.stationInfo.ocppStrictCompliance === true &&
-      this.stationInfo.outOfOrderEndMeterValues === false
-    ) {
-      const transactionEndMeterValue = buildTransactionEndMeterValue(
-        this,
-        connectorId,
-        this.getEnergyActiveImportRegisterByTransactionId(transactionId)
-      )
-      await this.ocppRequestService.requestHandler<MeterValuesRequest, MeterValuesResponse>(
-        this,
-        RequestCommand.METER_VALUES,
-        {
-          connectorId,
-          meterValue: [transactionEndMeterValue],
-          transactionId,
-        }
-      )
+  private terminateWSConnection (): void {
+    if (this.isWebSocketConnectionOpened()) {
+      this.wsConnection?.terminate()
+      this.wsConnection = null
     }
-    return await this.ocppRequestService.requestHandler<
-      Partial<StopTransactionRequest>,
-      StopTransactionResponse
-    >(this, RequestCommand.STOP_TRANSACTION, {
-      meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true),
-      transactionId,
-      ...(reason != null && { reason }),
-    })
-  }
-
-  public get hasEvses (): boolean {
-    return this.connectors.size === 0 && this.evses.size > 0
-  }
-
-  public get wsConnectionUrl (): URL {
-    const wsConnectionBaseUrlStr = `${
-      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
-      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
-      isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value)
-        ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value
-        : this.configuredSupervisionUrl.href
-    }`
-    return new URL(
-      `${wsConnectionBaseUrlStr}${
-        !wsConnectionBaseUrlStr.endsWith('/') ? '/' : ''
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-      }${this.stationInfo?.chargingStationId}`
-    )
   }
 }
index b182cc4f1678888e0a812e118ab3a7d0ee29c86d..037f75ebd6e2bad263d60a2c4bd833aa9a3bde54 100644 (file)
@@ -3,6 +3,10 @@ import type { ChargingStation } from './ChargingStation.js'
 
 import { logger } from '../utils/index.js'
 
+interface AddConfigurationKeyParams {
+  overwrite?: boolean
+  save?: boolean
+}
 interface ConfigurationKeyOptions {
   readonly?: boolean
   reboot?: boolean
@@ -12,10 +16,6 @@ interface DeleteConfigurationKeyParams {
   caseInsensitive?: boolean
   save?: boolean
 }
-interface AddConfigurationKeyParams {
-  overwrite?: boolean
-  save?: boolean
-}
 
 export const getConfigurationKey = (
   chargingStation: ChargingStation,
index b1993ab84d51fd9701241bbee552d2b7dc309be9..c10a5e0ff995ded350716a6de2cafb645620c7ad 100644 (file)
@@ -23,10 +23,6 @@ export class IdTagsCache {
   private readonly idTagsCaches: Map<string, IdTagsCacheValueType>
   private readonly idTagsCachesAddressableIndexes: Map<string, number>
 
-  private readonly logPrefix = (file: string): string => {
-    return logPrefix(` Id tags cache for id tags file '${file}' |`)
-  }
-
   private constructor () {
     this.idTagsCaches = new Map<string, IdTagsCacheValueType>()
     this.idTagsCachesAddressableIndexes = new Map<string, number>()
@@ -39,6 +35,52 @@ export class IdTagsCache {
     return IdTagsCache.instance
   }
 
+  public deleteIdTags (file: string): boolean {
+    return this.deleteIdTagsCache(file) && this.deleteIdTagsCacheIndexes(file)
+  }
+
+  /**
+   * Gets one idtag from the cache given the distribution
+   * Must be called after checking the cache is not an empty array
+   * @param distribution -
+   * @param chargingStation -
+   * @param connectorId -
+   * @returns string
+   */
+  public getIdTag (
+    distribution: IdTagDistribution,
+    chargingStation: ChargingStation,
+    connectorId: number
+  ): string {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const hashId = chargingStation.stationInfo!.hashId
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const idTagsFile = getIdTagsFile(chargingStation.stationInfo!)!
+    switch (distribution) {
+      case IdTagDistribution.CONNECTOR_AFFINITY:
+        return this.getConnectorAffinityIdTag(chargingStation, connectorId)
+      case IdTagDistribution.RANDOM:
+        return this.getRandomIdTag(hashId, idTagsFile)
+      case IdTagDistribution.ROUND_ROBIN:
+        return this.getRoundRobinIdTag(hashId, idTagsFile)
+      default:
+        return this.getRoundRobinIdTag(hashId, idTagsFile)
+    }
+  }
+
+  /**
+   * Gets all idtags from the cache
+   * Must be called after checking the cache is not an empty array
+   * @param file -
+   * @returns string[] | undefined
+   */
+  public getIdTags (file: string): string[] | undefined {
+    if (!this.hasIdTagsCache(file)) {
+      this.setIdTagsCache(file, this.getIdTagsFromFile(file))
+    }
+    return this.getIdTagsCache(file)
+  }
+
   private deleteIdTagsCache (file: string): boolean {
     this.idTagsCaches.get(file)?.idTagsFileWatcher?.close()
     return this.idTagsCaches.delete(file)
@@ -125,6 +167,10 @@ export class IdTagsCache {
     return this.idTagsCaches.has(file)
   }
 
+  private readonly logPrefix = (file: string): string => {
+    return logPrefix(` Id tags cache for id tags file '${file}' |`)
+  }
+
   private setIdTagsCache (file: string, idTags: string[]): Map<string, IdTagsCacheValueType> {
     return this.idTagsCaches.set(file, {
       idTags,
@@ -156,50 +202,4 @@ export class IdTagsCache {
       ),
     })
   }
-
-  public deleteIdTags (file: string): boolean {
-    return this.deleteIdTagsCache(file) && this.deleteIdTagsCacheIndexes(file)
-  }
-
-  /**
-   * Gets one idtag from the cache given the distribution
-   * Must be called after checking the cache is not an empty array
-   * @param distribution -
-   * @param chargingStation -
-   * @param connectorId -
-   * @returns string
-   */
-  public getIdTag (
-    distribution: IdTagDistribution,
-    chargingStation: ChargingStation,
-    connectorId: number
-  ): string {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const hashId = chargingStation.stationInfo!.hashId
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const idTagsFile = getIdTagsFile(chargingStation.stationInfo!)!
-    switch (distribution) {
-      case IdTagDistribution.CONNECTOR_AFFINITY:
-        return this.getConnectorAffinityIdTag(chargingStation, connectorId)
-      case IdTagDistribution.RANDOM:
-        return this.getRandomIdTag(hashId, idTagsFile)
-      case IdTagDistribution.ROUND_ROBIN:
-        return this.getRoundRobinIdTag(hashId, idTagsFile)
-      default:
-        return this.getRoundRobinIdTag(hashId, idTagsFile)
-    }
-  }
-
-  /**
-   * Gets all idtags from the cache
-   * Must be called after checking the cache is not an empty array
-   * @param file -
-   * @returns string[] | undefined
-   */
-  public getIdTags (file: string): string[] | undefined {
-    if (!this.hasIdTagsCache(file)) {
-      this.setIdTagsCache(file, this.getIdTagsFromFile(file))
-    }
-    return this.getIdTagsCache(file)
-  }
 }
index 1023ab8cf4013e2eb7ac2dcc17f7545e4d95ff70..12cbc43075d88173346f5f57531f6abfe3aab2d8 100644 (file)
@@ -32,45 +32,6 @@ export class SharedLRUCache {
     return SharedLRUCache.instance
   }
 
-  private delete (key: string): void {
-    this.lruCache.delete(key)
-  }
-
-  private get (key: string): CacheValueType | undefined {
-    return this.lruCache.get(key)
-  }
-
-  private getChargingStationConfigurationKey (hash: string): string {
-    return `${CacheType.chargingStationConfiguration}${hash}`
-  }
-
-  private getChargingStationTemplateKey (hash: string): string {
-    return `${CacheType.chargingStationTemplate}${hash}`
-  }
-
-  private has (key: string): boolean {
-    return this.lruCache.has(key)
-  }
-
-  private isChargingStationConfigurationCacheable (
-    chargingStationConfiguration: ChargingStationConfiguration
-  ): boolean {
-    return (
-      chargingStationConfiguration.configurationKey != null &&
-      chargingStationConfiguration.stationInfo != null &&
-      chargingStationConfiguration.automaticTransactionGenerator != null &&
-      chargingStationConfiguration.configurationHash != null &&
-      isNotEmptyArray(chargingStationConfiguration.configurationKey) &&
-      !isEmpty(chargingStationConfiguration.stationInfo) &&
-      !isEmpty(chargingStationConfiguration.automaticTransactionGenerator) &&
-      isNotEmptyString(chargingStationConfiguration.configurationHash)
-    )
-  }
-
-  private set (key: string, value: CacheValueType): void {
-    this.lruCache.set(key, value)
-  }
-
   public clear (): void {
     this.lruCache.clear()
   }
@@ -124,4 +85,43 @@ export class SharedLRUCache {
       chargingStationTemplate
     )
   }
+
+  private delete (key: string): void {
+    this.lruCache.delete(key)
+  }
+
+  private get (key: string): CacheValueType | undefined {
+    return this.lruCache.get(key)
+  }
+
+  private getChargingStationConfigurationKey (hash: string): string {
+    return `${CacheType.chargingStationConfiguration}${hash}`
+  }
+
+  private getChargingStationTemplateKey (hash: string): string {
+    return `${CacheType.chargingStationTemplate}${hash}`
+  }
+
+  private has (key: string): boolean {
+    return this.lruCache.has(key)
+  }
+
+  private isChargingStationConfigurationCacheable (
+    chargingStationConfiguration: ChargingStationConfiguration
+  ): boolean {
+    return (
+      chargingStationConfiguration.configurationKey != null &&
+      chargingStationConfiguration.stationInfo != null &&
+      chargingStationConfiguration.automaticTransactionGenerator != null &&
+      chargingStationConfiguration.configurationHash != null &&
+      isNotEmptyArray(chargingStationConfiguration.configurationKey) &&
+      !isEmpty(chargingStationConfiguration.stationInfo) &&
+      !isEmpty(chargingStationConfiguration.automaticTransactionGenerator) &&
+      isNotEmptyString(chargingStationConfiguration.configurationHash)
+    )
+  }
+
+  private set (key: string, value: CacheValueType): void {
+    this.lruCache.set(key, value)
+  }
 }
index e70e128db294374d50f8dde1cc89372f6e2b3a59..a0eea259f096db68baad9894ea8c23e5c9ffe8ac 100644 (file)
@@ -46,6 +46,11 @@ import { WorkerBroadcastChannel } from './WorkerBroadcastChannel.js'
 
 const moduleName = 'ChargingStationWorkerBroadcastChannel'
 
+type CommandHandler = (
+  requestPayload?: BroadcastChannelRequestPayload
+  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
+) => CommandResponse | Promise<CommandResponse | void> | void
+
 type CommandResponse =
   | AuthorizeResponse
   | BootNotificationResponse
@@ -55,11 +60,6 @@ type CommandResponse =
   | StartTransactionResponse
   | StopTransactionResponse
 
-type CommandHandler = (
-  requestPayload?: BroadcastChannelRequestPayload
-  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
-) => CommandResponse | Promise<CommandResponse | void> | void
-
 export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
   private readonly chargingStation: ChargingStation
   private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>
index 66c4c0de6aa266580a9be0b77250dd62e2acabe9..cb371457816222c32a53038c17f5318b67577385 100644 (file)
@@ -12,14 +12,14 @@ import { logger, logPrefix, validateUUID } from '../../utils/index.js'
 const moduleName = 'WorkerBroadcastChannel'
 
 export abstract class WorkerBroadcastChannel extends BroadcastChannel {
-  private readonly logPrefix = (modName: string, methodName: string): string => {
-    return logPrefix(` Worker Broadcast Channel | ${modName}.${methodName}:`)
-  }
-
   protected constructor () {
     super('worker')
   }
 
+  public sendRequest (request: BroadcastChannelRequest): void {
+    this.postMessage(request)
+  }
+
   protected isRequest (message: JsonType[]): boolean {
     return Array.isArray(message) && message.length === 3
   }
@@ -54,7 +54,7 @@ export abstract class WorkerBroadcastChannel extends BroadcastChannel {
     return messageEvent
   }
 
-  public sendRequest (request: BroadcastChannelRequest): void {
-    this.postMessage(request)
+  private readonly logPrefix = (modName: string, methodName: string): string => {
+    return logPrefix(` Worker Broadcast Channel | ${modName}.${methodName}:`)
   }
 }
index bcc96e6e16ce624c5199cec550a9abf7baf97165..ec1631589173d9e9a03e3f881c5a3321b89fce87 100644 (file)
@@ -571,6 +571,94 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     this.validatePayload = this.validatePayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
+    chargingStation: ChargingStation,
+    messageId: string,
+    commandName: OCPP16IncomingRequestCommand,
+    commandPayload: ReqType
+  ): Promise<void> {
+    let response: ResType
+    if (
+      chargingStation.stationInfo?.ocppStrictCompliance === true &&
+      chargingStation.inPendingState() &&
+      (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
+        commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
+    ) {
+      throw new OCPPError(
+        ErrorType.SECURITY_ERROR,
+        `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
+          commandPayload,
+          undefined,
+          2
+        )} while the charging station is in pending state on the central server`,
+        commandName,
+        commandPayload
+      )
+    }
+    if (
+      chargingStation.isRegistered() ||
+      (chargingStation.stationInfo?.ocppStrictCompliance === false &&
+        chargingStation.inUnknownState())
+    ) {
+      if (
+        this.incomingRequestHandlers.has(commandName) &&
+        OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
+      ) {
+        try {
+          this.validatePayload(chargingStation, commandName, commandPayload)
+          // Call the method to build the response
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
+          if (isAsyncFunction(incomingRequestHandler)) {
+            response = (await incomingRequestHandler(chargingStation, commandPayload)) as ResType
+          } else {
+            response = incomingRequestHandler(chargingStation, commandPayload) as ResType
+          }
+        } catch (error) {
+          // Log
+          logger.error(
+            `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
+            error
+          )
+          throw error
+        }
+      } else {
+        // Throw exception
+        throw new OCPPError(
+          ErrorType.NOT_IMPLEMENTED,
+          `${commandName} is not implemented to handle request PDU ${JSON.stringify(
+            commandPayload,
+            undefined,
+            2
+          )}`,
+          commandName,
+          commandPayload
+        )
+      }
+    } else {
+      throw new OCPPError(
+        ErrorType.SECURITY_ERROR,
+        `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
+          commandPayload,
+          undefined,
+          2
+        )} while the charging station is not registered on the central server`,
+        commandName,
+        commandPayload
+      )
+    }
+    // Send the built response
+    await chargingStation.ocppRequestService.sendResponse(
+      chargingStation,
+      messageId,
+      response,
+      commandName
+    )
+    // Emit command name event to allow delayed handling
+    this.emit(commandName, chargingStation, commandPayload, response)
+  }
+
   private async handleRequestCancelReservation (
     chargingStation: ChargingStation,
     commandPayload: OCPP16CancelReservationRequest
@@ -1245,16 +1333,16 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     try {
       await removeExpiredReservations(chargingStation)
       switch (connectorStatus.status) {
-        case OCPP16ChargePointStatus.Faulted:
-          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
-          break
-        case OCPP16ChargePointStatus.Preparing:
         case OCPP16ChargePointStatus.Charging:
+        case OCPP16ChargePointStatus.Finishing:
+        case OCPP16ChargePointStatus.Preparing:
         case OCPP16ChargePointStatus.SuspendedEV:
         case OCPP16ChargePointStatus.SuspendedEVSE:
-        case OCPP16ChargePointStatus.Finishing:
           response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
           break
+        case OCPP16ChargePointStatus.Faulted:
+          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
+          break
         case OCPP16ChargePointStatus.Unavailable:
           response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
           break
@@ -1694,92 +1782,4 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     )
     return false
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
-  public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
-    chargingStation: ChargingStation,
-    messageId: string,
-    commandName: OCPP16IncomingRequestCommand,
-    commandPayload: ReqType
-  ): Promise<void> {
-    let response: ResType
-    if (
-      chargingStation.stationInfo?.ocppStrictCompliance === true &&
-      chargingStation.inPendingState() &&
-      (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
-        commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
-    ) {
-      throw new OCPPError(
-        ErrorType.SECURITY_ERROR,
-        `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
-          commandPayload,
-          undefined,
-          2
-        )} while the charging station is in pending state on the central server`,
-        commandName,
-        commandPayload
-      )
-    }
-    if (
-      chargingStation.isRegistered() ||
-      (chargingStation.stationInfo?.ocppStrictCompliance === false &&
-        chargingStation.inUnknownState())
-    ) {
-      if (
-        this.incomingRequestHandlers.has(commandName) &&
-        OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
-      ) {
-        try {
-          this.validatePayload(chargingStation, commandName, commandPayload)
-          // Call the method to build the response
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
-          if (isAsyncFunction(incomingRequestHandler)) {
-            response = (await incomingRequestHandler(chargingStation, commandPayload)) as ResType
-          } else {
-            response = incomingRequestHandler(chargingStation, commandPayload) as ResType
-          }
-        } catch (error) {
-          // Log
-          logger.error(
-            `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
-            error
-          )
-          throw error
-        }
-      } else {
-        // Throw exception
-        throw new OCPPError(
-          ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle request PDU ${JSON.stringify(
-            commandPayload,
-            undefined,
-            2
-          )}`,
-          commandName,
-          commandPayload
-        )
-      }
-    } else {
-      throw new OCPPError(
-        ErrorType.SECURITY_ERROR,
-        `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
-          commandPayload,
-          undefined,
-          2
-        )} while the charging station is not registered on the central server`,
-        commandName,
-        commandPayload
-      )
-    }
-    // Send the built response
-    await chargingStation.ocppRequestService.sendResponse(
-      chargingStation,
-      messageId,
-      response,
-      commandName
-    )
-    // Emit command name event to allow delayed handling
-    this.emit(commandName, chargingStation, commandPayload, response)
-  }
 }
index 655ca809f5d52aea945d5497aacb96a1da67552f..5c096532df4a5ebc3e8db6a3f20ba4b2e6282b1e 100644 (file)
@@ -145,6 +145,42 @@ export class OCPP16RequestService extends OCPPRequestService {
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: OCPP16RequestCommand,
+    commandParams?: RequestType,
+    params?: RequestParams
+  ): Promise<ResponseType> {
+    // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
+    if (OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
+      // Pre request actions hook
+      switch (commandName) {
+        case OCPP16RequestCommand.START_TRANSACTION:
+          await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+            chargingStation,
+            (commandParams as OCPP16StartTransactionRequest).connectorId,
+            OCPP16ChargePointStatus.Preparing
+          )
+          break
+      }
+      return (await this.sendMessage(
+        chargingStation,
+        generateUUID(),
+        this.buildRequestPayload<RequestType>(chargingStation, commandName, commandParams),
+        commandName,
+        params
+      )) as ResponseType
+    }
+    // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+    throw new OCPPError(
+      ErrorType.NOT_SUPPORTED,
+      `Unsupported OCPP command ${commandName}`,
+      commandName,
+      commandParams
+    )
+  }
+
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   private buildRequestPayload<Request extends JsonType>(
     chargingStation: ChargingStation,
@@ -230,40 +266,4 @@ export class OCPP16RequestService extends OCPPRequestService {
         )
     }
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
-  public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
-    chargingStation: ChargingStation,
-    commandName: OCPP16RequestCommand,
-    commandParams?: RequestType,
-    params?: RequestParams
-  ): Promise<ResponseType> {
-    // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
-    if (OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
-      // Pre request actions hook
-      switch (commandName) {
-        case OCPP16RequestCommand.START_TRANSACTION:
-          await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-            chargingStation,
-            (commandParams as OCPP16StartTransactionRequest).connectorId,
-            OCPP16ChargePointStatus.Preparing
-          )
-          break
-      }
-      return (await this.sendMessage(
-        chargingStation,
-        generateUUID(),
-        this.buildRequestPayload<RequestType>(chargingStation, commandName, commandParams),
-        commandName,
-        params
-      )) as ResponseType
-    }
-    // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
-    throw new OCPPError(
-      ErrorType.NOT_SUPPORTED,
-      `Unsupported OCPP command ${commandName}`,
-      commandName,
-      commandParams
-    )
-  }
 }
index 06fc0cd4096bcc97064d5bd5ce48513c9930741b..bac3f9fa9605cc72fca53c1bd786092ada849feb 100644 (file)
@@ -59,13 +59,14 @@ import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
 const moduleName = 'OCPP16ResponseService'
 
 export class OCPP16ResponseService extends OCPPResponseService {
-  protected payloadValidateFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
-  private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>
   public incomingRequestResponsePayloadValidateFunctions: Map<
     OCPP16IncomingRequestCommand,
     ValidateFunction<JsonType>
   >
 
+  protected payloadValidateFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
+  private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>
+
   public constructor () {
     // if (new.target.name === moduleName) {
     //   throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
@@ -378,6 +379,67 @@ export class OCPP16ResponseService extends OCPPResponseService {
     this.validatePayload = this.validatePayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: OCPP16RequestCommand,
+    payload: ResType,
+    requestPayload: ReqType
+  ): Promise<void> {
+    if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
+      if (
+        this.responseHandlers.has(commandName) &&
+        OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
+      ) {
+        try {
+          this.validatePayload(chargingStation, commandName, payload)
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          const responseHandler = this.responseHandlers.get(commandName)!
+          if (isAsyncFunction(responseHandler)) {
+            await responseHandler(chargingStation, payload, requestPayload)
+          } else {
+            ;(
+              responseHandler as (
+                chargingStation: ChargingStation,
+                payload: JsonType,
+                requestPayload?: JsonType
+              ) => void
+            )(chargingStation, payload, requestPayload)
+          }
+        } catch (error) {
+          logger.error(
+            `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
+            error
+          )
+          throw error
+        }
+      } else {
+        // Throw exception
+        throw new OCPPError(
+          ErrorType.NOT_IMPLEMENTED,
+          `${commandName} is not implemented to handle response PDU ${JSON.stringify(
+            payload,
+            undefined,
+            2
+          )}`,
+          commandName,
+          payload
+        )
+      }
+    } else {
+      throw new OCPPError(
+        ErrorType.SECURITY_ERROR,
+        `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
+          payload,
+          undefined,
+          2
+        )} while the charging station is not registered on the central server`,
+        commandName,
+        payload
+      )
+    }
+  }
+
   private handleResponseAuthorize (
     chargingStation: ChargingStation,
     payload: OCPP16AuthorizeResponse,
@@ -781,65 +843,4 @@ export class OCPP16ResponseService extends OCPPResponseService {
     )
     return false
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
-  public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
-    chargingStation: ChargingStation,
-    commandName: OCPP16RequestCommand,
-    payload: ResType,
-    requestPayload: ReqType
-  ): Promise<void> {
-    if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
-      if (
-        this.responseHandlers.has(commandName) &&
-        OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
-      ) {
-        try {
-          this.validatePayload(chargingStation, commandName, payload)
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          const responseHandler = this.responseHandlers.get(commandName)!
-          if (isAsyncFunction(responseHandler)) {
-            await responseHandler(chargingStation, payload, requestPayload)
-          } else {
-            ;(
-              responseHandler as (
-                chargingStation: ChargingStation,
-                payload: JsonType,
-                requestPayload?: JsonType
-              ) => void
-            )(chargingStation, payload, requestPayload)
-          }
-        } catch (error) {
-          logger.error(
-            `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
-            error
-          )
-          throw error
-        }
-      } else {
-        // Throw exception
-        throw new OCPPError(
-          ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle response PDU ${JSON.stringify(
-            payload,
-            undefined,
-            2
-          )}`,
-          commandName,
-          payload
-        )
-      }
-    } else {
-      throw new OCPPError(
-        ErrorType.SECURITY_ERROR,
-        `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
-          payload,
-          undefined,
-          2
-        )} while the charging station is not registered on the central server`,
-        commandName,
-        payload
-      )
-    }
-  }
 }
index 9582e171d707b9b5e6fcc44a3cdd1bc458f778bc..010bd5b20ecd7be8b13ded1f2e079f1cc317c691 100644 (file)
@@ -43,6 +43,43 @@ import { OCPPServiceUtils } from '../OCPPServiceUtils.js'
 import { OCPP16Constants } from './OCPP16Constants.js'
 
 export class OCPP16ServiceUtils extends OCPPServiceUtils {
+  public static buildTransactionBeginMeterValue (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    meterStart: number | undefined
+  ): OCPP16MeterValue {
+    const meterValue: OCPP16MeterValue = {
+      sampledValue: [],
+      timestamp: new Date(),
+    }
+    // Energy.Active.Import.Register measurand (default)
+    const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
+      chargingStation,
+      connectorId
+    )
+    const unitDivider =
+      sampledValueTemplate?.unit === OCPP16MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
+    meterValue.sampledValue.push(
+      OCPP16ServiceUtils.buildSampledValue(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        sampledValueTemplate!,
+        roundTo((meterStart ?? 0) / unitDivider, 4),
+        OCPP16MeterValueContext.TRANSACTION_BEGIN
+      )
+    )
+    return meterValue
+  }
+
+  public static buildTransactionDataMeterValues (
+    transactionBeginMeterValue: OCPP16MeterValue,
+    transactionEndMeterValue: OCPP16MeterValue
+  ): OCPP16MeterValue[] {
+    const meterValues: OCPP16MeterValue[] = []
+    meterValues.push(transactionBeginMeterValue)
+    meterValues.push(transactionEndMeterValue)
+    return meterValues
+  }
+
   public static changeAvailability = async (
     chargingStation: ChargingStation,
     connectorIds: number[],
@@ -74,6 +111,22 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
   }
 
+  public static checkFeatureProfile (
+    chargingStation: ChargingStation,
+    featureProfile: OCPP16SupportedFeatureProfiles,
+    command: OCPP16IncomingRequestCommand | OCPP16RequestCommand
+  ): boolean {
+    if (hasFeatureProfile(chargingStation, featureProfile) === false) {
+      logger.warn(
+        `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
+          OCPP16StandardParametersKey.SupportedFeatureProfiles
+        } in configuration`
+      )
+      return false
+    }
+    return true
+  }
+
   public static clearChargingProfiles = (
     chargingStation: ChargingStation,
     commandPayload: OCPP16ClearChargingProfileRequest,
@@ -115,81 +168,6 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return clearedCP
   }
 
-  private static readonly composeChargingSchedule = (
-    chargingSchedule: OCPP16ChargingSchedule,
-    compositeInterval: Interval
-  ): OCPP16ChargingSchedule | undefined => {
-    const chargingScheduleInterval: Interval = {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      start: chargingSchedule.startSchedule!,
-    }
-    if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
-      chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod)
-      if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
-        return {
-          ...chargingSchedule,
-          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod
-            .filter((schedulePeriod, index) => {
-              if (
-                isWithinInterval(
-                  addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
-                  compositeInterval
-                )
-              ) {
-                return true
-              }
-              if (
-                index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
-                !isWithinInterval(
-                  addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
-                  compositeInterval
-                ) &&
-                isWithinInterval(
-                  addSeconds(
-                    chargingScheduleInterval.start,
-                    chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod
-                  ),
-                  compositeInterval
-                )
-              ) {
-                return true
-              }
-              return false
-            })
-            .map((schedulePeriod, index) => {
-              if (index === 0 && schedulePeriod.startPeriod !== 0) {
-                schedulePeriod.startPeriod = 0
-              }
-              return schedulePeriod
-            }),
-          duration: differenceInSeconds(
-            chargingScheduleInterval.end,
-            compositeInterval.start as Date
-          ),
-          startSchedule: compositeInterval.start as Date,
-        }
-      }
-      if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
-        return {
-          ...chargingSchedule,
-          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter(schedulePeriod =>
-            isWithinInterval(
-              addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
-              compositeInterval
-            )
-          ),
-          duration: differenceInSeconds(
-            compositeInterval.end as Date,
-            chargingScheduleInterval.start
-          ),
-        }
-      }
-      return chargingSchedule
-    }
-  }
-
   public static composeChargingSchedules = (
     chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
     chargingScheduleLower: OCPP16ChargingSchedule | undefined,
@@ -425,78 +403,6 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return false
   }
 
-  public static remoteStopTransaction = async (
-    chargingStation: ChargingStation,
-    connectorId: number
-  ): Promise<GenericResponse> => {
-    await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-      chargingStation,
-      connectorId,
-      OCPP16ChargePointStatus.Finishing
-    )
-    const stopResponse = await chargingStation.stopTransactionOnConnector(
-      connectorId,
-      OCPP16StopTransactionReason.REMOTE
-    )
-    if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
-      return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
-    }
-    return OCPP16Constants.OCPP_RESPONSE_REJECTED
-  }
-
-  public static buildTransactionBeginMeterValue (
-    chargingStation: ChargingStation,
-    connectorId: number,
-    meterStart: number | undefined
-  ): OCPP16MeterValue {
-    const meterValue: OCPP16MeterValue = {
-      sampledValue: [],
-      timestamp: new Date(),
-    }
-    // Energy.Active.Import.Register measurand (default)
-    const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
-      chargingStation,
-      connectorId
-    )
-    const unitDivider =
-      sampledValueTemplate?.unit === OCPP16MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
-    meterValue.sampledValue.push(
-      OCPP16ServiceUtils.buildSampledValue(
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        sampledValueTemplate!,
-        roundTo((meterStart ?? 0) / unitDivider, 4),
-        OCPP16MeterValueContext.TRANSACTION_BEGIN
-      )
-    )
-    return meterValue
-  }
-
-  public static buildTransactionDataMeterValues (
-    transactionBeginMeterValue: OCPP16MeterValue,
-    transactionEndMeterValue: OCPP16MeterValue
-  ): OCPP16MeterValue[] {
-    const meterValues: OCPP16MeterValue[] = []
-    meterValues.push(transactionBeginMeterValue)
-    meterValues.push(transactionEndMeterValue)
-    return meterValues
-  }
-
-  public static checkFeatureProfile (
-    chargingStation: ChargingStation,
-    featureProfile: OCPP16SupportedFeatureProfiles,
-    command: OCPP16IncomingRequestCommand | OCPP16RequestCommand
-  ): boolean {
-    if (hasFeatureProfile(chargingStation, featureProfile) === false) {
-      logger.warn(
-        `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
-          OCPP16StandardParametersKey.SupportedFeatureProfiles
-        } in configuration`
-      )
-      return false
-    }
-    return true
-  }
-
   public static isConfigurationKeyVisible (key: ConfigurationKey): boolean {
     if (key.visible == null) {
       return true
@@ -517,6 +423,25 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     )
   }
 
+  public static remoteStopTransaction = async (
+    chargingStation: ChargingStation,
+    connectorId: number
+  ): Promise<GenericResponse> => {
+    await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+      chargingStation,
+      connectorId,
+      OCPP16ChargePointStatus.Finishing
+    )
+    const stopResponse = await chargingStation.stopTransactionOnConnector(
+      connectorId,
+      OCPP16StopTransactionReason.REMOTE
+    )
+    if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
+      return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
+    }
+    return OCPP16Constants.OCPP_RESPONSE_REJECTED
+  }
+
   public static setChargingProfile (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -558,4 +483,79 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     }
     !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
   }
+
+  private static readonly composeChargingSchedule = (
+    chargingSchedule: OCPP16ChargingSchedule,
+    compositeInterval: Interval
+  ): OCPP16ChargingSchedule | undefined => {
+    const chargingScheduleInterval: Interval = {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      start: chargingSchedule.startSchedule!,
+    }
+    if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
+      chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod)
+      if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
+        return {
+          ...chargingSchedule,
+          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod
+            .filter((schedulePeriod, index) => {
+              if (
+                isWithinInterval(
+                  addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
+                  compositeInterval
+                )
+              ) {
+                return true
+              }
+              if (
+                index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
+                !isWithinInterval(
+                  addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
+                  compositeInterval
+                ) &&
+                isWithinInterval(
+                  addSeconds(
+                    chargingScheduleInterval.start,
+                    chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod
+                  ),
+                  compositeInterval
+                )
+              ) {
+                return true
+              }
+              return false
+            })
+            .map((schedulePeriod, index) => {
+              if (index === 0 && schedulePeriod.startPeriod !== 0) {
+                schedulePeriod.startPeriod = 0
+              }
+              return schedulePeriod
+            }),
+          duration: differenceInSeconds(
+            chargingScheduleInterval.end,
+            compositeInterval.start as Date
+          ),
+          startSchedule: compositeInterval.start as Date,
+        }
+      }
+      if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
+        return {
+          ...chargingSchedule,
+          chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter(schedulePeriod =>
+            isWithinInterval(
+              addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
+              compositeInterval
+            )
+          ),
+          duration: differenceInSeconds(
+            compositeInterval.end as Date,
+            chargingScheduleInterval.start
+          ),
+        }
+      }
+      return chargingSchedule
+    }
+  }
 }
index 9324fea9c3ee8d61e0aa2322bd320176b284355c..39075970b7d29639d698e95cbb3562468e816893 100644 (file)
@@ -53,20 +53,6 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     this.validatePayload = this.validatePayload.bind(this)
   }
 
-  private validatePayload (
-    chargingStation: ChargingStation,
-    commandName: OCPP20IncomingRequestCommand,
-    commandPayload: JsonType
-  ): boolean {
-    if (this.payloadValidateFunctions.has(commandName)) {
-      return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
-    }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
-
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
     chargingStation: ChargingStation,
@@ -154,4 +140,18 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     // Emit command name event to allow delayed handling
     this.emit(commandName, chargingStation, commandPayload, response)
   }
+
+  private validatePayload (
+    chargingStation: ChargingStation,
+    commandName: OCPP20IncomingRequestCommand,
+    commandPayload: JsonType
+  ): boolean {
+    if (this.payloadValidateFunctions.has(commandName)) {
+      return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
+    }
+    logger.warn(
+      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
+    )
+    return false
+  }
 }
index 4d519d427ac5bcfb5b74e20f8af521b260fee795..e44693658c909b91c6a369d924f1b366a9c610fb 100644 (file)
@@ -67,6 +67,33 @@ export class OCPP20RequestService extends OCPPRequestService {
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: OCPP20RequestCommand,
+    commandParams?: RequestType,
+    params?: RequestParams
+  ): Promise<ResponseType> {
+    // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
+    if (OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
+      // TODO: pre request actions hook
+      return (await this.sendMessage(
+        chargingStation,
+        generateUUID(),
+        this.buildRequestPayload<RequestType>(chargingStation, commandName, commandParams),
+        commandName,
+        params
+      )) as ResponseType
+    }
+    // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+    throw new OCPPError(
+      ErrorType.NOT_SUPPORTED,
+      `Unsupported OCPP command ${commandName}`,
+      commandName,
+      commandParams
+    )
+  }
+
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   private buildRequestPayload<Request extends JsonType>(
     chargingStation: ChargingStation,
@@ -95,31 +122,4 @@ export class OCPP20RequestService extends OCPPRequestService {
         )
     }
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
-  public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
-    chargingStation: ChargingStation,
-    commandName: OCPP20RequestCommand,
-    commandParams?: RequestType,
-    params?: RequestParams
-  ): Promise<ResponseType> {
-    // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
-    if (OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
-      // TODO: pre request actions hook
-      return (await this.sendMessage(
-        chargingStation,
-        generateUUID(),
-        this.buildRequestPayload<RequestType>(chargingStation, commandName, commandParams),
-        commandName,
-        params
-      )) as ResponseType
-    }
-    // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
-    throw new OCPPError(
-      ErrorType.NOT_SUPPORTED,
-      `Unsupported OCPP command ${commandName}`,
-      commandName,
-      commandParams
-    )
-  }
 }
index a3735d834a5742781445abc32782dc8b56cf7bbe..0b0c32a7b7b19ea82c6f50f4d2bdb0932933f558 100644 (file)
@@ -26,13 +26,14 @@ import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
 const moduleName = 'OCPP20ResponseService'
 
 export class OCPP20ResponseService extends OCPPResponseService {
-  protected payloadValidateFunctions: Map<OCPP20RequestCommand, ValidateFunction<JsonType>>
-  private readonly responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>
   public incomingRequestResponsePayloadValidateFunctions: Map<
     OCPP20IncomingRequestCommand,
     ValidateFunction<JsonType>
   >
 
+  protected payloadValidateFunctions: Map<OCPP20RequestCommand, ValidateFunction<JsonType>>
+  private readonly responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>
+
   public constructor () {
     // if (new.target.name === moduleName) {
     //   throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
@@ -96,56 +97,6 @@ export class OCPP20ResponseService extends OCPPResponseService {
     this.validatePayload = this.validatePayload.bind(this)
   }
 
-  private handleResponseBootNotification (
-    chargingStation: ChargingStation,
-    payload: OCPP20BootNotificationResponse
-  ): void {
-    if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
-      chargingStation.bootNotificationResponse = payload
-      if (chargingStation.isRegistered()) {
-        chargingStation.emit(ChargingStationEvents.registered)
-        if (chargingStation.inAcceptedState()) {
-          addConfigurationKey(
-            chargingStation,
-            OCPP20OptionalVariableName.HeartbeatInterval,
-            payload.interval.toString(),
-            {},
-            { overwrite: true, save: true }
-          )
-          chargingStation.emit(ChargingStationEvents.accepted)
-        }
-      } else if (chargingStation.inRejectedState()) {
-        chargingStation.emit(ChargingStationEvents.rejected)
-      }
-      const logMsg = `${chargingStation.logPrefix()} Charging station in '${
-        payload.status
-      }' state on the central server`
-      payload.status === RegistrationStatusEnumType.REJECTED
-        ? logger.warn(logMsg)
-        : logger.info(logMsg)
-    } else {
-      delete chargingStation.bootNotificationResponse
-      logger.error(
-        `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
-        payload
-      )
-    }
-  }
-
-  private validatePayload (
-    chargingStation: ChargingStation,
-    commandName: OCPP20RequestCommand,
-    payload: JsonType
-  ): boolean {
-    if (this.payloadValidateFunctions.has(commandName)) {
-      return this.validateResponsePayload(chargingStation, commandName, payload)
-    }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
-
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
     chargingStation: ChargingStation,
@@ -206,4 +157,54 @@ export class OCPP20ResponseService extends OCPPResponseService {
       )
     }
   }
+
+  private handleResponseBootNotification (
+    chargingStation: ChargingStation,
+    payload: OCPP20BootNotificationResponse
+  ): void {
+    if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
+      chargingStation.bootNotificationResponse = payload
+      if (chargingStation.isRegistered()) {
+        chargingStation.emit(ChargingStationEvents.registered)
+        if (chargingStation.inAcceptedState()) {
+          addConfigurationKey(
+            chargingStation,
+            OCPP20OptionalVariableName.HeartbeatInterval,
+            payload.interval.toString(),
+            {},
+            { overwrite: true, save: true }
+          )
+          chargingStation.emit(ChargingStationEvents.accepted)
+        }
+      } else if (chargingStation.inRejectedState()) {
+        chargingStation.emit(ChargingStationEvents.rejected)
+      }
+      const logMsg = `${chargingStation.logPrefix()} Charging station in '${
+        payload.status
+      }' state on the central server`
+      payload.status === RegistrationStatusEnumType.REJECTED
+        ? logger.warn(logMsg)
+        : logger.info(logMsg)
+    } else {
+      delete chargingStation.bootNotificationResponse
+      logger.error(
+        `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
+        payload
+      )
+    }
+  }
+
+  private validatePayload (
+    chargingStation: ChargingStation,
+    commandName: OCPP20RequestCommand,
+    payload: JsonType
+  ): boolean {
+    if (this.payloadValidateFunctions.has(commandName)) {
+      return this.validateResponsePayload(chargingStation, commandName, payload)
+    }
+    logger.warn(
+      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
+    )
+    return false
+  }
 }
index 01eab722a3d9dbebc01234635b3919e02526a7a5..b142b223f611b6480ba82b12b263b762ae7f7991 100644 (file)
@@ -50,6 +50,14 @@ export abstract class OCPPIncomingRequestService extends EventEmitter {
     return OCPPIncomingRequestService.instance as T
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-unnecessary-type-parameters
+  public abstract incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
+    chargingStation: ChargingStation,
+    messageId: string,
+    commandName: IncomingRequestCommand,
+    commandPayload: ReqType
+  ): Promise<void>
+
   protected handleRequestClearCache (chargingStation: ChargingStation): ClearCacheResponse {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     if (chargingStation.idTagsCache.deleteIdTags(getIdTagsFile(chargingStation.stationInfo!)!)) {
@@ -82,12 +90,4 @@ export abstract class OCPPIncomingRequestService extends EventEmitter {
       JSON.stringify(validate?.errors, undefined, 2)
     )
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-unnecessary-type-parameters
-  public abstract incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
-    chargingStation: ChargingStation,
-    messageId: string,
-    commandName: IncomingRequestCommand,
-    commandPayload: ReqType
-  ): Promise<void>
 }
index a8696bd581d00403bff588fadbf6c0b6378d7f5f..554f0a37e6d5f86e2b8b24a2ac752d699afc064b 100644 (file)
@@ -83,6 +83,69 @@ export abstract class OCPPRequestService {
     return OCPPRequestService.instance as T
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public abstract requestHandler<ReqType extends JsonType, ResType extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: RequestCommand,
+    commandParams?: ReqType,
+    params?: RequestParams
+  ): Promise<ResType>
+
+  public async sendError (
+    chargingStation: ChargingStation,
+    messageId: string,
+    ocppError: OCPPError,
+    commandName: IncomingRequestCommand | RequestCommand
+  ): Promise<ResponseType> {
+    try {
+      // Send error message
+      return await this.internalSendMessage(
+        chargingStation,
+        messageId,
+        ocppError,
+        MessageType.CALL_ERROR_MESSAGE,
+        commandName
+      )
+    } catch (error) {
+      handleSendMessageError(
+        chargingStation,
+        commandName,
+        MessageType.CALL_ERROR_MESSAGE,
+        error as Error
+      )
+      return null
+    }
+  }
+
+  public async sendResponse (
+    chargingStation: ChargingStation,
+    messageId: string,
+    messagePayload: JsonType,
+    commandName: IncomingRequestCommand
+  ): Promise<ResponseType> {
+    try {
+      // Send response message
+      return await this.internalSendMessage(
+        chargingStation,
+        messageId,
+        messagePayload,
+        MessageType.CALL_RESULT_MESSAGE,
+        commandName
+      )
+    } catch (error) {
+      handleSendMessageError(
+        chargingStation,
+        commandName,
+        MessageType.CALL_RESULT_MESSAGE,
+        error as Error,
+        {
+          throwError: true,
+        }
+      )
+      return null
+    }
+  }
+
   protected async sendMessage (
     chargingStation: ChargingStation,
     messageId: string,
@@ -449,67 +512,4 @@ export abstract class OCPPRequestService {
       JSON.stringify(validate?.errors, undefined, 2)
     )
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
-  public abstract requestHandler<ReqType extends JsonType, ResType extends JsonType>(
-    chargingStation: ChargingStation,
-    commandName: RequestCommand,
-    commandParams?: ReqType,
-    params?: RequestParams
-  ): Promise<ResType>
-
-  public async sendError (
-    chargingStation: ChargingStation,
-    messageId: string,
-    ocppError: OCPPError,
-    commandName: IncomingRequestCommand | RequestCommand
-  ): Promise<ResponseType> {
-    try {
-      // Send error message
-      return await this.internalSendMessage(
-        chargingStation,
-        messageId,
-        ocppError,
-        MessageType.CALL_ERROR_MESSAGE,
-        commandName
-      )
-    } catch (error) {
-      handleSendMessageError(
-        chargingStation,
-        commandName,
-        MessageType.CALL_ERROR_MESSAGE,
-        error as Error
-      )
-      return null
-    }
-  }
-
-  public async sendResponse (
-    chargingStation: ChargingStation,
-    messageId: string,
-    messagePayload: JsonType,
-    commandName: IncomingRequestCommand
-  ): Promise<ResponseType> {
-    try {
-      // Send response message
-      return await this.internalSendMessage(
-        chargingStation,
-        messageId,
-        messagePayload,
-        MessageType.CALL_RESULT_MESSAGE,
-        commandName
-      )
-    } catch (error) {
-      handleSendMessageError(
-        chargingStation,
-        commandName,
-        MessageType.CALL_RESULT_MESSAGE,
-        error as Error,
-        {
-          throwError: true,
-        }
-      )
-      return null
-    }
-  }
 }
index 0eaf22f8a50897d84ddfeaa7d0130f5526477c47..6868b4efe59fa6046a3d300a1037c23f8cc2a7f9 100644 (file)
@@ -21,15 +21,16 @@ const moduleName = 'OCPPResponseService'
 
 export abstract class OCPPResponseService {
   private static instance: null | OCPPResponseService = null
+  public abstract incomingRequestResponsePayloadValidateFunctions: Map<
+    IncomingRequestCommand,
+    ValidateFunction<JsonType>
+  >
+
   protected readonly ajv: Ajv
   protected readonly ajvIncomingRequest: Ajv
   protected emptyResponseHandler = Constants.EMPTY_FUNCTION
   protected abstract payloadValidateFunctions: Map<RequestCommand, ValidateFunction<JsonType>>
   private readonly version: OCPPVersion
-  public abstract incomingRequestResponsePayloadValidateFunctions: Map<
-    IncomingRequestCommand,
-    ValidateFunction<JsonType>
-  >
 
   protected constructor (version: OCPPVersion) {
     this.version = version
@@ -54,6 +55,14 @@ export abstract class OCPPResponseService {
     return OCPPResponseService.instance as T
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public abstract responseHandler<ReqType extends JsonType, ResType extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: RequestCommand,
+    payload: ResType,
+    requestPayload: ReqType
+  ): Promise<void>
+
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   protected validateResponsePayload<T extends JsonType>(
     chargingStation: ChargingStation,
@@ -78,12 +87,4 @@ export abstract class OCPPResponseService {
       JSON.stringify(validate?.errors, undefined, 2)
     )
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
-  public abstract responseHandler<ReqType extends JsonType, ResType extends JsonType>(
-    chargingStation: ChargingStation,
-    commandName: RequestCommand,
-    payload: ResType,
-    requestPayload: ReqType
-  ): Promise<void>
 }
index 226bffb270aefb3410e943914384962106c7f1eb..c3e0047daccdfddef330220ff3c0b4f46fa5d3da 100644 (file)
@@ -1299,25 +1299,13 @@ const getMeasurandDefaultLocation = (
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class OCPPServiceUtils {
-  protected static buildSampledValue = buildSampledValue
   public static readonly buildTransactionEndMeterValue = buildTransactionEndMeterValue
-  protected static getSampledValueTemplate = getSampledValueTemplate
   public static readonly isIdTagAuthorized = isIdTagAuthorized
-  private static readonly logPrefix = (
-    ocppVersion: OCPPVersion,
-    moduleName?: string,
-    methodName?: string
-  ): string => {
-    const logMsg =
-      isNotEmptyString(moduleName) && isNotEmptyString(methodName)
-        ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
-        : ` OCPP ${ocppVersion} |`
-    return logPrefix(logMsg)
-  }
-
   public static readonly restoreConnectorStatus = restoreConnectorStatus
-
   public static readonly sendAndSetConnectorStatus = sendAndSetConnectorStatus
+  protected static buildSampledValue = buildSampledValue
+
+  protected static getSampledValueTemplate = getSampledValueTemplate
 
   protected constructor () {
     // This is intentional
@@ -1417,4 +1405,16 @@ export class OCPPServiceUtils {
       return {} as JSONSchemaType<T>
     }
   }
+
+  private static readonly logPrefix = (
+    ocppVersion: OCPPVersion,
+    moduleName?: string,
+    methodName?: string
+  ): string => {
+    const logMsg =
+      isNotEmptyString(moduleName) && isNotEmptyString(methodName)
+        ? ` OCPP ${ocppVersion} | ${moduleName}.${methodName}:`
+        : ` OCPP ${ocppVersion} |`
+    return logPrefix(logMsg)
+  }
 }
index a639883db09b9a50f28c141c2070ae51badfd808..9068d4745d4f503a6e37301abf5ba5373c1bdf1b 100644 (file)
@@ -26,6 +26,9 @@ import { getUsernameAndPasswordFromAuthorizationToken } from './UIServerUtils.js
 const moduleName = 'AbstractUIServer'
 
 export abstract class AbstractUIServer {
+  public readonly chargingStations: Map<string, ChargingStationData>
+  public readonly chargingStationTemplates: Set<string>
+
   protected readonly httpServer: Http2Server | Server
   protected readonly responseHandlers: Map<
     `${string}-${string}-${string}-${string}-${string}`,
@@ -33,8 +36,6 @@ export abstract class AbstractUIServer {
   >
 
   protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>
-  public readonly chargingStations: Map<string, ChargingStationData>
-  public readonly chargingStationTemplates: Set<string>
 
   public constructor (protected readonly uiServerConfiguration: UIServerConfiguration) {
     this.chargingStations = new Map<string, ChargingStationData>()
@@ -59,6 +60,54 @@ export abstract class AbstractUIServer {
     this.uiServices = new Map<ProtocolVersion, AbstractUIService>()
   }
 
+  public buildProtocolRequest (
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
+    procedureName: ProcedureName,
+    requestPayload: RequestPayload
+  ): ProtocolRequest {
+    return [uuid, procedureName, requestPayload]
+  }
+
+  public buildProtocolResponse (
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
+    responsePayload: ResponsePayload
+  ): ProtocolResponse {
+    return [uuid, responsePayload]
+  }
+
+  public clearCaches (): void {
+    this.chargingStations.clear()
+    this.chargingStationTemplates.clear()
+  }
+
+  public hasResponseHandler (uuid: `${string}-${string}-${string}-${string}-${string}`): boolean {
+    return this.responseHandlers.has(uuid)
+  }
+
+  public abstract logPrefix (moduleName?: string, methodName?: string, prefixSuffix?: string): string
+
+  public async sendInternalRequest (request: ProtocolRequest): Promise<ProtocolResponse> {
+    const protocolVersion = ProtocolVersion['0.0.1']
+    this.registerProtocolVersionUIService(protocolVersion)
+    return await (this.uiServices
+      .get(protocolVersion)
+      ?.requestHandler(request) as Promise<ProtocolResponse>)
+  }
+
+  public abstract sendRequest (request: ProtocolRequest): void
+
+  public abstract sendResponse (response: ProtocolResponse): void
+
+  public abstract start (): void
+
+  public stop (): void {
+    this.stopHttpServer()
+    for (const uiService of this.uiServices.values()) {
+      uiService.stop()
+    }
+    this.clearCaches()
+  }
+
   protected authenticate (req: IncomingMessage, next: (err?: Error) => void): void {
     const authorizationError = new BaseError('Unauthorized')
     if (this.isBasicAuthEnabled()) {
@@ -144,49 +193,4 @@ export abstract class AbstractUIServer {
       this.httpServer.removeAllListeners()
     }
   }
-
-  public buildProtocolRequest (
-    uuid: `${string}-${string}-${string}-${string}-${string}`,
-    procedureName: ProcedureName,
-    requestPayload: RequestPayload
-  ): ProtocolRequest {
-    return [uuid, procedureName, requestPayload]
-  }
-
-  public buildProtocolResponse (
-    uuid: `${string}-${string}-${string}-${string}-${string}`,
-    responsePayload: ResponsePayload
-  ): ProtocolResponse {
-    return [uuid, responsePayload]
-  }
-
-  public clearCaches (): void {
-    this.chargingStations.clear()
-    this.chargingStationTemplates.clear()
-  }
-
-  public hasResponseHandler (uuid: `${string}-${string}-${string}-${string}-${string}`): boolean {
-    return this.responseHandlers.has(uuid)
-  }
-
-  public abstract logPrefix (moduleName?: string, methodName?: string, prefixSuffix?: string): string
-
-  public async sendInternalRequest (request: ProtocolRequest): Promise<ProtocolResponse> {
-    const protocolVersion = ProtocolVersion['0.0.1']
-    this.registerProtocolVersionUIService(protocolVersion)
-    return await (this.uiServices
-      .get(protocolVersion)
-      ?.requestHandler(request) as Promise<ProtocolResponse>)
-  }
-
-  public abstract sendRequest (request: ProtocolRequest): void
-  public abstract sendResponse (response: ProtocolResponse): void
-  public abstract start (): void
-  public stop (): void {
-    this.stopHttpServer()
-    for (const uiService of this.uiServices.values()) {
-      uiService.stop()
-    }
-    this.clearCaches()
-  }
 }
index 0b26ce1d84eec79b9903eecc03e940eb1313428a..b1357822a2045790ebba995a6bc9d1a7d6631f31 100644 (file)
@@ -36,6 +36,10 @@ enum HttpMethods {
 }
 
 export class UIHttpServer extends AbstractUIServer {
+  public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
+    super(uiServerConfiguration)
+  }
+
   public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
     const logMsgPrefix = prefixSuffix != null ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server'
     const logMsg =
@@ -45,8 +49,42 @@ export class UIHttpServer extends AbstractUIServer {
     return logPrefix(logMsg)
   }
 
-  public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
-    super(uiServerConfiguration)
+  public sendRequest (request: ProtocolRequest): void {
+    switch (this.uiServerConfiguration.version) {
+      case ApplicationProtocolVersion.VERSION_20:
+        this.httpServer.emit('request', request)
+        break
+    }
+  }
+
+  public sendResponse (response: ProtocolResponse): void {
+    const [uuid, payload] = response
+    try {
+      if (this.hasResponseHandler(uuid)) {
+        const res = this.responseHandlers.get(uuid) as ServerResponse
+        res
+          .writeHead(this.responseStatusToStatusCode(payload.status), {
+            'Content-Type': 'application/json',
+          })
+          .end(JSONStringify(payload, undefined, MapStringifyFormat.object))
+      } else {
+        logger.error(
+          `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}`
+        )
+      }
+    } catch (error) {
+      logger.error(
+        `${this.logPrefix(moduleName, 'sendResponse')} Error at sending response id '${uuid}':`,
+        error
+      )
+    } finally {
+      this.responseHandlers.delete(uuid)
+    }
+  }
+
+  public start (): void {
+    this.httpServer.on('request', this.requestListener.bind(this))
+    this.startHttpServer()
   }
 
   private requestListener (req: IncomingMessage, res: ServerResponse): void {
@@ -136,42 +174,4 @@ export class UIHttpServer extends AbstractUIServer {
         return StatusCodes.INTERNAL_SERVER_ERROR
     }
   }
-
-  public sendRequest (request: ProtocolRequest): void {
-    switch (this.uiServerConfiguration.version) {
-      case ApplicationProtocolVersion.VERSION_20:
-        this.httpServer.emit('request', request)
-        break
-    }
-  }
-
-  public sendResponse (response: ProtocolResponse): void {
-    const [uuid, payload] = response
-    try {
-      if (this.hasResponseHandler(uuid)) {
-        const res = this.responseHandlers.get(uuid) as ServerResponse
-        res
-          .writeHead(this.responseStatusToStatusCode(payload.status), {
-            'Content-Type': 'application/json',
-          })
-          .end(JSONStringify(payload, undefined, MapStringifyFormat.object))
-      } else {
-        logger.error(
-          `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}`
-        )
-      }
-    } catch (error) {
-      logger.error(
-        `${this.logPrefix(moduleName, 'sendResponse')} Error at sending response id '${uuid}':`,
-        error
-      )
-    } finally {
-      this.responseHandlers.delete(uuid)
-    }
-  }
-
-  public start (): void {
-    this.httpServer.on('request', this.requestListener.bind(this))
-    this.startHttpServer()
-  }
 }
index 830476b41ec88b2d8d1bf2b8937462f9796724cc..3bdf68f6641688ec55dee124e9fee0abc2a6c182 100644 (file)
@@ -17,15 +17,6 @@ import { UIWebSocketServer } from './UIWebSocketServer.js'
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class UIServerFactory {
-  private static readonly logPrefix = (modName?: string, methodName?: string): string => {
-    const logMsgPrefix = 'UI Server'
-    const logMsg =
-      modName != null && methodName != null
-        ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
-        : ` ${logMsgPrefix} |`
-    return logPrefix(logMsg)
-  }
-
   private constructor () {
     // This is intentional
   }
@@ -91,4 +82,13 @@ export class UIServerFactory {
         return new UIWebSocketServer(uiServerConfiguration)
     }
   }
+
+  private static readonly logPrefix = (modName?: string, methodName?: string): string => {
+    const logMsgPrefix = 'UI Server'
+    const logMsg =
+      modName != null && methodName != null
+        ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
+        : ` ${logMsgPrefix} |`
+    return logPrefix(logMsg)
+  }
 }
index ba557644735d0dd8278369dacbddf717fddacf91..dd57883e88b1bbe3273452ab3a43d0b3b7222608 100644 (file)
@@ -32,16 +32,6 @@ const moduleName = 'UIWebSocketServer'
 export class UIWebSocketServer extends AbstractUIServer {
   private readonly webSocketServer: WebSocketServer
 
-  public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
-    const logMsgPrefix =
-      prefixSuffix != null ? `UI WebSocket Server ${prefixSuffix}` : 'UI WebSocket Server'
-    const logMsg =
-      isNotEmptyString(modName) && isNotEmptyString(methodName)
-        ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
-        : ` ${logMsgPrefix} |`
-    return logPrefix(logMsg)
-  }
-
   public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
     super(uiServerConfiguration)
     this.webSocketServer = new WebSocketServer({
@@ -50,70 +40,14 @@ export class UIWebSocketServer extends AbstractUIServer {
     })
   }
 
-  private broadcastToClients (message: string): void {
-    for (const client of this.webSocketServer.clients) {
-      if (client.readyState === WebSocket.OPEN) {
-        client.send(message)
-      }
-    }
-  }
-
-  private validateRawDataRequest (rawData: RawData): false | ProtocolRequest {
-    // logger.debug(
-    //   `${this.logPrefix(
-    //     moduleName,
-    //     'validateRawDataRequest'
-    //     // eslint-disable-next-line @typescript-eslint/no-base-to-string
-    //   )} Raw data received in string format: ${rawData.toString()}`
-    // )
-
-    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(
-        `${this.logPrefix(
-          moduleName,
-          'validateRawDataRequest'
-        )} UI protocol request is not an array:`,
-        request
-      )
-      return false
-    }
-
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (request.length !== 3) {
-      logger.error(
-        `${this.logPrefix(moduleName, 'validateRawDataRequest')} UI protocol request is malformed:`,
-        request
-      )
-      return false
-    }
-
-    if (!validateUUID(request[0])) {
-      logger.error(
-        `${this.logPrefix(
-          moduleName,
-          'validateRawDataRequest'
-        )} UI protocol request UUID field is invalid:`,
-        request
-      )
-      return false
-    }
-
-    return request
+  public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
+    const logMsgPrefix =
+      prefixSuffix != null ? `UI WebSocket Server ${prefixSuffix}` : 'UI WebSocket Server'
+    const logMsg =
+      isNotEmptyString(modName) && isNotEmptyString(methodName)
+        ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
+        : ` ${logMsgPrefix} |`
+    return logPrefix(logMsg)
   }
 
   public sendRequest (request: ProtocolRequest): void {
@@ -243,4 +177,70 @@ export class UIWebSocketServer extends AbstractUIServer {
     })
     this.startHttpServer()
   }
+
+  private broadcastToClients (message: string): void {
+    for (const client of this.webSocketServer.clients) {
+      if (client.readyState === WebSocket.OPEN) {
+        client.send(message)
+      }
+    }
+  }
+
+  private validateRawDataRequest (rawData: RawData): false | ProtocolRequest {
+    // logger.debug(
+    //   `${this.logPrefix(
+    //     moduleName,
+    //     'validateRawDataRequest'
+    //     // eslint-disable-next-line @typescript-eslint/no-base-to-string
+    //   )} Raw data received in string format: ${rawData.toString()}`
+    // )
+
+    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(
+        `${this.logPrefix(
+          moduleName,
+          'validateRawDataRequest'
+        )} UI protocol request is not an array:`,
+        request
+      )
+      return false
+    }
+
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (request.length !== 3) {
+      logger.error(
+        `${this.logPrefix(moduleName, 'validateRawDataRequest')} UI protocol request is malformed:`,
+        request
+      )
+      return false
+    }
+
+    if (!validateUUID(request[0])) {
+      logger.error(
+        `${this.logPrefix(
+          moduleName,
+          'validateRawDataRequest'
+        )} UI protocol request UUID field is invalid:`,
+        request
+      )
+      return false
+    }
+
+    return request
+  }
 }
index 78ca06506d84c3e1a4a927aef97216573920c05a..0ca837e888353e16e88e5bc5cdb20170eda02234 100644 (file)
@@ -81,10 +81,6 @@ export abstract class AbstractUIService {
   private readonly uiServiceWorkerBroadcastChannel: UIServiceWorkerBroadcastChannel
   private readonly version: ProtocolVersion
 
-  public logPrefix = (modName: string, methodName: string): string => {
-    return this.uiServer.logPrefix(modName, methodName, this.version)
-  }
-
   constructor (uiServer: AbstractUIServer, version: ProtocolVersion) {
     this.uiServer = uiServer
     this.version = version
@@ -104,6 +100,89 @@ export abstract class AbstractUIService {
     >()
   }
 
+  public deleteBroadcastChannelRequest (
+    uuid: `${string}-${string}-${string}-${string}-${string}`
+  ): void {
+    this.broadcastChannelRequests.delete(uuid)
+  }
+
+  public getBroadcastChannelExpectedResponses (
+    uuid: `${string}-${string}-${string}-${string}-${string}`
+  ): number {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return this.broadcastChannelRequests.get(uuid)!
+  }
+
+  public logPrefix = (modName: string, methodName: string): string => {
+    return this.uiServer.logPrefix(modName, methodName, this.version)
+  }
+
+  public async requestHandler (request: ProtocolRequest): Promise<ProtocolResponse | undefined> {
+    let uuid: `${string}-${string}-${string}-${string}-${string}` | undefined
+    let command: ProcedureName | undefined
+    let requestPayload: RequestPayload | undefined
+    let responsePayload: ResponsePayload | undefined
+    try {
+      ;[uuid, command, requestPayload] = request
+
+      if (!this.requestHandlers.has(command)) {
+        throw new BaseError(
+          `'${command}' is not implemented to handle message payload ${JSON.stringify(
+            requestPayload,
+            undefined,
+            2
+          )}`
+        )
+      }
+
+      // Call the request handler to build the response payload
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const requestHandler = this.requestHandlers.get(command)!
+      if (isAsyncFunction(requestHandler)) {
+        responsePayload = await requestHandler(uuid, command, requestPayload)
+      } else {
+        responsePayload = (
+          requestHandler as (
+            uuid?: string,
+            procedureName?: ProcedureName,
+            payload?: RequestPayload
+          ) => ResponsePayload | undefined
+        )(uuid, command, requestPayload)
+      }
+    } catch (error) {
+      // Log
+      logger.error(`${this.logPrefix(moduleName, 'requestHandler')} Handle request error:`, error)
+      responsePayload = {
+        command,
+        errorDetails: (error as OCPPError).details,
+        errorMessage: (error as OCPPError).message,
+        errorStack: (error as OCPPError).stack,
+        hashIds: requestPayload?.hashIds,
+        requestPayload,
+        responsePayload,
+        status: ResponseStatus.FAILURE,
+      } satisfies ResponsePayload
+    }
+    if (responsePayload != null) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      return this.uiServer.buildProtocolResponse(uuid!, responsePayload)
+    }
+  }
+
+  public sendResponse (
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
+    responsePayload: ResponsePayload
+  ): void {
+    if (this.uiServer.hasResponseHandler(uuid)) {
+      this.uiServer.sendResponse(this.uiServer.buildProtocolResponse(uuid, responsePayload))
+    }
+  }
+
+  public stop (): void {
+    this.broadcastChannelRequests.clear()
+    this.uiServiceWorkerBroadcastChannel.close()
+  }
+
   protected handleProtocolRequest (
     uuid: `${string}-${string}-${string}-${string}-${string}`,
     procedureName: ProcedureName,
@@ -246,6 +325,16 @@ export abstract class AbstractUIService {
     }
   }
 
+  // public sendRequest (
+  //   uuid: `${string}-${string}-${string}-${string}-${string}`,
+  //   procedureName: ProcedureName,
+  //   requestPayload: RequestPayload
+  // ): void {
+  //   this.uiServer.sendRequest(
+  //     this.uiServer.buildProtocolRequest(uuid, procedureName, requestPayload)
+  //   )
+  // }
+
   private async handleStopSimulator (): Promise<ResponsePayload> {
     try {
       await Bootstrap.getInstance().stop()
@@ -293,93 +382,4 @@ export abstract class AbstractUIService {
     this.uiServiceWorkerBroadcastChannel.sendRequest([uuid, procedureName, payload])
     this.broadcastChannelRequests.set(uuid, expectedNumberOfResponses)
   }
-
-  public deleteBroadcastChannelRequest (
-    uuid: `${string}-${string}-${string}-${string}-${string}`
-  ): void {
-    this.broadcastChannelRequests.delete(uuid)
-  }
-
-  public getBroadcastChannelExpectedResponses (
-    uuid: `${string}-${string}-${string}-${string}-${string}`
-  ): number {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return this.broadcastChannelRequests.get(uuid)!
-  }
-
-  public async requestHandler (request: ProtocolRequest): Promise<ProtocolResponse | undefined> {
-    let uuid: `${string}-${string}-${string}-${string}-${string}` | undefined
-    let command: ProcedureName | undefined
-    let requestPayload: RequestPayload | undefined
-    let responsePayload: ResponsePayload | undefined
-    try {
-      ;[uuid, command, requestPayload] = request
-
-      if (!this.requestHandlers.has(command)) {
-        throw new BaseError(
-          `'${command}' is not implemented to handle message payload ${JSON.stringify(
-            requestPayload,
-            undefined,
-            2
-          )}`
-        )
-      }
-
-      // Call the request handler to build the response payload
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      const requestHandler = this.requestHandlers.get(command)!
-      if (isAsyncFunction(requestHandler)) {
-        responsePayload = await requestHandler(uuid, command, requestPayload)
-      } else {
-        responsePayload = (
-          requestHandler as (
-            uuid?: string,
-            procedureName?: ProcedureName,
-            payload?: RequestPayload
-          ) => ResponsePayload | undefined
-        )(uuid, command, requestPayload)
-      }
-    } catch (error) {
-      // Log
-      logger.error(`${this.logPrefix(moduleName, 'requestHandler')} Handle request error:`, error)
-      responsePayload = {
-        command,
-        errorDetails: (error as OCPPError).details,
-        errorMessage: (error as OCPPError).message,
-        errorStack: (error as OCPPError).stack,
-        hashIds: requestPayload?.hashIds,
-        requestPayload,
-        responsePayload,
-        status: ResponseStatus.FAILURE,
-      } satisfies ResponsePayload
-    }
-    if (responsePayload != null) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      return this.uiServer.buildProtocolResponse(uuid!, responsePayload)
-    }
-  }
-
-  // public sendRequest (
-  //   uuid: `${string}-${string}-${string}-${string}-${string}`,
-  //   procedureName: ProcedureName,
-  //   requestPayload: RequestPayload
-  // ): void {
-  //   this.uiServer.sendRequest(
-  //     this.uiServer.buildProtocolRequest(uuid, procedureName, requestPayload)
-  //   )
-  // }
-
-  public sendResponse (
-    uuid: `${string}-${string}-${string}-${string}-${string}`,
-    responsePayload: ResponsePayload
-  ): void {
-    if (this.uiServer.hasResponseHandler(uuid)) {
-      this.uiServer.sendResponse(this.uiServer.buildProtocolResponse(uuid, responsePayload))
-    }
-  }
-
-  public stop (): void {
-    this.broadcastChannelRequests.clear()
-    this.uiServiceWorkerBroadcastChannel.close()
-  }
 }
index 15b338812f62463deea4e84a1bc9b2c589bebc5f..9c16208b9b999ecd02c19c1f83075aaf24d7a43a 100644 (file)
@@ -43,21 +43,10 @@ export class PerformanceStatistics {
     PerformanceStatistics
   >()
 
-  private static readonly logPrefix = (): string => {
-    return logPrefix(' Performance statistics')
-  }
-
   private displayInterval?: NodeJS.Timeout
-  private readonly logPrefix = (): string => {
-    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-    return logPrefix(` ${this.objName} | Performance statistics`)
-  }
-
   private readonly objId: string | undefined
   private readonly objName: string | undefined
-
   private performanceObserver!: PerformanceObserver
-
   private readonly statistics: Statistics
 
   private constructor (objId: string, objName: string, uri: URL) {
@@ -128,6 +117,93 @@ export class PerformanceStatistics {
     return PerformanceStatistics.instances.get(objId)
   }
 
+  private static readonly logPrefix = (): string => {
+    return logPrefix(' Performance statistics')
+  }
+
+  public addRequestStatistic (
+    command: IncomingRequestCommand | RequestCommand,
+    messageType: MessageType
+  ): void {
+    switch (messageType) {
+      case MessageType.CALL_ERROR_MESSAGE:
+        if (
+          this.statistics.statisticsData.has(command) &&
+          this.statistics.statisticsData.get(command)?.errorCount != null
+        ) {
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          ++this.statistics.statisticsData.get(command)!.errorCount!
+        } else {
+          this.statistics.statisticsData.set(command, {
+            ...this.statistics.statisticsData.get(command),
+            errorCount: 1,
+          })
+        }
+        break
+      case MessageType.CALL_MESSAGE:
+        if (
+          this.statistics.statisticsData.has(command) &&
+          this.statistics.statisticsData.get(command)?.requestCount != null
+        ) {
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          ++this.statistics.statisticsData.get(command)!.requestCount!
+        } else {
+          this.statistics.statisticsData.set(command, {
+            ...this.statistics.statisticsData.get(command),
+            requestCount: 1,
+          })
+        }
+        break
+      case MessageType.CALL_RESULT_MESSAGE:
+        if (
+          this.statistics.statisticsData.has(command) &&
+          this.statistics.statisticsData.get(command)?.responseCount != null
+        ) {
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          ++this.statistics.statisticsData.get(command)!.responseCount!
+        } else {
+          this.statistics.statisticsData.set(command, {
+            ...this.statistics.statisticsData.get(command),
+            responseCount: 1,
+          })
+        }
+        break
+      default:
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        logger.error(`${this.logPrefix()} wrong message type ${messageType}`)
+        break
+    }
+  }
+
+  public restart (): void {
+    this.stop()
+    this.start()
+  }
+
+  public start (): void {
+    this.startLogStatisticsInterval()
+    const performanceStorageConfiguration =
+      Configuration.getConfigurationSection<StorageConfiguration>(
+        ConfigurationSection.performanceStorage
+      )
+    if (performanceStorageConfiguration.enabled === true) {
+      logger.info(
+        `${this.logPrefix()} storage enabled: type ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          performanceStorageConfiguration.type
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        }, uri: ${performanceStorageConfiguration.uri}`
+      )
+    }
+  }
+
+  public stop (): void {
+    this.stopLogStatisticsInterval()
+    performance.clearMarks()
+    performance.clearMeasures()
+    this.performanceObserver.disconnect()
+  }
+
   private addPerformanceEntryToStatistics (entry: PerformanceEntry): void {
     // Initialize command statistics
     if (!this.statistics.statisticsData.has(entry.name)) {
@@ -210,6 +286,11 @@ export class PerformanceStatistics {
     this.performanceObserver.observe({ entryTypes: ['measure'] })
   }
 
+  private readonly logPrefix = (): string => {
+    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+    return logPrefix(` ${this.objName} | Performance statistics`)
+  }
+
   private logStatistics (): void {
     logger.info(this.logPrefix(), {
       ...this.statistics,
@@ -252,87 +333,4 @@ export class PerformanceStatistics {
       delete this.displayInterval
     }
   }
-
-  public addRequestStatistic (
-    command: IncomingRequestCommand | RequestCommand,
-    messageType: MessageType
-  ): void {
-    switch (messageType) {
-      case MessageType.CALL_ERROR_MESSAGE:
-        if (
-          this.statistics.statisticsData.has(command) &&
-          this.statistics.statisticsData.get(command)?.errorCount != null
-        ) {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          ++this.statistics.statisticsData.get(command)!.errorCount!
-        } else {
-          this.statistics.statisticsData.set(command, {
-            ...this.statistics.statisticsData.get(command),
-            errorCount: 1,
-          })
-        }
-        break
-      case MessageType.CALL_MESSAGE:
-        if (
-          this.statistics.statisticsData.has(command) &&
-          this.statistics.statisticsData.get(command)?.requestCount != null
-        ) {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          ++this.statistics.statisticsData.get(command)!.requestCount!
-        } else {
-          this.statistics.statisticsData.set(command, {
-            ...this.statistics.statisticsData.get(command),
-            requestCount: 1,
-          })
-        }
-        break
-      case MessageType.CALL_RESULT_MESSAGE:
-        if (
-          this.statistics.statisticsData.has(command) &&
-          this.statistics.statisticsData.get(command)?.responseCount != null
-        ) {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          ++this.statistics.statisticsData.get(command)!.responseCount!
-        } else {
-          this.statistics.statisticsData.set(command, {
-            ...this.statistics.statisticsData.get(command),
-            responseCount: 1,
-          })
-        }
-        break
-      default:
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        logger.error(`${this.logPrefix()} wrong message type ${messageType}`)
-        break
-    }
-  }
-
-  public restart (): void {
-    this.stop()
-    this.start()
-  }
-
-  public start (): void {
-    this.startLogStatisticsInterval()
-    const performanceStorageConfiguration =
-      Configuration.getConfigurationSection<StorageConfiguration>(
-        ConfigurationSection.performanceStorage
-      )
-    if (performanceStorageConfiguration.enabled === true) {
-      logger.info(
-        `${this.logPrefix()} storage enabled: type ${
-          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-          performanceStorageConfiguration.type
-          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        }, uri: ${performanceStorageConfiguration.uri}`
-      )
-    }
-  }
-
-  public stop (): void {
-    this.stopLogStatisticsInterval()
-    performance.clearMarks()
-    performance.clearMeasures()
-    this.performanceObserver.disconnect()
-  }
 }
index e76bdb2fce31b2e6f3dde72c5477a5806f301f4c..bc1d45383ee9649b7f57e8bf796725cf9ac65a3c 100644 (file)
@@ -16,14 +16,6 @@ export class JsonFileStorage extends Storage {
     this.dbName = this.storageUri.pathname
   }
 
-  private checkPerformanceRecordsFile (): void {
-    if (this.fd == null) {
-      throw new BaseError(
-        `${this.logPrefix} Performance records '${this.dbName}' file descriptor not found`
-      )
-    }
-  }
-
   public close (): void {
     this.clearPerformanceStatistics()
     try {
@@ -79,4 +71,12 @@ export class JsonFileStorage extends Storage {
       )
     })
   }
+
+  private checkPerformanceRecordsFile (): void {
+    if (this.fd == null) {
+      throw new BaseError(
+        `${this.logPrefix} Performance records '${this.dbName}' file descriptor not found`
+      )
+    }
+  }
 }
index 2eb8644cd680cf57690dbcb9a1a21249f5a0b731..f9998a0d71797a119548af3f14014bc16b205faf 100644 (file)
@@ -17,31 +17,6 @@ export class MikroOrmStorage extends Storage {
     this.dbName = this.getDBName()
   }
 
-  private getClientUrl (): string | undefined {
-    switch (this.storageType) {
-      case StorageType.MARIA_DB:
-      case StorageType.MYSQL:
-      case StorageType.SQLITE:
-        return this.storageUri.toString()
-    }
-  }
-
-  private getDBName (): string {
-    if (this.storageType === StorageType.SQLITE) {
-      return `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`
-    }
-    return this.storageUri.pathname.replace(/(?:^\/)|(?:\/$)/g, '')
-  }
-
-  private getOptions (): MariaDbOptions | SqliteOptions {
-    return {
-      clientUrl: this.getClientUrl(),
-      dbName: this.dbName,
-      entities: ['./dist/types/orm/entities/*.js'],
-      entitiesTs: ['./src/types/orm/entities/*.ts'],
-    }
-  }
-
   public async close (): Promise<void> {
     this.clearPerformanceStatistics()
     try {
@@ -90,4 +65,29 @@ export class MikroOrmStorage extends Storage {
       )
     }
   }
+
+  private getClientUrl (): string | undefined {
+    switch (this.storageType) {
+      case StorageType.MARIA_DB:
+      case StorageType.MYSQL:
+      case StorageType.SQLITE:
+        return this.storageUri.toString()
+    }
+  }
+
+  private getDBName (): string {
+    if (this.storageType === StorageType.SQLITE) {
+      return `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`
+    }
+    return this.storageUri.pathname.replace(/(?:^\/)|(?:\/$)/g, '')
+  }
+
+  private getOptions (): MariaDbOptions | SqliteOptions {
+    return {
+      clientUrl: this.getClientUrl(),
+      dbName: this.dbName,
+      entities: ['./dist/types/orm/entities/*.js'],
+      entitiesTs: ['./src/types/orm/entities/*.ts'],
+    }
+  }
 }
index ef48f3bd33a44f381e806ce662a3ce953d470d0c..f092351afba5bb21def418cede3ff6f15218b959 100644 (file)
@@ -18,25 +18,6 @@ export class MongoDBStorage extends Storage {
     this.dbName = this.storageUri.pathname.replace(/(?:^\/)|(?:\/$)/g, '')
   }
 
-  private checkDBConnection (): void {
-    if (this.client == null) {
-      throw new BaseError(
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `${this.logPrefix} ${this.getDBNameFromStorageType(
-          StorageType.MONGO_DB
-        )} client initialization failed while trying to issue a request`
-      )
-    }
-    if (!this.connected) {
-      throw new BaseError(
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `${this.logPrefix} ${this.getDBNameFromStorageType(
-          StorageType.MONGO_DB
-        )} connection not opened while trying to issue a request`
-      )
-    }
-  }
-
   public async close (): Promise<void> {
     this.clearPerformanceStatistics()
     try {
@@ -78,4 +59,23 @@ export class MongoDBStorage extends Storage {
       )
     }
   }
+
+  private checkDBConnection (): void {
+    if (this.client == null) {
+      throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${this.logPrefix} ${this.getDBNameFromStorageType(
+          StorageType.MONGO_DB
+        )} client initialization failed while trying to issue a request`
+      )
+    }
+    if (!this.connected) {
+      throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${this.logPrefix} ${this.getDBNameFromStorageType(
+          StorageType.MONGO_DB
+        )} connection not opened while trying to issue a request`
+      )
+    }
+  }
 }
index 0e21515c1c0a80987e47e4ad37b38e711e4a6a1c..5abe052dd524aa3d9af2ba0534772e887e9513de 100644 (file)
@@ -22,6 +22,18 @@ export abstract class Storage {
     this.logPrefix = logPrefix
   }
 
+  public abstract close (): Promise<void> | void
+
+  public getPerformanceStatistics (): IterableIterator<Statistics> {
+    return Storage.performanceStatistics.values()
+  }
+
+  public abstract open (): Promise<void> | void
+
+  public abstract storePerformanceStatistics (
+    performanceStatistics: Statistics
+  ): Promise<void> | void
+
   protected clearPerformanceStatistics (): void {
     Storage.performanceStatistics.clear()
   }
@@ -72,14 +84,4 @@ export abstract class Storage {
   protected setPerformanceStatistics (performanceStatistics: Statistics): void {
     Storage.performanceStatistics.set(performanceStatistics.id, performanceStatistics)
   }
-
-  public abstract close (): Promise<void> | void
-
-  public getPerformanceStatistics (): IterableIterator<Statistics> {
-    return Storage.performanceStatistics.values()
-  }
-  public abstract open (): Promise<void> | void
-  public abstract storePerformanceStatistics (
-    performanceStatistics: Statistics
-  ): Promise<void> | void
 }
index e1fb9667d8ded144a763934a4b74fb2b37d273fc..61bb9bc3a0c582edd682f4f9ade5f10ecb7af61c 100644 (file)
@@ -19,6 +19,11 @@ export interface AutomaticTransactionGeneratorConfiguration extends JsonObject {
   stopAfterHours: number
 }
 
+export interface ChargingStationAutomaticTransactionGeneratorConfiguration {
+  automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
+  automaticTransactionGeneratorStatuses?: Status[]
+}
+
 export interface Status {
   acceptedAuthorizeRequests: number
   acceptedStartTransactionRequests: number
@@ -37,8 +42,3 @@ export interface Status {
   stoppedDate?: Date
   stopTransactionRequests: number
 }
-
-export interface ChargingStationAutomaticTransactionGeneratorConfiguration {
-  automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
-  automaticTransactionGeneratorStatuses?: Status[]
-}
index 1363ccbf30084860004880e6e9f8884c2790fc0a..bd7bf905fe0c4f28a0e99cfb2359b9d19a117a37 100644 (file)
@@ -4,22 +4,23 @@ import type { ChargingStationOcppConfiguration } from './ChargingStationOcppConf
 import type { ConnectorStatus } from './ConnectorStatus.js'
 import type { EvseStatus } from './Evse.js'
 
-interface ConnectorsConfiguration {
+export type ChargingStationConfiguration =
+  ChargingStationAutomaticTransactionGeneratorConfiguration &
+    ChargingStationInfoConfiguration &
+    ChargingStationOcppConfiguration &
+    ConnectorsConfiguration &
+    EvsesConfiguration & {
+      configurationHash?: string
+    }
+
+export type EvseStatusConfiguration = Omit<EvseStatus, 'connectors'> & {
   connectorsStatus?: ConnectorStatus[]
 }
 
-export type EvseStatusConfiguration = {
+interface ConnectorsConfiguration {
   connectorsStatus?: ConnectorStatus[]
-} & Omit<EvseStatus, 'connectors'>
+}
 
 interface EvsesConfiguration {
   evsesStatus?: EvseStatusConfiguration[]
 }
-
-export type ChargingStationConfiguration = {
-  configurationHash?: string
-} & ChargingStationAutomaticTransactionGeneratorConfiguration &
-  ChargingStationInfoConfiguration &
-  ChargingStationOcppConfiguration &
-  ConnectorsConfiguration &
-  EvsesConfiguration
index b1fb3454a802ddd218fdad37a38d955a4547b3be..09550060a38f9fd344f8a799c616ecb6895ae803 100644 (file)
@@ -1,7 +1,19 @@
 import type { ChargingStationTemplate } from './ChargingStationTemplate.js'
 import type { FirmwareStatus } from './ocpp/Requests.js'
 
-export type ChargingStationInfo = {
+export type ChargingStationInfo = Omit<
+  ChargingStationTemplate,
+  | 'AutomaticTransactionGenerator'
+  | 'chargeBoxSerialNumberPrefix'
+  | 'chargePointSerialNumberPrefix'
+  | 'Configuration'
+  | 'Connectors'
+  | 'Evses'
+  | 'meterSerialNumberPrefix'
+  | 'numberOfConnectors'
+  | 'power'
+  | 'powerUnit'
+> & {
   chargeBoxSerialNumber?: string
   chargePointSerialNumber?: string
   chargingStationId?: string
@@ -14,19 +26,7 @@ export type ChargingStationInfo = {
   meterSerialNumber?: string
   templateIndex: number
   templateName: string
-} & Omit<
-  ChargingStationTemplate,
-  | 'AutomaticTransactionGenerator'
-  | 'chargeBoxSerialNumberPrefix'
-  | 'chargePointSerialNumberPrefix'
-  | 'Configuration'
-  | 'Connectors'
-  | 'Evses'
-  | 'meterSerialNumberPrefix'
-  | 'numberOfConnectors'
-  | 'power'
-  | 'powerUnit'
->
+}
 
 export interface ChargingStationInfoConfiguration {
   stationInfo?: ChargingStationInfo
index efa2f35d19ccd4d510772ede8ab4c877d607554e..cdd1d3ff17ca7ee3bb655cf9f0eed41bffd71fae 100644 (file)
@@ -1,11 +1,11 @@
 import type { JsonObject } from './JsonType.js'
 import type { OCPPConfigurationKey } from './ocpp/Configuration.js'
 
+export interface ChargingStationOcppConfiguration extends JsonObject {
+  configurationKey?: ConfigurationKey[]
+}
+
 export interface ConfigurationKey extends OCPPConfigurationKey {
   reboot?: boolean
   visible?: boolean
 }
-
-export interface ChargingStationOcppConfiguration extends JsonObject {
-  configurationKey?: ConfigurationKey[]
-}
index 571e4458b578ebb091b68335b76be35496673d47..c17544f736c4b8e0d1d60254590e6bb1aee731a5 100644 (file)
@@ -15,6 +15,13 @@ import type {
   RequestCommand,
 } from './ocpp/Requests.js'
 
+export enum AmpereUnits {
+  AMPERE = 'A',
+  CENTI_AMPERE = 'cA',
+  DECI_AMPERE = 'dA',
+  MILLI_AMPERE = 'mA',
+}
+
 export enum CurrentType {
   AC = 'AC',
   DC = 'DC',
@@ -25,13 +32,6 @@ export enum PowerUnits {
   WATT = 'W',
 }
 
-export enum AmpereUnits {
-  AMPERE = 'A',
-  CENTI_AMPERE = 'cA',
-  DECI_AMPERE = 'dA',
-  MILLI_AMPERE = 'mA',
-}
-
 export enum Voltage {
   VOLTAGE_110 = 110,
   VOLTAGE_230 = 230,
@@ -39,22 +39,6 @@ export enum Voltage {
   VOLTAGE_800 = 800,
 }
 
-export type WsOptions = ClientOptions & ClientRequestArgs
-
-export interface FirmwareUpgrade extends JsonObject {
-  failureStatus?: FirmwareStatus
-  reset?: boolean
-  versionUpgrade?: {
-    patternGroup?: number
-    step?: number
-  }
-}
-
-interface CommandsSupport extends JsonObject {
-  incomingCommands: Record<IncomingRequestCommand, boolean>
-  outgoingCommands?: Record<RequestCommand, boolean>
-}
-
 enum x509CertificateType {
   ChargingStationCertificate = 'ChargingStationCertificate',
   CSMSRootCertificate = 'CSMSRootCertificate',
@@ -132,3 +116,19 @@ export interface ChargingStationTemplate {
   wsOptions?: WsOptions
   x509Certificates?: Record<x509CertificateType, string>
 }
+
+export interface FirmwareUpgrade extends JsonObject {
+  failureStatus?: FirmwareStatus
+  reset?: boolean
+  versionUpgrade?: {
+    patternGroup?: number
+    step?: number
+  }
+}
+
+export type WsOptions = ClientOptions & ClientRequestArgs
+
+interface CommandsSupport extends JsonObject {
+  incomingCommands: Record<IncomingRequestCommand, boolean>
+  outgoingCommands?: Record<RequestCommand, boolean>
+}
index a732471acbd862ce3cc1c07b2af42cd8d5a1a9b2..05e3a85f86b9c9ad04637cfd446e283c83f900e4 100644 (file)
@@ -12,26 +12,10 @@ import type { Statistics } from './Statistics.js'
 
 import { ChargingStationEvents } from './ChargingStationEvents.js'
 
-export interface ChargingStationOptions extends JsonObject {
-  autoRegister?: boolean
-  autoStart?: boolean
-  enableStatistics?: boolean
-  ocppStrictCompliance?: boolean
-  persistentConfiguration?: boolean
-  stopTransactionsOnStopped?: boolean
-  supervisionUrls?: string | string[]
-}
-
-export interface ChargingStationWorkerData extends WorkerData {
-  index: number
-  options?: ChargingStationOptions
-  templateFile: string
+enum ChargingStationMessageEvents {
+  performanceStatistics = 'performanceStatistics',
 }
 
-export type EvseStatusWorkerType = {
-  connectors?: ConnectorStatus[]
-} & Omit<EvseStatus, 'connectors'>
-
 export interface ChargingStationData extends WorkerData {
   automaticTransactionGenerator?: ChargingStationAutomaticTransactionGeneratorConfiguration
   bootNotificationResponse?: BootNotificationResponse
@@ -48,10 +32,29 @@ export interface ChargingStationData extends WorkerData {
     | typeof WebSocket.OPEN
 }
 
-enum ChargingStationMessageEvents {
-  performanceStatistics = 'performanceStatistics',
+export interface ChargingStationOptions extends JsonObject {
+  autoRegister?: boolean
+  autoStart?: boolean
+  enableStatistics?: boolean
+  ocppStrictCompliance?: boolean
+  persistentConfiguration?: boolean
+  stopTransactionsOnStopped?: boolean
+  supervisionUrls?: string | string[]
+}
+
+export interface ChargingStationWorkerData extends WorkerData {
+  index: number
+  options?: ChargingStationOptions
+  templateFile: string
 }
 
+export interface ChargingStationWorkerMessage<T extends ChargingStationWorkerMessageData> {
+  data: T
+  event: ChargingStationWorkerMessageEvents
+}
+
+export type ChargingStationWorkerMessageData = ChargingStationData | Statistics
+
 export const ChargingStationWorkerMessageEvents = {
   ...ChargingStationEvents,
   ...ChargingStationMessageEvents,
@@ -61,9 +64,6 @@ export type ChargingStationWorkerMessageEvents =
   | ChargingStationEvents
   | ChargingStationMessageEvents
 
-export type ChargingStationWorkerMessageData = ChargingStationData | Statistics
-
-export interface ChargingStationWorkerMessage<T extends ChargingStationWorkerMessageData> {
-  data: T
-  event: ChargingStationWorkerMessageEvents
+export type EvseStatusWorkerType = Omit<EvseStatus, 'connectors'> & {
+  connectors?: ConnectorStatus[]
 }
index 19d932482fdb67e7adcd31e545d689a1bd68a922..d66050241c8f67f538fbea99c0ab74daf9b623f0 100644 (file)
@@ -6,7 +6,10 @@ import type { WorkerProcessType } from '../worker/index.js'
 import type { StorageType } from './Storage.js'
 import type { ApplicationProtocol, AuthenticationType } from './UIProtocol.js'
 
-type ServerOptions = ListenOptions
+export enum ApplicationProtocolVersion {
+  VERSION_11 = '1.1',
+  VERSION_20 = '2.0',
+}
 
 export enum ConfigurationSection {
   log = 'log',
@@ -21,63 +24,6 @@ export enum SupervisionUrlDistribution {
   ROUND_ROBIN = 'round-robin',
 }
 
-export interface StationTemplateUrl {
-  file: string
-  numberOfStations: number
-  provisionedNumberOfStations?: number
-}
-
-export interface LogConfiguration {
-  console?: boolean
-  enabled?: boolean
-  errorFile?: string
-  file?: string
-  format?: string
-  level?: string
-  maxFiles?: number | string
-  maxSize?: number | string
-  rotate?: boolean
-  statisticsInterval?: number
-}
-
-export enum ApplicationProtocolVersion {
-  VERSION_11 = '1.1',
-  VERSION_20 = '2.0',
-}
-
-export interface UIServerConfiguration {
-  authentication?: {
-    enabled: boolean
-    password?: string
-    type: AuthenticationType
-    username?: string
-  }
-  enabled?: boolean
-  options?: ServerOptions
-  type?: ApplicationProtocol
-  version?: ApplicationProtocolVersion
-}
-
-export interface StorageConfiguration {
-  enabled?: boolean
-  type?: StorageType
-  uri?: string
-}
-
-export type ElementsPerWorkerType = 'all' | 'auto' | number
-
-export interface WorkerConfiguration {
-  elementAddDelay?: number
-  elementsPerWorker?: ElementsPerWorkerType
-  /** @deprecated Use `elementAddDelay` instead. */
-  elementStartDelay?: number
-  poolMaxSize?: number
-  poolMinSize?: number
-  processType?: WorkerProcessType
-  resourceLimits?: ResourceLimits
-  startDelay?: number
-}
-
 export interface ConfigurationData {
   /** @deprecated Moved to charging station template. */
   autoReconnectMaxRetries?: number
@@ -123,3 +69,57 @@ export interface ConfigurationData {
   /** @deprecated Moved to worker configuration section. */
   workerStartDelay?: number
 }
+
+export type ElementsPerWorkerType = 'all' | 'auto' | number
+
+export interface LogConfiguration {
+  console?: boolean
+  enabled?: boolean
+  errorFile?: string
+  file?: string
+  format?: string
+  level?: string
+  maxFiles?: number | string
+  maxSize?: number | string
+  rotate?: boolean
+  statisticsInterval?: number
+}
+
+export interface StationTemplateUrl {
+  file: string
+  numberOfStations: number
+  provisionedNumberOfStations?: number
+}
+
+export interface StorageConfiguration {
+  enabled?: boolean
+  type?: StorageType
+  uri?: string
+}
+
+export interface UIServerConfiguration {
+  authentication?: {
+    enabled: boolean
+    password?: string
+    type: AuthenticationType
+    username?: string
+  }
+  enabled?: boolean
+  options?: ServerOptions
+  type?: ApplicationProtocol
+  version?: ApplicationProtocolVersion
+}
+
+export interface WorkerConfiguration {
+  elementAddDelay?: number
+  elementsPerWorker?: ElementsPerWorkerType
+  /** @deprecated Use `elementAddDelay` instead. */
+  elementStartDelay?: number
+  poolMaxSize?: number
+  poolMinSize?: number
+  processType?: WorkerProcessType
+  resourceLimits?: ResourceLimits
+  startDelay?: number
+}
+
+type ServerOptions = ListenOptions
index dcb00ad55ada651a78a4d53126bcd858a8858141..96eba54d065acf74cdcc8b78a53ffa66efe7e03f 100644 (file)
@@ -1,11 +1,11 @@
 import type { ConnectorStatus } from './ConnectorStatus.js'
 import type { AvailabilityType } from './ocpp/Requests.js'
 
-export interface EvseTemplate {
-  Connectors: Record<string, ConnectorStatus>
-}
-
 export interface EvseStatus {
   availability: AvailabilityType
   connectors: Map<number, ConnectorStatus>
 }
+
+export interface EvseTemplate {
+  Connectors: Record<string, ConnectorStatus>
+}
index bca5d9588c33293dbdd63dbcd52add9148d895d2..a7a6103e069f549640a0be2dfc72d49ba19e1d80 100644 (file)
@@ -1,8 +1,8 @@
-type JsonPrimitive = boolean | Date | null | number | string
-
 // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
 export type JsonObject = {
   [key in string]?: JsonType
 }
 
 export type JsonType = JsonObject | JsonPrimitive | JsonType[]
+
+type JsonPrimitive = boolean | Date | null | number | string
index 99572673edbfaa7d36450b860b8f190d13522d4c..f35f4f64fe9079df592f1fb3ccdc9265d52c0f06 100644 (file)
@@ -1,12 +1,12 @@
 import type { SampledValue } from './ocpp/MeterValues.js'
 
-export interface SampledValueTemplate extends SampledValue {
-  fluctuationPercent?: number
-  minimumValue?: number
-}
-
 export interface MeasurandPerPhaseSampledValueTemplates {
   L1?: SampledValueTemplate
   L2?: SampledValueTemplate
   L3?: SampledValueTemplate
 }
+
+export interface SampledValueTemplate extends SampledValue {
+  fluctuationPercent?: number
+  minimumValue?: number
+}
index 7df512e2a58d958328c8079a63824e9bf4a0241b..83da1905a2104f6402ada3aafc6101f3781bc1e5 100644 (file)
@@ -3,9 +3,13 @@ import type { CircularBuffer } from 'mnemonist'
 import type { WorkerData } from '../worker/index.js'
 import type { IncomingRequestCommand, RequestCommand } from './ocpp/Requests.js'
 
-export interface TimestampedData {
-  timestamp: number
-  value: number
+export interface Statistics extends WorkerData {
+  createdAt: Date
+  id: string
+  name: string
+  statisticsData: Map<IncomingRequestCommand | RequestCommand | string, StatisticsData>
+  updatedAt?: Date
+  uri: string
 }
 
 export type StatisticsData = Partial<{
@@ -24,15 +28,6 @@ export type StatisticsData = Partial<{
   totalTimeMeasurement: number
 }>
 
-export interface Statistics extends WorkerData {
-  createdAt: Date
-  id: string
-  name: string
-  statisticsData: Map<IncomingRequestCommand | RequestCommand | string, StatisticsData>
-  updatedAt?: Date
-  uri: string
-}
-
 export interface TemplateStatistics {
   added: number
   configured: number
@@ -40,3 +35,8 @@ export interface TemplateStatistics {
   provisioned: number
   started: number
 }
+
+export interface TimestampedData {
+  timestamp: number
+  value: number
+}
index f942670af9b7ec684a06c24ad527ac38a258c640..f2d73330b4ecae8f4521690df871bc1289df7db1 100644 (file)
@@ -1,3 +1,10 @@
+export enum DBName {
+  MARIA_DB = 'MariaDB',
+  MONGO_DB = 'MongoDB',
+  MYSQL = 'MySQL',
+  SQLITE = 'SQLite',
+}
+
 export enum StorageType {
   JSON_FILE = 'jsonfile',
   MARIA_DB = 'mariadb',
@@ -6,10 +13,3 @@ export enum StorageType {
   NONE = 'none',
   SQLITE = 'sqlite',
 }
-
-export enum DBName {
-  MARIA_DB = 'MariaDB',
-  MONGO_DB = 'MongoDB',
-  MYSQL = 'MySQL',
-  SQLITE = 'SQLite',
-}
index 50e70ed9c2cb4c51b765c5ae629447e226e62797..76e3ee7e3a78441378da4ceccfe6d356a4d2b7f1 100644 (file)
@@ -1,10 +1,6 @@
 import type { JsonObject } from './JsonType.js'
 import type { BroadcastChannelResponsePayload } from './WorkerBroadcastChannel.js'
 
-export enum Protocol {
-  UI = 'ui',
-}
-
 export enum ApplicationProtocol {
   HTTP = 'http',
   WS = 'ws',
@@ -15,26 +11,6 @@ export enum AuthenticationType {
   PROTOCOL_BASIC_AUTH = 'protocol-basic-auth',
 }
 
-export enum ProtocolVersion {
-  '0.0.1' = '0.0.1',
-}
-
-export type ProtocolRequest = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  ProcedureName,
-  RequestPayload
-]
-export type ProtocolResponse = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  ResponsePayload
-]
-
-export type ProtocolRequestHandler = (
-  uuid?: `${string}-${string}-${string}-${string}-${string}`,
-  procedureName?: ProcedureName,
-  payload?: RequestPayload
-) => Promise<ResponsePayload> | Promise<undefined> | ResponsePayload | undefined
-
 export enum ProcedureName {
   ADD_CHARGING_STATIONS = 'addChargingStations',
   AUTHORIZE = 'authorize',
@@ -63,16 +39,40 @@ export enum ProcedureName {
   STOP_TRANSACTION = 'stopTransaction',
 }
 
-export interface RequestPayload extends JsonObject {
-  connectorIds?: number[]
-  hashIds?: string[]
+export enum Protocol {
+  UI = 'ui',
 }
 
+export enum ProtocolVersion {
+  '0.0.1' = '0.0.1',
+}
 export enum ResponseStatus {
   FAILURE = 'failure',
   SUCCESS = 'success',
 }
 
+export type ProtocolRequest = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ProcedureName,
+  RequestPayload
+]
+
+export type ProtocolRequestHandler = (
+  uuid?: `${string}-${string}-${string}-${string}-${string}`,
+  procedureName?: ProcedureName,
+  payload?: RequestPayload
+) => Promise<ResponsePayload> | Promise<undefined> | ResponsePayload | undefined
+
+export type ProtocolResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ResponsePayload
+]
+
+export interface RequestPayload extends JsonObject {
+  connectorIds?: number[]
+  hashIds?: string[]
+}
+
 export interface ResponsePayload extends JsonObject {
   hashIdsFailed?: string[]
   hashIdsSucceeded?: string[]
index 396c768b4c051dddcb1e834a716eb4c694133e47..65fffc5771d618fadb2be657394b48091a9afadb 100644 (file)
@@ -1,4 +1,3 @@
-/* eslint-disable perfectionist/sort-enums */
 export const WebSocketCloseEventStatusString: Record<WebSocketCloseEventStatusCode, string> =
   Object.freeze({
     1000: 'Normal Closure',
@@ -19,6 +18,7 @@ export const WebSocketCloseEventStatusString: Record<WebSocketCloseEventStatusCo
     1015: 'TLS Handshake',
   })
 
+/* eslint-disable perfectionist/sort-enums */
 export enum WebSocketCloseEventStatusCode {
   CLOSE_NORMAL = 1000,
   CLOSE_GOING_AWAY = 1001,
@@ -37,6 +37,7 @@ export enum WebSocketCloseEventStatusCode {
   CLOSE_BAD_GATEWAY = 1014,
   CLOSE_TLS_HANDSHAKE = 1015,
 }
+/* eslint-enable perfectionist/sort-enums */
 
 export interface WSError extends Error {
   code?: string
index 8f1cebfc9b311e4795e67537ec9d324cdfb50334..97811d934f1191be6d3e72fd08ad6070dbbf8020 100644 (file)
@@ -1,15 +1,5 @@
 import type { RequestPayload, ResponsePayload } from './UIProtocol.js'
 
-export type BroadcastChannelRequest = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  BroadcastChannelProcedureName,
-  BroadcastChannelRequestPayload
-]
-export type BroadcastChannelResponse = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  BroadcastChannelResponsePayload
-]
-
 export enum BroadcastChannelProcedureName {
   AUTHORIZE = 'authorize',
   BOOT_NOTIFICATION = 'bootNotification',
@@ -31,11 +21,22 @@ export enum BroadcastChannelProcedureName {
   STOP_TRANSACTION = 'stopTransaction',
 }
 
+export type BroadcastChannelRequest = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  BroadcastChannelProcedureName,
+  BroadcastChannelRequestPayload
+]
+
 export interface BroadcastChannelRequestPayload extends RequestPayload {
   connectorId?: number
   transactionId?: number
 }
 
+export type BroadcastChannelResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  BroadcastChannelResponsePayload
+]
+
 export interface BroadcastChannelResponsePayload
   extends Omit<ResponsePayload, 'hashIdsFailed' | 'hashIdsSucceeded' | 'responsesFailed'> {
   hashId: string | undefined
index cea7d569f269421ff973bea0219ef71ced0a5929..8a26dbd215b7308c49e41b589d987eb0df45166c 100644 (file)
@@ -1,5 +1,27 @@
 import type { JsonObject } from '../../JsonType.js'
 
+export enum OCPP16ChargingProfileKindType {
+  ABSOLUTE = 'Absolute',
+  RECURRING = 'Recurring',
+  RELATIVE = 'Relative',
+}
+
+export enum OCPP16ChargingProfilePurposeType {
+  CHARGE_POINT_MAX_PROFILE = 'ChargePointMaxProfile',
+  TX_DEFAULT_PROFILE = 'TxDefaultProfile',
+  TX_PROFILE = 'TxProfile',
+}
+
+export enum OCPP16ChargingRateUnitType {
+  AMPERE = 'A',
+  WATT = 'W',
+}
+
+export enum OCPP16RecurrencyKindType {
+  DAILY = 'Daily',
+  WEEKLY = 'Weekly',
+}
+
 export interface OCPP16ChargingProfile extends JsonObject {
   chargingProfileId: number
   chargingProfileKind: OCPP16ChargingProfileKindType
@@ -25,25 +47,3 @@ export interface OCPP16ChargingSchedulePeriod extends JsonObject {
   numberPhases?: number
   startPeriod: number
 }
-
-export enum OCPP16ChargingRateUnitType {
-  AMPERE = 'A',
-  WATT = 'W',
-}
-
-export enum OCPP16ChargingProfileKindType {
-  ABSOLUTE = 'Absolute',
-  RECURRING = 'Recurring',
-  RELATIVE = 'Relative',
-}
-
-export enum OCPP16ChargingProfilePurposeType {
-  CHARGE_POINT_MAX_PROFILE = 'ChargePointMaxProfile',
-  TX_DEFAULT_PROFILE = 'TxDefaultProfile',
-  TX_PROFILE = 'TxProfile',
-}
-
-export enum OCPP16RecurrencyKindType {
-  DAILY = 'Daily',
-  WEEKLY = 'Weekly',
-}
index 22658ee6f810c8fb5eb2d573775d3f9988a10094..12074b23fffcfb2031fe84f766a32cf7b731267b 100644 (file)
@@ -1,12 +1,3 @@
-export enum OCPP16SupportedFeatureProfiles {
-  Core = 'Core',
-  FirmwareManagement = 'FirmwareManagement',
-  LocalAuthListManagement = 'LocalAuthListManagement',
-  RemoteTrigger = 'RemoteTrigger',
-  Reservation = 'Reservation',
-  SmartCharging = 'SmartCharging',
-}
-
 export enum OCPP16StandardParametersKey {
   AllowOfflineTxForUnknownId = 'AllowOfflineTxForUnknownId',
   AuthorizationCacheEnabled = 'AuthorizationCacheEnabled',
@@ -54,6 +45,15 @@ export enum OCPP16StandardParametersKey {
   WebSocketPingInterval = 'WebSocketPingInterval',
 }
 
+export enum OCPP16SupportedFeatureProfiles {
+  Core = 'Core',
+  FirmwareManagement = 'FirmwareManagement',
+  LocalAuthListManagement = 'LocalAuthListManagement',
+  RemoteTrigger = 'RemoteTrigger',
+  Reservation = 'Reservation',
+  SmartCharging = 'SmartCharging',
+}
+
 export enum OCPP16VendorParametersKey {
   ConnectionUrl = 'ConnectionUrl',
 }
index ff4f304a7d174b4d8396b5c1dbe93861fb1f175c..9630f0629ff0b8080dcf2569ed1f23666821f8b7 100644 (file)
@@ -1,25 +1,6 @@
 import type { EmptyObject } from '../../EmptyObject.js'
 import type { JsonObject } from '../../JsonType.js'
 
-export enum OCPP16MeterValueUnit {
-  AMP = 'A',
-  KILO_VAR = 'kvar',
-  KILO_VAR_HOUR = 'kvarh',
-  KILO_VOLT_AMP = 'kVA',
-  KILO_WATT = 'kW',
-  KILO_WATT_HOUR = 'kWh',
-  PERCENT = 'Percent',
-  TEMP_CELSIUS = 'Celsius',
-  TEMP_FAHRENHEIT = 'Fahrenheit',
-  TEMP_KELVIN = 'K',
-  VAR = 'var',
-  VAR_HOUR = 'varh',
-  VOLT = 'V',
-  VOLT_AMP = 'VA',
-  WATT = 'W',
-  WATT_HOUR = 'Wh',
-}
-
 export enum OCPP16MeterValueContext {
   INTERRUPTION_BEGIN = 'Interruption.Begin',
   INTERRUPTION_END = 'Interruption.End',
@@ -31,6 +12,14 @@ export enum OCPP16MeterValueContext {
   TRIGGER = 'Trigger',
 }
 
+export enum OCPP16MeterValueLocation {
+  BODY = 'Body',
+  CABLE = 'Cable',
+  EV = 'EV',
+  INLET = 'Inlet',
+  OUTLET = 'Outlet',
+}
+
 export enum OCPP16MeterValueMeasurand {
   CURRENT_EXPORT = 'Current.Export',
   CURRENT_IMPORT = 'Current.Import',
@@ -56,14 +45,6 @@ export enum OCPP16MeterValueMeasurand {
   VOLTAGE = 'Voltage',
 }
 
-export enum OCPP16MeterValueLocation {
-  BODY = 'Body',
-  CABLE = 'Cable',
-  EV = 'EV',
-  INLET = 'Inlet',
-  OUTLET = 'Outlet',
-}
-
 export enum OCPP16MeterValuePhase {
   L1 = 'L1',
   L1_L2 = 'L1-L2',
@@ -77,21 +58,30 @@ export enum OCPP16MeterValuePhase {
   N = 'N',
 }
 
+export enum OCPP16MeterValueUnit {
+  AMP = 'A',
+  KILO_VAR = 'kvar',
+  KILO_VAR_HOUR = 'kvarh',
+  KILO_VOLT_AMP = 'kVA',
+  KILO_WATT = 'kW',
+  KILO_WATT_HOUR = 'kWh',
+  PERCENT = 'Percent',
+  TEMP_CELSIUS = 'Celsius',
+  TEMP_FAHRENHEIT = 'Fahrenheit',
+  TEMP_KELVIN = 'K',
+  VAR = 'var',
+  VAR_HOUR = 'varh',
+  VOLT = 'V',
+  VOLT_AMP = 'VA',
+  WATT = 'W',
+  WATT_HOUR = 'Wh',
+}
+
 enum OCPP16MeterValueFormat {
   RAW = 'Raw',
   SIGNED_DATA = 'SignedData',
 }
 
-export interface OCPP16SampledValue extends JsonObject {
-  context?: OCPP16MeterValueContext
-  format?: OCPP16MeterValueFormat
-  location?: OCPP16MeterValueLocation
-  measurand?: OCPP16MeterValueMeasurand
-  phase?: OCPP16MeterValuePhase
-  unit?: OCPP16MeterValueUnit
-  value: string
-}
-
 export interface OCPP16MeterValue extends JsonObject {
   sampledValue: OCPP16SampledValue[]
   timestamp: Date
@@ -104,3 +94,13 @@ export interface OCPP16MeterValuesRequest extends JsonObject {
 }
 
 export type OCPP16MeterValuesResponse = EmptyObject
+
+export interface OCPP16SampledValue extends JsonObject {
+  context?: OCPP16MeterValueContext
+  format?: OCPP16MeterValueFormat
+  location?: OCPP16MeterValueLocation
+  measurand?: OCPP16MeterValueMeasurand
+  phase?: OCPP16MeterValuePhase
+  unit?: OCPP16MeterValueUnit
+  value: string
+}
index 5715dd120f8cdb0cfb19b1d75c8bba1bf5ece4dd..44327c8b47f622566612b3621f0f7fadd4954fdb 100644 (file)
@@ -10,17 +10,21 @@ import type {
 import type { OCPP16StandardParametersKey, OCPP16VendorParametersKey } from './Configuration.js'
 import type { OCPP16DiagnosticsStatus } from './DiagnosticsStatus.js'
 
-export enum OCPP16RequestCommand {
-  AUTHORIZE = 'Authorize',
-  BOOT_NOTIFICATION = 'BootNotification',
-  DATA_TRANSFER = 'DataTransfer',
-  DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
-  FIRMWARE_STATUS_NOTIFICATION = 'FirmwareStatusNotification',
-  HEARTBEAT = 'Heartbeat',
-  METER_VALUES = 'MeterValues',
-  START_TRANSACTION = 'StartTransaction',
-  STATUS_NOTIFICATION = 'StatusNotification',
-  STOP_TRANSACTION = 'StopTransaction',
+export enum OCPP16AvailabilityType {
+  Inoperative = 'Inoperative',
+  Operative = 'Operative',
+}
+
+export enum OCPP16DataTransferVendorId {}
+
+export enum OCPP16FirmwareStatus {
+  Downloaded = 'Downloaded',
+  DownloadFailed = 'DownloadFailed',
+  Downloading = 'Downloading',
+  Idle = 'Idle',
+  InstallationFailed = 'InstallationFailed',
+  Installed = 'Installed',
+  Installing = 'Installing',
 }
 
 export enum OCPP16IncomingRequestCommand {
@@ -43,7 +47,49 @@ export enum OCPP16IncomingRequestCommand {
   UPDATE_FIRMWARE = 'UpdateFirmware',
 }
 
-export type OCPP16HeartbeatRequest = EmptyObject
+export enum OCPP16MessageTrigger {
+  BootNotification = 'BootNotification',
+  DiagnosticsStatusNotification = 'DiagnosticsStatusNotification',
+  FirmwareStatusNotification = 'FirmwareStatusNotification',
+  Heartbeat = 'Heartbeat',
+  MeterValues = 'MeterValues',
+  StatusNotification = 'StatusNotification',
+}
+
+export enum OCPP16RequestCommand {
+  AUTHORIZE = 'Authorize',
+  BOOT_NOTIFICATION = 'BootNotification',
+  DATA_TRANSFER = 'DataTransfer',
+  DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
+  FIRMWARE_STATUS_NOTIFICATION = 'FirmwareStatusNotification',
+  HEARTBEAT = 'Heartbeat',
+  METER_VALUES = 'MeterValues',
+  START_TRANSACTION = 'StartTransaction',
+  STATUS_NOTIFICATION = 'StatusNotification',
+  STOP_TRANSACTION = 'StopTransaction',
+}
+
+enum ResetType {
+  HARD = 'Hard',
+  SOFT = 'Soft',
+}
+
+export interface ChangeConfigurationRequest extends JsonObject {
+  key: OCPP16ConfigurationKey
+  value: string
+}
+
+export interface GetConfigurationRequest extends JsonObject {
+  key?: OCPP16ConfigurationKey[]
+}
+
+export interface GetDiagnosticsRequest extends JsonObject {
+  location: string
+  retries?: number
+  retryInterval?: number
+  startTime?: Date
+  stopTime?: Date
+}
 
 export interface OCPP16BootNotificationRequest extends JsonObject {
   chargeBoxSerialNumber?: string
@@ -57,50 +103,36 @@ export interface OCPP16BootNotificationRequest extends JsonObject {
   meterType?: string
 }
 
-export interface OCPP16StatusNotificationRequest extends JsonObject {
+export interface OCPP16CancelReservationRequest extends JsonObject {
+  reservationId: number
+}
+
+export interface OCPP16ChangeAvailabilityRequest extends JsonObject {
   connectorId: number
-  errorCode: OCPP16ChargePointErrorCode
-  info?: string
-  status: OCPP16ChargePointStatus
-  timestamp?: Date
-  vendorErrorCode?: string
-  vendorId?: string
+  type: OCPP16AvailabilityType
 }
 
 export type OCPP16ClearCacheRequest = EmptyObject
 
-type OCPP16ConfigurationKey = OCPP16StandardParametersKey | OCPP16VendorParametersKey | string
-
-export interface ChangeConfigurationRequest extends JsonObject {
-  key: OCPP16ConfigurationKey
-  value: string
-}
-
-export interface RemoteStartTransactionRequest extends JsonObject {
-  chargingProfile?: OCPP16ChargingProfile
+export interface OCPP16ClearChargingProfileRequest extends JsonObject {
+  chargingProfilePurpose?: OCPP16ChargingProfilePurposeType
   connectorId?: number
-  idTag: string
-}
-
-export interface RemoteStopTransactionRequest extends JsonObject {
-  transactionId: number
-}
-
-export interface UnlockConnectorRequest extends JsonObject {
-  connectorId: number
+  id?: number
+  stackLevel?: number
 }
 
-export interface GetConfigurationRequest extends JsonObject {
-  key?: OCPP16ConfigurationKey[]
+export interface OCPP16DataTransferRequest extends JsonObject {
+  data?: string
+  messageId?: string
+  vendorId: string
 }
 
-enum ResetType {
-  HARD = 'Hard',
-  SOFT = 'Soft',
+export interface OCPP16DiagnosticsStatusNotificationRequest extends JsonObject {
+  status: OCPP16DiagnosticsStatus
 }
 
-export interface ResetRequest extends JsonObject {
-  type: ResetType
+export interface OCPP16FirmwareStatusNotificationRequest extends JsonObject {
+  status: OCPP16FirmwareStatus
 }
 
 export interface OCPP16GetCompositeScheduleRequest extends JsonObject {
@@ -109,26 +141,29 @@ export interface OCPP16GetCompositeScheduleRequest extends JsonObject {
   duration: number
 }
 
-export interface SetChargingProfileRequest extends JsonObject {
-  connectorId: number
-  csChargingProfiles: OCPP16ChargingProfile
-}
+export type OCPP16HeartbeatRequest = EmptyObject
 
-export enum OCPP16AvailabilityType {
-  Inoperative = 'Inoperative',
-  Operative = 'Operative',
+export interface OCPP16ReserveNowRequest extends JsonObject {
+  connectorId: number
+  expiryDate: Date
+  idTag: string
+  parentIdTag?: string
+  reservationId: number
 }
 
-export interface OCPP16ChangeAvailabilityRequest extends JsonObject {
+export interface OCPP16StatusNotificationRequest extends JsonObject {
   connectorId: number
-  type: OCPP16AvailabilityType
+  errorCode: OCPP16ChargePointErrorCode
+  info?: string
+  status: OCPP16ChargePointStatus
+  timestamp?: Date
+  vendorErrorCode?: string
+  vendorId?: string
 }
 
-export interface OCPP16ClearChargingProfileRequest extends JsonObject {
-  chargingProfilePurpose?: OCPP16ChargingProfilePurposeType
+export interface OCPP16TriggerMessageRequest extends JsonObject {
   connectorId?: number
-  id?: number
-  stackLevel?: number
+  requestedMessage: OCPP16MessageTrigger
 }
 
 export interface OCPP16UpdateFirmwareRequest extends JsonObject {
@@ -138,62 +173,27 @@ export interface OCPP16UpdateFirmwareRequest extends JsonObject {
   retryInterval?: number
 }
 
-export enum OCPP16FirmwareStatus {
-  Downloaded = 'Downloaded',
-  DownloadFailed = 'DownloadFailed',
-  Downloading = 'Downloading',
-  Idle = 'Idle',
-  InstallationFailed = 'InstallationFailed',
-  Installed = 'Installed',
-  Installing = 'Installing',
-}
-
-export interface OCPP16FirmwareStatusNotificationRequest extends JsonObject {
-  status: OCPP16FirmwareStatus
-}
-
-export interface GetDiagnosticsRequest extends JsonObject {
-  location: string
-  retries?: number
-  retryInterval?: number
-  startTime?: Date
-  stopTime?: Date
-}
-
-export interface OCPP16DiagnosticsStatusNotificationRequest extends JsonObject {
-  status: OCPP16DiagnosticsStatus
+export interface RemoteStartTransactionRequest extends JsonObject {
+  chargingProfile?: OCPP16ChargingProfile
+  connectorId?: number
+  idTag: string
 }
 
-export enum OCPP16MessageTrigger {
-  BootNotification = 'BootNotification',
-  DiagnosticsStatusNotification = 'DiagnosticsStatusNotification',
-  FirmwareStatusNotification = 'FirmwareStatusNotification',
-  Heartbeat = 'Heartbeat',
-  MeterValues = 'MeterValues',
-  StatusNotification = 'StatusNotification',
+export interface RemoteStopTransactionRequest extends JsonObject {
+  transactionId: number
 }
 
-export interface OCPP16TriggerMessageRequest extends JsonObject {
-  connectorId?: number
-  requestedMessage: OCPP16MessageTrigger
+export interface ResetRequest extends JsonObject {
+  type: ResetType
 }
 
-export enum OCPP16DataTransferVendorId {}
-
-export interface OCPP16DataTransferRequest extends JsonObject {
-  data?: string
-  messageId?: string
-  vendorId: string
+export interface SetChargingProfileRequest extends JsonObject {
+  connectorId: number
+  csChargingProfiles: OCPP16ChargingProfile
 }
 
-export interface OCPP16ReserveNowRequest extends JsonObject {
+export interface UnlockConnectorRequest extends JsonObject {
   connectorId: number
-  expiryDate: Date
-  idTag: string
-  parentIdTag?: string
-  reservationId: number
 }
 
-export interface OCPP16CancelReservationRequest extends JsonObject {
-  reservationId: number
-}
+type OCPP16ConfigurationKey = OCPP16StandardParametersKey | OCPP16VendorParametersKey | string
index 18eddc9a35772bd950f65ccafb29a0e537fa266d..6dc0358065370752ea7f59edcb021ac15043336a 100644 (file)
@@ -4,18 +4,21 @@ import type { GenericStatus, RegistrationStatusEnumType } from '../Common.js'
 import type { OCPPConfigurationKey } from '../Configuration.js'
 import type { OCPP16ChargingSchedule } from './ChargingProfile.js'
 
-export interface OCPP16HeartbeatResponse extends JsonObject {
-  currentTime: Date
+export enum OCPP16AvailabilityStatus {
+  ACCEPTED = 'Accepted',
+  REJECTED = 'Rejected',
+  SCHEDULED = 'Scheduled',
 }
 
-export enum OCPP16UnlockStatus {
+export enum OCPP16ChargingProfileStatus {
+  ACCEPTED = 'Accepted',
   NOT_SUPPORTED = 'NotSupported',
-  UNLOCK_FAILED = 'UnlockFailed',
-  UNLOCKED = 'Unlocked',
+  REJECTED = 'Rejected',
 }
 
-export interface UnlockConnectorResponse extends JsonObject {
-  status: OCPP16UnlockStatus
+export enum OCPP16ClearChargingProfileStatus {
+  ACCEPTED = 'Accepted',
+  UNKNOWN = 'Unknown',
 }
 
 export enum OCPP16ConfigurationStatus {
@@ -25,100 +28,97 @@ export enum OCPP16ConfigurationStatus {
   REJECTED = 'Rejected',
 }
 
-export interface ChangeConfigurationResponse extends JsonObject {
-  status: OCPP16ConfigurationStatus
+export enum OCPP16DataTransferStatus {
+  ACCEPTED = 'Accepted',
+  REJECTED = 'Rejected',
+  UNKNOWN_MESSAGE_ID = 'UnknownMessageId',
+  UNKNOWN_VENDOR_ID = 'UnknownVendorId',
 }
 
-export interface OCPP16BootNotificationResponse extends JsonObject {
-  currentTime: Date
-  interval: number
-  status: RegistrationStatusEnumType
+export enum OCPP16ReservationStatus {
+  ACCEPTED = 'Accepted',
+  FAULTED = 'Faulted',
+  NOT_SUPPORTED = 'NotSupported',
+  OCCUPIED = 'Occupied',
+  REJECTED = 'Rejected',
+  UNAVAILABLE = 'Unavailable',
 }
 
-export type OCPP16StatusNotificationResponse = EmptyObject
-
-export interface GetConfigurationResponse extends JsonObject {
-  configurationKey: OCPPConfigurationKey[]
-  unknownKey: string[]
+export enum OCPP16TriggerMessageStatus {
+  ACCEPTED = 'Accepted',
+  NOT_IMPLEMENTED = 'NotImplemented',
+  REJECTED = 'Rejected',
 }
 
-export enum OCPP16ChargingProfileStatus {
-  ACCEPTED = 'Accepted',
+export enum OCPP16UnlockStatus {
   NOT_SUPPORTED = 'NotSupported',
-  REJECTED = 'Rejected',
+  UNLOCK_FAILED = 'UnlockFailed',
+  UNLOCKED = 'Unlocked',
 }
 
-export interface OCPP16GetCompositeScheduleResponse extends JsonObject {
-  chargingSchedule?: OCPP16ChargingSchedule
-  connectorId?: number
-  scheduleStart?: Date
-  status: GenericStatus
+export interface ChangeConfigurationResponse extends JsonObject {
+  status: OCPP16ConfigurationStatus
 }
 
-export interface SetChargingProfileResponse extends JsonObject {
-  status: OCPP16ChargingProfileStatus
+export interface GetConfigurationResponse extends JsonObject {
+  configurationKey: OCPPConfigurationKey[]
+  unknownKey: string[]
 }
 
-export enum OCPP16AvailabilityStatus {
-  ACCEPTED = 'Accepted',
-  REJECTED = 'Rejected',
-  SCHEDULED = 'Scheduled',
+export interface GetDiagnosticsResponse extends JsonObject {
+  fileName?: string
 }
 
-export interface OCPP16ChangeAvailabilityResponse extends JsonObject {
-  status: OCPP16AvailabilityStatus
+export interface OCPP16BootNotificationResponse extends JsonObject {
+  currentTime: Date
+  interval: number
+  status: RegistrationStatusEnumType
 }
 
-export enum OCPP16ClearChargingProfileStatus {
-  ACCEPTED = 'Accepted',
-  UNKNOWN = 'Unknown',
+export interface OCPP16ChangeAvailabilityResponse extends JsonObject {
+  status: OCPP16AvailabilityStatus
 }
 
 export interface OCPP16ClearChargingProfileResponse extends JsonObject {
   status: OCPP16ClearChargingProfileStatus
 }
 
-export type OCPP16UpdateFirmwareResponse = EmptyObject
+export interface OCPP16DataTransferResponse extends JsonObject {
+  data?: string
+  status: OCPP16DataTransferStatus
+}
+
+export type OCPP16DiagnosticsStatusNotificationResponse = EmptyObject
 
 export type OCPP16FirmwareStatusNotificationResponse = EmptyObject
 
-export interface GetDiagnosticsResponse extends JsonObject {
-  fileName?: string
+export interface OCPP16GetCompositeScheduleResponse extends JsonObject {
+  chargingSchedule?: OCPP16ChargingSchedule
+  connectorId?: number
+  scheduleStart?: Date
+  status: GenericStatus
 }
 
-export type OCPP16DiagnosticsStatusNotificationResponse = EmptyObject
+export interface OCPP16HeartbeatResponse extends JsonObject {
+  currentTime: Date
+}
 
-export enum OCPP16TriggerMessageStatus {
-  ACCEPTED = 'Accepted',
-  NOT_IMPLEMENTED = 'NotImplemented',
-  REJECTED = 'Rejected',
+export interface OCPP16ReserveNowResponse extends JsonObject {
+  status: OCPP16ReservationStatus
 }
 
+export type OCPP16StatusNotificationResponse = EmptyObject
+
 export interface OCPP16TriggerMessageResponse extends JsonObject {
   status: OCPP16TriggerMessageStatus
 }
 
-export enum OCPP16DataTransferStatus {
-  ACCEPTED = 'Accepted',
-  REJECTED = 'Rejected',
-  UNKNOWN_MESSAGE_ID = 'UnknownMessageId',
-  UNKNOWN_VENDOR_ID = 'UnknownVendorId',
-}
-
-export interface OCPP16DataTransferResponse extends JsonObject {
-  data?: string
-  status: OCPP16DataTransferStatus
-}
+export type OCPP16UpdateFirmwareResponse = EmptyObject
 
-export enum OCPP16ReservationStatus {
-  ACCEPTED = 'Accepted',
-  FAULTED = 'Faulted',
-  NOT_SUPPORTED = 'NotSupported',
-  OCCUPIED = 'Occupied',
-  REJECTED = 'Rejected',
-  UNAVAILABLE = 'Unavailable',
+export interface SetChargingProfileResponse extends JsonObject {
+  status: OCPP16ChargingProfileStatus
 }
 
-export interface OCPP16ReserveNowResponse extends JsonObject {
-  status: OCPP16ReservationStatus
+export interface UnlockConnectorResponse extends JsonObject {
+  status: OCPP16UnlockStatus
 }
index beec7cd8447dd4f6d3b183e51647c2396f4def4c..2aa5db38e8d5663a64ec27134c7cca0d83a137b2 100644 (file)
@@ -1,6 +1,14 @@
 import type { JsonObject } from '../../JsonType.js'
 import type { OCPP16MeterValue } from './MeterValues.js'
 
+export enum OCPP16AuthorizationStatus {
+  ACCEPTED = 'Accepted',
+  BLOCKED = 'Blocked',
+  CONCURRENT_TX = 'ConcurrentTx',
+  EXPIRED = 'Expired',
+  INVALID = 'Invalid',
+}
+
 export enum OCPP16StopTransactionReason {
   DE_AUTHORIZED = 'DeAuthorized',
   EMERGENCY_STOP = 'EmergencyStop',
@@ -15,20 +23,6 @@ export enum OCPP16StopTransactionReason {
   UNLOCK_COMMAND = 'UnlockCommand',
 }
 
-export enum OCPP16AuthorizationStatus {
-  ACCEPTED = 'Accepted',
-  BLOCKED = 'Blocked',
-  CONCURRENT_TX = 'ConcurrentTx',
-  EXPIRED = 'Expired',
-  INVALID = 'Invalid',
-}
-
-interface IdTagInfo extends JsonObject {
-  expiryDate?: Date
-  parentIdTag?: string
-  status: OCPP16AuthorizationStatus
-}
-
 export interface OCPP16AuthorizeRequest extends JsonObject {
   idTag: string
 }
@@ -62,3 +56,9 @@ export interface OCPP16StopTransactionRequest extends JsonObject {
 export interface OCPP16StopTransactionResponse extends JsonObject {
   idTagInfo?: IdTagInfo
 }
+
+interface IdTagInfo extends JsonObject {
+  expiryDate?: Date
+  parentIdTag?: string
+  status: OCPP16AuthorizationStatus
+}
index d13279e93cd36b81894fb0e888c8c6fdc0d50307..1bd577bedb4a585141f946f367d483fed203b5b6 100644 (file)
@@ -1,17 +1,6 @@
 import type { JsonObject } from '../../JsonType.js'
 import type { GenericStatus } from '../Common.js'
 
-export enum DataEnumType {
-  boolean = 'boolean',
-  dateTime = 'dateTime',
-  decimal = 'decimal',
-  integer = 'integer',
-  MemberList = 'MemberList',
-  OptionList = 'OptionList',
-  SequenceList = 'SequenceList',
-  string = 'string',
-}
-
 export enum BootReasonEnumType {
   ApplicationReset = 'ApplicationReset',
   FirmwareUpdate = 'FirmwareUpdate',
@@ -24,25 +13,31 @@ export enum BootReasonEnumType {
   Watchdog = 'Watchdog',
 }
 
-export enum OperationalStatusEnumType {
-  Inoperative = 'Inoperative',
-  Operative = 'Operative',
+export enum CertificateActionEnumType {
+  Install = 'Install',
+  Update = 'Update',
 }
 
-export enum OCPP20ConnectorStatusEnumType {
-  Available = 'Available',
-  Faulted = 'Faulted',
-  Occupied = 'Occupied',
-  Reserved = 'Reserved',
-  Unavailable = 'Unavailable',
+export enum CertificateSigningUseEnumType {
+  ChargingStationCertificate = 'ChargingStationCertificate',
+  V2GCertificate = 'V2GCertificate',
 }
 
-export type GenericStatusEnumType = GenericStatus
+export enum DataEnumType {
+  boolean = 'boolean',
+  dateTime = 'dateTime',
+  decimal = 'decimal',
+  integer = 'integer',
+  MemberList = 'MemberList',
+  OptionList = 'OptionList',
+  SequenceList = 'SequenceList',
+  string = 'string',
+}
 
-export enum HashAlgorithmEnumType {
-  SHA256 = 'SHA256',
-  SHA384 = 'SHA384',
-  SHA512 = 'SHA512',
+export enum DeleteCertificateStatusEnumType {
+  Accepted = 'Accepted',
+  Failed = 'Failed',
+  NotFound = 'NotFound',
 }
 
 export enum GetCertificateIdUseEnumType {
@@ -63,6 +58,12 @@ export enum GetInstalledCertificateStatusEnumType {
   NotFound = 'NotFound',
 }
 
+export enum HashAlgorithmEnumType {
+  SHA256 = 'SHA256',
+  SHA384 = 'SHA384',
+  SHA512 = 'SHA512',
+}
+
 export enum InstallCertificateStatusEnumType {
   Accepted = 'Accepted',
   Failed = 'Failed',
@@ -76,24 +77,25 @@ export enum InstallCertificateUseEnumType {
   V2GRootCertificate = 'V2GRootCertificate',
 }
 
-export enum DeleteCertificateStatusEnumType {
-  Accepted = 'Accepted',
-  Failed = 'Failed',
-  NotFound = 'NotFound',
+export enum OCPP20ConnectorStatusEnumType {
+  Available = 'Available',
+  Faulted = 'Faulted',
+  Occupied = 'Occupied',
+  Reserved = 'Reserved',
+  Unavailable = 'Unavailable',
 }
 
-export enum CertificateActionEnumType {
-  Install = 'Install',
-  Update = 'Update',
+export enum OperationalStatusEnumType {
+  Inoperative = 'Inoperative',
+  Operative = 'Operative',
 }
 
-export enum CertificateSigningUseEnumType {
-  ChargingStationCertificate = 'ChargingStationCertificate',
-  V2GCertificate = 'V2GCertificate',
+export interface CertificateHashDataChainType extends JsonObject {
+  certificateHashData: CertificateHashDataType
+  certificateType: GetCertificateIdUseEnumType
+  childCertificateHashData?: CertificateHashDataType
 }
 
-export type CertificateSignedStatusEnumType = GenericStatusEnumType
-
 export interface CertificateHashDataType extends JsonObject {
   hashAlgorithm: HashAlgorithmEnumType
   issuerKeyHash: string
@@ -101,12 +103,15 @@ export interface CertificateHashDataType extends JsonObject {
   serialNumber: string
 }
 
-export interface CertificateHashDataChainType extends JsonObject {
-  certificateHashData: CertificateHashDataType
-  certificateType: GetCertificateIdUseEnumType
-  childCertificateHashData?: CertificateHashDataType
+export type CertificateSignedStatusEnumType = GenericStatusEnumType
+
+export interface EVSEType extends JsonObject {
+  connectorId?: string
+  id: number
 }
 
+export type GenericStatusEnumType = GenericStatus
+
 export interface OCSPRequestDataType extends JsonObject {
   hashAlgorithm: HashAlgorithmEnumType
   issuerKeyHash: string
@@ -119,8 +124,3 @@ export interface StatusInfoType extends JsonObject {
   additionalInfo?: string
   reasonCode: string
 }
-
-export interface EVSEType extends JsonObject {
-  connectorId?: string
-  id: number
-}
index 6fd56088900f18bd61d18cb406049bbf2f8ef8ea..0cf30f92916921be5e28319d036f8acd62d2a38b 100644 (file)
@@ -7,29 +7,16 @@ import type {
 } from './Common.js'
 import type { OCPP20SetVariableDataType } from './Variables.js'
 
-export enum OCPP20RequestCommand {
-  BOOT_NOTIFICATION = 'BootNotification',
-  HEARTBEAT = 'Heartbeat',
-  STATUS_NOTIFICATION = 'StatusNotification',
-}
-
 export enum OCPP20IncomingRequestCommand {
   CLEAR_CACHE = 'ClearCache',
   REQUEST_START_TRANSACTION = 'RequestStartTransaction',
   REQUEST_STOP_TRANSACTION = 'RequestStopTransaction',
 }
 
-interface ModemType extends JsonObject {
-  iccid?: string
-  imsi?: string
-}
-
-interface ChargingStationType extends JsonObject {
-  firmwareVersion?: string
-  model: string
-  modem?: ModemType
-  serialNumber?: string
-  vendorName: string
+export enum OCPP20RequestCommand {
+  BOOT_NOTIFICATION = 'BootNotification',
+  HEARTBEAT = 'Heartbeat',
+  STATUS_NOTIFICATION = 'StatusNotification',
 }
 
 export interface OCPP20BootNotificationRequest extends JsonObject {
@@ -37,9 +24,18 @@ export interface OCPP20BootNotificationRequest extends JsonObject {
   reason: BootReasonEnumType
 }
 
+export type OCPP20ClearCacheRequest = EmptyObject
+
 export type OCPP20HeartbeatRequest = EmptyObject
 
-export type OCPP20ClearCacheRequest = EmptyObject
+export interface OCPP20InstallCertificateRequest extends JsonObject {
+  certificate: string
+  certificateType: InstallCertificateUseEnumType
+}
+
+export interface OCPP20SetVariablesRequest extends JsonObject {
+  setVariableData: OCPP20SetVariableDataType[]
+}
 
 export interface OCPP20StatusNotificationRequest extends JsonObject {
   connectorId: number
@@ -48,11 +44,15 @@ export interface OCPP20StatusNotificationRequest extends JsonObject {
   timestamp: Date
 }
 
-export interface OCPP20SetVariablesRequest extends JsonObject {
-  setVariableData: OCPP20SetVariableDataType[]
+interface ChargingStationType extends JsonObject {
+  firmwareVersion?: string
+  model: string
+  modem?: ModemType
+  serialNumber?: string
+  vendorName: string
 }
 
-export interface OCPP20InstallCertificateRequest extends JsonObject {
-  certificate: string
-  certificateType: InstallCertificateUseEnumType
+interface ModemType extends JsonObject {
+  iccid?: string
+  imsi?: string
 }
index 816eea0bcaa7ab2bfa2247c6982eb3fc5c934efb..e735c788ff8cd1b835a844ea6713f4c11cb2eea8 100644 (file)
@@ -15,22 +15,22 @@ export interface OCPP20BootNotificationResponse extends JsonObject {
   statusInfo?: StatusInfoType
 }
 
-export interface OCPP20HeartbeatResponse extends JsonObject {
-  currentTime: Date
-}
-
 export interface OCPP20ClearCacheResponse extends JsonObject {
   status: GenericStatusEnumType
   statusInfo?: StatusInfoType
 }
 
-export type OCPP20StatusNotificationResponse = EmptyObject
-
-export interface OCPP20SetVariablesResponse extends JsonObject {
-  setVariableResult: OCPP20SetVariableResultType[]
+export interface OCPP20HeartbeatResponse extends JsonObject {
+  currentTime: Date
 }
 
 export interface OCPP20InstallCertificateResponse extends JsonObject {
   status: InstallCertificateStatusEnumType
   statusInfo?: StatusInfoType
 }
+
+export interface OCPP20SetVariablesResponse extends JsonObject {
+  setVariableResult: OCPP20SetVariableResultType[]
+}
+
+export type OCPP20StatusNotificationResponse = EmptyObject
index dac4a3a69fcd5ac9cf576a6742ab7b047553a6fd..e31da2202cc34383f03f437bfe457b2a9b0dc794 100644 (file)
@@ -1,25 +1,9 @@
 import type { JsonObject } from '../../JsonType.js'
 import type { EVSEType, StatusInfoType } from './Common.js'
 
-enum OCPP20ComponentName {
-  AlignedDataCtrlr = 'AlignedDataCtrlr',
-  AuthCacheCtrlr = 'AuthCacheCtrlr',
-  AuthCtrlr = 'AuthCtrlr',
-  CHAdeMOCtrlr = 'CHAdeMOCtrlr',
-  ClockCtrlr = 'ClockCtrlr',
-  CustomizationCtrlr = 'CustomizationCtrlr',
-  DeviceDataCtrlr = 'DeviceDataCtrlr',
-  DisplayMessageCtrlr = 'DisplayMessageCtrlr',
-  ISO15118Ctrlr = 'ISO15118Ctrlr',
-  LocalAuthListCtrlr = 'LocalAuthListCtrlr',
-  MonitoringCtrlr = 'MonitoringCtrlr',
-  OCPPCommCtrlr = 'OCPPCommCtrlr',
-  ReservationCtrlr = 'ReservationCtrlr',
-  SampledDataCtrlr = 'SampledDataCtrlr',
-  SecurityCtrlr = 'SecurityCtrlr',
-  SmartChargingCtrlr = 'SmartChargingCtrlr',
-  TariffCostCtrlr = 'TariffCostCtrlr',
-  TxCtrlr = 'TxCtrlr',
+export enum OCPP20OptionalVariableName {
+  HeartbeatInterval = 'HeartbeatInterval',
+  WebSocketPingInterval = 'WebSocketPingInterval',
 }
 
 export enum OCPP20RequiredVariableName {
@@ -53,11 +37,6 @@ export enum OCPP20RequiredVariableName {
   UnlockOnEVSideDisconnect = 'UnlockOnEVSideDisconnect',
 }
 
-export enum OCPP20OptionalVariableName {
-  HeartbeatInterval = 'HeartbeatInterval',
-  WebSocketPingInterval = 'WebSocketPingInterval',
-}
-
 export enum OCPP20VendorVariableName {
   ConnectionUrl = 'ConnectionUrl',
 }
@@ -69,21 +48,39 @@ enum AttributeEnumType {
   Target = 'Target',
 }
 
-interface ComponentType extends JsonObject {
-  evse?: EVSEType
-  instance?: string
-  name: OCPP20ComponentName | string
+enum OCPP20ComponentName {
+  AlignedDataCtrlr = 'AlignedDataCtrlr',
+  AuthCacheCtrlr = 'AuthCacheCtrlr',
+  AuthCtrlr = 'AuthCtrlr',
+  CHAdeMOCtrlr = 'CHAdeMOCtrlr',
+  ClockCtrlr = 'ClockCtrlr',
+  CustomizationCtrlr = 'CustomizationCtrlr',
+  DeviceDataCtrlr = 'DeviceDataCtrlr',
+  DisplayMessageCtrlr = 'DisplayMessageCtrlr',
+  ISO15118Ctrlr = 'ISO15118Ctrlr',
+  LocalAuthListCtrlr = 'LocalAuthListCtrlr',
+  MonitoringCtrlr = 'MonitoringCtrlr',
+  OCPPCommCtrlr = 'OCPPCommCtrlr',
+  ReservationCtrlr = 'ReservationCtrlr',
+  SampledDataCtrlr = 'SampledDataCtrlr',
+  SecurityCtrlr = 'SecurityCtrlr',
+  SmartChargingCtrlr = 'SmartChargingCtrlr',
+  TariffCostCtrlr = 'TariffCostCtrlr',
+  TxCtrlr = 'TxCtrlr',
 }
 
-type VariableName =
-  | OCPP20OptionalVariableName
-  | OCPP20RequiredVariableName
-  | OCPP20VendorVariableName
-  | string
+enum SetVariableStatusEnumType {
+  Accepted = 'Accepted',
+  NotSupportedAttributeType = 'NotSupportedAttributeType',
+  RebootRequired = 'RebootRequired',
+  Rejected = 'Rejected',
+  UnknownComponent = 'UnknownComponent',
+  UnknownVariable = 'UnknownVariable',
+}
 
-interface VariableType extends JsonObject {
-  instance?: string
-  name: VariableName
+export interface OCPP20ComponentVariableType extends JsonObject {
+  component: ComponentType
+  variable?: VariableType
 }
 
 export interface OCPP20SetVariableDataType extends JsonObject {
@@ -93,15 +90,6 @@ export interface OCPP20SetVariableDataType extends JsonObject {
   variable: VariableType
 }
 
-enum SetVariableStatusEnumType {
-  Accepted = 'Accepted',
-  NotSupportedAttributeType = 'NotSupportedAttributeType',
-  RebootRequired = 'RebootRequired',
-  Rejected = 'Rejected',
-  UnknownComponent = 'UnknownComponent',
-  UnknownVariable = 'UnknownVariable',
-}
-
 export interface OCPP20SetVariableResultType extends JsonObject {
   attributeStatus: SetVariableStatusEnumType
   attributeStatusInfo?: StatusInfoType
@@ -110,7 +98,19 @@ export interface OCPP20SetVariableResultType extends JsonObject {
   variable: VariableType
 }
 
-export interface OCPP20ComponentVariableType extends JsonObject {
-  component: ComponentType
-  variable?: VariableType
+interface ComponentType extends JsonObject {
+  evse?: EVSEType
+  instance?: string
+  name: OCPP20ComponentName | string
+}
+
+type VariableName =
+  | OCPP20OptionalVariableName
+  | OCPP20RequiredVariableName
+  | OCPP20VendorVariableName
+  | string
+
+interface VariableType extends JsonObject {
+  instance?: string
+  name: VariableName
 }
index e8923973aefba5504e95049dd6e969417c6ad879..7190d9a4483f3ccb53095b5c76eb2d645651d68e 100644 (file)
@@ -5,12 +5,12 @@ export enum GenericStatus {
   Rejected = 'Rejected',
 }
 
-export interface GenericResponse extends JsonObject {
-  status: GenericStatus
-}
-
 export enum RegistrationStatusEnumType {
   ACCEPTED = 'Accepted',
   PENDING = 'Pending',
   REJECTED = 'Rejected',
 }
+
+export interface GenericResponse extends JsonObject {
+  status: GenericStatus
+}
index c0de4370d3205246baf60cad35849f38f1acaab7..f076074238b8c6aabbce1efa9127a8ce5f334e69 100644 (file)
@@ -11,6 +11,25 @@ import {
   OCPP20VendorVariableName,
 } from './2.0/Variables.js'
 
+export enum ConnectorPhaseRotation {
+  NotApplicable = 'NotApplicable',
+  RST = 'RST',
+  RTS = 'RTS',
+  SRT = 'SRT',
+  STR = 'STR',
+  TRS = 'TRS',
+  TSR = 'TSR',
+  Unknown = 'Unknown',
+}
+
+export type ConfigurationKeyType = StandardParametersKey | string | VendorParametersKey
+
+export interface OCPPConfigurationKey extends JsonObject {
+  key: ConfigurationKeyType
+  readonly: boolean
+  value?: string
+}
+
 export const StandardParametersKey = {
   ...OCPP16StandardParametersKey,
   ...OCPP20RequiredVariableName,
@@ -31,22 +50,3 @@ export const SupportedFeatureProfiles = {
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type SupportedFeatureProfiles = OCPP16SupportedFeatureProfiles
-
-export enum ConnectorPhaseRotation {
-  NotApplicable = 'NotApplicable',
-  RST = 'RST',
-  RTS = 'RTS',
-  SRT = 'SRT',
-  STR = 'STR',
-  TRS = 'TRS',
-  TSR = 'TSR',
-  Unknown = 'Unknown',
-}
-
-export type ConfigurationKeyType = StandardParametersKey | string | VendorParametersKey
-
-export interface OCPPConfigurationKey extends JsonObject {
-  key: ConfigurationKeyType
-  readonly: boolean
-  value?: string
-}
index c44cb88f7d79be11b6c822c6187a63d3714a4a73..d8573eee5fe3ce5146c5c127d7f5d599eeb082b2 100644 (file)
@@ -1,6 +1,7 @@
+/* eslint-disable perfectionist/sort-enums */
 export enum MessageType {
   CALL_MESSAGE = 2, // Caller to Callee
   CALL_RESULT_MESSAGE = 3, // Callee to Caller
-  // eslint-disable-next-line perfectionist/sort-enums
   CALL_ERROR_MESSAGE = 4, // Callee to Caller
 }
+/* eslint-enable perfectionist/sort-enums */
index 6a44bb7456d4a78a3edcdfbea3ecea38019a04da..7c222e3a719e81f5394635dfb4e3fbfa103e8a67 100644 (file)
@@ -8,6 +8,8 @@ import {
   type OCPP16SampledValue,
 } from './1.6/MeterValues.js'
 
+export type MeterValue = OCPP16MeterValue
+
 export const MeterValueUnit = {
   ...OCPP16MeterValueUnit,
 } as const
@@ -20,18 +22,18 @@ export const MeterValueContext = {
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MeterValueContext = OCPP16MeterValueContext
 
-export const MeterValueMeasurand = {
-  ...OCPP16MeterValueMeasurand,
-} as const
-// eslint-disable-next-line @typescript-eslint/no-redeclare
-export type MeterValueMeasurand = OCPP16MeterValueMeasurand
-
 export const MeterValueLocation = {
   ...OCPP16MeterValueLocation,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MeterValueLocation = OCPP16MeterValueLocation
 
+export const MeterValueMeasurand = {
+  ...OCPP16MeterValueMeasurand,
+} as const
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type MeterValueMeasurand = OCPP16MeterValueMeasurand
+
 export const MeterValuePhase = {
   ...OCPP16MeterValuePhase,
 } as const
@@ -39,5 +41,3 @@ export const MeterValuePhase = {
 export type MeterValuePhase = OCPP16MeterValuePhase
 
 export type SampledValue = OCPP16SampledValue
-
-export type MeterValue = OCPP16MeterValue
index a6171b2d8273772c6cc63a1049e5723af6b3d522..79bd00b39427b93f8a005b1799699b51bc05a407 100644 (file)
@@ -28,20 +28,28 @@ import {
   type OCPP20StatusNotificationRequest,
 } from './2.0/Requests.js'
 
-export const RequestCommand = {
-  ...OCPP16RequestCommand,
-  ...OCPP20RequestCommand,
-} as const
-// eslint-disable-next-line @typescript-eslint/no-redeclare
-export type RequestCommand = OCPP16RequestCommand | OCPP20RequestCommand
+export type BootNotificationRequest = OCPP16BootNotificationRequest | OCPP20BootNotificationRequest
 
-export type OutgoingRequest = [MessageType.CALL_MESSAGE, string, RequestCommand, JsonType]
+export type CachedRequest = [
+  ResponseCallback,
+  ErrorCallback,
+  IncomingRequestCommand | RequestCommand,
+  JsonType
+]
 
-export interface RequestParams {
-  skipBufferingOnError?: boolean
-  throwError?: boolean
-  triggerMessage?: boolean
-}
+export type DataTransferRequest = OCPP16DataTransferRequest
+
+export type DiagnosticsStatusNotificationRequest = OCPP16DiagnosticsStatusNotificationRequest
+
+export type ErrorCallback = (ocppError: OCPPError, requestStatistic?: boolean) => void
+
+export type FirmwareStatusNotificationRequest = OCPP16FirmwareStatusNotificationRequest
+
+export type HeartbeatRequest = OCPP16HeartbeatRequest
+
+export type IncomingRequest = [MessageType.CALL_MESSAGE, string, IncomingRequestCommand, JsonType]
+
+export type OutgoingRequest = [MessageType.CALL_MESSAGE, string, RequestCommand, JsonType]
 
 export const IncomingRequestCommand = {
   ...OCPP16IncomingRequestCommand,
@@ -50,23 +58,23 @@ export const IncomingRequestCommand = {
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type IncomingRequestCommand = OCPP16IncomingRequestCommand | OCPP20IncomingRequestCommand
 
-export type IncomingRequest = [MessageType.CALL_MESSAGE, string, IncomingRequestCommand, JsonType]
-
 export type IncomingRequestHandler = (
   chargingStation: ChargingStation,
   commandPayload: JsonType
 ) => JsonType | Promise<JsonType>
 
-export type ResponseCallback = (payload: JsonType, requestPayload: JsonType) => void
-
-export type ErrorCallback = (ocppError: OCPPError, requestStatistic?: boolean) => void
+export const RequestCommand = {
+  ...OCPP16RequestCommand,
+  ...OCPP20RequestCommand,
+} as const
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type RequestCommand = OCPP16RequestCommand | OCPP20RequestCommand
 
-export type CachedRequest = [
-  ResponseCallback,
-  ErrorCallback,
-  IncomingRequestCommand | RequestCommand,
-  JsonType
-]
+export interface RequestParams {
+  skipBufferingOnError?: boolean
+  throwError?: boolean
+  triggerMessage?: boolean
+}
 
 export const MessageTrigger = {
   ...OCPP16MessageTrigger,
@@ -74,22 +82,14 @@ export const MessageTrigger = {
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MessageTrigger = OCPP16MessageTrigger
 
-export type BootNotificationRequest = OCPP16BootNotificationRequest | OCPP20BootNotificationRequest
+export type MeterValuesRequest = OCPP16MeterValuesRequest
 
-export type HeartbeatRequest = OCPP16HeartbeatRequest
+export type ResponseCallback = (payload: JsonType, requestPayload: JsonType) => void
 
 export type StatusNotificationRequest =
   | OCPP16StatusNotificationRequest
   | OCPP20StatusNotificationRequest
 
-export type MeterValuesRequest = OCPP16MeterValuesRequest
-
-export type DataTransferRequest = OCPP16DataTransferRequest
-
-export type DiagnosticsStatusNotificationRequest = OCPP16DiagnosticsStatusNotificationRequest
-
-export type FirmwareStatusNotificationRequest = OCPP16FirmwareStatusNotificationRequest
-
 export const AvailabilityType = {
   ...OCPP16AvailabilityType,
   ...OperationalStatusEnumType,
@@ -97,6 +97,8 @@ export const AvailabilityType = {
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type AvailabilityType = OCPP16AvailabilityType | OperationalStatusEnumType
 
+export type CancelReservationRequest = OCPP16CancelReservationRequest
+
 export const DiagnosticsStatus = {
   ...OCPP16DiagnosticsStatus,
 } as const
@@ -109,8 +111,6 @@ export const FirmwareStatus = {
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type FirmwareStatus = OCPP16FirmwareStatus
 
-export type ResponseType = JsonType | OCPPError
-
 export type ReserveNowRequest = OCPP16ReserveNowRequest
 
-export type CancelReservationRequest = OCPP16CancelReservationRequest
+export type ResponseType = JsonType | OCPPError
index 1a1b95c1f607296b4a369c9f196691b36f1ff28d..bc4fa26662950aecd467a22e84b6c9b4d559c460 100644 (file)
@@ -1,9 +1,5 @@
 import type { OCPP16ReserveNowRequest } from './1.6/Requests.js'
 
-export type Reservation = OCPP16ReserveNowRequest
-
-export type ReservationKey = keyof Reservation
-
 export enum ReservationTerminationReason {
   CONNECTOR_STATE_CHANGED = 'ConnectorStateChanged',
   EXPIRED = 'Expired',
@@ -11,3 +7,7 @@ export enum ReservationTerminationReason {
   RESERVATION_CANCELED = 'ReservationCanceled',
   TRANSACTION_STARTED = 'TransactionStarted',
 }
+
+export type Reservation = OCPP16ReserveNowRequest
+
+export type ReservationKey = keyof Reservation
index 9676ff74ecc9d925083bc2fc46bdea284caaa237..ec444fcf855d0961366da7d7bb33b8d0c20bba2a 100644 (file)
@@ -23,34 +23,34 @@ import {
 } from './1.6/Responses.js'
 import { type GenericResponse, GenericStatus } from './Common.js'
 
-export type Response = [MessageType.CALL_RESULT_MESSAGE, string, JsonType]
-
-export type ErrorResponse = [MessageType.CALL_ERROR_MESSAGE, string, ErrorType, string, JsonType]
-
-export type ResponseHandler = (
-  chargingStation: ChargingStation,
-  payload: JsonType,
-  requestPayload?: JsonType
-) => Promise<void> | void
-
 export type BootNotificationResponse =
   | OCPP16BootNotificationResponse
   | OCPP20BootNotificationResponse
 
-export type HeartbeatResponse = OCPP16HeartbeatResponse
-
 export type ClearCacheResponse = GenericResponse | OCPP20ClearCacheResponse
 
-export type StatusNotificationResponse = OCPP16StatusNotificationResponse
-
-export type MeterValuesResponse = OCPP16MeterValuesResponse
-
 export type DataTransferResponse = OCPP16DataTransferResponse
 
 export type DiagnosticsStatusNotificationResponse = OCPP16DiagnosticsStatusNotificationResponse
 
+export type ErrorResponse = [MessageType.CALL_ERROR_MESSAGE, string, ErrorType, string, JsonType]
+
 export type FirmwareStatusNotificationResponse = OCPP16FirmwareStatusNotificationResponse
 
+export type HeartbeatResponse = OCPP16HeartbeatResponse
+
+export type MeterValuesResponse = OCPP16MeterValuesResponse
+
+export type Response = [MessageType.CALL_RESULT_MESSAGE, string, JsonType]
+
+export type ResponseHandler = (
+  chargingStation: ChargingStation,
+  payload: JsonType,
+  requestPayload?: JsonType
+) => Promise<void> | void
+
+export type StatusNotificationResponse = OCPP16StatusNotificationResponse
+
 export const AvailabilityStatus = {
   ...OCPP16AvailabilityStatus,
 } as const
index 5379f11192d8b35c618218743047cb61b0cf6afd..83f16acc4fcc14d086495402e48c3e2cd1b90040 100644 (file)
@@ -19,16 +19,16 @@ export type AuthorizeRequest = OCPP16AuthorizeRequest
 
 export type AuthorizeResponse = OCPP16AuthorizeResponse
 
+export type StartTransactionRequest = OCPP16StartTransactionRequest
+
+export type StartTransactionResponse = OCPP16StartTransactionResponse
+
 export const StopTransactionReason = {
   ...OCPP16StopTransactionReason,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type StopTransactionReason = OCPP16StopTransactionReason
 
-export type StartTransactionRequest = OCPP16StartTransactionRequest
-
-export type StartTransactionResponse = OCPP16StartTransactionResponse
-
 export type StopTransactionRequest = OCPP16StopTransactionRequest
 
 export type StopTransactionResponse = OCPP16StopTransactionResponse
index e8c66f95d851f8276b2696f80be89ad4f872a707..49565046dda460d8fa12d87db67b451216c02969 100644 (file)
@@ -21,6 +21,19 @@ export class AsyncLock {
     this.resolveQueue = new Queue<ResolveType>()
   }
 
+  public static async runExclusive<T>(type: AsyncLockType, fn: () => Promise<T> | T): Promise<T> {
+    try {
+      await AsyncLock.acquire(type)
+      if (isAsyncFunction(fn)) {
+        return await fn()
+      } else {
+        return fn() as T
+      }
+    } finally {
+      await AsyncLock.release(type)
+    }
+  }
+
   private static async acquire (type: AsyncLockType): Promise<void> {
     const asyncLock = AsyncLock.getAsyncLock(type)
     if (!asyncLock.acquired) {
@@ -52,17 +65,4 @@ export class AsyncLock {
       resolve()
     })
   }
-
-  public static async runExclusive<T>(type: AsyncLockType, fn: () => Promise<T> | T): Promise<T> {
-    try {
-      await AsyncLock.acquire(type)
-      if (isAsyncFunction(fn)) {
-        return await fn()
-      } else {
-        return fn() as T
-      }
-    } finally {
-      await AsyncLock.release(type)
-    }
-  }
 }
index d9b19b8723362c4da24dcaab875d7820585d9731..c970832361d5de4eceb101ca960fda642ec2dc8b 100644 (file)
@@ -127,6 +127,82 @@ export class Configuration {
     // This is intentional
   }
 
+  public static getConfigurationData (): ConfigurationData | undefined {
+    if (
+      Configuration.configurationData == null &&
+      Configuration.configurationFile != null &&
+      Configuration.configurationFile.length > 0
+    ) {
+      try {
+        Configuration.configurationData = JSON.parse(
+          readFileSync(Configuration.configurationFile, 'utf8')
+        ) as ConfigurationData
+        if (Configuration.configurationFileWatcher == null) {
+          Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher()
+        }
+      } catch (error) {
+        handleFileException(
+          Configuration.configurationFile,
+          FileType.Configuration,
+          error as NodeJS.ErrnoException,
+          logPrefix()
+        )
+      }
+    }
+    return Configuration.configurationData
+  }
+
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public static getConfigurationSection<T extends ConfigurationSectionType>(
+    sectionName: ConfigurationSection
+  ): T {
+    if (!Configuration.isConfigurationSectionCached(sectionName)) {
+      Configuration.cacheConfigurationSection(sectionName)
+    }
+    return Configuration.configurationSectionCache.get(sectionName) as T
+  }
+
+  public static getStationTemplateUrls (): StationTemplateUrl[] | undefined {
+    const checkDeprecatedConfigurationKeysOnce = once(
+      Configuration.checkDeprecatedConfigurationKeys.bind(Configuration)
+    )
+    checkDeprecatedConfigurationKeysOnce()
+    return Configuration.getConfigurationData()?.stationTemplateUrls
+  }
+
+  public static getSupervisionUrlDistribution (): SupervisionUrlDistribution | undefined {
+    return has(Configuration.getConfigurationData(), 'supervisionUrlDistribution')
+      ? Configuration.getConfigurationData()?.supervisionUrlDistribution
+      : SupervisionUrlDistribution.ROUND_ROBIN
+  }
+
+  public static getSupervisionUrls (): string | string[] | undefined {
+    if (
+      Configuration.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData] != null
+    ) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      Configuration.getConfigurationData()!.supervisionUrls = Configuration.getConfigurationData()![
+        'supervisionURLs' as keyof ConfigurationData
+      ] as string | string[]
+    }
+    return Configuration.getConfigurationData()?.supervisionUrls
+  }
+
+  public static workerDynamicPoolInUse (): boolean {
+    return (
+      Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
+        .processType === WorkerProcessType.dynamicPool
+    )
+  }
+
+  public static workerPoolInUse (): boolean {
+    return [WorkerProcessType.dynamicPool, WorkerProcessType.fixedPool].includes(
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
+        .processType!
+    )
+  }
+
   private static buildLogSection (): LogConfiguration {
     const deprecatedLogConfiguration: LogConfiguration = {
       ...(has('logEnabled', Configuration.getConfigurationData()) && {
@@ -501,31 +577,6 @@ export class Configuration {
     }
   }
 
-  public static getConfigurationData (): ConfigurationData | undefined {
-    if (
-      Configuration.configurationData == null &&
-      Configuration.configurationFile != null &&
-      Configuration.configurationFile.length > 0
-    ) {
-      try {
-        Configuration.configurationData = JSON.parse(
-          readFileSync(Configuration.configurationFile, 'utf8')
-        ) as ConfigurationData
-        if (Configuration.configurationFileWatcher == null) {
-          Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher()
-        }
-      } catch (error) {
-        handleFileException(
-          Configuration.configurationFile,
-          FileType.Configuration,
-          error as NodeJS.ErrnoException,
-          logPrefix()
-        )
-      }
-    }
-    return Configuration.configurationData
-  }
-
   private static getConfigurationFileWatcher (): FSWatcher | undefined {
     if (Configuration.configurationFile == null || Configuration.configurationFile.length === 0) {
       return
@@ -571,42 +622,6 @@ export class Configuration {
     }
   }
 
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
-  public static getConfigurationSection<T extends ConfigurationSectionType>(
-    sectionName: ConfigurationSection
-  ): T {
-    if (!Configuration.isConfigurationSectionCached(sectionName)) {
-      Configuration.cacheConfigurationSection(sectionName)
-    }
-    return Configuration.configurationSectionCache.get(sectionName) as T
-  }
-
-  public static getStationTemplateUrls (): StationTemplateUrl[] | undefined {
-    const checkDeprecatedConfigurationKeysOnce = once(
-      Configuration.checkDeprecatedConfigurationKeys.bind(Configuration)
-    )
-    checkDeprecatedConfigurationKeysOnce()
-    return Configuration.getConfigurationData()?.stationTemplateUrls
-  }
-
-  public static getSupervisionUrlDistribution (): SupervisionUrlDistribution | undefined {
-    return has(Configuration.getConfigurationData(), 'supervisionUrlDistribution')
-      ? Configuration.getConfigurationData()?.supervisionUrlDistribution
-      : SupervisionUrlDistribution.ROUND_ROBIN
-  }
-
-  public static getSupervisionUrls (): string | string[] | undefined {
-    if (
-      Configuration.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData] != null
-    ) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      Configuration.getConfigurationData()!.supervisionUrls = Configuration.getConfigurationData()![
-        'supervisionURLs' as keyof ConfigurationData
-      ] as string | string[]
-    }
-    return Configuration.getConfigurationData()?.supervisionUrls
-  }
-
   private static isConfigurationSectionCached (sectionName: ConfigurationSection): boolean {
     return Configuration.configurationSectionCache.has(sectionName)
   }
@@ -643,19 +658,4 @@ export class Configuration {
       )
     }
   }
-
-  public static workerDynamicPoolInUse (): boolean {
-    return (
-      Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
-        .processType === WorkerProcessType.dynamicPool
-    )
-  }
-
-  public static workerPoolInUse (): boolean {
-    return [WorkerProcessType.dynamicPool, WorkerProcessType.fixedPool].includes(
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
-        .processType!
-    )
-  }
 }
index 8dfd29db0fc8f7b096480327c5613aca08cee033..8ee1d8abdf94ccfee707948526900b54394c3508 100644 (file)
@@ -1,8 +1,8 @@
 import type { FormatWrap } from 'logform'
 
 import { createLogger, format, type transport } from 'winston'
-import TransportType from 'winston/lib/winston/transports/index.js'
 import DailyRotateFile from 'winston-daily-rotate-file'
+import TransportType from 'winston/lib/winston/transports/index.js'
 
 import { ConfigurationSection, type LogConfiguration } from '../types/index.js'
 import { Configuration } from './Configuration.js'
index 6f0e6b9fca7fdcec2b8ca8fe8d57e1e7ce207c09..57e7f7e341d18b14f0dd86243ed1c8317f265cf2 100644 (file)
@@ -228,7 +228,7 @@ export const isCFEnvironment = (): boolean => {
 }
 
 declare const nonEmptyString: unique symbol
-type NonEmptyString = { [nonEmptyString]: true } & string
+type NonEmptyString = string & { [nonEmptyString]: true }
 export const isNotEmptyString = (value: unknown): value is NonEmptyString => {
   return typeof value === 'string' && value.trim().length > 0
 }
index d985209eb62a1e383cc45e72a02a0e29adef0dd1..d4282e0c31141ef50c80be99b357b5f3c75129f4 100644 (file)
@@ -6,13 +6,14 @@ import { existsSync } from 'node:fs'
 import type { SetInfo, WorkerData, WorkerOptions } from './WorkerTypes.js'
 
 export abstract class WorkerAbstract<D extends WorkerData, R extends WorkerData> {
-  protected readonly workerOptions: WorkerOptions
-  protected readonly workerScript: string
   public abstract readonly emitter: EventEmitterAsyncResource | undefined
   public abstract readonly info: PoolInfo | SetInfo
   public abstract readonly maxElementsPerWorker: number | undefined
   public abstract readonly size: number
 
+  protected readonly workerOptions: WorkerOptions
+  protected readonly workerScript: string
+
   /**
    * `WorkerAbstract` constructor.
    * @param workerScript -
index 07c55757a0c844f808562b890ccd4b6d10d8ca97..0d7407dcdef8887db1fe231620408e47cf000626 100644 (file)
@@ -16,7 +16,7 @@ export const DEFAULT_POOL_MIN_SIZE = Math.floor(availableParallelism() / 2)
 export const DEFAULT_POOL_MAX_SIZE = Math.round(availableParallelism() * 1.5)
 export const DEFAULT_ELEMENTS_PER_WORKER = 1
 
-export const DEFAULT_WORKER_OPTIONS: WorkerOptions = Object.freeze({
+export const DEFAULT_WORKER_OPTIONS: Readonly<WorkerOptions> = Object.freeze({
   elementAddDelay: DEFAULT_ELEMENT_ADD_DELAY,
   elementsPerWorker: DEFAULT_ELEMENTS_PER_WORKER,
   poolMaxSize: DEFAULT_POOL_MAX_SIZE,
index 38115c0be8aed7f3768adade8cd5fccd4cfdefd9..e5808d300e5e05075639c973000d017994135293 100644 (file)
@@ -11,6 +11,22 @@ export class WorkerDynamicPool<D extends WorkerData, R extends WorkerData> exten
   D,
   R
 > {
+  get emitter (): EventEmitterAsyncResource | undefined {
+    return this.pool.emitter
+  }
+
+  get info (): PoolInfo {
+    return this.pool.info
+  }
+
+  get maxElementsPerWorker (): number | undefined {
+    return undefined
+  }
+
+  get size (): number {
+    return this.pool.info.workerNodes
+  }
+
   private readonly pool: DynamicThreadPool<D, R>
 
   /**
@@ -48,20 +64,4 @@ export class WorkerDynamicPool<D extends WorkerData, R extends WorkerData> exten
   public async stop (): Promise<void> {
     await this.pool.destroy()
   }
-
-  get emitter (): EventEmitterAsyncResource | undefined {
-    return this.pool.emitter
-  }
-
-  get info (): PoolInfo {
-    return this.pool.info
-  }
-
-  get maxElementsPerWorker (): number | undefined {
-    return undefined
-  }
-
-  get size (): number {
-    return this.pool.info.workerNodes
-  }
 }
index 22d6fcd1313a583d815d25a5dbfb28bc54b28851..8bddad7de428dc334efa0a2d9bd9ba61574214f8 100644 (file)
@@ -11,6 +11,22 @@ export class WorkerFixedPool<D extends WorkerData, R extends WorkerData> extends
   D,
   R
 > {
+  get emitter (): EventEmitterAsyncResource | undefined {
+    return this.pool.emitter
+  }
+
+  get info (): PoolInfo {
+    return this.pool.info
+  }
+
+  get maxElementsPerWorker (): number | undefined {
+    return undefined
+  }
+
+  get size (): number {
+    return this.pool.info.workerNodes
+  }
+
   private readonly pool: FixedThreadPool<D, R>
 
   /**
@@ -47,20 +63,4 @@ export class WorkerFixedPool<D extends WorkerData, R extends WorkerData> extends
   public async stop (): Promise<void> {
     await this.pool.destroy()
   }
-
-  get emitter (): EventEmitterAsyncResource | undefined {
-    return this.pool.emitter
-  }
-
-  get info (): PoolInfo {
-    return this.pool.info
-  }
-
-  get maxElementsPerWorker (): number | undefined {
-    return undefined
-  }
-
-  get size (): number {
-    return this.pool.info.workerNodes
-  }
 }
index 2cec2d5f61ed826abd7162c7cf9b1c9a5a065d2f..babf6932487b903f594ae85a7f330e00c66ba7ba 100644 (file)
@@ -24,6 +24,32 @@ interface ResponseWrapper<R extends WorkerData> {
 }
 
 export class WorkerSet<D extends WorkerData, R extends WorkerData> extends WorkerAbstract<D, R> {
+  public readonly emitter: EventEmitterAsyncResource | undefined
+
+  get info (): SetInfo {
+    return {
+      elementsExecuting: [...this.workerSet].reduce(
+        (accumulator, workerSetElement) => accumulator + workerSetElement.numberOfWorkerElements,
+        0
+      ),
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      elementsPerWorker: this.maxElementsPerWorker!,
+      size: this.size,
+      started: this.started,
+      type: 'set',
+      version: workerSetVersion,
+      worker: 'thread',
+    }
+  }
+
+  get maxElementsPerWorker (): number | undefined {
+    return this.workerOptions.elementsPerWorker
+  }
+
+  get size (): number {
+    return this.workerSet.size
+  }
+
   private readonly promiseResponseMap: Map<
     `${string}-${string}-${string}-${string}`,
     ResponseWrapper<R>
@@ -32,7 +58,6 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
   private started: boolean
   private readonly workerSet: Set<WorkerSetElement>
   private workerStartup: boolean
-  public readonly emitter: EventEmitterAsyncResource | undefined
 
   /**
    * Creates a new `WorkerSet`.
@@ -62,6 +87,65 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
     this.workerStartup = false
   }
 
+  /** @inheritDoc */
+  public async addElement (elementData: D): Promise<R> {
+    if (!this.started) {
+      throw new Error('Cannot add a WorkerSet element: not started')
+    }
+    const workerSetElement = await this.getWorkerSetElement()
+    const sendMessageToWorker = new Promise<R>((resolve, reject) => {
+      const message = {
+        data: elementData,
+        event: WorkerMessageEvents.addWorkerElement,
+        uuid: randomUUID(),
+      } satisfies WorkerMessage<D>
+      workerSetElement.worker.postMessage(message)
+      this.promiseResponseMap.set(message.uuid, {
+        reject,
+        resolve,
+        workerSetElement,
+      })
+    })
+    const response = await sendMessageToWorker
+    // Add element sequentially to optimize memory at startup
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    if (this.workerOptions.elementAddDelay! > 0) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      await sleep(randomizeDelay(this.workerOptions.elementAddDelay!))
+    }
+    return response
+  }
+
+  /** @inheritDoc */
+  public async start (): Promise<void> {
+    this.addWorkerSetElement()
+    // Add worker set element sequentially to optimize memory at startup
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.workerOptions.workerStartDelay! > 0 &&
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      (await sleep(randomizeDelay(this.workerOptions.workerStartDelay!)))
+    this.emitter?.emit(WorkerSetEvents.started, this.info)
+    this.started = true
+  }
+
+  /** @inheritDoc */
+  public async stop (): Promise<void> {
+    for (const workerSetElement of this.workerSet) {
+      const worker = workerSetElement.worker
+      const waitWorkerExit = new Promise<void>(resolve => {
+        worker.once('exit', () => {
+          resolve()
+        })
+      })
+      worker.unref()
+      await worker.terminate()
+      await waitWorkerExit
+    }
+    this.emitter?.emit(WorkerSetEvents.stopped, this.info)
+    this.started = false
+    this.emitter?.emitDestroy()
+  }
+
   /**
    * Adds a new `WorkerSetElement`.
    * @returns The new `WorkerSetElement`.
@@ -167,87 +251,4 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
     }
     this.workerSet.delete(workerSetElement)
   }
-
-  /** @inheritDoc */
-  public async addElement (elementData: D): Promise<R> {
-    if (!this.started) {
-      throw new Error('Cannot add a WorkerSet element: not started')
-    }
-    const workerSetElement = await this.getWorkerSetElement()
-    const sendMessageToWorker = new Promise<R>((resolve, reject) => {
-      const message = {
-        data: elementData,
-        event: WorkerMessageEvents.addWorkerElement,
-        uuid: randomUUID(),
-      } satisfies WorkerMessage<D>
-      workerSetElement.worker.postMessage(message)
-      this.promiseResponseMap.set(message.uuid, {
-        reject,
-        resolve,
-        workerSetElement,
-      })
-    })
-    const response = await sendMessageToWorker
-    // Add element sequentially to optimize memory at startup
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    if (this.workerOptions.elementAddDelay! > 0) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      await sleep(randomizeDelay(this.workerOptions.elementAddDelay!))
-    }
-    return response
-  }
-
-  /** @inheritDoc */
-  public async start (): Promise<void> {
-    this.addWorkerSetElement()
-    // Add worker set element sequentially to optimize memory at startup
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.workerOptions.workerStartDelay! > 0 &&
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      (await sleep(randomizeDelay(this.workerOptions.workerStartDelay!)))
-    this.emitter?.emit(WorkerSetEvents.started, this.info)
-    this.started = true
-  }
-
-  /** @inheritDoc */
-  public async stop (): Promise<void> {
-    for (const workerSetElement of this.workerSet) {
-      const worker = workerSetElement.worker
-      const waitWorkerExit = new Promise<void>(resolve => {
-        worker.once('exit', () => {
-          resolve()
-        })
-      })
-      worker.unref()
-      await worker.terminate()
-      await waitWorkerExit
-    }
-    this.emitter?.emit(WorkerSetEvents.stopped, this.info)
-    this.started = false
-    this.emitter?.emitDestroy()
-  }
-
-  get info (): SetInfo {
-    return {
-      elementsExecuting: [...this.workerSet].reduce(
-        (accumulator, workerSetElement) => accumulator + workerSetElement.numberOfWorkerElements,
-        0
-      ),
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      elementsPerWorker: this.maxElementsPerWorker!,
-      size: this.size,
-      started: this.started,
-      type: 'set',
-      version: workerSetVersion,
-      worker: 'thread',
-    }
-  }
-
-  get maxElementsPerWorker (): number | undefined {
-    return this.workerOptions.elementsPerWorker
-  }
-
-  get size (): number {
-    return this.workerSet.size
-  }
 }
index 336c5c627e8221d03c480f8d1487f9478475b7fe..0f272fe954a348f8f823898f3dd62b8c4fa8ad67 100644 (file)
@@ -2,6 +2,12 @@ import type { Worker } from 'node:worker_threads'
 
 import { type PoolEvent, PoolEvents, type ThreadPoolOptions } from 'poolifier'
 
+export enum WorkerMessageEvents {
+  addedWorkerElement = 'addedWorkerElement',
+  addWorkerElement = 'addWorkerElement',
+  workerElementError = 'workerElementError',
+}
+
 export enum WorkerProcessType {
   /** @experimental */
   dynamicPool = 'dynamicPool',
@@ -9,6 +15,14 @@ export enum WorkerProcessType {
   workerSet = 'workerSet',
 }
 
+export enum WorkerSetEvents {
+  elementAdded = 'elementAdded',
+  elementError = 'elementError',
+  error = 'error',
+  started = 'started',
+  stopped = 'stopped',
+}
+
 export interface SetInfo {
   elementsExecuting: number
   elementsPerWorker: number
@@ -19,12 +33,13 @@ export interface SetInfo {
   worker: string
 }
 
-export enum WorkerSetEvents {
-  elementAdded = 'elementAdded',
-  elementError = 'elementError',
-  error = 'error',
-  started = 'started',
-  stopped = 'stopped',
+export type WorkerData = Record<string, unknown>
+
+export interface WorkerDataError extends WorkerData {
+  event: WorkerMessageEvents
+  message: string
+  name: string
+  stack?: string
 }
 
 export const WorkerEvents = {
@@ -34,6 +49,12 @@ export const WorkerEvents = {
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type WorkerEvents = PoolEvent | WorkerSetEvents
 
+export interface WorkerMessage<T extends WorkerData> {
+  data: T
+  event: WorkerMessageEvents
+  uuid: `${string}-${string}-${string}-${string}`
+}
+
 export interface WorkerOptions {
   elementAddDelay?: number
   elementsPerWorker?: number
@@ -43,28 +64,7 @@ export interface WorkerOptions {
   workerStartDelay?: number
 }
 
-export type WorkerData = Record<string, unknown>
-
-export interface WorkerDataError extends WorkerData {
-  event: WorkerMessageEvents
-  message: string
-  name: string
-  stack?: string
-}
-
 export interface WorkerSetElement {
   numberOfWorkerElements: number
   worker: Worker
 }
-
-export interface WorkerMessage<T extends WorkerData> {
-  data: T
-  event: WorkerMessageEvents
-  uuid: `${string}-${string}-${string}-${string}`
-}
-
-export enum WorkerMessageEvents {
-  addedWorkerElement = 'addedWorkerElement',
-  addWorkerElement = 'addWorkerElement',
-  workerElementError = 'workerElementError',
-}
index c88baa0fdc0657a463c71694f8b64b61e3ea3bf5..bf87433002742a07b4eac31808cf2973bab12fdb 100644 (file)
@@ -254,15 +254,15 @@ await describe('Utils test suite', async () => {
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     expect(isAsyncFunction(async function named () {})).toBe(true)
     class TestClass {
-      // eslint-disable-next-line @typescript-eslint/no-empty-function
-      public testArrowAsync = async (): Promise<void> => {}
-      // eslint-disable-next-line @typescript-eslint/no-empty-function
-      public testArrowSync = (): void => {}
       // eslint-disable-next-line @typescript-eslint/no-empty-function
       public static async testStaticAsync (): Promise<void> {}
       // eslint-disable-next-line @typescript-eslint/no-empty-function
       public static testStaticSync (): void {}
       // eslint-disable-next-line @typescript-eslint/no-empty-function
+      public testArrowAsync = async (): Promise<void> => {}
+      // eslint-disable-next-line @typescript-eslint/no-empty-function
+      public testArrowSync = (): void => {}
+      // eslint-disable-next-line @typescript-eslint/no-empty-function
       public async testAsync (): Promise<void> {}
       // eslint-disable-next-line @typescript-eslint/no-empty-function
       public testSync (): void {}
index 73817b5353caedf810878f912d6f9de820db375f..cae83074f84f9aa7c614979af8a6263767deb9b9 100644 (file)
@@ -36,7 +36,7 @@
   "devDependencies": {
     "@tsconfig/node22": "^22.0.0",
     "@types/jsdom": "^21.1.7",
-    "@types/node": "^22.9.0",
+    "@types/node": "^22.9.1",
     "@vitejs/plugin-vue": "^5.2.0",
     "@vitejs/plugin-vue-jsx": "^4.1.0",
     "@vitest/coverage-v8": "^2.1.5",
index 398edd8182d13bac325ee397c5cab8163f6b023a..4bb33ab38893cb93b69e2802fff22350e77f392c 100644 (file)
@@ -46,126 +46,6 @@ export class UIClient {
     return UIClient.instance
   }
 
-  private openWS (): void {
-    const protocols =
-      this.uiServerConfiguration.authentication?.enabled === true &&
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-      this.uiServerConfiguration.authentication.type === AuthenticationType.PROTOCOL_BASIC_AUTH
-        ? [
-            `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`,
-            `authorization.basic.${btoa(
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-              `${this.uiServerConfiguration.authentication.username}:${this.uiServerConfiguration.authentication.password}`
-            ).replace(/={1,2}$/, '')}`,
-          ]
-        : `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`
-    this.ws = new WebSocket(
-      `${
-        this.uiServerConfiguration.secure === true
-          ? ApplicationProtocol.WSS
-          : ApplicationProtocol.WS
-      }://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}`,
-      protocols
-    )
-    this.ws.onopen = () => {
-      useToast().success(
-        `WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}' successfully opened`
-      )
-    }
-    this.ws.onmessage = this.responseHandler.bind(this)
-    this.ws.onerror = errorEvent => {
-      useToast().error(
-        `Error in WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}'`
-      )
-      console.error(
-        `Error in WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}'`,
-        errorEvent
-      )
-    }
-    this.ws.onclose = () => {
-      useToast().info('WebSocket to UI server closed')
-    }
-  }
-
-  private responseHandler (messageEvent: MessageEvent<string>): void {
-    let response: ProtocolResponse
-    try {
-      response = JSON.parse(messageEvent.data) as ProtocolResponse
-    } catch (error) {
-      useToast().error('Invalid response JSON format')
-      console.error('Invalid response JSON format', error)
-      return
-    }
-
-    if (!Array.isArray(response)) {
-      useToast().error('Response not an array')
-      console.error('Response not an array:', response)
-      return
-    }
-
-    const [uuid, responsePayload] = response
-
-    if (!validateUUID(uuid)) {
-      useToast().error('Response UUID field is invalid')
-      console.error('Response UUID field is invalid:', response)
-      return
-    }
-
-    if (this.responseHandlers.has(uuid)) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      const { procedureName, reject, resolve } = this.responseHandlers.get(uuid)!
-      switch (responsePayload.status) {
-        case ResponseStatus.FAILURE:
-          reject(responsePayload)
-          break
-        case ResponseStatus.SUCCESS:
-          resolve(responsePayload)
-          break
-        default:
-          reject(
-            new Error(
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-              `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`
-            )
-          )
-      }
-      this.responseHandlers.delete(uuid)
-    } else {
-      throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`)
-    }
-  }
-
-  private async sendRequest (
-    procedureName: ProcedureName,
-    payload: RequestPayload
-  ): Promise<ResponsePayload> {
-    return new Promise<ResponsePayload>((resolve, reject) => {
-      if (this.ws?.readyState === WebSocket.OPEN) {
-        const uuid = randomUUID()
-        const msg = JSON.stringify([uuid, procedureName, payload])
-        const sendTimeout = setTimeout(() => {
-          this.responseHandlers.delete(uuid)
-          reject(new Error(`Send request '${procedureName}' message: connection timeout`))
-        }, 60000)
-        try {
-          this.ws.send(msg)
-          this.responseHandlers.set(uuid, { procedureName, reject, resolve })
-        } catch (error) {
-          this.responseHandlers.delete(uuid)
-          reject(
-            new Error(
-              `Send request '${procedureName}' message: error ${(error as Error).toString()}`
-            )
-          )
-        } finally {
-          clearTimeout(sendTimeout)
-        }
-      } else {
-        reject(new Error(`Send request '${procedureName}' message: connection closed`))
-      }
-    })
-  }
-
   public async addChargingStations (
     template: string,
     numberOfStations: number,
@@ -301,4 +181,124 @@ export class UIClient {
   ) {
     this.ws?.removeEventListener(event, listener, options)
   }
+
+  private openWS (): void {
+    const protocols =
+      this.uiServerConfiguration.authentication?.enabled === true &&
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+      this.uiServerConfiguration.authentication.type === AuthenticationType.PROTOCOL_BASIC_AUTH
+        ? [
+            `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`,
+            `authorization.basic.${btoa(
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+              `${this.uiServerConfiguration.authentication.username}:${this.uiServerConfiguration.authentication.password}`
+            ).replace(/={1,2}$/, '')}`,
+          ]
+        : `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`
+    this.ws = new WebSocket(
+      `${
+        this.uiServerConfiguration.secure === true
+          ? ApplicationProtocol.WSS
+          : ApplicationProtocol.WS
+      }://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}`,
+      protocols
+    )
+    this.ws.onopen = () => {
+      useToast().success(
+        `WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}' successfully opened`
+      )
+    }
+    this.ws.onmessage = this.responseHandler.bind(this)
+    this.ws.onerror = errorEvent => {
+      useToast().error(
+        `Error in WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}'`
+      )
+      console.error(
+        `Error in WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}'`,
+        errorEvent
+      )
+    }
+    this.ws.onclose = () => {
+      useToast().info('WebSocket to UI server closed')
+    }
+  }
+
+  private responseHandler (messageEvent: MessageEvent<string>): void {
+    let response: ProtocolResponse
+    try {
+      response = JSON.parse(messageEvent.data) as ProtocolResponse
+    } catch (error) {
+      useToast().error('Invalid response JSON format')
+      console.error('Invalid response JSON format', error)
+      return
+    }
+
+    if (!Array.isArray(response)) {
+      useToast().error('Response not an array')
+      console.error('Response not an array:', response)
+      return
+    }
+
+    const [uuid, responsePayload] = response
+
+    if (!validateUUID(uuid)) {
+      useToast().error('Response UUID field is invalid')
+      console.error('Response UUID field is invalid:', response)
+      return
+    }
+
+    if (this.responseHandlers.has(uuid)) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const { procedureName, reject, resolve } = this.responseHandlers.get(uuid)!
+      switch (responsePayload.status) {
+        case ResponseStatus.FAILURE:
+          reject(responsePayload)
+          break
+        case ResponseStatus.SUCCESS:
+          resolve(responsePayload)
+          break
+        default:
+          reject(
+            new Error(
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+              `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`
+            )
+          )
+      }
+      this.responseHandlers.delete(uuid)
+    } else {
+      throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`)
+    }
+  }
+
+  private async sendRequest (
+    procedureName: ProcedureName,
+    payload: RequestPayload
+  ): Promise<ResponsePayload> {
+    return new Promise<ResponsePayload>((resolve, reject) => {
+      if (this.ws?.readyState === WebSocket.OPEN) {
+        const uuid = randomUUID()
+        const msg = JSON.stringify([uuid, procedureName, payload])
+        const sendTimeout = setTimeout(() => {
+          this.responseHandlers.delete(uuid)
+          reject(new Error(`Send request '${procedureName}' message: connection timeout`))
+        }, 60000)
+        try {
+          this.ws.send(msg)
+          this.responseHandlers.set(uuid, { procedureName, reject, resolve })
+        } catch (error) {
+          this.responseHandlers.delete(uuid)
+          reject(
+            new Error(
+              `Send request '${procedureName}' message: error ${(error as Error).toString()}`
+            )
+          )
+        } finally {
+          clearTimeout(sendTimeout)
+        }
+      } else {
+        reject(new Error(`Send request '${procedureName}' message: connection closed`))
+      }
+    })
+  }
 }
index a074f159e477a6aa8113f1c98dac155c9db86650..357d177d56f78745f798cc3de62a438a942f47b4 100644 (file)
 import type { JsonObject } from './JsonType'
 
+export enum AmpereUnits {
+  AMPERE = 'A',
+  CENTI_AMPERE = 'cA',
+  DECI_AMPERE = 'dA',
+  MILLI_AMPERE = 'mA',
+}
+
+export enum CurrentType {
+  AC = 'AC',
+  DC = 'DC',
+}
+
 export enum IdTagDistribution {
   CONNECTOR_AFFINITY = 'connector-affinity',
   RANDOM = 'random',
   ROUND_ROBIN = 'round-robin',
 }
 
+export enum OCPP16AvailabilityType {
+  INOPERATIVE = 'Inoperative',
+  OPERATIVE = 'Operative',
+}
+
+export enum OCPP16ChargePointStatus {
+  AVAILABLE = 'Available',
+  CHARGING = 'Charging',
+  FAULTED = 'Faulted',
+  FINISHING = 'Finishing',
+  OCCUPIED = 'Occupied',
+  PREPARING = 'Preparing',
+  RESERVED = 'Reserved',
+  SUSPENDED_EV = 'SuspendedEV',
+  SUSPENDED_EVSE = 'SuspendedEVSE',
+  UNAVAILABLE = 'Unavailable',
+}
+
+export enum OCPP16FirmwareStatus {
+  Downloaded = 'Downloaded',
+  DownloadFailed = 'DownloadFailed',
+  Downloading = 'Downloading',
+  Idle = 'Idle',
+  InstallationFailed = 'InstallationFailed',
+  Installed = 'Installed',
+  Installing = 'Installing',
+}
+
+export enum OCPP16IncomingRequestCommand {
+  CHANGE_AVAILABILITY = 'ChangeAvailability',
+  CHANGE_CONFIGURATION = 'ChangeConfiguration',
+  CLEAR_CACHE = 'ClearCache',
+  CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
+  GET_CONFIGURATION = 'GetConfiguration',
+  GET_DIAGNOSTICS = 'GetDiagnostics',
+  REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
+  REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
+  RESET = 'Reset',
+  SET_CHARGING_PROFILE = 'SetChargingProfile',
+  TRIGGER_MESSAGE = 'TriggerMessage',
+  UNLOCK_CONNECTOR = 'UnlockConnector',
+}
+
+export enum OCPP16MessageTrigger {
+  BootNotification = 'BootNotification',
+  DiagnosticsStatusNotification = 'DiagnosticsStatusNotification',
+  FirmwareStatusNotification = 'FirmwareStatusNotification',
+  Heartbeat = 'Heartbeat',
+  MeterValues = 'MeterValues',
+  StatusNotification = 'StatusNotification',
+}
+
+export enum OCPP16RegistrationStatus {
+  ACCEPTED = 'Accepted',
+  PENDING = 'Pending',
+  REJECTED = 'Rejected',
+}
+
+export enum OCPP16RequestCommand {
+  AUTHORIZE = 'Authorize',
+  BOOT_NOTIFICATION = 'BootNotification',
+  DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
+  HEARTBEAT = 'Heartbeat',
+  METER_VALUES = 'MeterValues',
+  START_TRANSACTION = 'StartTransaction',
+  STATUS_NOTIFICATION = 'StatusNotification',
+  STOP_TRANSACTION = 'StopTransaction',
+}
+
+export enum OCPPProtocol {
+  JSON = 'json',
+}
+
+export enum OCPPVersion {
+  VERSION_16 = '1.6',
+  VERSION_20 = '2.0',
+  VERSION_201 = '2.0.1',
+}
+
+export enum Voltage {
+  VOLTAGE_110 = 110,
+  VOLTAGE_230 = 230,
+  VOLTAGE_400 = 400,
+  VOLTAGE_800 = 800,
+}
+
 export interface AutomaticTransactionGeneratorConfiguration extends JsonObject {
   enable: boolean
   idTagDistribution?: IdTagDistribution
@@ -19,6 +117,12 @@ export interface AutomaticTransactionGeneratorConfiguration extends JsonObject {
   stopAfterHours: number
 }
 
+export type AvailabilityType = OCPP16AvailabilityType
+
+export type BootNotificationResponse = OCPP16BootNotificationResponse
+
+export type ChargePointStatus = OCPP16ChargePointStatus
+
 export interface ChargingStationAutomaticTransactionGeneratorConfiguration extends JsonObject {
   automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
   automaticTransactionGeneratorStatuses?: Status[]
@@ -40,41 +144,6 @@ export interface ChargingStationData extends JsonObject {
     | typeof WebSocket.OPEN
 }
 
-export enum OCPP16FirmwareStatus {
-  Downloaded = 'Downloaded',
-  DownloadFailed = 'DownloadFailed',
-  Downloading = 'Downloading',
-  Idle = 'Idle',
-  InstallationFailed = 'InstallationFailed',
-  Installed = 'Installed',
-  Installing = 'Installing',
-}
-
-export interface FirmwareUpgrade extends JsonObject {
-  failureStatus?: FirmwareStatus
-  reset?: boolean
-  versionUpgrade?: {
-    patternGroup?: number
-    step?: number
-  }
-}
-
-export const FirmwareStatus = {
-  ...OCPP16FirmwareStatus,
-} as const
-// eslint-disable-next-line @typescript-eslint/no-redeclare
-export type FirmwareStatus = OCPP16FirmwareStatus
-
-export interface ChargingStationOptions extends JsonObject {
-  autoRegister?: boolean
-  autoStart?: boolean
-  enableStatistics?: boolean
-  ocppStrictCompliance?: boolean
-  persistentConfiguration?: boolean
-  stopTransactionsOnStopped?: boolean
-  supervisionUrls?: string | string[]
-}
-
 export interface ChargingStationInfo extends JsonObject {
   amperageLimitationOcppKey?: string
   amperageLimitationUnit?: AmpereUnits
@@ -142,62 +211,62 @@ export interface ChargingStationOcppConfiguration extends JsonObject {
   configurationKey?: ConfigurationKey[]
 }
 
+export interface ChargingStationOptions extends JsonObject {
+  autoRegister?: boolean
+  autoStart?: boolean
+  enableStatistics?: boolean
+  ocppStrictCompliance?: boolean
+  persistentConfiguration?: boolean
+  stopTransactionsOnStopped?: boolean
+  supervisionUrls?: string | string[]
+}
+
 export interface ConfigurationKey extends OCPPConfigurationKey {
   reboot?: boolean
   visible?: boolean
 }
 
-export interface OCPPConfigurationKey extends JsonObject {
-  key: string
-  readonly: boolean
-  value?: string
+export interface ConnectorStatus extends JsonObject {
+  authorizeIdTag?: string
+  availability: AvailabilityType
+  bootStatus?: ChargePointStatus
+  energyActiveImportRegisterValue?: number // In Wh
+  idTagAuthorized?: boolean
+  idTagLocalAuthorized?: boolean
+  localAuthorizeIdTag?: string
+  status?: ChargePointStatus
+  transactionEnergyActiveImportRegisterValue?: number // In Wh
+  transactionId?: number
+  transactionIdTag?: string
+  transactionRemoteStarted?: boolean
+  transactionStarted?: boolean
 }
 
-export enum OCPP16IncomingRequestCommand {
-  CHANGE_AVAILABILITY = 'ChangeAvailability',
-  CHANGE_CONFIGURATION = 'ChangeConfiguration',
-  CLEAR_CACHE = 'ClearCache',
-  CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
-  GET_CONFIGURATION = 'GetConfiguration',
-  GET_DIAGNOSTICS = 'GetDiagnostics',
-  REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
-  REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
-  RESET = 'Reset',
-  SET_CHARGING_PROFILE = 'SetChargingProfile',
-  TRIGGER_MESSAGE = 'TriggerMessage',
-  UNLOCK_CONNECTOR = 'UnlockConnector',
+export interface EvseStatus extends JsonObject {
+  availability: AvailabilityType
+  connectors?: ConnectorStatus[]
 }
 
-export const IncomingRequestCommand = {
-  ...OCPP16IncomingRequestCommand,
+export const FirmwareStatus = {
+  ...OCPP16FirmwareStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
-export type IncomingRequestCommand = OCPP16IncomingRequestCommand
+export type FirmwareStatus = OCPP16FirmwareStatus
 
-export enum OCPP16RequestCommand {
-  AUTHORIZE = 'Authorize',
-  BOOT_NOTIFICATION = 'BootNotification',
-  DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
-  HEARTBEAT = 'Heartbeat',
-  METER_VALUES = 'MeterValues',
-  START_TRANSACTION = 'StartTransaction',
-  STATUS_NOTIFICATION = 'StatusNotification',
-  STOP_TRANSACTION = 'StopTransaction',
+export interface FirmwareUpgrade extends JsonObject {
+  failureStatus?: FirmwareStatus
+  reset?: boolean
+  versionUpgrade?: {
+    patternGroup?: number
+    step?: number
+  }
 }
 
-export const RequestCommand = {
-  ...OCPP16RequestCommand,
+export const IncomingRequestCommand = {
+  ...OCPP16IncomingRequestCommand,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
-export type RequestCommand = OCPP16RequestCommand
-
-export type BootNotificationResponse = OCPP16BootNotificationResponse
-
-export enum OCPP16RegistrationStatus {
-  ACCEPTED = 'Accepted',
-  PENDING = 'Pending',
-  REJECTED = 'Rejected',
-}
+export type IncomingRequestCommand = OCPP16IncomingRequestCommand
 
 export interface OCPP16BootNotificationResponse extends JsonObject {
   currentTime: Date
@@ -205,13 +274,10 @@ export interface OCPP16BootNotificationResponse extends JsonObject {
   status: OCPP16RegistrationStatus
 }
 
-export enum OCPP16MessageTrigger {
-  BootNotification = 'BootNotification',
-  DiagnosticsStatusNotification = 'DiagnosticsStatusNotification',
-  FirmwareStatusNotification = 'FirmwareStatusNotification',
-  Heartbeat = 'Heartbeat',
-  MeterValues = 'MeterValues',
-  StatusNotification = 'StatusNotification',
+export interface OCPPConfigurationKey extends JsonObject {
+  key: string
+  readonly: boolean
+  value?: string
 }
 
 export const MessageTrigger = {
@@ -220,80 +286,11 @@ export const MessageTrigger = {
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MessageTrigger = OCPP16MessageTrigger
 
-interface CommandsSupport extends JsonObject {
-  incomingCommands: Record<IncomingRequestCommand, boolean>
-  outgoingCommands?: Record<RequestCommand, boolean>
-}
-
-export enum OCPPVersion {
-  VERSION_16 = '1.6',
-  VERSION_20 = '2.0',
-  VERSION_201 = '2.0.1',
-}
-
-export enum OCPPProtocol {
-  JSON = 'json',
-}
-
-export enum CurrentType {
-  AC = 'AC',
-  DC = 'DC',
-}
-
-export enum Voltage {
-  VOLTAGE_110 = 110,
-  VOLTAGE_230 = 230,
-  VOLTAGE_400 = 400,
-  VOLTAGE_800 = 800,
-}
-
-export enum AmpereUnits {
-  AMPERE = 'A',
-  CENTI_AMPERE = 'cA',
-  DECI_AMPERE = 'dA',
-  MILLI_AMPERE = 'mA',
-}
-
-export interface ConnectorStatus extends JsonObject {
-  authorizeIdTag?: string
-  availability: AvailabilityType
-  bootStatus?: ChargePointStatus
-  energyActiveImportRegisterValue?: number // In Wh
-  idTagAuthorized?: boolean
-  idTagLocalAuthorized?: boolean
-  localAuthorizeIdTag?: string
-  status?: ChargePointStatus
-  transactionEnergyActiveImportRegisterValue?: number // In Wh
-  transactionId?: number
-  transactionIdTag?: string
-  transactionRemoteStarted?: boolean
-  transactionStarted?: boolean
-}
-
-export interface EvseStatus extends JsonObject {
-  availability: AvailabilityType
-  connectors?: ConnectorStatus[]
-}
-
-export enum OCPP16AvailabilityType {
-  INOPERATIVE = 'Inoperative',
-  OPERATIVE = 'Operative',
-}
-export type AvailabilityType = OCPP16AvailabilityType
-
-export enum OCPP16ChargePointStatus {
-  AVAILABLE = 'Available',
-  CHARGING = 'Charging',
-  FAULTED = 'Faulted',
-  FINISHING = 'Finishing',
-  OCCUPIED = 'Occupied',
-  PREPARING = 'Preparing',
-  RESERVED = 'Reserved',
-  SUSPENDED_EV = 'SuspendedEV',
-  SUSPENDED_EVSE = 'SuspendedEVSE',
-  UNAVAILABLE = 'Unavailable',
-}
-export type ChargePointStatus = OCPP16ChargePointStatus
+export const RequestCommand = {
+  ...OCPP16RequestCommand,
+} as const
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type RequestCommand = OCPP16RequestCommand
 
 export interface Status extends JsonObject {
   acceptedAuthorizeRequests?: number
@@ -313,3 +310,8 @@ export interface Status extends JsonObject {
   stoppedDate?: Date
   stopTransactionRequests?: number
 }
+
+interface CommandsSupport extends JsonObject {
+  incomingCommands: Record<IncomingRequestCommand, boolean>
+  outgoingCommands?: Record<RequestCommand, boolean>
+}
index 163bb955c600d7231da05f362c224c96294ac7a5..6acff3c76a5673f2f72d6a64d9e83d8087798857 100644 (file)
@@ -1,4 +1,4 @@
-type JsonPrimitive = boolean | Date | null | number | string
 // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
 export type JsonObject = { [key in string]?: JsonType }
 export type JsonType = JsonObject | JsonPrimitive | JsonType[]
+type JsonPrimitive = boolean | Date | null | number | string
index 44df63d21ba2a3d6785351584623909f79b2b569..169a713e6357f509b5f217e436adda4e1e322ba0 100644 (file)
@@ -1,36 +1,14 @@
 import type { JsonObject } from './JsonType'
 
-export enum Protocol {
-  UI = 'ui',
-}
-
 export enum ApplicationProtocol {
   WS = 'ws',
   WSS = 'wss',
 }
 
-export enum ProtocolVersion {
-  '0.0.1' = '0.0.1',
-}
-
 export enum AuthenticationType {
   PROTOCOL_BASIC_AUTH = 'protocol-basic-auth',
 }
 
-export type ProtocolRequest = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  ProcedureName,
-  RequestPayload
-]
-export type ProtocolResponse = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  ResponsePayload
-]
-
-export type ProtocolRequestHandler = (
-  payload: RequestPayload
-) => Promise<ResponsePayload> | ResponsePayload
-
 export enum ProcedureName {
   ADD_CHARGING_STATIONS = 'addChargingStations',
   CLOSE_CONNECTION = 'closeConnection',
@@ -50,30 +28,52 @@ export enum ProcedureName {
   STOP_TRANSACTION = 'stopTransaction',
 }
 
-export interface RequestPayload extends JsonObject {
-  connectorIds?: number[]
-  hashIds?: string[]
+export enum Protocol {
+  UI = 'ui',
 }
 
+export enum ProtocolVersion {
+  '0.0.1' = '0.0.1',
+}
 export enum ResponseStatus {
   FAILURE = 'failure',
   SUCCESS = 'success',
 }
 
+export type ProtocolRequest = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ProcedureName,
+  RequestPayload
+]
+
+export type ProtocolRequestHandler = (
+  payload: RequestPayload
+) => Promise<ResponsePayload> | ResponsePayload
+
+export type ProtocolResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ResponsePayload
+]
+
+export interface RequestPayload extends JsonObject {
+  connectorIds?: number[]
+  hashIds?: string[]
+}
+
 export interface ResponsePayload extends JsonObject {
   hashIds?: string[]
   status: ResponseStatus
 }
 
+export interface SimulatorState extends JsonObject {
+  started: boolean
+  templateStatistics: Record<string, TemplateStatistics>
+  version: string
+}
+
 interface TemplateStatistics extends JsonObject {
   added: number
   configured: number
   indexes: number[]
   started: number
 }
-
-export interface SimulatorState extends JsonObject {
-  started: boolean
-  templateStatistics: Record<string, TemplateStatistics>
-  version: string
-}