"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",
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))
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
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))
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
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
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:
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':
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':
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':
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'
'@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==}
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:
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==}
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==}
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:
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
'@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
- '@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
'@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
'@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
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
'@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)
'@types/conventional-commits-parser@5.0.0':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.9.1
'@types/estree@1.0.6': {}
'@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
'@types/long@4.0.2': {}
- '@types/node@22.9.0':
+ '@types/node@22.9.1':
dependencies:
undici-types: 6.19.8
'@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:
'@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
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
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:
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: {}
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: {}
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
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:
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
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
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
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:
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))
optionalDependencies:
encoding: 0.1.13
- node-gyp-build@4.8.3:
+ node-gyp-build@4.8.4:
optional: true
node-gyp@8.4.1:
'@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
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: {}
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
- 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
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
yaml@2.5.1: {}
- yaml@2.6.0: {}
+ yaml@2.6.1: {}
yargs-parser@15.0.3:
dependencies:
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
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
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!) {
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()
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`)
- }
- }
}
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
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']) {
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 {
)
}
+ private readonly logPrefix = (): string => {
+ return logPrefix(' Bootstrap |')
+ }
+
private messageHandler (
msg: ChargingStationWorkerMessage<ChargingStationWorkerMessageData>
): void {
})
}
- 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)
}
}
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
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
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()
}
}
- 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}`
- )
}
}
import { logger } from '../utils/index.js'
+interface AddConfigurationKeyParams {
+ overwrite?: boolean
+ save?: boolean
+}
interface ConfigurationKeyOptions {
readonly?: boolean
reboot?: boolean
caseInsensitive?: boolean
save?: boolean
}
-interface AddConfigurationKeyParams {
- overwrite?: boolean
- save?: boolean
-}
export const getConfigurationKey = (
chargingStation: ChargingStation,
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>()
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)
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,
),
})
}
-
- 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)
- }
}
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()
}
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)
+ }
}
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
| 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>
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
}
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}:`)
}
}
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
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
)
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)
- }
}
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,
)
}
}
-
- // 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
- )
- }
}
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`)
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,
)
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
- )
- }
- }
}
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[],
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,
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,
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
)
}
+ 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,
}
!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
+ }
+ }
}
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,
// 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
+ }
}
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,
)
}
}
-
- // 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
- )
- }
}
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`)
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,
)
}
}
+
+ 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
+ }
}
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!)!)) {
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>
}
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,
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
- }
- }
}
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
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,
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>
}
// 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
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)
+ }
}
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}`,
>
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>()
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()) {
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()
- }
}
}
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 =
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 {
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()
- }
}
// 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
}
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)
+ }
}
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({
})
}
- 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 {
})
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
+ }
}
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
>()
}
+ 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,
}
}
+ // 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()
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()
- }
}
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) {
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)) {
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,
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()
- }
}
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 {
)
})
}
+
+ private checkPerformanceRecordsFile (): void {
+ if (this.fd == null) {
+ throw new BaseError(
+ `${this.logPrefix} Performance records '${this.dbName}' file descriptor not found`
+ )
+ }
+ }
}
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 {
)
}
}
+
+ 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'],
+ }
+ }
}
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 {
)
}
}
+
+ 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`
+ )
+ }
+ }
}
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()
}
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
}
stopAfterHours: number
}
+export interface ChargingStationAutomaticTransactionGeneratorConfiguration {
+ automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
+ automaticTransactionGeneratorStatuses?: Status[]
+}
+
export interface Status {
acceptedAuthorizeRequests: number
acceptedStartTransactionRequests: number
stoppedDate?: Date
stopTransactionRequests: number
}
-
-export interface ChargingStationAutomaticTransactionGeneratorConfiguration {
- automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
- automaticTransactionGeneratorStatuses?: Status[]
-}
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
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
meterSerialNumber?: string
templateIndex: number
templateName: string
-} & Omit<
- ChargingStationTemplate,
- | 'AutomaticTransactionGenerator'
- | 'chargeBoxSerialNumberPrefix'
- | 'chargePointSerialNumberPrefix'
- | 'Configuration'
- | 'Connectors'
- | 'Evses'
- | 'meterSerialNumberPrefix'
- | 'numberOfConnectors'
- | 'power'
- | 'powerUnit'
->
+}
export interface ChargingStationInfoConfiguration {
stationInfo?: ChargingStationInfo
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[]
-}
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',
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,
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',
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>
+}
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
| 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,
| 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[]
}
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',
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
/** @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
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>
+}
-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
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
+}
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<{
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
provisioned: number
started: number
}
+
+export interface TimestampedData {
+ timestamp: number
+ value: number
+}
+export enum DBName {
+ MARIA_DB = 'MariaDB',
+ MONGO_DB = 'MongoDB',
+ MYSQL = 'MySQL',
+ SQLITE = 'SQLite',
+}
+
export enum StorageType {
JSON_FILE = 'jsonfile',
MARIA_DB = 'mariadb',
NONE = 'none',
SQLITE = 'sqlite',
}
-
-export enum DBName {
- MARIA_DB = 'MariaDB',
- MONGO_DB = 'MongoDB',
- MYSQL = 'MySQL',
- SQLITE = 'SQLite',
-}
import type { JsonObject } from './JsonType.js'
import type { BroadcastChannelResponsePayload } from './WorkerBroadcastChannel.js'
-export enum Protocol {
- UI = 'ui',
-}
-
export enum ApplicationProtocol {
HTTP = 'http',
WS = 'ws',
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',
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[]
-/* eslint-disable perfectionist/sort-enums */
export const WebSocketCloseEventStatusString: Record<WebSocketCloseEventStatusCode, string> =
Object.freeze({
1000: 'Normal Closure',
1015: 'TLS Handshake',
})
+/* eslint-disable perfectionist/sort-enums */
export enum WebSocketCloseEventStatusCode {
CLOSE_NORMAL = 1000,
CLOSE_GOING_AWAY = 1001,
CLOSE_BAD_GATEWAY = 1014,
CLOSE_TLS_HANDSHAKE = 1015,
}
+/* eslint-enable perfectionist/sort-enums */
export interface WSError extends Error {
code?: string
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',
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
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
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',
-}
-export enum OCPP16SupportedFeatureProfiles {
- Core = 'Core',
- FirmwareManagement = 'FirmwareManagement',
- LocalAuthListManagement = 'LocalAuthListManagement',
- RemoteTrigger = 'RemoteTrigger',
- Reservation = 'Reservation',
- SmartCharging = 'SmartCharging',
-}
-
export enum OCPP16StandardParametersKey {
AllowOfflineTxForUnknownId = 'AllowOfflineTxForUnknownId',
AuthorizationCacheEnabled = 'AuthorizationCacheEnabled',
WebSocketPingInterval = 'WebSocketPingInterval',
}
+export enum OCPP16SupportedFeatureProfiles {
+ Core = 'Core',
+ FirmwareManagement = 'FirmwareManagement',
+ LocalAuthListManagement = 'LocalAuthListManagement',
+ RemoteTrigger = 'RemoteTrigger',
+ Reservation = 'Reservation',
+ SmartCharging = 'SmartCharging',
+}
+
export enum OCPP16VendorParametersKey {
ConnectionUrl = 'ConnectionUrl',
}
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',
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',
VOLTAGE = 'Voltage',
}
-export enum OCPP16MeterValueLocation {
- BODY = 'Body',
- CABLE = 'Cable',
- EV = 'EV',
- INLET = 'Inlet',
- OUTLET = 'Outlet',
-}
-
export enum OCPP16MeterValuePhase {
L1 = 'L1',
L1_L2 = 'L1-L2',
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
}
export type OCPP16MeterValuesResponse = EmptyObject
+
+export interface OCPP16SampledValue extends JsonObject {
+ context?: OCPP16MeterValueContext
+ format?: OCPP16MeterValueFormat
+ location?: OCPP16MeterValueLocation
+ measurand?: OCPP16MeterValueMeasurand
+ phase?: OCPP16MeterValuePhase
+ unit?: OCPP16MeterValueUnit
+ value: string
+}
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 {
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
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 {
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 {
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
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 {
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
}
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',
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
}
export interface OCPP16StopTransactionResponse extends JsonObject {
idTagInfo?: IdTagInfo
}
+
+interface IdTagInfo extends JsonObject {
+ expiryDate?: Date
+ parentIdTag?: string
+ status: OCPP16AuthorizationStatus
+}
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',
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 {
NotFound = 'NotFound',
}
+export enum HashAlgorithmEnumType {
+ SHA256 = 'SHA256',
+ SHA384 = 'SHA384',
+ SHA512 = 'SHA512',
+}
+
export enum InstallCertificateStatusEnumType {
Accepted = 'Accepted',
Failed = 'Failed',
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
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
additionalInfo?: string
reasonCode: string
}
-
-export interface EVSEType extends JsonObject {
- connectorId?: string
- id: number
-}
} 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 {
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
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
}
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
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 {
UnlockOnEVSideDisconnect = 'UnlockOnEVSideDisconnect',
}
-export enum OCPP20OptionalVariableName {
- HeartbeatInterval = 'HeartbeatInterval',
- WebSocketPingInterval = 'WebSocketPingInterval',
-}
-
export enum OCPP20VendorVariableName {
ConnectionUrl = 'ConnectionUrl',
}
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 {
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
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
}
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
+}
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,
} 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
-}
+/* 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 */
type OCPP16SampledValue,
} from './1.6/MeterValues.js'
+export type MeterValue = OCPP16MeterValue
+
export const MeterValueUnit = {
...OCPP16MeterValueUnit,
} as const
// 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
export type MeterValuePhase = OCPP16MeterValuePhase
export type SampledValue = OCPP16SampledValue
-
-export type MeterValue = OCPP16MeterValue
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,
// 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,
// 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,
// eslint-disable-next-line @typescript-eslint/no-redeclare
export type AvailabilityType = OCPP16AvailabilityType | OperationalStatusEnumType
+export type CancelReservationRequest = OCPP16CancelReservationRequest
+
export const DiagnosticsStatus = {
...OCPP16DiagnosticsStatus,
} as const
// 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
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',
RESERVATION_CANCELED = 'ReservationCanceled',
TRANSACTION_STARTED = 'TransactionStarted',
}
+
+export type Reservation = OCPP16ReserveNowRequest
+
+export type ReservationKey = keyof Reservation
} 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
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
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) {
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)
- }
- }
}
// 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()) && {
}
}
- 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
}
}
- // 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)
}
)
}
}
-
- 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!
- )
- }
}
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'
}
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
}
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 -
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,
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>
/**
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
- }
}
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>
/**
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
- }
}
}
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>
private started: boolean
private readonly workerSet: Set<WorkerSetElement>
private workerStartup: boolean
- public readonly emitter: EventEmitterAsyncResource | undefined
/**
* Creates a new `WorkerSet`.
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`.
}
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
- }
}
import { type PoolEvent, PoolEvents, type ThreadPoolOptions } from 'poolifier'
+export enum WorkerMessageEvents {
+ addedWorkerElement = 'addedWorkerElement',
+ addWorkerElement = 'addWorkerElement',
+ workerElementError = 'workerElementError',
+}
+
export enum WorkerProcessType {
/** @experimental */
dynamicPool = 'dynamicPool',
workerSet = 'workerSet',
}
+export enum WorkerSetEvents {
+ elementAdded = 'elementAdded',
+ elementError = 'elementError',
+ error = 'error',
+ started = 'started',
+ stopped = 'stopped',
+}
+
export interface SetInfo {
elementsExecuting: number
elementsPerWorker: number
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 = {
// 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
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',
-}
// 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 {}
"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",
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,
) {
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`))
+ }
+ })
+ }
}
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
stopAfterHours: number
}
+export type AvailabilityType = OCPP16AvailabilityType
+
+export type BootNotificationResponse = OCPP16BootNotificationResponse
+
+export type ChargePointStatus = OCPP16ChargePointStatus
+
export interface ChargingStationAutomaticTransactionGeneratorConfiguration extends JsonObject {
automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
automaticTransactionGeneratorStatuses?: Status[]
| 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
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
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 = {
// 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
stoppedDate?: Date
stopTransactionRequests?: number
}
+
+interface CommandsSupport extends JsonObject {
+ incomingCommands: Record<IncomingRequestCommand, boolean>
+ outgoingCommands?: Record<RequestCommand, boolean>
+}
-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
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',
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
-}