Merge pull request #634 from SAP/fix-limitation-from-profiles
[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)!.transactionStart =
623 requestPayload.timestamp;
624 chargingStation.getConnectorStatus(transactionConnectorId)!.transactionId =
625 payload.transactionId;
626 chargingStation.getConnectorStatus(transactionConnectorId)!.transactionIdTag =
627 requestPayload.idTag;
628 chargingStation.getConnectorStatus(
629 transactionConnectorId,
630 )!.transactionEnergyActiveImportRegisterValue = 0;
631 chargingStation.getConnectorStatus(transactionConnectorId)!.transactionBeginMeterValue =
632 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
633 chargingStation,
634 transactionConnectorId,
635 requestPayload.meterStart,
636 );
637 chargingStation.getBeginEndMeterValues() &&
638 (await chargingStation.ocppRequestService.requestHandler<
639 OCPP16MeterValuesRequest,
640 OCPP16MeterValuesResponse
641 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
642 connectorId: transactionConnectorId,
643 transactionId: payload.transactionId,
644 meterValue: [
645 chargingStation.getConnectorStatus(transactionConnectorId)!.transactionBeginMeterValue,
646 ],
647 } as OCPP16MeterValuesRequest));
648 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
649 chargingStation,
650 transactionConnectorId,
651 OCPP16ChargePointStatus.Charging,
652 );
653 logger.info(
654 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId.toString()} STARTED on ${
655 chargingStation.stationInfo.chargingStationId
656 }#${transactionConnectorId.toString()} for idTag '${requestPayload.idTag}'`,
657 );
658 if (chargingStation.stationInfo.powerSharedByConnectors) {
659 ++chargingStation.powerDivider;
660 }
661 const configuredMeterValueSampleInterval = getConfigurationKey(
662 chargingStation,
663 OCPP16StandardParametersKey.MeterValueSampleInterval,
664 );
665 chargingStation.startMeterValues(
666 transactionConnectorId,
667 configuredMeterValueSampleInterval
668 ? convertToInt(configuredMeterValueSampleInterval.value) * 1000
669 : Constants.DEFAULT_METER_VALUES_INTERVAL,
670 );
671 } else {
672 logger.warn(
673 `${chargingStation.logPrefix()} Starting transaction with id ${payload.transactionId.toString()} REJECTED with status '${payload
674 .idTagInfo?.status}', idTag '${requestPayload.idTag}'`,
675 );
676 await this.resetConnectorOnStartTransactionError(chargingStation, transactionConnectorId);
677 }
678 }
679
680 private async resetConnectorOnStartTransactionError(
681 chargingStation: ChargingStation,
682 connectorId: number,
683 ): Promise<void> {
684 resetConnectorStatus(chargingStation.getConnectorStatus(connectorId)!);
685 chargingStation.stopMeterValues(connectorId);
686 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
687 if (
688 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
689 ) {
690 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
691 chargingStation,
692 connectorId,
693 OCPP16ChargePointStatus.Available,
694 );
695 }
696 }
697
698 private async handleResponseStopTransaction(
699 chargingStation: ChargingStation,
700 payload: OCPP16StopTransactionResponse,
701 requestPayload: OCPP16StopTransactionRequest,
702 ): Promise<void> {
703 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
704 requestPayload.transactionId,
705 );
706 if (isNullOrUndefined(transactionConnectorId)) {
707 logger.error(
708 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${requestPayload.transactionId.toString()}`,
709 );
710 return;
711 }
712 chargingStation.getBeginEndMeterValues() === true &&
713 chargingStation.getOcppStrictCompliance() === false &&
714 chargingStation.getOutOfOrderEndMeterValues() === true &&
715 (await chargingStation.ocppRequestService.requestHandler<
716 OCPP16MeterValuesRequest,
717 OCPP16MeterValuesResponse
718 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
719 connectorId: transactionConnectorId,
720 transactionId: requestPayload.transactionId,
721 meterValue: [
722 OCPP16ServiceUtils.buildTransactionEndMeterValue(
723 chargingStation,
724 transactionConnectorId!,
725 requestPayload.meterStop,
726 ),
727 ],
728 }));
729 if (
730 chargingStation.isChargingStationAvailable() === false ||
731 chargingStation.isConnectorAvailable(transactionConnectorId!) === false
732 ) {
733 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
734 chargingStation,
735 transactionConnectorId!,
736 OCPP16ChargePointStatus.Unavailable,
737 );
738 } else {
739 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
740 chargingStation,
741 transactionConnectorId!,
742 OCPP16ChargePointStatus.Available,
743 );
744 }
745 if (chargingStation.stationInfo.powerSharedByConnectors) {
746 chargingStation.powerDivider--;
747 }
748 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId!)!);
749 chargingStation.stopMeterValues(transactionConnectorId!);
750 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
751 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${requestPayload.transactionId.toString()} STOPPED on ${
752 chargingStation.stationInfo.chargingStationId
753 }#${transactionConnectorId?.toString()} with status '${
754 payload.idTagInfo?.status ?? 'undefined'
755 }'`;
756 if (
757 isNullOrUndefined(payload.idTagInfo) ||
758 payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED
759 ) {
760 logger.info(logMsg);
761 } else {
762 logger.warn(logMsg);
763 }
764 }
765 }