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