refactor: more coding style fixes
[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'
54import { Constants, convertToInt, isNullOrUndefined, logger } from '../../../utils/index.js'
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 () {
b768993d 69 // if (new.target?.name === moduleName) {
66a7748d 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) {
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!)
465 const authorizeConnectorIdDefined = !isNullOrUndefined(authorizeConnectorId)
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() &&
d929adcc 508 connectorStatus?.idTagLocalAuthorized === false
e7aeea18
JB
509 ) {
510 logger.error(
66a7748d
JB
511 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`
512 )
513 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
514 return
a2653482 515 }
e7aeea18 516 if (
d929adcc 517 connectorStatus?.transactionRemoteStarted === true &&
66a7748d 518 chargingStation.getAuthorizeRemoteTxRequests() &&
5398cecf 519 chargingStation.stationInfo?.remoteAuthorization === true &&
d929adcc
JB
520 connectorStatus?.idTagLocalAuthorized === false &&
521 connectorStatus?.idTagAuthorized === false
e7aeea18
JB
522 ) {
523 logger.error(
66a7748d
JB
524 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`
525 )
526 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
527 return
a2653482 528 }
e7aeea18 529 if (
66a7748d 530 connectorStatus?.idTagAuthorized === true &&
d929adcc 531 connectorStatus?.authorizeIdTag !== requestPayload.idTag
e7aeea18
JB
532 ) {
533 logger.error(
44eb6026
JB
534 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
535 requestPayload.idTag
66a7748d
JB
536 } different from the authorize request one ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`
537 )
538 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
539 return
a2653482 540 }
e7aeea18 541 if (
66a7748d 542 connectorStatus?.idTagLocalAuthorized === true &&
d929adcc 543 connectorStatus?.localAuthorizeIdTag !== requestPayload.idTag
e7aeea18
JB
544 ) {
545 logger.error(
44eb6026
JB
546 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
547 requestPayload.idTag
66a7748d
JB
548 } different from the local authorized one ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`
549 )
550 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
551 return
163547b1 552 }
d929adcc 553 if (connectorStatus?.transactionStarted === true) {
649287f8 554 logger.error(
66a7748d
JB
555 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${connectorStatus?.transactionIdTag}`
556 )
557 return
c0560973 558 }
649287f8
JB
559 if (chargingStation.hasEvses) {
560 for (const [evseId, evseStatus] of chargingStation.evses) {
561 if (evseStatus.connectors.size > 1) {
d929adcc
JB
562 for (const [id, status] of evseStatus.connectors) {
563 if (id !== connectorId && status?.transactionStarted === true) {
649287f8 564 logger.error(
66a7748d
JB
565 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${status?.transactionIdTag}`
566 )
567 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
568 return
649287f8
JB
569 }
570 }
571 }
572 }
573 }
e7aeea18 574 if (
d929adcc
JB
575 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
576 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
e7aeea18
JB
577 ) {
578 logger.error(
66a7748d
JB
579 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
580 )
581 return
290d006c 582 }
d1504492 583 if (!Number.isSafeInteger(payload.transactionId)) {
54ebb82c 584 logger.warn(
d929adcc 585 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
54ebb82c 586 payload.transactionId
66a7748d
JB
587 }, converting to integer`
588 )
589 payload.transactionId = convertToInt(payload.transactionId)
54ebb82c 590 }
c0560973 591
f0c6ed89 592 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d
JB
593 connectorStatus.transactionStarted = true
594 connectorStatus.transactionStart = requestPayload.timestamp
595 connectorStatus.transactionId = payload.transactionId
596 connectorStatus.transactionIdTag = requestPayload.idTag
597 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
d929adcc 598 connectorStatus.transactionBeginMeterValue =
e7aeea18 599 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
08f130a0 600 chargingStation,
d929adcc 601 connectorId,
66a7748d
JB
602 requestPayload.meterStart
603 )
604 if (requestPayload.reservationId != null) {
605 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
90aceaf6
JB
606 const reservation = chargingStation.getReservationBy(
607 'reservationId',
66a7748d
JB
608 requestPayload.reservationId
609 )!
90aceaf6
JB
610 if (reservation.idTag !== requestPayload.idTag) {
611 logger.warn(
56563a3c 612 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6
JB
613 payload.transactionId
614 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
615 reservation.idTag
66a7748d
JB
616 }`
617 )
90aceaf6
JB
618 }
619 if (hasReservationExpired(reservation)) {
620 logger.warn(
56563a3c 621 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6
JB
622 payload.transactionId
623 } started with expired reservation ${
624 requestPayload.reservationId
66a7748d
JB
625 } (expiry date: ${reservation.expiryDate.toISOString()}))`
626 )
90aceaf6 627 }
d984c13f 628 await chargingStation.removeReservation(
90aceaf6 629 reservation,
66a7748d
JB
630 ReservationTerminationReason.TRANSACTION_STARTED
631 )
d984c13f 632 }
66a7748d 633 chargingStation.stationInfo?.beginEndMeterValues === true &&
08f130a0 634 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
635 OCPP16MeterValuesRequest,
636 OCPP16MeterValuesResponse
08f130a0 637 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
d929adcc 638 connectorId,
ef6fa3fb 639 transactionId: payload.transactionId,
66a7748d
JB
640 meterValue: [connectorStatus.transactionBeginMeterValue]
641 } satisfies OCPP16MeterValuesRequest))
4ecff7ce
JB
642 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
643 chargingStation,
d929adcc 644 connectorId,
66a7748d
JB
645 OCPP16ChargePointStatus.Charging
646 )
e7aeea18 647 logger.info(
d929adcc 648 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
44eb6026 649 chargingStation.stationInfo.chargingStationId
66a7748d
JB
650 }#${connectorId} for idTag '${requestPayload.idTag}'`
651 )
652 if (chargingStation.stationInfo.powerSharedByConnectors === true) {
653 ++chargingStation.powerDivider
c0560973 654 }
f2d5e3d9
JB
655 const configuredMeterValueSampleInterval = getConfigurationKey(
656 chargingStation,
66a7748d
JB
657 OCPP16StandardParametersKey.MeterValueSampleInterval
658 )
08f130a0 659 chargingStation.startMeterValues(
d929adcc 660 connectorId,
a807045b 661 configuredMeterValueSampleInterval != null
be4c6702 662 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
66a7748d
JB
663 : Constants.DEFAULT_METER_VALUES_INTERVAL
664 )
c0560973 665 } else {
e7aeea18 666 logger.warn(
d929adcc
JB
667 `${chargingStation.logPrefix()} Starting transaction with id ${
668 payload.transactionId
669 } REJECTED on ${
56563a3c 670 chargingStation.stationInfo.chargingStationId
d929adcc 671 }#${connectorId} with status '${payload.idTagInfo?.status}', idTag '${
56563a3c
JB
672 requestPayload.idTag
673 }'${
d929adcc 674 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
56563a3c
JB
675 ? `, reservationId '${requestPayload.reservationId}'`
676 : ''
66a7748d
JB
677 }`
678 )
679 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
a2653482
JB
680 }
681 }
682
66a7748d 683 private async resetConnectorOnStartTransactionError (
08f130a0 684 chargingStation: ChargingStation,
66a7748d 685 connectorId: number
08f130a0 686 ): Promise<void> {
66a7748d
JB
687 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
688 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
689 resetConnectorStatus(connectorStatus!)
690 chargingStation.stopMeterValues(connectorId)
d929adcc 691 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
692 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
693 chargingStation,
ef6fa3fb 694 connectorId,
66a7748d
JB
695 OCPP16ChargePointStatus.Available
696 )
c0560973
JB
697 }
698 }
699
66a7748d 700 private async handleResponseStopTransaction (
08f130a0 701 chargingStation: ChargingStation,
e7aeea18 702 payload: OCPP16StopTransactionResponse,
66a7748d 703 requestPayload: OCPP16StopTransactionRequest
e7aeea18 704 ): Promise<void> {
08f130a0 705 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
66a7748d
JB
706 requestPayload.transactionId
707 )
9bf0ef23 708 if (isNullOrUndefined(transactionConnectorId)) {
e7aeea18 709 logger.error(
d929adcc
JB
710 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
711 requestPayload.transactionId
66a7748d
JB
712 }`
713 )
714 return
c0560973 715 }
5398cecf
JB
716 chargingStation.stationInfo?.beginEndMeterValues === true &&
717 chargingStation.stationInfo?.ocppStrictCompliance === false &&
718 chargingStation.stationInfo?.outOfOrderEndMeterValues === true &&
2cace1a5 719 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
720 OCPP16MeterValuesRequest,
721 OCPP16MeterValuesResponse
2cace1a5
JB
722 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
723 connectorId: transactionConnectorId,
724 transactionId: requestPayload.transactionId,
725 meterValue: [
726 OCPP16ServiceUtils.buildTransactionEndMeterValue(
727 chargingStation,
66a7748d 728 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 729 transactionConnectorId!,
66a7748d
JB
730 requestPayload.meterStop
731 )
732 ]
733 }))
2cace1a5 734 if (
66a7748d
JB
735 !chargingStation.isChargingStationAvailable() ||
736 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
737 !chargingStation.isConnectorAvailable(transactionConnectorId!)
2cace1a5 738 ) {
4ecff7ce
JB
739 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
740 chargingStation,
66a7748d 741 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 742 transactionConnectorId!,
66a7748d
JB
743 OCPP16ChargePointStatus.Unavailable
744 )
c0560973 745 } else {
4ecff7ce
JB
746 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
747 chargingStation,
66a7748d 748 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 749 transactionConnectorId!,
66a7748d
JB
750 OCPP16ChargePointStatus.Available
751 )
2cace1a5 752 }
66a7748d
JB
753 if (chargingStation.stationInfo.powerSharedByConnectors === true) {
754 chargingStation.powerDivider--
2cace1a5 755 }
66a7748d
JB
756 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
757 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId!)!)
758 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
759 chargingStation.stopMeterValues(transactionConnectorId!)
d929adcc
JB
760 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
761 requestPayload.transactionId
762 } STOPPED on ${
8ca6874c 763 chargingStation.stationInfo.chargingStationId
66a7748d 764 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
2cace1a5 765 if (
9bf0ef23 766 isNullOrUndefined(payload.idTagInfo) ||
2cace1a5
JB
767 payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED
768 ) {
66a7748d 769 logger.info(logMsg)
2cace1a5 770 } else {
66a7748d 771 logger.warn(logMsg)
c0560973
JB
772 }
773 }
c0560973 774}