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