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