Ensure a transaction is always stopped, whatever is the CSMS response.
[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() +
347 ' Charging station boot notification response received: %j with undefined registration status',
348 payload
349 );
350 }
351 }
352
353 private handleResponseAuthorize(
354 chargingStation: ChargingStation,
355 payload: OCPP16AuthorizeResponse,
356 requestPayload: OCPP16AuthorizeRequest
357 ): void {
358 let authorizeConnectorId: number;
359 for (const connectorId of chargingStation.connectors.keys()) {
360 if (
361 connectorId > 0 &&
362 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
363 ) {
364 authorizeConnectorId = connectorId;
365 break;
366 }
367 }
368 const isAuthorizeConnectorIdDefined = authorizeConnectorId !== undefined;
369 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
370 isAuthorizeConnectorIdDefined &&
371 (chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true);
372 logger.debug(
373 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' accepted${
374 isAuthorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}` : ''
375 }`
376 );
377 } else {
378 if (isAuthorizeConnectorIdDefined) {
379 chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false;
380 delete chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag;
381 }
382 logger.debug(
383 `${chargingStation.logPrefix()} IdTag '${requestPayload.idTag}' rejected with status '${
384 payload.idTagInfo.status
385 }'${isAuthorizeConnectorIdDefined ? ` on connector ${authorizeConnectorId}` : ''}`
386 );
387 }
388 }
389
390 private async handleResponseStartTransaction(
391 chargingStation: ChargingStation,
392 payload: OCPP16StartTransactionResponse,
393 requestPayload: OCPP16StartTransactionRequest
394 ): Promise<void> {
395 const connectorId = requestPayload.connectorId;
396
397 let transactionConnectorId: number;
398 for (const id of chargingStation.connectors.keys()) {
399 if (id > 0 && id === connectorId) {
400 transactionConnectorId = id;
401 break;
402 }
403 }
404 if (!transactionConnectorId) {
405 logger.error(
406 chargingStation.logPrefix() +
407 ' Trying to start a transaction on a non existing connector Id ' +
408 connectorId.toString()
409 );
410 return;
411 }
412 if (
413 chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
414 chargingStation.getAuthorizeRemoteTxRequests() === true &&
415 chargingStation.getLocalAuthListEnabled() === true &&
416 chargingStation.hasAuthorizedTags() &&
417 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false
418 ) {
419 logger.error(
420 chargingStation.logPrefix() +
421 ' Trying to start a transaction with a not local authorized idTag ' +
422 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
423 ' on connector Id ' +
424 connectorId.toString()
425 );
426 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
427 return;
428 }
429 if (
430 chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted === true &&
431 chargingStation.getAuthorizeRemoteTxRequests() === true &&
432 chargingStation.getMustAuthorizeAtRemoteStart() === true &&
433 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized === false &&
434 chargingStation.getConnectorStatus(connectorId).idTagAuthorized === false
435 ) {
436 logger.error(
437 chargingStation.logPrefix() +
438 ' Trying to start a transaction with a not authorized idTag ' +
439 chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
440 ' on connector Id ' +
441 connectorId.toString()
442 );
443 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
444 return;
445 }
446 if (
447 chargingStation.getConnectorStatus(connectorId).idTagAuthorized &&
448 chargingStation.getConnectorStatus(connectorId).authorizeIdTag !== requestPayload.idTag
449 ) {
450 logger.error(
451 chargingStation.logPrefix() +
452 ' Trying to start a transaction with an idTag ' +
453 requestPayload.idTag +
454 ' different from the authorize request one ' +
455 chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
456 ' on connector Id ' +
457 connectorId.toString()
458 );
459 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
460 return;
461 }
462 if (
463 chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
464 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag !== requestPayload.idTag
465 ) {
466 logger.error(
467 chargingStation.logPrefix() +
468 ' Trying to start a transaction with an idTag ' +
469 requestPayload.idTag +
470 ' different from the local authorized one ' +
471 chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
472 ' on connector Id ' +
473 connectorId.toString()
474 );
475 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
476 return;
477 }
478 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
479 logger.debug(
480 chargingStation.logPrefix() +
481 ' Trying to start a transaction on an already used connector ' +
482 connectorId.toString() +
483 ': %j',
484 chargingStation.getConnectorStatus(connectorId)
485 );
486 return;
487 }
488 if (
489 chargingStation.getConnectorStatus(connectorId)?.status !==
490 OCPP16ChargePointStatus.AVAILABLE &&
491 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.PREPARING
492 ) {
493 logger.error(
494 `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${
495 chargingStation.getConnectorStatus(connectorId)?.status
496 }`
497 );
498 return;
499 }
500 // if (!Number.isInteger(payload.transactionId)) {
501 // logger.warn(
502 // `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
503 // payload.transactionId
504 // }, converting to integer`
505 // );
506 // payload.transactionId = Utils.convertToInt(payload.transactionId);
507 // }
508
509 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
510 chargingStation.getConnectorStatus(connectorId).transactionStarted = true;
511 chargingStation.getConnectorStatus(connectorId).transactionId = payload.transactionId;
512 chargingStation.getConnectorStatus(connectorId).transactionIdTag = requestPayload.idTag;
513 chargingStation.getConnectorStatus(
514 connectorId
515 ).transactionEnergyActiveImportRegisterValue = 0;
516 chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue =
517 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
518 chargingStation,
519 connectorId,
520 requestPayload.meterStart
521 );
522 chargingStation.getBeginEndMeterValues() &&
523 (await chargingStation.ocppRequestService.requestHandler<
524 OCPP16MeterValuesRequest,
525 OCPP16MeterValuesResponse
526 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
527 connectorId,
528 transactionId: payload.transactionId,
529 meterValue: [chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue],
530 }));
531 await chargingStation.ocppRequestService.requestHandler<
532 OCPP16StatusNotificationRequest,
533 OCPP16StatusNotificationResponse
534 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
535 connectorId,
536 status: OCPP16ChargePointStatus.CHARGING,
537 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
538 });
539 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.CHARGING;
540 logger.info(
541 chargingStation.logPrefix() +
542 ' Transaction ' +
543 payload.transactionId.toString() +
544 ' STARTED on ' +
545 chargingStation.stationInfo.chargingStationId +
546 '#' +
547 connectorId.toString() +
548 " for idTag '" +
549 requestPayload.idTag +
550 "'"
551 );
552 if (chargingStation.stationInfo.powerSharedByConnectors) {
553 chargingStation.powerDivider++;
554 }
555 const configuredMeterValueSampleInterval =
556 ChargingStationConfigurationUtils.getConfigurationKey(
557 chargingStation,
558 OCPP16StandardParametersKey.MeterValueSampleInterval
559 );
560 chargingStation.startMeterValues(
561 connectorId,
562 configuredMeterValueSampleInterval
563 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
564 : Constants.DEFAULT_METER_VALUES_INTERVAL
565 );
566 } else {
567 logger.warn(
568 chargingStation.logPrefix() +
569 ' Starting transaction id ' +
570 payload.transactionId.toString() +
571 " REJECTED with status '" +
572 payload?.idTagInfo?.status +
573 "', idTag '" +
574 requestPayload.idTag +
575 "'"
576 );
577 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
578 }
579 }
580
581 private async resetConnectorOnStartTransactionError(
582 chargingStation: ChargingStation,
583 connectorId: number
584 ): Promise<void> {
585 chargingStation.resetConnectorStatus(connectorId);
586 if (
587 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
588 ) {
589 await chargingStation.ocppRequestService.requestHandler<
590 OCPP16StatusNotificationRequest,
591 OCPP16StatusNotificationResponse
592 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
593 connectorId,
594 status: OCPP16ChargePointStatus.AVAILABLE,
595 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
596 });
597 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
598 }
599 }
600
601 private async handleResponseStopTransaction(
602 chargingStation: ChargingStation,
603 payload: OCPP16StopTransactionResponse,
604 requestPayload: OCPP16StopTransactionRequest
605 ): Promise<void> {
606 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
607 requestPayload.transactionId
608 );
609 if (!transactionConnectorId) {
610 logger.error(
611 chargingStation.logPrefix() +
612 ' Trying to stop a non existing transaction ' +
613 requestPayload.transactionId.toString()
614 );
615 return;
616 }
617 chargingStation.getBeginEndMeterValues() === true &&
618 chargingStation.getOcppStrictCompliance() === false &&
619 chargingStation.getOutOfOrderEndMeterValues() === true &&
620 (await chargingStation.ocppRequestService.requestHandler<
621 OCPP16MeterValuesRequest,
622 OCPP16MeterValuesResponse
623 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
624 connectorId: transactionConnectorId,
625 transactionId: requestPayload.transactionId,
626 meterValue: [
627 OCPP16ServiceUtils.buildTransactionEndMeterValue(
628 chargingStation,
629 transactionConnectorId,
630 requestPayload.meterStop
631 ),
632 ],
633 }));
634 if (
635 chargingStation.isChargingStationAvailable() === false ||
636 chargingStation.isConnectorAvailable(transactionConnectorId) === false
637 ) {
638 await chargingStation.ocppRequestService.requestHandler<
639 OCPP16StatusNotificationRequest,
640 OCPP16StatusNotificationResponse
641 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
642 connectorId: transactionConnectorId,
643 status: OCPP16ChargePointStatus.UNAVAILABLE,
644 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
645 });
646 chargingStation.getConnectorStatus(transactionConnectorId).status =
647 OCPP16ChargePointStatus.UNAVAILABLE;
648 } else {
649 await chargingStation.ocppRequestService.requestHandler<
650 OCPP16BootNotificationRequest,
651 OCPP16BootNotificationResponse
652 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
653 connectorId: transactionConnectorId,
654 status: OCPP16ChargePointStatus.AVAILABLE,
655 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
656 });
657 chargingStation.getConnectorStatus(transactionConnectorId).status =
658 OCPP16ChargePointStatus.AVAILABLE;
659 }
660 if (chargingStation.stationInfo.powerSharedByConnectors) {
661 chargingStation.powerDivider--;
662 }
663 chargingStation.resetConnectorStatus(transactionConnectorId);
664 const logMsg =
665 chargingStation.logPrefix() +
666 ' Transaction ' +
667 requestPayload.transactionId.toString() +
668 ' STOPPED on ' +
669 chargingStation.stationInfo.chargingStationId +
670 '#' +
671 transactionConnectorId.toString() +
672 " with status '" +
673 payload.idTagInfo?.status ?? 'undefined' + "'";
674 if (
675 Utils.isNullOrUndefined(payload.idTagInfo) ||
676 payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED
677 ) {
678 logger.info(logMsg);
679 } else {
680 logger.warn(logMsg);
681 }
682 }
683
684 private parseJsonSchemaFile<T extends JsonType>(relativePath: string): JSONSchemaType<T> {
685 return JSON.parse(
686 fs.readFileSync(
687 path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
688 'utf8'
689 )
690 ) as JSONSchemaType<T>;
691 }
692 }