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