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