refactor: cleanup nullish values handling
[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 }
f938317f
JB
463 if (authorizeConnectorId != null) {
464 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
465 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
466 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
467 authorizeConnectorStatus.idTagAuthorized = true
468 logger.debug(
469 `${chargingStation.logPrefix()} idTag '${
470 requestPayload.idTag
471 }' accepted on connector id ${authorizeConnectorId}`
472 )
473 } else {
474 authorizeConnectorStatus.idTagAuthorized = false
475 delete authorizeConnectorStatus.authorizeIdTag
476 logger.debug(
477 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
478 payload.idTagInfo.status
479 }`
480 )
d984c13f 481 }
58144adb 482 } else {
f938317f
JB
483 logger.error(
484 `${chargingStation.logPrefix()} idTag '${
485 requestPayload.idTag
486 }' has no authorize request pending`
66a7748d 487 )
58144adb
JB
488 }
489 }
490
66a7748d 491 private async handleResponseStartTransaction (
08f130a0 492 chargingStation: ChargingStation,
e7aeea18 493 payload: OCPP16StartTransactionResponse,
66a7748d 494 requestPayload: OCPP16StartTransactionRequest
e7aeea18 495 ): Promise<void> {
66a7748d
JB
496 const { connectorId } = requestPayload
497 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
e7aeea18 498 logger.error(
66a7748d
JB
499 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
500 )
501 return
c0560973 502 }
66a7748d 503 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
e7aeea18 504 if (
d929adcc 505 connectorStatus?.transactionRemoteStarted === true &&
66a7748d
JB
506 chargingStation.getAuthorizeRemoteTxRequests() &&
507 chargingStation.getLocalAuthListEnabled() &&
508 chargingStation.hasIdTags() &&
5199f9fd 509 connectorStatus.idTagLocalAuthorized === false
e7aeea18
JB
510 ) {
511 logger.error(
5199f9fd
JB
512 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
513 connectorStatus.localAuthorizeIdTag
514 } on connector id ${connectorId}`
66a7748d
JB
515 )
516 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
517 return
a2653482 518 }
e7aeea18 519 if (
d929adcc 520 connectorStatus?.transactionRemoteStarted === true &&
66a7748d 521 chargingStation.getAuthorizeRemoteTxRequests() &&
5398cecf 522 chargingStation.stationInfo?.remoteAuthorization === true &&
5199f9fd
JB
523 connectorStatus.idTagLocalAuthorized === false &&
524 connectorStatus.idTagAuthorized === false
e7aeea18
JB
525 ) {
526 logger.error(
5199f9fd
JB
527 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
528 connectorStatus.authorizeIdTag
529 } on connector id ${connectorId}`
66a7748d
JB
530 )
531 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
532 return
a2653482 533 }
e7aeea18 534 if (
66a7748d 535 connectorStatus?.idTagAuthorized === true &&
5199f9fd 536 connectorStatus.authorizeIdTag !== requestPayload.idTag
e7aeea18
JB
537 ) {
538 logger.error(
44eb6026
JB
539 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
540 requestPayload.idTag
5199f9fd
JB
541 } different from the authorize request one ${
542 connectorStatus.authorizeIdTag
543 } on connector id ${connectorId}`
66a7748d
JB
544 )
545 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
546 return
a2653482 547 }
e7aeea18 548 if (
66a7748d 549 connectorStatus?.idTagLocalAuthorized === true &&
5199f9fd 550 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
e7aeea18
JB
551 ) {
552 logger.error(
44eb6026
JB
553 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
554 requestPayload.idTag
5199f9fd
JB
555 } different from the local authorized one ${
556 connectorStatus.localAuthorizeIdTag
557 } on connector id ${connectorId}`
66a7748d
JB
558 )
559 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
560 return
163547b1 561 }
d929adcc 562 if (connectorStatus?.transactionStarted === true) {
649287f8 563 logger.error(
5199f9fd
JB
564 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
565 connectorStatus.transactionIdTag
566 }`
66a7748d
JB
567 )
568 return
c0560973 569 }
649287f8
JB
570 if (chargingStation.hasEvses) {
571 for (const [evseId, evseStatus] of chargingStation.evses) {
572 if (evseStatus.connectors.size > 1) {
d929adcc 573 for (const [id, status] of evseStatus.connectors) {
5199f9fd 574 if (id !== connectorId && status.transactionStarted === true) {
649287f8 575 logger.error(
5199f9fd
JB
576 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
577 status.transactionIdTag
578 }`
66a7748d
JB
579 )
580 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
581 return
649287f8
JB
582 }
583 }
584 }
585 }
586 }
e7aeea18 587 if (
d929adcc
JB
588 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
589 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
e7aeea18
JB
590 ) {
591 logger.error(
66a7748d
JB
592 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
593 )
594 return
290d006c 595 }
d1504492 596 if (!Number.isSafeInteger(payload.transactionId)) {
54ebb82c 597 logger.warn(
d929adcc 598 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
54ebb82c 599 payload.transactionId
66a7748d
JB
600 }, converting to integer`
601 )
602 payload.transactionId = convertToInt(payload.transactionId)
54ebb82c 603 }
c0560973 604
5199f9fd 605 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d
JB
606 connectorStatus.transactionStarted = true
607 connectorStatus.transactionStart = requestPayload.timestamp
608 connectorStatus.transactionId = payload.transactionId
609 connectorStatus.transactionIdTag = requestPayload.idTag
610 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
d929adcc 611 connectorStatus.transactionBeginMeterValue =
e7aeea18 612 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
08f130a0 613 chargingStation,
d929adcc 614 connectorId,
66a7748d
JB
615 requestPayload.meterStart
616 )
617 if (requestPayload.reservationId != null) {
618 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
90aceaf6
JB
619 const reservation = chargingStation.getReservationBy(
620 'reservationId',
66a7748d
JB
621 requestPayload.reservationId
622 )!
90aceaf6
JB
623 if (reservation.idTag !== requestPayload.idTag) {
624 logger.warn(
56563a3c 625 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6
JB
626 payload.transactionId
627 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
628 reservation.idTag
66a7748d
JB
629 }`
630 )
90aceaf6
JB
631 }
632 if (hasReservationExpired(reservation)) {
633 logger.warn(
56563a3c 634 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6
JB
635 payload.transactionId
636 } started with expired reservation ${
637 requestPayload.reservationId
66a7748d
JB
638 } (expiry date: ${reservation.expiryDate.toISOString()}))`
639 )
90aceaf6 640 }
d984c13f 641 await chargingStation.removeReservation(
90aceaf6 642 reservation,
66a7748d
JB
643 ReservationTerminationReason.TRANSACTION_STARTED
644 )
d984c13f 645 }
66a7748d 646 chargingStation.stationInfo?.beginEndMeterValues === true &&
08f130a0 647 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
648 OCPP16MeterValuesRequest,
649 OCPP16MeterValuesResponse
08f130a0 650 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
d929adcc 651 connectorId,
ef6fa3fb 652 transactionId: payload.transactionId,
66a7748d
JB
653 meterValue: [connectorStatus.transactionBeginMeterValue]
654 } satisfies OCPP16MeterValuesRequest))
4ecff7ce
JB
655 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
656 chargingStation,
d929adcc 657 connectorId,
66a7748d
JB
658 OCPP16ChargePointStatus.Charging
659 )
e7aeea18 660 logger.info(
5199f9fd
JB
661 `${chargingStation.logPrefix()} Transaction with id ${
662 payload.transactionId
663 } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
664 requestPayload.idTag
665 }'`
66a7748d 666 )
5199f9fd
JB
667 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
668 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
669 ++chargingStation.powerDivider!
c0560973 670 }
f2d5e3d9
JB
671 const configuredMeterValueSampleInterval = getConfigurationKey(
672 chargingStation,
66a7748d
JB
673 OCPP16StandardParametersKey.MeterValueSampleInterval
674 )
08f130a0 675 chargingStation.startMeterValues(
d929adcc 676 connectorId,
a807045b 677 configuredMeterValueSampleInterval != null
be4c6702 678 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
66a7748d
JB
679 : Constants.DEFAULT_METER_VALUES_INTERVAL
680 )
c0560973 681 } else {
e7aeea18 682 logger.warn(
d929adcc
JB
683 `${chargingStation.logPrefix()} Starting transaction with id ${
684 payload.transactionId
5199f9fd
JB
685 } REJECTED on ${chargingStation.stationInfo
686 ?.chargingStationId}#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
56563a3c
JB
687 requestPayload.idTag
688 }'${
d929adcc 689 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
56563a3c
JB
690 ? `, reservationId '${requestPayload.reservationId}'`
691 : ''
66a7748d
JB
692 }`
693 )
694 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
a2653482
JB
695 }
696 }
697
66a7748d 698 private async resetConnectorOnStartTransactionError (
08f130a0 699 chargingStation: ChargingStation,
66a7748d 700 connectorId: number
08f130a0 701 ): Promise<void> {
66a7748d 702 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f938317f 703 resetConnectorStatus(connectorStatus)
66a7748d 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 }
f938317f 767 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
aa63c9b7 768 chargingStation.stopMeterValues(transactionConnectorId)
d929adcc
JB
769 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
770 requestPayload.transactionId
5199f9fd
JB
771 } STOPPED on ${chargingStation.stationInfo
772 ?.chargingStationId}#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
2cace1a5 773 if (
aa63c9b7 774 payload.idTagInfo == null ||
5199f9fd 775 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
2cace1a5 776 ) {
66a7748d 777 logger.info(logMsg)
2cace1a5 778 } else {
66a7748d 779 logger.warn(logMsg)
c0560973
JB
780 }
781 }
c0560973 782}