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