refactor(simulator): cleanup JSON type usage in OCPP payload validation
[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 hasReservationExpired,
14 resetConnectorStatus,
15 } from '../../../charging-station';
16 import { OCPPError } from '../../../exception';
17 import {
18 type ChangeConfigurationResponse,
19 type ClearChargingProfileResponse,
20 ErrorType,
21 type GenericResponse,
22 type GetConfigurationResponse,
23 type GetDiagnosticsResponse,
24 type JsonType,
25 OCPP16AuthorizationStatus,
26 type OCPP16AuthorizeRequest,
27 type OCPP16AuthorizeResponse,
28 type OCPP16BootNotificationResponse,
29 type OCPP16ChangeAvailabilityResponse,
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<JsonType>
71 >;
72
73 private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
74 private jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonType>>;
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<JsonType>>([
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<OCPP16ChangeAvailabilityResponse>(
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 undefined,
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 undefined,
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 authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId!);
473 const authorizeConnectorIdDefined = !isNullOrUndefined(authorizeConnectorId);
474 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
475 if (authorizeConnectorIdDefined) {
476 // authorizeConnectorStatus!.authorizeIdTag = requestPayload.idTag;
477 authorizeConnectorStatus!.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 authorizeConnectorStatus!.idTagAuthorized = false;
487 delete authorizeConnectorStatus?.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 { connectorId } = requestPayload;
503 if (connectorId === 0 || chargingStation.hasConnector(connectorId) === false) {
504 logger.error(
505 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`,
506 );
507 return;
508 }
509 const connectorStatus = chargingStation.getConnectorStatus(connectorId);
510 if (
511 connectorStatus?.transactionRemoteStarted === true &&
512 chargingStation.getAuthorizeRemoteTxRequests() === true &&
513 chargingStation.getLocalAuthListEnabled() === true &&
514 chargingStation.hasIdTags() === true &&
515 connectorStatus?.idTagLocalAuthorized === false
516 ) {
517 logger.error(
518 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`,
519 );
520 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
521 return;
522 }
523 if (
524 connectorStatus?.transactionRemoteStarted === true &&
525 chargingStation.getAuthorizeRemoteTxRequests() === true &&
526 chargingStation.getRemoteAuthorization() === true &&
527 connectorStatus?.idTagLocalAuthorized === false &&
528 connectorStatus?.idTagAuthorized === false
529 ) {
530 logger.error(
531 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`,
532 );
533 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
534 return;
535 }
536 if (
537 connectorStatus?.idTagAuthorized &&
538 connectorStatus?.authorizeIdTag !== requestPayload.idTag
539 ) {
540 logger.error(
541 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
542 requestPayload.idTag
543 } different from the authorize request one ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`,
544 );
545 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
546 return;
547 }
548 if (
549 connectorStatus?.idTagLocalAuthorized &&
550 connectorStatus?.localAuthorizeIdTag !== requestPayload.idTag
551 ) {
552 logger.error(
553 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
554 requestPayload.idTag
555 } different from the local authorized one ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`,
556 );
557 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
558 return;
559 }
560 if (connectorStatus?.transactionStarted === true) {
561 logger.error(
562 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${connectorStatus?.transactionIdTag}}`,
563 );
564 return;
565 }
566 if (chargingStation.hasEvses) {
567 for (const [evseId, evseStatus] of chargingStation.evses) {
568 if (evseStatus.connectors.size > 1) {
569 for (const [id, status] of evseStatus.connectors) {
570 if (id !== connectorId && status?.transactionStarted === true) {
571 logger.error(
572 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${status?.transactionIdTag}}`,
573 );
574 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
575 return;
576 }
577 }
578 }
579 }
580 }
581 if (
582 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
583 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
584 ) {
585 logger.error(
586 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`,
587 );
588 return;
589 }
590 if (!Number.isSafeInteger(payload.transactionId)) {
591 logger.warn(
592 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
593 payload.transactionId
594 }, converting to integer`,
595 );
596 payload.transactionId = convertToInt(payload.transactionId);
597 }
598
599 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
600 connectorStatus.transactionStarted = true;
601 connectorStatus.transactionStart = requestPayload.timestamp;
602 connectorStatus.transactionId = payload.transactionId;
603 connectorStatus.transactionIdTag = requestPayload.idTag;
604 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
605 connectorStatus.transactionBeginMeterValue =
606 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
607 chargingStation,
608 connectorId,
609 requestPayload.meterStart,
610 );
611 if (requestPayload.reservationId) {
612 const reservation = chargingStation.getReservationBy(
613 'reservationId',
614 requestPayload.reservationId,
615 )!;
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,
637 );
638 }
639 chargingStation.getBeginEndMeterValues() &&
640 (await chargingStation.ocppRequestService.requestHandler<
641 OCPP16MeterValuesRequest,
642 OCPP16MeterValuesResponse
643 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
644 connectorId,
645 transactionId: payload.transactionId,
646 meterValue: [connectorStatus.transactionBeginMeterValue],
647 } as OCPP16MeterValuesRequest));
648 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
649 chargingStation,
650 connectorId,
651 OCPP16ChargePointStatus.Charging,
652 );
653 logger.info(
654 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
655 chargingStation.stationInfo.chargingStationId
656 }#${connectorId} for idTag '${requestPayload.idTag}'`,
657 );
658 if (chargingStation.stationInfo.powerSharedByConnectors) {
659 ++chargingStation.powerDivider;
660 }
661 const configuredMeterValueSampleInterval = getConfigurationKey(
662 chargingStation,
663 OCPP16StandardParametersKey.MeterValueSampleInterval,
664 );
665 chargingStation.startMeterValues(
666 connectorId,
667 configuredMeterValueSampleInterval
668 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
669 : Constants.DEFAULT_METER_VALUES_INTERVAL,
670 );
671 } else {
672 logger.warn(
673 `${chargingStation.logPrefix()} Starting transaction with id ${
674 payload.transactionId
675 } REJECTED on ${
676 chargingStation.stationInfo.chargingStationId
677 }#${connectorId} with status '${payload.idTagInfo?.status}', idTag '${
678 requestPayload.idTag
679 }'${
680 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
681 ? `, reservationId '${requestPayload.reservationId}'`
682 : ''
683 }`,
684 );
685 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
686 }
687 }
688
689 private async resetConnectorOnStartTransactionError(
690 chargingStation: ChargingStation,
691 connectorId: number,
692 ): Promise<void> {
693 const connectorStatus = chargingStation.getConnectorStatus(connectorId);
694 resetConnectorStatus(connectorStatus!);
695 chargingStation.stopMeterValues(connectorId);
696 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
697 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
698 chargingStation,
699 connectorId,
700 OCPP16ChargePointStatus.Available,
701 );
702 }
703 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
704 }
705
706 private async handleResponseStopTransaction(
707 chargingStation: ChargingStation,
708 payload: OCPP16StopTransactionResponse,
709 requestPayload: OCPP16StopTransactionRequest,
710 ): Promise<void> {
711 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
712 requestPayload.transactionId,
713 );
714 if (isNullOrUndefined(transactionConnectorId)) {
715 logger.error(
716 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
717 requestPayload.transactionId
718 }`,
719 );
720 return;
721 }
722 chargingStation.getBeginEndMeterValues() === true &&
723 chargingStation.getOcppStrictCompliance() === false &&
724 chargingStation.getOutOfOrderEndMeterValues() === true &&
725 (await chargingStation.ocppRequestService.requestHandler<
726 OCPP16MeterValuesRequest,
727 OCPP16MeterValuesResponse
728 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
729 connectorId: transactionConnectorId,
730 transactionId: requestPayload.transactionId,
731 meterValue: [
732 OCPP16ServiceUtils.buildTransactionEndMeterValue(
733 chargingStation,
734 transactionConnectorId!,
735 requestPayload.meterStop,
736 ),
737 ],
738 }));
739 if (
740 chargingStation.isChargingStationAvailable() === false ||
741 chargingStation.isConnectorAvailable(transactionConnectorId!) === false
742 ) {
743 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
744 chargingStation,
745 transactionConnectorId!,
746 OCPP16ChargePointStatus.Unavailable,
747 );
748 } else {
749 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
750 chargingStation,
751 transactionConnectorId!,
752 OCPP16ChargePointStatus.Available,
753 );
754 }
755 if (chargingStation.stationInfo.powerSharedByConnectors) {
756 chargingStation.powerDivider--;
757 }
758 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId!)!);
759 chargingStation.stopMeterValues(transactionConnectorId!);
760 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
761 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
762 requestPayload.transactionId
763 } STOPPED on ${
764 chargingStation.stationInfo.chargingStationId
765 }#${transactionConnectorId} with status '${payload.idTagInfo?.status ?? 'undefined'}'`;
766 if (
767 isNullOrUndefined(payload.idTagInfo) ||
768 payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED
769 ) {
770 logger.info(logMsg);
771 } else {
772 logger.warn(logMsg);
773 }
774 }
775 }