refactor: switch eslint configuration to strict type checking
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
CommitLineData
edd13439 1// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
c8eeb62b 2
66a7748d
JB
3import type { JSONSchemaType } from 'ajv'
4import { secondsToMilliseconds } from 'date-fns'
844e496b 5
66a7748d 6import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
04b1261c
JB
7import {
8 type ChargingStation,
f2d5e3d9
JB
9 addConfigurationKey,
10 getConfigurationKey,
90aceaf6 11 hasReservationExpired,
66a7748d
JB
12 resetConnectorStatus
13} from '../../../charging-station/index.js'
14import { OCPPError } from '../../../exception/index.js'
ef6fa3fb 15import {
268a74bb 16 type ChangeConfigurationResponse,
268a74bb
JB
17 ErrorType,
18 type GenericResponse,
19 type GetConfigurationResponse,
20 type GetDiagnosticsResponse,
268a74bb 21 type JsonType,
8114d10e 22 OCPP16AuthorizationStatus,
27782dbc
JB
23 type OCPP16AuthorizeRequest,
24 type OCPP16AuthorizeResponse,
268a74bb 25 type OCPP16BootNotificationResponse,
366f75f6 26 type OCPP16ChangeAvailabilityResponse,
268a74bb 27 OCPP16ChargePointStatus,
41f3983a 28 type OCPP16ClearChargingProfileResponse,
268a74bb
JB
29 type OCPP16DataTransferResponse,
30 type OCPP16DiagnosticsStatusNotificationResponse,
31 type OCPP16FirmwareStatusNotificationResponse,
41189456 32 type OCPP16GetCompositeScheduleResponse,
268a74bb
JB
33 type OCPP16HeartbeatResponse,
34 OCPP16IncomingRequestCommand,
35 type OCPP16MeterValuesRequest,
36 type OCPP16MeterValuesResponse,
37 OCPP16RequestCommand,
66dd3447 38 type OCPP16ReserveNowResponse,
268a74bb 39 OCPP16StandardParametersKey,
27782dbc
JB
40 type OCPP16StartTransactionRequest,
41 type OCPP16StartTransactionResponse,
268a74bb 42 type OCPP16StatusNotificationResponse,
27782dbc
JB
43 type OCPP16StopTransactionRequest,
44 type OCPP16StopTransactionResponse,
268a74bb
JB
45 type OCPP16TriggerMessageResponse,
46 type OCPP16UpdateFirmwareResponse,
47 OCPPVersion,
02887891 48 RegistrationStatusEnumType,
d984c13f 49 ReservationTerminationReason,
02887891 50 type ResponseHandler,
268a74bb 51 type SetChargingProfileResponse,
66a7748d
JB
52 type UnlockConnectorResponse
53} from '../../../types/index.js'
aa63c9b7 54import { Constants, convertToInt, logger } from '../../../utils/index.js'
66a7748d 55import { OCPPResponseService } from '../OCPPResponseService.js'
c0560973 56
66a7748d 57const moduleName = 'OCPP16ResponseService'
909dcf2d 58
268a74bb 59export class OCPP16ResponseService extends OCPPResponseService {
b3fc3ff5 60 public jsonIncomingRequestResponseSchemas: Map<
66a7748d
JB
61 OCPP16IncomingRequestCommand,
62 JSONSchemaType<JsonType>
63 >
b3fc3ff5 64
66a7748d
JB
65 private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>
66 private readonly jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonType>>
58144adb 67
66a7748d 68 public constructor () {
5199f9fd
JB
69 // if (new.target.name === moduleName) {
70 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
b768993d 71 // }
66a7748d 72 super(OCPPVersion.VERSION_16)
58144adb 73 this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
a37fc6dc
JB
74 [
75 OCPP16RequestCommand.BOOT_NOTIFICATION,
66a7748d 76 this.handleResponseBootNotification.bind(this) as ResponseHandler
a37fc6dc
JB
77 ],
78 [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this) as ResponseHandler],
79 [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler],
80 [
81 OCPP16RequestCommand.START_TRANSACTION,
66a7748d 82 this.handleResponseStartTransaction.bind(this) as ResponseHandler
a37fc6dc
JB
83 ],
84 [
85 OCPP16RequestCommand.STOP_TRANSACTION,
66a7748d 86 this.handleResponseStopTransaction.bind(this) as ResponseHandler
a37fc6dc
JB
87 ],
88 [
89 OCPP16RequestCommand.STATUS_NOTIFICATION,
66a7748d 90 this.emptyResponseHandler.bind(this) as ResponseHandler
a37fc6dc
JB
91 ],
92 [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler.bind(this) as ResponseHandler],
93 [
94 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
66a7748d 95 this.emptyResponseHandler.bind(this) as ResponseHandler
a37fc6dc
JB
96 ],
97 [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler.bind(this) as ResponseHandler],
98 [
99 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
66a7748d
JB
100 this.emptyResponseHandler.bind(this) as ResponseHandler
101 ]
102 ])
291b5ec8 103 this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonType>>([
b52c969d
JB
104 [
105 OCPP16RequestCommand.BOOT_NOTIFICATION,
130783a7 106 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>(
51022aa0 107 'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json',
1b271a54 108 moduleName,
66a7748d
JB
109 'constructor'
110 )
b52c969d
JB
111 ],
112 [
113 OCPP16RequestCommand.HEARTBEAT,
130783a7 114 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>(
51022aa0 115 'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json',
1b271a54 116 moduleName,
66a7748d
JB
117 'constructor'
118 )
b52c969d
JB
119 ],
120 [
121 OCPP16RequestCommand.AUTHORIZE,
130783a7 122 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>(
51022aa0 123 'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json',
1b271a54 124 moduleName,
66a7748d
JB
125 'constructor'
126 )
b52c969d
JB
127 ],
128 [
129 OCPP16RequestCommand.START_TRANSACTION,
130783a7 130 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>(
51022aa0 131 'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json',
1b271a54 132 moduleName,
66a7748d
JB
133 'constructor'
134 )
b52c969d
JB
135 ],
136 [
137 OCPP16RequestCommand.STOP_TRANSACTION,
130783a7 138 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>(
51022aa0 139 'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json',
1b271a54 140 moduleName,
66a7748d
JB
141 'constructor'
142 )
b52c969d
JB
143 ],
144 [
145 OCPP16RequestCommand.STATUS_NOTIFICATION,
130783a7 146 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>(
51022aa0 147 'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json',
1b271a54 148 moduleName,
66a7748d
JB
149 'constructor'
150 )
b52c969d
JB
151 ],
152 [
153 OCPP16RequestCommand.METER_VALUES,
130783a7 154 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>(
51022aa0 155 'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json',
1b271a54 156 moduleName,
66a7748d
JB
157 'constructor'
158 )
b52c969d
JB
159 ],
160 [
161 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
130783a7 162 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>(
51022aa0 163 'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json',
1b271a54 164 moduleName,
66a7748d
JB
165 'constructor'
166 )
b52c969d 167 ],
91a7d3ea
JB
168 [
169 OCPP16RequestCommand.DATA_TRANSFER,
130783a7 170 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
51022aa0 171 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
1b271a54 172 moduleName,
66a7748d
JB
173 'constructor'
174 )
91a7d3ea 175 ],
c9a4f9ea
JB
176 [
177 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
130783a7 178 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>(
51022aa0 179 'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json',
1b271a54 180 moduleName,
66a7748d
JB
181 'constructor'
182 )
183 ]
184 ])
02887891
JB
185 this.jsonIncomingRequestResponseSchemas = new Map([
186 [
187 OCPP16IncomingRequestCommand.RESET,
130783a7 188 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
51022aa0 189 'assets/json-schemas/ocpp/1.6/ResetResponse.json',
1b271a54 190 moduleName,
66a7748d
JB
191 'constructor'
192 )
02887891
JB
193 ],
194 [
195 OCPP16IncomingRequestCommand.CLEAR_CACHE,
130783a7 196 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
51022aa0 197 'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
1b271a54 198 moduleName,
66a7748d
JB
199 'constructor'
200 )
02887891
JB
201 ],
202 [
203 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
366f75f6 204 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>(
51022aa0 205 'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
1b271a54 206 moduleName,
66a7748d
JB
207 'constructor'
208 )
02887891
JB
209 ],
210 [
211 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
130783a7 212 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>(
51022aa0 213 'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
1b271a54 214 moduleName,
66a7748d
JB
215 'constructor'
216 )
02887891
JB
217 ],
218 [
219 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
130783a7 220 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>(
51022aa0 221 'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
1b271a54 222 moduleName,
66a7748d
JB
223 'constructor'
224 )
02887891
JB
225 ],
226 [
227 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
130783a7 228 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>(
51022aa0 229 'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
1b271a54 230 moduleName,
66a7748d
JB
231 'constructor'
232 )
02887891 233 ],
41189456
JB
234 [
235 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
236 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>(
51022aa0 237 'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
41189456 238 moduleName,
66a7748d
JB
239 'constructor'
240 )
41189456 241 ],
02887891
JB
242 [
243 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
130783a7 244 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>(
51022aa0 245 'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
1b271a54 246 moduleName,
66a7748d
JB
247 'constructor'
248 )
02887891
JB
249 ],
250 [
251 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
41f3983a 252 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>(
51022aa0 253 'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
1b271a54 254 moduleName,
66a7748d
JB
255 'constructor'
256 )
02887891
JB
257 ],
258 [
259 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
130783a7 260 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
51022aa0 261 'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
1b271a54 262 moduleName,
66a7748d
JB
263 'constructor'
264 )
02887891
JB
265 ],
266 [
267 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
130783a7 268 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
51022aa0 269 'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
1b271a54 270 moduleName,
66a7748d
JB
271 'constructor'
272 )
02887891
JB
273 ],
274 [
275 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
130783a7 276 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>(
51022aa0 277 'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
1b271a54 278 moduleName,
66a7748d
JB
279 'constructor'
280 )
02887891
JB
281 ],
282 [
283 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
130783a7 284 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>(
51022aa0 285 'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
1b271a54 286 moduleName,
66a7748d
JB
287 'constructor'
288 )
02887891
JB
289 ],
290 [
291 OCPP16IncomingRequestCommand.DATA_TRANSFER,
130783a7 292 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
51022aa0 293 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
1b271a54 294 moduleName,
66a7748d
JB
295 'constructor'
296 )
02887891
JB
297 ],
298 [
299 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
130783a7 300 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>(
51022aa0 301 'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
1b271a54 302 moduleName,
66a7748d
JB
303 'constructor'
304 )
02887891 305 ],
28fe900f
JB
306 [
307 OCPP16IncomingRequestCommand.RESERVE_NOW,
308 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>(
309 'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json',
310 moduleName,
66a7748d
JB
311 'constructor'
312 )
28fe900f
JB
313 ],
314 [
315 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
b1f1b0f6 316 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
28fe900f
JB
317 'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json',
318 moduleName,
66a7748d
JB
319 'constructor'
320 )
321 ]
322 ])
31f59c6d
JB
323 this.validatePayload = this.validatePayload.bind(this) as (
324 chargingStation: ChargingStation,
325 commandName: OCPP16RequestCommand,
66a7748d
JB
326 payload: JsonType
327 ) => boolean
58144adb
JB
328 }
329
9429aa42 330 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
08f130a0 331 chargingStation: ChargingStation,
e7aeea18 332 commandName: OCPP16RequestCommand,
9429aa42 333 payload: ResType,
66a7748d 334 requestPayload: ReqType
e7aeea18 335 ): Promise<void> {
66a7748d 336 if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
65554cc3 337 if (
66a7748d
JB
338 this.responseHandlers.has(commandName) &&
339 OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
65554cc3 340 ) {
124f3553 341 try {
66a7748d
JB
342 this.validatePayload(chargingStation, commandName, payload)
343 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
344 await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload)
124f3553 345 } catch (error) {
6c8f5d90
JB
346 logger.error(
347 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
66a7748d
JB
348 error
349 )
350 throw error
124f3553
JB
351 }
352 } else {
353 // Throw exception
e7aeea18
JB
354 throw new OCPPError(
355 ErrorType.NOT_IMPLEMENTED,
6c8f5d90 356 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
e7aeea18 357 payload,
4ed03b6e 358 undefined,
66a7748d 359 2
e7aeea18 360 )}`,
7369e417 361 commandName,
66a7748d
JB
362 payload
363 )
887fef76 364 }
c0560973 365 } else {
e7aeea18
JB
366 throw new OCPPError(
367 ErrorType.SECURITY_ERROR,
6c8f5d90 368 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
e7aeea18 369 payload,
4ed03b6e 370 undefined,
66a7748d 371 2
439fc71b 372 )} while the charging station is not registered on the central server.`,
7369e417 373 commandName,
66a7748d
JB
374 payload
375 )
c0560973
JB
376 }
377 }
378
66a7748d 379 private validatePayload (
9c5c4195
JB
380 chargingStation: ChargingStation,
381 commandName: OCPP16RequestCommand,
66a7748d 382 payload: JsonType
9c5c4195 383 ): boolean {
66a7748d 384 if (this.jsonSchemas.has(commandName)) {
9c5c4195
JB
385 return this.validateResponsePayload(
386 chargingStation,
387 commandName,
66a7748d 388 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 389 this.jsonSchemas.get(commandName)!,
66a7748d
JB
390 payload
391 )
9c5c4195
JB
392 }
393 logger.warn(
66a7748d
JB
394 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
395 )
396 return false
9c5c4195
JB
397 }
398
66a7748d 399 private handleResponseBootNotification (
08f130a0 400 chargingStation: ChargingStation,
66a7748d 401 payload: OCPP16BootNotificationResponse
08f130a0 402 ): void {
d270cc87 403 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
f2d5e3d9 404 addConfigurationKey(
17ac262c 405 chargingStation,
f0f65a62 406 OCPP16StandardParametersKey.HeartbeatInterval,
a95873d8 407 payload.interval.toString(),
abe9e9dd 408 {},
66a7748d
JB
409 { overwrite: true, save: true }
410 )
f2d5e3d9 411 addConfigurationKey(
17ac262c 412 chargingStation,
f0f65a62 413 OCPP16StandardParametersKey.HeartBeatInterval,
e7aeea18 414 payload.interval.toString(),
00db15b8 415 { visible: false },
66a7748d
JB
416 { overwrite: true, save: true }
417 )
418 OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
672fed6e 419 }
d270cc87 420 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
08f130a0 421 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
e7aeea18 422 payload.status
66a7748d 423 }' state on the central server`
d270cc87 424 payload.status === RegistrationStatusEnumType.REJECTED
e7aeea18 425 ? logger.warn(logMsg)
66a7748d 426 : logger.info(logMsg)
c0560973 427 } else {
e7aeea18 428 logger.error(
44eb6026 429 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
66a7748d
JB
430 payload
431 )
c0560973
JB
432 }
433 }
434
66a7748d 435 private handleResponseAuthorize (
08f130a0 436 chargingStation: ChargingStation,
e7aeea18 437 payload: OCPP16AuthorizeResponse,
66a7748d 438 requestPayload: OCPP16AuthorizeRequest
e7aeea18 439 ): void {
66a7748d 440 let authorizeConnectorId: number | undefined
ded57f02
JB
441 if (chargingStation.hasEvses) {
442 for (const [evseId, evseStatus] of chargingStation.evses) {
443 if (evseId > 0) {
444 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 445 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
66a7748d
JB
446 authorizeConnectorId = connectorId
447 break
ded57f02
JB
448 }
449 }
450 }
451 }
452 } else {
453 for (const connectorId of chargingStation.connectors.keys()) {
454 if (
455 connectorId > 0 &&
456 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
457 ) {
66a7748d
JB
458 authorizeConnectorId = connectorId
459 break
ded57f02 460 }
58144adb
JB
461 }
462 }
66a7748d
JB
463 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
464 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId!)
aa63c9b7 465 const authorizeConnectorIdDefined = authorizeConnectorId != null
58144adb 466 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
d984c13f 467 if (authorizeConnectorIdDefined) {
66a7748d
JB
468 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
469 authorizeConnectorStatus!.idTagAuthorized = true
d984c13f 470 }
e7aeea18 471 logger.debug(
2585c6e9 472 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' accepted${
54ebb82c 473 authorizeConnectorIdDefined ? ` on connector id ${authorizeConnectorId}` : ''
66a7748d
JB
474 }`
475 )
58144adb 476 } else {
44eb6026 477 if (authorizeConnectorIdDefined) {
66a7748d
JB
478 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
479 authorizeConnectorStatus!.idTagAuthorized = false
480 delete authorizeConnectorStatus?.authorizeIdTag
1984f194 481 }
e7aeea18 482 logger.debug(
2585c6e9 483 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
e7aeea18 484 payload.idTagInfo.status
66a7748d
JB
485 }'${authorizeConnectorIdDefined ? ` on connector id ${authorizeConnectorId}` : ''}`
486 )
58144adb
JB
487 }
488 }
489
66a7748d 490 private async handleResponseStartTransaction (
08f130a0 491 chargingStation: ChargingStation,
e7aeea18 492 payload: OCPP16StartTransactionResponse,
66a7748d 493 requestPayload: OCPP16StartTransactionRequest
e7aeea18 494 ): Promise<void> {
66a7748d
JB
495 const { connectorId } = requestPayload
496 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
e7aeea18 497 logger.error(
66a7748d
JB
498 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
499 )
500 return
c0560973 501 }
66a7748d 502 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
e7aeea18 503 if (
d929adcc 504 connectorStatus?.transactionRemoteStarted === true &&
66a7748d
JB
505 chargingStation.getAuthorizeRemoteTxRequests() &&
506 chargingStation.getLocalAuthListEnabled() &&
507 chargingStation.hasIdTags() &&
5199f9fd 508 connectorStatus.idTagLocalAuthorized === false
e7aeea18
JB
509 ) {
510 logger.error(
5199f9fd
JB
511 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
512 connectorStatus.localAuthorizeIdTag
513 } on connector id ${connectorId}`
66a7748d
JB
514 )
515 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
516 return
a2653482 517 }
e7aeea18 518 if (
d929adcc 519 connectorStatus?.transactionRemoteStarted === true &&
66a7748d 520 chargingStation.getAuthorizeRemoteTxRequests() &&
5398cecf 521 chargingStation.stationInfo?.remoteAuthorization === true &&
5199f9fd
JB
522 connectorStatus.idTagLocalAuthorized === false &&
523 connectorStatus.idTagAuthorized === false
e7aeea18
JB
524 ) {
525 logger.error(
5199f9fd
JB
526 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
527 connectorStatus.authorizeIdTag
528 } on connector id ${connectorId}`
66a7748d
JB
529 )
530 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
531 return
a2653482 532 }
e7aeea18 533 if (
66a7748d 534 connectorStatus?.idTagAuthorized === true &&
5199f9fd 535 connectorStatus.authorizeIdTag !== requestPayload.idTag
e7aeea18
JB
536 ) {
537 logger.error(
44eb6026
JB
538 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
539 requestPayload.idTag
5199f9fd
JB
540 } different from the authorize request one ${
541 connectorStatus.authorizeIdTag
542 } on connector id ${connectorId}`
66a7748d
JB
543 )
544 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
545 return
a2653482 546 }
e7aeea18 547 if (
66a7748d 548 connectorStatus?.idTagLocalAuthorized === true &&
5199f9fd 549 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
e7aeea18
JB
550 ) {
551 logger.error(
44eb6026
JB
552 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
553 requestPayload.idTag
5199f9fd
JB
554 } different from the local authorized one ${
555 connectorStatus.localAuthorizeIdTag
556 } on connector id ${connectorId}`
66a7748d
JB
557 )
558 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
559 return
163547b1 560 }
d929adcc 561 if (connectorStatus?.transactionStarted === true) {
649287f8 562 logger.error(
5199f9fd
JB
563 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
564 connectorStatus.transactionIdTag
565 }`
66a7748d
JB
566 )
567 return
c0560973 568 }
649287f8
JB
569 if (chargingStation.hasEvses) {
570 for (const [evseId, evseStatus] of chargingStation.evses) {
571 if (evseStatus.connectors.size > 1) {
d929adcc 572 for (const [id, status] of evseStatus.connectors) {
5199f9fd 573 if (id !== connectorId && status.transactionStarted === true) {
649287f8 574 logger.error(
5199f9fd
JB
575 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
576 status.transactionIdTag
577 }`
66a7748d
JB
578 )
579 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
580 return
649287f8
JB
581 }
582 }
583 }
584 }
585 }
e7aeea18 586 if (
d929adcc
JB
587 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
588 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
e7aeea18
JB
589 ) {
590 logger.error(
66a7748d
JB
591 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
592 )
593 return
290d006c 594 }
d1504492 595 if (!Number.isSafeInteger(payload.transactionId)) {
54ebb82c 596 logger.warn(
d929adcc 597 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
54ebb82c 598 payload.transactionId
66a7748d
JB
599 }, converting to integer`
600 )
601 payload.transactionId = convertToInt(payload.transactionId)
54ebb82c 602 }
c0560973 603
5199f9fd 604 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d
JB
605 connectorStatus.transactionStarted = true
606 connectorStatus.transactionStart = requestPayload.timestamp
607 connectorStatus.transactionId = payload.transactionId
608 connectorStatus.transactionIdTag = requestPayload.idTag
609 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
d929adcc 610 connectorStatus.transactionBeginMeterValue =
e7aeea18 611 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
08f130a0 612 chargingStation,
d929adcc 613 connectorId,
66a7748d
JB
614 requestPayload.meterStart
615 )
616 if (requestPayload.reservationId != null) {
617 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
90aceaf6
JB
618 const reservation = chargingStation.getReservationBy(
619 'reservationId',
66a7748d
JB
620 requestPayload.reservationId
621 )!
90aceaf6
JB
622 if (reservation.idTag !== requestPayload.idTag) {
623 logger.warn(
56563a3c 624 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6
JB
625 payload.transactionId
626 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
627 reservation.idTag
66a7748d
JB
628 }`
629 )
90aceaf6
JB
630 }
631 if (hasReservationExpired(reservation)) {
632 logger.warn(
56563a3c 633 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6
JB
634 payload.transactionId
635 } started with expired reservation ${
636 requestPayload.reservationId
66a7748d
JB
637 } (expiry date: ${reservation.expiryDate.toISOString()}))`
638 )
90aceaf6 639 }
d984c13f 640 await chargingStation.removeReservation(
90aceaf6 641 reservation,
66a7748d
JB
642 ReservationTerminationReason.TRANSACTION_STARTED
643 )
d984c13f 644 }
66a7748d 645 chargingStation.stationInfo?.beginEndMeterValues === true &&
08f130a0 646 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
647 OCPP16MeterValuesRequest,
648 OCPP16MeterValuesResponse
08f130a0 649 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
d929adcc 650 connectorId,
ef6fa3fb 651 transactionId: payload.transactionId,
66a7748d
JB
652 meterValue: [connectorStatus.transactionBeginMeterValue]
653 } satisfies OCPP16MeterValuesRequest))
4ecff7ce
JB
654 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
655 chargingStation,
d929adcc 656 connectorId,
66a7748d
JB
657 OCPP16ChargePointStatus.Charging
658 )
e7aeea18 659 logger.info(
5199f9fd
JB
660 `${chargingStation.logPrefix()} Transaction with id ${
661 payload.transactionId
662 } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
663 requestPayload.idTag
664 }'`
66a7748d 665 )
5199f9fd
JB
666 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
667 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
668 ++chargingStation.powerDivider!
c0560973 669 }
f2d5e3d9
JB
670 const configuredMeterValueSampleInterval = getConfigurationKey(
671 chargingStation,
66a7748d
JB
672 OCPP16StandardParametersKey.MeterValueSampleInterval
673 )
08f130a0 674 chargingStation.startMeterValues(
d929adcc 675 connectorId,
a807045b 676 configuredMeterValueSampleInterval != null
be4c6702 677 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
66a7748d
JB
678 : Constants.DEFAULT_METER_VALUES_INTERVAL
679 )
c0560973 680 } else {
e7aeea18 681 logger.warn(
d929adcc
JB
682 `${chargingStation.logPrefix()} Starting transaction with id ${
683 payload.transactionId
5199f9fd
JB
684 } REJECTED on ${chargingStation.stationInfo
685 ?.chargingStationId}#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
56563a3c
JB
686 requestPayload.idTag
687 }'${
d929adcc 688 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
56563a3c
JB
689 ? `, reservationId '${requestPayload.reservationId}'`
690 : ''
66a7748d
JB
691 }`
692 )
693 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
a2653482
JB
694 }
695 }
696
66a7748d 697 private async resetConnectorOnStartTransactionError (
08f130a0 698 chargingStation: ChargingStation,
66a7748d 699 connectorId: number
08f130a0 700 ): Promise<void> {
66a7748d
JB
701 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
702 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
703 resetConnectorStatus(connectorStatus!)
704 chargingStation.stopMeterValues(connectorId)
d929adcc 705 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
706 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
707 chargingStation,
ef6fa3fb 708 connectorId,
66a7748d
JB
709 OCPP16ChargePointStatus.Available
710 )
c0560973
JB
711 }
712 }
713
66a7748d 714 private async handleResponseStopTransaction (
08f130a0 715 chargingStation: ChargingStation,
e7aeea18 716 payload: OCPP16StopTransactionResponse,
66a7748d 717 requestPayload: OCPP16StopTransactionRequest
e7aeea18 718 ): Promise<void> {
08f130a0 719 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
66a7748d
JB
720 requestPayload.transactionId
721 )
aa63c9b7 722 if (transactionConnectorId == null) {
e7aeea18 723 logger.error(
d929adcc
JB
724 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
725 requestPayload.transactionId
66a7748d
JB
726 }`
727 )
728 return
c0560973 729 }
5398cecf 730 chargingStation.stationInfo?.beginEndMeterValues === true &&
5199f9fd
JB
731 chargingStation.stationInfo.ocppStrictCompliance === false &&
732 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
2cace1a5 733 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
734 OCPP16MeterValuesRequest,
735 OCPP16MeterValuesResponse
2cace1a5
JB
736 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
737 connectorId: transactionConnectorId,
738 transactionId: requestPayload.transactionId,
739 meterValue: [
740 OCPP16ServiceUtils.buildTransactionEndMeterValue(
741 chargingStation,
aa63c9b7 742 transactionConnectorId,
66a7748d
JB
743 requestPayload.meterStop
744 )
745 ]
746 }))
2cace1a5 747 if (
66a7748d 748 !chargingStation.isChargingStationAvailable() ||
aa63c9b7 749 !chargingStation.isConnectorAvailable(transactionConnectorId)
2cace1a5 750 ) {
4ecff7ce
JB
751 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
752 chargingStation,
aa63c9b7 753 transactionConnectorId,
66a7748d
JB
754 OCPP16ChargePointStatus.Unavailable
755 )
c0560973 756 } else {
4ecff7ce
JB
757 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
758 chargingStation,
aa63c9b7 759 transactionConnectorId,
66a7748d
JB
760 OCPP16ChargePointStatus.Available
761 )
2cace1a5 762 }
5199f9fd
JB
763 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
764 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
765 chargingStation.powerDivider!--
2cace1a5 766 }
66a7748d 767 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
aa63c9b7
JB
768 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId)!)
769 chargingStation.stopMeterValues(transactionConnectorId)
d929adcc
JB
770 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
771 requestPayload.transactionId
5199f9fd
JB
772 } STOPPED on ${chargingStation.stationInfo
773 ?.chargingStationId}#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
2cace1a5 774 if (
aa63c9b7 775 payload.idTagInfo == null ||
5199f9fd 776 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
2cace1a5 777 ) {
66a7748d 778 logger.info(logMsg)
2cace1a5 779 } else {
66a7748d 780 logger.warn(logMsg)
c0560973
JB
781 }
782 }
c0560973 783}