refactor: cleanup unneeded type casting
[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 ])
ba9a56a6 320 this.validatePayload = this.validatePayload.bind(this)
58144adb
JB
321 }
322
9429aa42 323 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
08f130a0 324 chargingStation: ChargingStation,
e7aeea18 325 commandName: OCPP16RequestCommand,
9429aa42 326 payload: ResType,
66a7748d 327 requestPayload: ReqType
e7aeea18 328 ): Promise<void> {
66a7748d 329 if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
65554cc3 330 if (
66a7748d
JB
331 this.responseHandlers.has(commandName) &&
332 OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
65554cc3 333 ) {
124f3553 334 try {
66a7748d
JB
335 this.validatePayload(chargingStation, commandName, payload)
336 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
337 await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload)
124f3553 338 } catch (error) {
6c8f5d90
JB
339 logger.error(
340 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
66a7748d
JB
341 error
342 )
343 throw error
124f3553
JB
344 }
345 } else {
346 // Throw exception
e7aeea18
JB
347 throw new OCPPError(
348 ErrorType.NOT_IMPLEMENTED,
6c8f5d90 349 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
e7aeea18 350 payload,
4ed03b6e 351 undefined,
66a7748d 352 2
e7aeea18 353 )}`,
7369e417 354 commandName,
66a7748d
JB
355 payload
356 )
887fef76 357 }
c0560973 358 } else {
e7aeea18
JB
359 throw new OCPPError(
360 ErrorType.SECURITY_ERROR,
6c8f5d90 361 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
e7aeea18 362 payload,
4ed03b6e 363 undefined,
66a7748d 364 2
439fc71b 365 )} while the charging station is not registered on the central server.`,
7369e417 366 commandName,
66a7748d
JB
367 payload
368 )
c0560973
JB
369 }
370 }
371
66a7748d 372 private validatePayload (
9c5c4195
JB
373 chargingStation: ChargingStation,
374 commandName: OCPP16RequestCommand,
66a7748d 375 payload: JsonType
9c5c4195 376 ): boolean {
66a7748d 377 if (this.jsonSchemas.has(commandName)) {
9c5c4195
JB
378 return this.validateResponsePayload(
379 chargingStation,
380 commandName,
66a7748d 381 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 382 this.jsonSchemas.get(commandName)!,
66a7748d
JB
383 payload
384 )
9c5c4195
JB
385 }
386 logger.warn(
66a7748d
JB
387 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
388 )
389 return false
9c5c4195
JB
390 }
391
66a7748d 392 private handleResponseBootNotification (
08f130a0 393 chargingStation: ChargingStation,
66a7748d 394 payload: OCPP16BootNotificationResponse
08f130a0 395 ): void {
d270cc87 396 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
f2d5e3d9 397 addConfigurationKey(
17ac262c 398 chargingStation,
f0f65a62 399 OCPP16StandardParametersKey.HeartbeatInterval,
a95873d8 400 payload.interval.toString(),
abe9e9dd 401 {},
66a7748d
JB
402 { overwrite: true, save: true }
403 )
f2d5e3d9 404 addConfigurationKey(
17ac262c 405 chargingStation,
f0f65a62 406 OCPP16StandardParametersKey.HeartBeatInterval,
e7aeea18 407 payload.interval.toString(),
00db15b8 408 { visible: false },
66a7748d
JB
409 { overwrite: true, save: true }
410 )
411 OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
672fed6e 412 }
d270cc87 413 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
08f130a0 414 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
e7aeea18 415 payload.status
66a7748d 416 }' state on the central server`
d270cc87 417 payload.status === RegistrationStatusEnumType.REJECTED
e7aeea18 418 ? logger.warn(logMsg)
66a7748d 419 : logger.info(logMsg)
c0560973 420 } else {
e7aeea18 421 logger.error(
44eb6026 422 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
66a7748d
JB
423 payload
424 )
c0560973
JB
425 }
426 }
427
66a7748d 428 private handleResponseAuthorize (
08f130a0 429 chargingStation: ChargingStation,
e7aeea18 430 payload: OCPP16AuthorizeResponse,
66a7748d 431 requestPayload: OCPP16AuthorizeRequest
e7aeea18 432 ): void {
66a7748d 433 let authorizeConnectorId: number | undefined
ded57f02
JB
434 if (chargingStation.hasEvses) {
435 for (const [evseId, evseStatus] of chargingStation.evses) {
436 if (evseId > 0) {
437 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 438 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
66a7748d
JB
439 authorizeConnectorId = connectorId
440 break
ded57f02
JB
441 }
442 }
443 }
444 }
445 } else {
446 for (const connectorId of chargingStation.connectors.keys()) {
447 if (
448 connectorId > 0 &&
449 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
450 ) {
66a7748d
JB
451 authorizeConnectorId = connectorId
452 break
ded57f02 453 }
58144adb
JB
454 }
455 }
f938317f
JB
456 if (authorizeConnectorId != null) {
457 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
458 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
459 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
460 authorizeConnectorStatus.idTagAuthorized = true
461 logger.debug(
462 `${chargingStation.logPrefix()} idTag '${
463 requestPayload.idTag
464 }' accepted on connector id ${authorizeConnectorId}`
465 )
466 } else {
467 authorizeConnectorStatus.idTagAuthorized = false
468 delete authorizeConnectorStatus.authorizeIdTag
469 logger.debug(
470 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
471 payload.idTagInfo.status
472 }`
473 )
d984c13f 474 }
58144adb 475 } else {
f938317f
JB
476 logger.error(
477 `${chargingStation.logPrefix()} idTag '${
478 requestPayload.idTag
479 }' has no authorize request pending`
66a7748d 480 )
58144adb
JB
481 }
482 }
483
66a7748d 484 private async handleResponseStartTransaction (
08f130a0 485 chargingStation: ChargingStation,
e7aeea18 486 payload: OCPP16StartTransactionResponse,
66a7748d 487 requestPayload: OCPP16StartTransactionRequest
e7aeea18 488 ): Promise<void> {
66a7748d
JB
489 const { connectorId } = requestPayload
490 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
e7aeea18 491 logger.error(
66a7748d
JB
492 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
493 )
494 return
c0560973 495 }
66a7748d 496 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
e7aeea18 497 if (
d929adcc 498 connectorStatus?.transactionRemoteStarted === true &&
66a7748d
JB
499 chargingStation.getAuthorizeRemoteTxRequests() &&
500 chargingStation.getLocalAuthListEnabled() &&
501 chargingStation.hasIdTags() &&
5199f9fd 502 connectorStatus.idTagLocalAuthorized === false
e7aeea18
JB
503 ) {
504 logger.error(
5199f9fd
JB
505 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
506 connectorStatus.localAuthorizeIdTag
507 } on connector id ${connectorId}`
66a7748d
JB
508 )
509 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
510 return
a2653482 511 }
e7aeea18 512 if (
d929adcc 513 connectorStatus?.transactionRemoteStarted === true &&
66a7748d 514 chargingStation.getAuthorizeRemoteTxRequests() &&
5398cecf 515 chargingStation.stationInfo?.remoteAuthorization === true &&
5199f9fd
JB
516 connectorStatus.idTagLocalAuthorized === false &&
517 connectorStatus.idTagAuthorized === false
e7aeea18
JB
518 ) {
519 logger.error(
5199f9fd
JB
520 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
521 connectorStatus.authorizeIdTag
522 } on connector id ${connectorId}`
66a7748d
JB
523 )
524 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
525 return
a2653482 526 }
e7aeea18 527 if (
66a7748d 528 connectorStatus?.idTagAuthorized === true &&
5199f9fd 529 connectorStatus.authorizeIdTag !== requestPayload.idTag
e7aeea18
JB
530 ) {
531 logger.error(
44eb6026
JB
532 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
533 requestPayload.idTag
5199f9fd
JB
534 } different from the authorize request one ${
535 connectorStatus.authorizeIdTag
536 } on connector id ${connectorId}`
66a7748d
JB
537 )
538 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
539 return
a2653482 540 }
e7aeea18 541 if (
66a7748d 542 connectorStatus?.idTagLocalAuthorized === true &&
5199f9fd 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
5199f9fd
JB
548 } different from the local authorized one ${
549 connectorStatus.localAuthorizeIdTag
550 } on connector id ${connectorId}`
66a7748d
JB
551 )
552 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
553 return
163547b1 554 }
d929adcc 555 if (connectorStatus?.transactionStarted === true) {
649287f8 556 logger.error(
5199f9fd
JB
557 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
558 connectorStatus.transactionIdTag
559 }`
66a7748d
JB
560 )
561 return
c0560973 562 }
649287f8
JB
563 if (chargingStation.hasEvses) {
564 for (const [evseId, evseStatus] of chargingStation.evses) {
565 if (evseStatus.connectors.size > 1) {
d929adcc 566 for (const [id, status] of evseStatus.connectors) {
5199f9fd 567 if (id !== connectorId && status.transactionStarted === true) {
649287f8 568 logger.error(
5199f9fd
JB
569 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
570 status.transactionIdTag
571 }`
66a7748d
JB
572 )
573 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
574 return
649287f8
JB
575 }
576 }
577 }
578 }
579 }
e7aeea18 580 if (
d929adcc
JB
581 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
582 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
e7aeea18
JB
583 ) {
584 logger.error(
66a7748d
JB
585 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
586 )
587 return
290d006c 588 }
d1504492 589 if (!Number.isSafeInteger(payload.transactionId)) {
54ebb82c 590 logger.warn(
d929adcc 591 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
54ebb82c 592 payload.transactionId
66a7748d
JB
593 }, converting to integer`
594 )
595 payload.transactionId = convertToInt(payload.transactionId)
54ebb82c 596 }
c0560973 597
5199f9fd 598 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d
JB
599 connectorStatus.transactionStarted = true
600 connectorStatus.transactionStart = requestPayload.timestamp
601 connectorStatus.transactionId = payload.transactionId
602 connectorStatus.transactionIdTag = requestPayload.idTag
603 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
d929adcc 604 connectorStatus.transactionBeginMeterValue =
e7aeea18 605 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
08f130a0 606 chargingStation,
d929adcc 607 connectorId,
66a7748d
JB
608 requestPayload.meterStart
609 )
610 if (requestPayload.reservationId != null) {
90aceaf6
JB
611 const reservation = chargingStation.getReservationBy(
612 'reservationId',
66a7748d 613 requestPayload.reservationId
a095d7d7
JB
614 )
615 if (reservation != null) {
616 if (reservation.idTag !== requestPayload.idTag) {
617 logger.warn(
618 `${chargingStation.logPrefix()} Reserved transaction ${
619 payload.transactionId
620 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
621 reservation.idTag
622 }`
623 )
624 }
625 if (hasReservationExpired(reservation)) {
626 logger.warn(
627 `${chargingStation.logPrefix()} Reserved transaction ${
628 payload.transactionId
629 } started with expired reservation ${
630 requestPayload.reservationId
631 } (expiry date: ${reservation.expiryDate.toISOString()}))`
632 )
633 }
634 await chargingStation.removeReservation(
635 reservation,
636 ReservationTerminationReason.TRANSACTION_STARTED
66a7748d 637 )
a095d7d7 638 } else {
90aceaf6 639 logger.warn(
56563a3c 640 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6 641 payload.transactionId
a095d7d7 642 } started with unknown reservation ${requestPayload.reservationId}`
66a7748d 643 )
90aceaf6 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
a223d9be
JB
685 } REJECTED on ${
686 chargingStation.stationInfo?.chargingStationId
687 }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
56563a3c
JB
688 requestPayload.idTag
689 }'${
d929adcc 690 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
56563a3c
JB
691 ? `, reservationId '${requestPayload.reservationId}'`
692 : ''
66a7748d
JB
693 }`
694 )
695 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
a2653482
JB
696 }
697 }
698
66a7748d 699 private async resetConnectorOnStartTransactionError (
08f130a0 700 chargingStation: ChargingStation,
66a7748d 701 connectorId: number
08f130a0 702 ): Promise<void> {
66a7748d 703 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f938317f 704 resetConnectorStatus(connectorStatus)
66a7748d 705 chargingStation.stopMeterValues(connectorId)
d929adcc 706 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
707 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
708 chargingStation,
ef6fa3fb 709 connectorId,
66a7748d
JB
710 OCPP16ChargePointStatus.Available
711 )
c0560973
JB
712 }
713 }
714
66a7748d 715 private async handleResponseStopTransaction (
08f130a0 716 chargingStation: ChargingStation,
e7aeea18 717 payload: OCPP16StopTransactionResponse,
66a7748d 718 requestPayload: OCPP16StopTransactionRequest
e7aeea18 719 ): Promise<void> {
08f130a0 720 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
66a7748d
JB
721 requestPayload.transactionId
722 )
aa63c9b7 723 if (transactionConnectorId == null) {
e7aeea18 724 logger.error(
d929adcc
JB
725 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
726 requestPayload.transactionId
66a7748d
JB
727 }`
728 )
729 return
c0560973 730 }
5398cecf 731 chargingStation.stationInfo?.beginEndMeterValues === true &&
5199f9fd
JB
732 chargingStation.stationInfo.ocppStrictCompliance === false &&
733 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
2cace1a5 734 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
735 OCPP16MeterValuesRequest,
736 OCPP16MeterValuesResponse
2cace1a5
JB
737 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
738 connectorId: transactionConnectorId,
739 transactionId: requestPayload.transactionId,
740 meterValue: [
741 OCPP16ServiceUtils.buildTransactionEndMeterValue(
742 chargingStation,
aa63c9b7 743 transactionConnectorId,
66a7748d
JB
744 requestPayload.meterStop
745 )
746 ]
747 }))
2cace1a5 748 if (
66a7748d 749 !chargingStation.isChargingStationAvailable() ||
aa63c9b7 750 !chargingStation.isConnectorAvailable(transactionConnectorId)
2cace1a5 751 ) {
4ecff7ce
JB
752 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
753 chargingStation,
aa63c9b7 754 transactionConnectorId,
66a7748d
JB
755 OCPP16ChargePointStatus.Unavailable
756 )
c0560973 757 } else {
4ecff7ce
JB
758 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
759 chargingStation,
aa63c9b7 760 transactionConnectorId,
66a7748d
JB
761 OCPP16ChargePointStatus.Available
762 )
2cace1a5 763 }
5199f9fd
JB
764 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
765 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
766 chargingStation.powerDivider!--
2cace1a5 767 }
f938317f 768 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
aa63c9b7 769 chargingStation.stopMeterValues(transactionConnectorId)
d929adcc
JB
770 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
771 requestPayload.transactionId
a223d9be
JB
772 } STOPPED on ${
773 chargingStation.stationInfo?.chargingStationId
774 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
2cace1a5 775 if (
aa63c9b7 776 payload.idTagInfo == null ||
5199f9fd 777 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
2cace1a5 778 ) {
66a7748d 779 logger.info(logMsg)
2cace1a5 780 } else {
66a7748d 781 logger.warn(logMsg)
c0560973
JB
782 }
783 }
c0560973 784}