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