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