fix: fix reservation removal at transaction start
[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!)!.idTagAuthorized = true;
476 chargingStation.getConnectorStatus(authorizeConnectorId!)!.authorizeIdTag =
477 requestPayload.idTag;
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.isInteger(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 const reservation = chargingStation.getReservationBy(
650 'connectorId',
651 reservedOnConnectorZero ? 0 : transactionConnectorId,
652 )!;
653 payload.reservationId = reservation.reservationId;
654 await chargingStation.removeReservation(
655 reservation,
656 ReservationTerminationReason.TRANSACTION_STARTED,
657 );
658 }
659 chargingStation.getBeginEndMeterValues() &&
660 (await chargingStation.ocppRequestService.requestHandler<
661 OCPP16MeterValuesRequest,
662 OCPP16MeterValuesResponse
663 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
664 connectorId: transactionConnectorId,
665 transactionId: payload.transactionId,
666 meterValue: [
667 chargingStation.getConnectorStatus(transactionConnectorId)!.transactionBeginMeterValue,
668 ],
669 } as OCPP16MeterValuesRequest));
670 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
671 chargingStation,
672 transactionConnectorId,
673 OCPP16ChargePointStatus.Charging,
674 );
675 logger.info(
676 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId.toString()} STARTED on ${
677 chargingStation.stationInfo.chargingStationId
678 }#${transactionConnectorId.toString()} for idTag '${requestPayload.idTag}'`,
679 );
680 if (chargingStation.stationInfo.powerSharedByConnectors) {
681 ++chargingStation.powerDivider;
682 }
683 const configuredMeterValueSampleInterval = getConfigurationKey(
684 chargingStation,
685 OCPP16StandardParametersKey.MeterValueSampleInterval,
686 );
687 chargingStation.startMeterValues(
688 transactionConnectorId,
689 configuredMeterValueSampleInterval
690 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
691 : Constants.DEFAULT_METER_VALUES_INTERVAL,
692 );
693 } else {
694 logger.warn(
695 `${chargingStation.logPrefix()} Starting transaction with id ${payload.transactionId.toString()} REJECTED with status '${payload
696 .idTagInfo?.status}', idTag '${requestPayload.idTag}'`,
697 );
698 await this.resetConnectorOnStartTransactionError(chargingStation, transactionConnectorId);
699 }
700 }
701
702 private async resetConnectorOnStartTransactionError(
703 chargingStation: ChargingStation,
704 connectorId: number,
705 ): Promise<void> {
706 resetConnectorStatus(chargingStation.getConnectorStatus(connectorId)!);
707 chargingStation.stopMeterValues(connectorId);
708 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
709 if (
710 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
711 ) {
712 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
713 chargingStation,
714 connectorId,
715 OCPP16ChargePointStatus.Available,
716 );
717 }
718 }
719
720 private async handleResponseStopTransaction(
721 chargingStation: ChargingStation,
722 payload: OCPP16StopTransactionResponse,
723 requestPayload: OCPP16StopTransactionRequest,
724 ): Promise<void> {
725 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
726 requestPayload.transactionId,
727 );
728 if (isNullOrUndefined(transactionConnectorId)) {
729 logger.error(
730 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${requestPayload.transactionId.toString()}`,
731 );
732 return;
733 }
734 chargingStation.getBeginEndMeterValues() === true &&
735 chargingStation.getOcppStrictCompliance() === false &&
736 chargingStation.getOutOfOrderEndMeterValues() === true &&
737 (await chargingStation.ocppRequestService.requestHandler<
738 OCPP16MeterValuesRequest,
739 OCPP16MeterValuesResponse
740 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
741 connectorId: transactionConnectorId,
742 transactionId: requestPayload.transactionId,
743 meterValue: [
744 OCPP16ServiceUtils.buildTransactionEndMeterValue(
745 chargingStation,
746 transactionConnectorId!,
747 requestPayload.meterStop,
748 ),
749 ],
750 }));
751 if (
752 chargingStation.isChargingStationAvailable() === false ||
753 chargingStation.isConnectorAvailable(transactionConnectorId!) === false
754 ) {
755 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
756 chargingStation,
757 transactionConnectorId!,
758 OCPP16ChargePointStatus.Unavailable,
759 );
760 } else {
761 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
762 chargingStation,
763 transactionConnectorId!,
764 OCPP16ChargePointStatus.Available,
765 );
766 }
767 if (chargingStation.stationInfo.powerSharedByConnectors) {
768 chargingStation.powerDivider--;
769 }
770 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId!)!);
771 chargingStation.stopMeterValues(transactionConnectorId!);
772 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
773 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${requestPayload.transactionId.toString()} STOPPED on ${
774 chargingStation.stationInfo.chargingStationId
775 }#${transactionConnectorId?.toString()} with status '${
776 payload.idTagInfo?.status ?? 'undefined'
777 }'`;
778 if (
779 isNullOrUndefined(payload.idTagInfo) ||
780 payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED
781 ) {
782 logger.info(logMsg);
783 } else {
784 logger.warn(logMsg);
785 }
786 }
787 }