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