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