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