Fixes to OCA OCPP 2.0.1 JSON schemas
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
CommitLineData
edd13439 1// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
c8eeb62b 2
8114d10e
JB
3import fs from 'fs';
4import path from 'path';
5import { URL, fileURLToPath } from 'url';
6
6c1761d4 7import type { JSONSchemaType } from 'ajv';
27782dbc 8import { Client, type FTPResponse } from 'basic-ftp';
8114d10e
JB
9import tar from 'tar';
10
11import OCPPError from '../../../exception/OCPPError';
6c1761d4 12import type { JsonObject, JsonType } from '../../../types/JsonType';
8114d10e
JB
13import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
14import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
15import {
16 ChargingProfilePurposeType,
27782dbc 17 type OCPP16ChargingProfile,
8114d10e
JB
18} from '../../../types/ocpp/1.6/ChargingProfile';
19import {
20 OCPP16StandardParametersKey,
21 OCPP16SupportedFeatureProfiles,
22} from '../../../types/ocpp/1.6/Configuration';
23import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
e7aeea18 24import {
27782dbc
JB
25 type ChangeAvailabilityRequest,
26 type ChangeConfigurationRequest,
27 type ClearChargingProfileRequest,
28 type DiagnosticsStatusNotificationRequest,
29 type GetConfigurationRequest,
30 type GetDiagnosticsRequest,
e7aeea18 31 OCPP16AvailabilityType,
27782dbc
JB
32 type OCPP16BootNotificationRequest,
33 type OCPP16ClearCacheRequest,
34 type OCPP16DataTransferRequest,
77b95a89 35 OCPP16DataTransferVendorId,
27782dbc 36 type OCPP16HeartbeatRequest,
e7aeea18 37 OCPP16IncomingRequestCommand,
c60ed4b8 38 OCPP16MessageTrigger,
94a464f9 39 OCPP16RequestCommand,
27782dbc
JB
40 type OCPP16StatusNotificationRequest,
41 type OCPP16TriggerMessageRequest,
42 type OCPP16UpdateFirmwareRequest,
43 type RemoteStartTransactionRequest,
44 type RemoteStopTransactionRequest,
45 type ResetRequest,
46 type SetChargingProfileRequest,
47 type UnlockConnectorRequest,
e7aeea18 48} from '../../../types/ocpp/1.6/Requests';
77b95a89 49import {
27782dbc
JB
50 type ChangeAvailabilityResponse,
51 type ChangeConfigurationResponse,
52 type ClearChargingProfileResponse,
53 type DiagnosticsStatusNotificationResponse,
54 type GetConfigurationResponse,
55 type GetDiagnosticsResponse,
56 type OCPP16BootNotificationResponse,
57 type OCPP16DataTransferResponse,
77b95a89 58 OCPP16DataTransferStatus,
27782dbc
JB
59 type OCPP16HeartbeatResponse,
60 type OCPP16StatusNotificationResponse,
61 type OCPP16TriggerMessageResponse,
62 type OCPP16UpdateFirmwareResponse,
63 type SetChargingProfileResponse,
64 type UnlockConnectorResponse,
e7aeea18 65} from '../../../types/ocpp/1.6/Responses';
e7aeea18
JB
66import {
67 OCPP16AuthorizationStatus,
27782dbc
JB
68 type OCPP16AuthorizeRequest,
69 type OCPP16AuthorizeResponse,
70 type OCPP16StartTransactionRequest,
71 type OCPP16StartTransactionResponse,
e7aeea18
JB
72 OCPP16StopTransactionReason,
73} from '../../../types/ocpp/1.6/Transaction';
6c1761d4 74import type { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
8114d10e 75import { ErrorType } from '../../../types/ocpp/ErrorType';
d270cc87 76import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
6c1761d4
JB
77import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
78import type { DefaultResponse } from '../../../types/ocpp/Responses';
8114d10e
JB
79import Constants from '../../../utils/Constants';
80import logger from '../../../utils/Logger';
81import Utils from '../../../utils/Utils';
73b9adec 82import type ChargingStation from '../../ChargingStation';
17ac262c 83import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils';
9d7484a4 84import { ChargingStationUtils } from '../../ChargingStationUtils';
bf53cadf 85import OCPPConstants from '../OCPPConstants';
c0560973 86import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
8114d10e 87import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
c0560973 88
2a115f87 89const moduleName = 'OCPP16IncomingRequestService';
909dcf2d 90
c0560973 91export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
58144adb 92 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
b52c969d 93 private jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
58144adb 94
08f130a0 95 public constructor() {
909dcf2d 96 if (new.target?.name === moduleName) {
06127450 97 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
9f2e3130 98 }
d270cc87 99 super(OCPPVersion.VERSION_16);
58144adb
JB
100 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
101 [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)],
102 [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
103 [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)],
e7aeea18
JB
104 [
105 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
106 this.handleRequestGetConfiguration.bind(this),
107 ],
108 [
109 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
110 this.handleRequestChangeConfiguration.bind(this),
111 ],
112 [
113 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
114 this.handleRequestSetChargingProfile.bind(this),
115 ],
116 [
117 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
118 this.handleRequestClearChargingProfile.bind(this),
119 ],
120 [
121 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
122 this.handleRequestChangeAvailability.bind(this),
123 ],
124 [
125 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
126 this.handleRequestRemoteStartTransaction.bind(this),
127 ],
128 [
129 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
130 this.handleRequestRemoteStopTransaction.bind(this),
131 ],
734d790d 132 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
e7aeea18 133 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
77b95a89 134 [OCPP16IncomingRequestCommand.DATA_TRANSFER, this.handleRequestDataTransfer.bind(this)],
b03df580 135 // [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)],
58144adb 136 ]);
b52c969d
JB
137 this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
138 [
139 OCPP16IncomingRequestCommand.RESET,
140 JSON.parse(
141 fs.readFileSync(
142 path.resolve(
143 path.dirname(fileURLToPath(import.meta.url)),
144 '../../../assets/json-schemas/ocpp/1.6/Reset.json'
145 ),
146 'utf8'
147 )
148 ) as JSONSchemaType<ResetRequest>,
149 ],
150 [
151 OCPP16IncomingRequestCommand.CLEAR_CACHE,
152 JSON.parse(
153 fs.readFileSync(
154 path.resolve(
155 path.dirname(fileURLToPath(import.meta.url)),
156 '../../../assets/json-schemas/ocpp/1.6/ClearCache.json'
157 ),
158 'utf8'
159 )
160 ) as JSONSchemaType<OCPP16ClearCacheRequest>,
161 ],
162 [
163 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
164 JSON.parse(
165 fs.readFileSync(
166 path.resolve(
167 path.dirname(fileURLToPath(import.meta.url)),
168 '../../../assets/json-schemas/ocpp/1.6/UnlockConnector.json'
169 ),
170 'utf8'
171 )
172 ) as JSONSchemaType<UnlockConnectorRequest>,
173 ],
174 [
175 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
176 JSON.parse(
177 fs.readFileSync(
178 path.resolve(
179 path.dirname(fileURLToPath(import.meta.url)),
180 '../../../assets/json-schemas/ocpp/1.6/GetConfiguration.json'
181 ),
182 'utf8'
183 )
184 ) as JSONSchemaType<GetConfigurationRequest>,
185 ],
186 [
187 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
188 JSON.parse(
189 fs.readFileSync(
190 path.resolve(
191 path.dirname(fileURLToPath(import.meta.url)),
192 '../../../assets/json-schemas/ocpp/1.6/ChangeConfiguration.json'
193 ),
194 'utf8'
195 )
196 ) as JSONSchemaType<ChangeConfigurationRequest>,
197 ],
198 [
199 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
200 JSON.parse(
201 fs.readFileSync(
202 path.resolve(
203 path.dirname(fileURLToPath(import.meta.url)),
204 '../../../assets/json-schemas/ocpp/1.6/GetDiagnostics.json'
205 ),
206 'utf8'
207 )
208 ) as JSONSchemaType<GetDiagnosticsRequest>,
209 ],
210 [
211 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
212 JSON.parse(
213 fs.readFileSync(
214 path.resolve(
215 path.dirname(fileURLToPath(import.meta.url)),
216 '../../../assets/json-schemas/ocpp/1.6/SetChargingProfile.json'
217 ),
218 'utf8'
219 )
220 ) as JSONSchemaType<SetChargingProfileRequest>,
221 ],
222 [
223 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
224 JSON.parse(
225 fs.readFileSync(
226 path.resolve(
227 path.dirname(fileURLToPath(import.meta.url)),
228 '../../../assets/json-schemas/ocpp/1.6/ClearChargingProfile.json'
229 ),
230 'utf8'
231 )
232 ) as JSONSchemaType<ClearChargingProfileRequest>,
233 ],
234 [
235 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
236 JSON.parse(
237 fs.readFileSync(
238 path.resolve(
239 path.dirname(fileURLToPath(import.meta.url)),
240 '../../../assets/json-schemas/ocpp/1.6/ChangeAvailability.json'
241 ),
242 'utf8'
243 )
244 ) as JSONSchemaType<ChangeAvailabilityRequest>,
245 ],
246 [
247 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
248 JSON.parse(
249 fs.readFileSync(
250 path.resolve(
251 path.dirname(fileURLToPath(import.meta.url)),
252 '../../../assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json'
253 ),
254 'utf8'
255 )
256 ) as JSONSchemaType<RemoteStartTransactionRequest>,
257 ],
258 [
259 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
260 JSON.parse(
261 fs.readFileSync(
262 path.resolve(
263 path.dirname(fileURLToPath(import.meta.url)),
264 '../../../assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json'
265 ),
266 'utf8'
267 )
268 ) as JSONSchemaType<RemoteStopTransactionRequest>,
269 ],
270 [
271 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
272 JSON.parse(
273 fs.readFileSync(
274 path.resolve(
275 path.dirname(fileURLToPath(import.meta.url)),
276 '../../../assets/json-schemas/ocpp/1.6/TriggerMessage.json'
277 ),
278 'utf8'
279 )
280 ) as JSONSchemaType<OCPP16TriggerMessageRequest>,
281 ],
77b95a89
JB
282 [
283 OCPP16IncomingRequestCommand.DATA_TRANSFER,
284 JSON.parse(
285 fs.readFileSync(
286 path.resolve(
287 path.dirname(fileURLToPath(import.meta.url)),
288 '../../../assets/json-schemas/ocpp/1.6/DataTransfer.json'
289 ),
290 'utf8'
291 )
292 ) as JSONSchemaType<OCPP16DataTransferRequest>,
293 ],
b52c969d 294 ]);
9952c548 295 this.validatePayload.bind(this);
58144adb
JB
296 }
297
f7f98c68 298 public async incomingRequestHandler(
08f130a0 299 chargingStation: ChargingStation,
e7aeea18
JB
300 messageId: string,
301 commandName: OCPP16IncomingRequestCommand,
5cc4b63b 302 commandPayload: JsonType
e7aeea18 303 ): Promise<void> {
5cc4b63b 304 let response: JsonType;
e7aeea18 305 if (
3a13fc92
JB
306 chargingStation.getOcppStrictCompliance() === true &&
307 chargingStation.isInPendingState() === true &&
e7aeea18
JB
308 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
309 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
310 ) {
311 throw new OCPPError(
312 ErrorType.SECURITY_ERROR,
e3018bc4 313 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18
JB
314 commandPayload,
315 null,
316 2
317 )} while the charging station is in pending state on the central server`,
7369e417
JB
318 commandName,
319 commandPayload
e7aeea18 320 );
caad9d6b 321 }
e7aeea18 322 if (
ed6cfcff
JB
323 chargingStation.isRegistered() === true ||
324 (chargingStation.getOcppStrictCompliance() === false &&
325 chargingStation.isInUnknownState() === true)
e7aeea18 326 ) {
65554cc3 327 if (
ed6cfcff
JB
328 this.incomingRequestHandlers.has(commandName) === true &&
329 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
65554cc3 330 ) {
124f3553 331 try {
9c5c4195 332 this.validatePayload(chargingStation, commandName, commandPayload);
c75a6675 333 // Call the method to build the response
08f130a0
JB
334 response = await this.incomingRequestHandlers.get(commandName)(
335 chargingStation,
336 commandPayload
337 );
124f3553
JB
338 } catch (error) {
339 // Log
6c8f5d90
JB
340 logger.error(
341 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
342 error
343 );
124f3553
JB
344 throw error;
345 }
346 } else {
347 // Throw exception
e7aeea18
JB
348 throw new OCPPError(
349 ErrorType.NOT_IMPLEMENTED,
e3018bc4 350 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
e7aeea18
JB
351 commandPayload,
352 null,
353 2
354 )}`,
7369e417
JB
355 commandName,
356 commandPayload
e7aeea18 357 );
c0560973
JB
358 }
359 } else {
e7aeea18
JB
360 throw new OCPPError(
361 ErrorType.SECURITY_ERROR,
e3018bc4 362 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18
JB
363 commandPayload,
364 null,
365 2
366 )} while the charging station is not registered on the central server.`,
7369e417
JB
367 commandName,
368 commandPayload
e7aeea18 369 );
c0560973 370 }
c75a6675 371 // Send the built response
08f130a0
JB
372 await chargingStation.ocppRequestService.sendResponse(
373 chargingStation,
374 messageId,
375 response,
376 commandName
377 );
c0560973
JB
378 }
379
9c5c4195
JB
380 private validatePayload(
381 chargingStation: ChargingStation,
382 commandName: OCPP16IncomingRequestCommand,
383 commandPayload: JsonType
384 ): boolean {
45988780 385 if (this.jsonSchemas.has(commandName) === true) {
9c5c4195
JB
386 return this.validateIncomingRequestPayload(
387 chargingStation,
388 commandName,
389 this.jsonSchemas.get(commandName),
390 commandPayload
391 );
392 }
393 logger.warn(
ee307db5 394 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
9c5c4195
JB
395 );
396 return false;
397 }
398
c0560973 399 // Simulate charging station restart
08f130a0
JB
400 private handleRequestReset(
401 chargingStation: ChargingStation,
402 commandPayload: ResetRequest
403 ): DefaultResponse {
64818750
JB
404 this.asyncResource
405 .runInAsyncScope(
406 chargingStation.reset.bind(chargingStation) as (
407 this: ChargingStation,
408 ...args: any[]
409 ) => Promise<void>,
410 chargingStation,
411 (commandPayload.type + 'Reset') as OCPP16StopTransactionReason
412 )
413 .catch(() => {
414 /* This is intentional */
415 });
e7aeea18 416 logger.info(
08f130a0 417 `${chargingStation.logPrefix()} ${
e7aeea18
JB
418 commandPayload.type
419 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
08f130a0 420 chargingStation.stationInfo.resetTime
e7aeea18
JB
421 )}`
422 );
bf53cadf 423 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
c0560973
JB
424 }
425
a36bad10
JB
426 private handleRequestClearCache(chargingStation: ChargingStation): DefaultResponse {
427 chargingStation.authorizedTagsCache.deleteAuthorizedTags(
428 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
429 );
bf53cadf 430 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
c0560973
JB
431 }
432
e7aeea18 433 private async handleRequestUnlockConnector(
08f130a0 434 chargingStation: ChargingStation,
e7aeea18
JB
435 commandPayload: UnlockConnectorRequest
436 ): Promise<UnlockConnectorResponse> {
c0560973 437 const connectorId = commandPayload.connectorId;
c60ed4b8
JB
438 if (chargingStation.connectors.has(connectorId) === false) {
439 logger.error(
440 `${chargingStation.logPrefix()} Trying to unlock a non existing connector Id ${connectorId.toString()}`
441 );
bf53cadf 442 return OCPPConstants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c60ed4b8 443 }
c0560973 444 if (connectorId === 0) {
e7aeea18 445 logger.error(
c60ed4b8 446 chargingStation.logPrefix() + ' Trying to unlock connector Id ' + connectorId.toString()
e7aeea18 447 );
bf53cadf 448 return OCPPConstants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c0560973 449 }
5e3cb728
JB
450 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
451 const stopResponse = await chargingStation.stopTransactionOnConnector(
452 connectorId,
453 OCPP16StopTransactionReason.UNLOCK_COMMAND
454 );
c0560973 455 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
bf53cadf 456 return OCPPConstants.OCPP_RESPONSE_UNLOCKED;
c0560973 457 }
bf53cadf 458 return OCPPConstants.OCPP_RESPONSE_UNLOCK_FAILED;
c0560973 459 }
08f130a0 460 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
461 OCPP16StatusNotificationRequest,
462 OCPP16StatusNotificationResponse
08f130a0 463 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
464 connectorId,
465 status: OCPP16ChargePointStatus.AVAILABLE,
466 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
467 });
08f130a0 468 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
bf53cadf 469 return OCPPConstants.OCPP_RESPONSE_UNLOCKED;
c0560973
JB
470 }
471
e7aeea18 472 private handleRequestGetConfiguration(
08f130a0 473 chargingStation: ChargingStation,
e7aeea18
JB
474 commandPayload: GetConfigurationRequest
475 ): GetConfigurationResponse {
c0560973
JB
476 const configurationKey: OCPPConfigurationKey[] = [];
477 const unknownKey: string[] = [];
a723e7e9 478 if (Utils.isEmptyArray(commandPayload.key) === true) {
08f130a0 479 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
a723e7e9 480 if (Utils.isUndefined(configuration.visible) === true) {
7f7b65ca 481 configuration.visible = true;
c0560973 482 }
da8629bb 483 if (configuration.visible === false) {
c0560973
JB
484 continue;
485 }
486 configurationKey.push({
7f7b65ca
JB
487 key: configuration.key,
488 readonly: configuration.readonly,
489 value: configuration.value,
c0560973
JB
490 });
491 }
492 } else {
493 for (const key of commandPayload.key) {
17ac262c
JB
494 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
495 chargingStation,
496 key
497 );
c0560973 498 if (keyFound) {
a723e7e9 499 if (Utils.isUndefined(keyFound.visible) === true) {
c0560973
JB
500 keyFound.visible = true;
501 }
a723e7e9 502 if (keyFound.visible === false) {
c0560973
JB
503 continue;
504 }
505 configurationKey.push({
506 key: keyFound.key,
507 readonly: keyFound.readonly,
508 value: keyFound.value,
509 });
510 } else {
511 unknownKey.push(key);
512 }
513 }
514 }
515 return {
516 configurationKey,
517 unknownKey,
518 };
519 }
520
e7aeea18 521 private handleRequestChangeConfiguration(
08f130a0 522 chargingStation: ChargingStation,
e7aeea18
JB
523 commandPayload: ChangeConfigurationRequest
524 ): ChangeConfigurationResponse {
17ac262c
JB
525 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
526 chargingStation,
527 commandPayload.key,
528 true
529 );
c0560973 530 if (!keyToChange) {
bf53cadf 531 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
c0560973 532 } else if (keyToChange && keyToChange.readonly) {
bf53cadf 533 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
c0560973 534 } else if (keyToChange && !keyToChange.readonly) {
c0560973 535 let valueChanged = false;
a95873d8 536 if (keyToChange.value !== commandPayload.value) {
17ac262c
JB
537 ChargingStationConfigurationUtils.setConfigurationKeyValue(
538 chargingStation,
539 commandPayload.key,
540 commandPayload.value,
541 true
542 );
c0560973
JB
543 valueChanged = true;
544 }
545 let triggerHeartbeatRestart = false;
546 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
17ac262c
JB
547 ChargingStationConfigurationUtils.setConfigurationKeyValue(
548 chargingStation,
e7aeea18
JB
549 OCPP16StandardParametersKey.HeartbeatInterval,
550 commandPayload.value
551 );
c0560973
JB
552 triggerHeartbeatRestart = true;
553 }
554 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
555 ChargingStationConfigurationUtils.setConfigurationKeyValue(
556 chargingStation,
e7aeea18
JB
557 OCPP16StandardParametersKey.HeartBeatInterval,
558 commandPayload.value
559 );
c0560973
JB
560 triggerHeartbeatRestart = true;
561 }
562 if (triggerHeartbeatRestart) {
08f130a0 563 chargingStation.restartHeartbeat();
c0560973
JB
564 }
565 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
08f130a0 566 chargingStation.restartWebSocketPing();
c0560973
JB
567 }
568 if (keyToChange.reboot) {
bf53cadf 569 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
c0560973 570 }
bf53cadf 571 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
c0560973
JB
572 }
573 }
574
e7aeea18 575 private handleRequestSetChargingProfile(
08f130a0 576 chargingStation: ChargingStation,
e7aeea18
JB
577 commandPayload: SetChargingProfileRequest
578 ): SetChargingProfileResponse {
370ae4ee 579 if (
1789ba2c 580 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 581 chargingStation,
370ae4ee
JB
582 OCPP16SupportedFeatureProfiles.SmartCharging,
583 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
1789ba2c 584 ) === false
370ae4ee 585 ) {
bf53cadf 586 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
68cb8b91 587 }
1789ba2c 588 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 589 logger.error(
08f130a0 590 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
e7aeea18
JB
591 commandPayload.connectorId
592 }`
593 );
bf53cadf 594 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 595 }
e7aeea18
JB
596 if (
597 commandPayload.csChargingProfiles.chargingProfilePurpose ===
598 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
599 commandPayload.connectorId !== 0
600 ) {
bf53cadf 601 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 602 }
e7aeea18
JB
603 if (
604 commandPayload.csChargingProfiles.chargingProfilePurpose ===
605 ChargingProfilePurposeType.TX_PROFILE &&
606 (commandPayload.connectorId === 0 ||
5e3cb728
JB
607 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
608 false)
e7aeea18 609 ) {
db0af086
JB
610 logger.error(
611 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${
612 commandPayload.connectorId
613 } without a started transaction`
614 );
bf53cadf 615 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 616 }
ed3d2808 617 OCPP16ServiceUtils.setChargingProfile(
7cb5b17f 618 chargingStation,
e7aeea18
JB
619 commandPayload.connectorId,
620 commandPayload.csChargingProfiles
621 );
622 logger.debug(
08f130a0 623 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7
JB
624 commandPayload.connectorId
625 }, dump their stack: %j`,
08f130a0 626 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
e7aeea18 627 );
bf53cadf 628 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
629 }
630
e7aeea18 631 private handleRequestClearChargingProfile(
08f130a0 632 chargingStation: ChargingStation,
e7aeea18
JB
633 commandPayload: ClearChargingProfileRequest
634 ): ClearChargingProfileResponse {
370ae4ee 635 if (
a36bad10 636 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 637 chargingStation,
370ae4ee
JB
638 OCPP16SupportedFeatureProfiles.SmartCharging,
639 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
a36bad10 640 ) === false
370ae4ee 641 ) {
bf53cadf 642 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
68cb8b91 643 }
1789ba2c 644 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 645 logger.error(
08f130a0 646 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
e7aeea18
JB
647 commandPayload.connectorId
648 }`
649 );
bf53cadf 650 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973 651 }
1789ba2c 652 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
658e2d16
JB
653 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
654 connectorStatus.chargingProfiles = [];
e7aeea18 655 logger.debug(
08f130a0 656 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7
JB
657 commandPayload.connectorId
658 }, dump their stack: %j`,
658e2d16 659 connectorStatus.chargingProfiles
e7aeea18 660 );
bf53cadf 661 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
662 }
663 if (!commandPayload.connectorId) {
664 let clearedCP = false;
08f130a0
JB
665 for (const connectorId of chargingStation.connectors.keys()) {
666 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
667 chargingStation
e7aeea18
JB
668 .getConnectorStatus(connectorId)
669 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
670 let clearCurrentCP = false;
671 if (chargingProfile.chargingProfileId === commandPayload.id) {
672 clearCurrentCP = true;
673 }
674 if (
675 !commandPayload.chargingProfilePurpose &&
676 chargingProfile.stackLevel === commandPayload.stackLevel
677 ) {
678 clearCurrentCP = true;
679 }
680 if (
681 !chargingProfile.stackLevel &&
682 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
683 ) {
684 clearCurrentCP = true;
685 }
686 if (
687 chargingProfile.stackLevel === commandPayload.stackLevel &&
688 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
689 ) {
690 clearCurrentCP = true;
691 }
692 if (clearCurrentCP) {
ccb1d6e9 693 connectorStatus.chargingProfiles.splice(index, 1);
e7aeea18 694 logger.debug(
08f130a0 695 `${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
ad8537a7
JB
696 commandPayload.connectorId
697 }, dump their stack: %j`,
658e2d16 698 connectorStatus.chargingProfiles
e7aeea18
JB
699 );
700 clearedCP = true;
701 }
702 });
c0560973
JB
703 }
704 }
705 if (clearedCP) {
bf53cadf 706 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
707 }
708 }
bf53cadf 709 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973
JB
710 }
711
e7aeea18 712 private async handleRequestChangeAvailability(
08f130a0 713 chargingStation: ChargingStation,
e7aeea18
JB
714 commandPayload: ChangeAvailabilityRequest
715 ): Promise<ChangeAvailabilityResponse> {
c0560973 716 const connectorId: number = commandPayload.connectorId;
c60ed4b8 717 if (chargingStation.connectors.has(connectorId) === false) {
e7aeea18 718 logger.error(
08f130a0 719 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 720 );
bf53cadf 721 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973 722 }
e7aeea18
JB
723 const chargePointStatus: OCPP16ChargePointStatus =
724 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
725 ? OCPP16ChargePointStatus.AVAILABLE
726 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973 727 if (connectorId === 0) {
bf53cadf 728 let response: ChangeAvailabilityResponse = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 729 for (const id of chargingStation.connectors.keys()) {
5e3cb728 730 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
bf53cadf 731 response = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 732 }
08f130a0 733 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
bf53cadf 734 if (response === OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 735 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
736 OCPP16StatusNotificationRequest,
737 OCPP16StatusNotificationResponse
08f130a0 738 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
739 connectorId: id,
740 status: chargePointStatus,
741 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
742 });
08f130a0 743 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
744 }
745 }
746 return response;
e7aeea18
JB
747 } else if (
748 connectorId > 0 &&
56eb297e
JB
749 (chargingStation.isChargingStationAvailable() === true ||
750 (chargingStation.isChargingStationAvailable() === false &&
e7aeea18
JB
751 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
752 ) {
5e3cb728 753 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 754 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
bf53cadf 755 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 756 }
08f130a0
JB
757 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
758 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
759 OCPP16StatusNotificationRequest,
760 OCPP16StatusNotificationResponse
08f130a0 761 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
762 connectorId,
763 status: chargePointStatus,
764 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
765 });
08f130a0 766 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
bf53cadf 767 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 768 }
bf53cadf 769 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
770 }
771
e7aeea18 772 private async handleRequestRemoteStartTransaction(
08f130a0 773 chargingStation: ChargingStation,
e7aeea18
JB
774 commandPayload: RemoteStartTransactionRequest
775 ): Promise<DefaultResponse> {
658e2d16 776 const transactionConnectorId = commandPayload.connectorId;
1789ba2c 777 if (chargingStation.connectors.has(transactionConnectorId) === true) {
91a4f151
JB
778 const remoteStartTransactionLogMsg =
779 chargingStation.logPrefix() +
780 ' Transaction remotely STARTED on ' +
781 chargingStation.stationInfo.chargingStationId +
782 '#' +
783 transactionConnectorId.toString() +
784 " for idTag '" +
785 commandPayload.idTag +
786 "'";
08f130a0 787 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
788 OCPP16StatusNotificationRequest,
789 OCPP16StatusNotificationResponse
08f130a0 790 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
791 connectorId: transactionConnectorId,
792 status: OCPP16ChargePointStatus.PREPARING,
793 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
794 });
1789ba2c 795 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
658e2d16 796 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
1789ba2c 797 if (chargingStation.isChargingStationAvailable() === true) {
e060fe58 798 // Check if authorized
1789ba2c 799 if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
a7fc8211 800 let authorized = false;
e7aeea18 801 if (
1789ba2c
JB
802 chargingStation.getLocalAuthListEnabled() === true &&
803 chargingStation.hasAuthorizedTags() === true &&
9d7484a4
JB
804 chargingStation.authorizedTagsCache
805 .getAuthorizedTags(
806 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
807 )
1789ba2c 808 .find((idTag) => idTag === commandPayload.idTag)
e7aeea18 809 ) {
658e2d16
JB
810 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
811 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 812 authorized = true;
1789ba2c 813 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
658e2d16 814 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 815 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 816 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
817 OCPP16AuthorizeRequest,
818 OCPP16AuthorizeResponse
08f130a0 819 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
820 idTag: commandPayload.idTag,
821 });
a7fc8211
JB
822 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
823 authorized = true;
a7fc8211 824 }
71068fb9 825 } else {
e7aeea18 826 logger.warn(
08f130a0 827 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 828 );
a7fc8211 829 }
1789ba2c 830 if (authorized === true) {
a7fc8211 831 // Authorization successful, start transaction
e7aeea18
JB
832 if (
833 this.setRemoteStartTransactionChargingProfile(
08f130a0 834 chargingStation,
e7aeea18
JB
835 transactionConnectorId,
836 commandPayload.chargingProfile
1789ba2c 837 ) === true
e7aeea18 838 ) {
658e2d16 839 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
840 if (
841 (
08f130a0 842 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
843 OCPP16StartTransactionRequest,
844 OCPP16StartTransactionResponse
08f130a0 845 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
846 connectorId: transactionConnectorId,
847 idTag: commandPayload.idTag,
848 })
e7aeea18
JB
849 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
850 ) {
91a4f151 851 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 852 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 853 }
e7aeea18 854 return this.notifyRemoteStartTransactionRejected(
08f130a0 855 chargingStation,
e7aeea18
JB
856 transactionConnectorId,
857 commandPayload.idTag
858 );
e060fe58 859 }
e7aeea18 860 return this.notifyRemoteStartTransactionRejected(
08f130a0 861 chargingStation,
e7aeea18
JB
862 transactionConnectorId,
863 commandPayload.idTag
864 );
a7fc8211 865 }
e7aeea18 866 return this.notifyRemoteStartTransactionRejected(
08f130a0 867 chargingStation,
e7aeea18
JB
868 transactionConnectorId,
869 commandPayload.idTag
870 );
36f6a92e 871 }
a7fc8211 872 // No authorization check required, start transaction
e7aeea18
JB
873 if (
874 this.setRemoteStartTransactionChargingProfile(
08f130a0 875 chargingStation,
e7aeea18
JB
876 transactionConnectorId,
877 commandPayload.chargingProfile
1789ba2c 878 ) === true
e7aeea18 879 ) {
658e2d16 880 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
881 if (
882 (
08f130a0 883 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
884 OCPP16StartTransactionRequest,
885 OCPP16StartTransactionResponse
08f130a0 886 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
887 connectorId: transactionConnectorId,
888 idTag: commandPayload.idTag,
889 })
e7aeea18
JB
890 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
891 ) {
91a4f151 892 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 893 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 894 }
e7aeea18 895 return this.notifyRemoteStartTransactionRejected(
08f130a0 896 chargingStation,
e7aeea18
JB
897 transactionConnectorId,
898 commandPayload.idTag
899 );
e060fe58 900 }
e7aeea18 901 return this.notifyRemoteStartTransactionRejected(
08f130a0 902 chargingStation,
e7aeea18
JB
903 transactionConnectorId,
904 commandPayload.idTag
905 );
c0560973 906 }
e7aeea18 907 return this.notifyRemoteStartTransactionRejected(
08f130a0 908 chargingStation,
e7aeea18
JB
909 transactionConnectorId,
910 commandPayload.idTag
911 );
c0560973 912 }
08f130a0
JB
913 return this.notifyRemoteStartTransactionRejected(
914 chargingStation,
915 transactionConnectorId,
916 commandPayload.idTag
917 );
a7fc8211
JB
918 }
919
e7aeea18 920 private async notifyRemoteStartTransactionRejected(
08f130a0 921 chargingStation: ChargingStation,
e7aeea18
JB
922 connectorId: number,
923 idTag: string
924 ): Promise<DefaultResponse> {
925 if (
08f130a0 926 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
e7aeea18 927 ) {
08f130a0 928 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
929 OCPP16StatusNotificationRequest,
930 OCPP16StatusNotificationResponse
08f130a0 931 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
932 connectorId,
933 status: OCPP16ChargePointStatus.AVAILABLE,
934 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
935 });
08f130a0 936 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 937 }
e7aeea18 938 logger.warn(
08f130a0 939 chargingStation.logPrefix() +
e7aeea18
JB
940 ' Remote starting transaction REJECTED on connector Id ' +
941 connectorId.toString() +
91a4f151 942 ", idTag '" +
e7aeea18 943 idTag +
91a4f151 944 "', availability '" +
08f130a0 945 chargingStation.getConnectorStatus(connectorId).availability +
91a4f151
JB
946 "', status '" +
947 chargingStation.getConnectorStatus(connectorId).status +
948 "'"
e7aeea18 949 );
bf53cadf 950 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
951 }
952
e7aeea18 953 private setRemoteStartTransactionChargingProfile(
08f130a0 954 chargingStation: ChargingStation,
e7aeea18
JB
955 connectorId: number,
956 cp: OCPP16ChargingProfile
957 ): boolean {
a7fc8211 958 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
ed3d2808 959 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
e7aeea18 960 logger.debug(
08f130a0
JB
961 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
962 chargingStation.getConnectorStatus(connectorId).chargingProfiles
e7aeea18 963 );
a7fc8211
JB
964 return true;
965 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 966 logger.warn(
08f130a0 967 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
968 cp.chargingProfilePurpose
969 } charging profile(s) at remote start transaction`
970 );
a7fc8211 971 return false;
e060fe58
JB
972 } else if (!cp) {
973 return true;
a7fc8211
JB
974 }
975 }
976
e7aeea18 977 private async handleRequestRemoteStopTransaction(
08f130a0 978 chargingStation: ChargingStation,
e7aeea18
JB
979 commandPayload: RemoteStopTransactionRequest
980 ): Promise<DefaultResponse> {
c0560973 981 const transactionId = commandPayload.transactionId;
08f130a0 982 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
983 if (
984 connectorId > 0 &&
08f130a0 985 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 986 ) {
08f130a0 987 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
988 OCPP16StatusNotificationRequest,
989 OCPP16StatusNotificationResponse
08f130a0 990 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
991 connectorId,
992 status: OCPP16ChargePointStatus.FINISHING,
993 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
994 });
08f130a0 995 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
5e3cb728
JB
996 const stopResponse = await chargingStation.stopTransactionOnConnector(
997 connectorId,
a65319ba 998 OCPP16StopTransactionReason.REMOTE
5e3cb728
JB
999 );
1000 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
bf53cadf 1001 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
ef6fa3fb 1002 }
bf53cadf 1003 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
1004 }
1005 }
44b9b577 1006 logger.warn(
08f130a0 1007 chargingStation.logPrefix() +
e7aeea18
JB
1008 ' Trying to remote stop a non existing transaction ' +
1009 transactionId.toString()
1010 );
bf53cadf 1011 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973 1012 }
47e22477 1013
b03df580
JB
1014 private handleRequestUpdateFirmware(
1015 chargingStation: ChargingStation,
1016 commandPayload: OCPP16UpdateFirmwareRequest
1017 ): OCPP16UpdateFirmwareResponse {
1018 if (
1019 OCPP16ServiceUtils.checkFeatureProfile(
1020 chargingStation,
1021 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1022 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1023 ) === false
1024 ) {
bf53cadf 1025 return OCPPConstants.OCPP_RESPONSE_EMPTY;
b03df580
JB
1026 }
1027 logger.debug(
1028 chargingStation.logPrefix() +
1029 ' ' +
1030 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE +
1031 ' request received: %j',
1032 commandPayload
1033 );
1034 }
1035
e7aeea18 1036 private async handleRequestGetDiagnostics(
08f130a0 1037 chargingStation: ChargingStation,
e7aeea18
JB
1038 commandPayload: GetDiagnosticsRequest
1039 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1040 if (
1789ba2c 1041 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1042 chargingStation,
370ae4ee
JB
1043 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1044 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1045 ) === false
68cb8b91 1046 ) {
bf53cadf 1047 return OCPPConstants.OCPP_RESPONSE_EMPTY;
68cb8b91 1048 }
e7aeea18 1049 logger.debug(
08f130a0 1050 chargingStation.logPrefix() +
e7aeea18
JB
1051 ' ' +
1052 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS +
1053 ' request received: %j',
1054 commandPayload
1055 );
a3868ec4 1056 const uri = new URL(commandPayload.location);
47e22477
JB
1057 if (uri.protocol.startsWith('ftp:')) {
1058 let ftpClient: Client;
1059 try {
e7aeea18 1060 const logFiles = fs
0d8140bd 1061 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
1062 .filter((file) => file.endsWith('.log'))
1063 .map((file) => path.join('./', file));
08f130a0 1064 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
1065 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1066 ftpClient = new Client();
1067 const accessResponse = await ftpClient.access({
1068 host: uri.host,
e8191622
JB
1069 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1070 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1071 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1072 });
1073 let uploadResponse: FTPResponse;
1074 if (accessResponse.code === 220) {
1075 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1076 ftpClient.trackProgress(async (info) => {
e7aeea18 1077 logger.info(
08f130a0 1078 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1079 info.bytes / 1024
1080 } bytes transferred from diagnostics archive ${info.name}`
1081 );
08f130a0 1082 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1083 DiagnosticsStatusNotificationRequest,
1084 DiagnosticsStatusNotificationResponse
08f130a0 1085 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1086 status: OCPP16DiagnosticsStatus.Uploading,
1087 });
47e22477 1088 });
e7aeea18 1089 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1090 path.join(
1091 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1092 diagnosticsArchive
1093 ),
e7aeea18
JB
1094 uri.pathname + diagnosticsArchive
1095 );
47e22477 1096 if (uploadResponse.code === 226) {
08f130a0 1097 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1098 DiagnosticsStatusNotificationRequest,
1099 DiagnosticsStatusNotificationResponse
08f130a0 1100 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1101 status: OCPP16DiagnosticsStatus.Uploaded,
1102 });
47e22477
JB
1103 if (ftpClient) {
1104 ftpClient.close();
1105 }
1106 return { fileName: diagnosticsArchive };
1107 }
e7aeea18
JB
1108 throw new OCPPError(
1109 ErrorType.GENERIC_ERROR,
1110 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1111 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1112 }`,
1113 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1114 );
47e22477 1115 }
e7aeea18
JB
1116 throw new OCPPError(
1117 ErrorType.GENERIC_ERROR,
1118 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1119 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1120 }`,
1121 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1122 );
47e22477 1123 } catch (error) {
08f130a0 1124 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1125 DiagnosticsStatusNotificationRequest,
1126 DiagnosticsStatusNotificationResponse
08f130a0 1127 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1128 status: OCPP16DiagnosticsStatus.UploadFailed,
1129 });
47e22477
JB
1130 if (ftpClient) {
1131 ftpClient.close();
1132 }
e7aeea18 1133 return this.handleIncomingRequestError(
08f130a0 1134 chargingStation,
e7aeea18
JB
1135 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1136 error as Error,
bf53cadf 1137 { errorResponse: OCPPConstants.OCPP_RESPONSE_EMPTY }
e7aeea18 1138 );
47e22477
JB
1139 }
1140 } else {
e7aeea18 1141 logger.error(
08f130a0 1142 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1143 uri.protocol
1144 } to transfer the diagnostic logs archive`
1145 );
08f130a0 1146 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1147 DiagnosticsStatusNotificationRequest,
1148 DiagnosticsStatusNotificationResponse
08f130a0 1149 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1150 status: OCPP16DiagnosticsStatus.UploadFailed,
1151 });
bf53cadf 1152 return OCPPConstants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1153 }
1154 }
802cfa13 1155
e7aeea18 1156 private handleRequestTriggerMessage(
08f130a0 1157 chargingStation: ChargingStation,
e7aeea18
JB
1158 commandPayload: OCPP16TriggerMessageRequest
1159 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1160 if (
1161 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1162 chargingStation,
370ae4ee
JB
1163 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1164 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1165 ) ||
1166 !OCPP16ServiceUtils.isMessageTriggerSupported(
1167 chargingStation,
1168 commandPayload.requestedMessage
370ae4ee
JB
1169 )
1170 ) {
bf53cadf 1171 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1172 }
c60ed4b8 1173 if (
4caa7e67 1174 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1175 chargingStation,
1176 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1177 commandPayload.connectorId
1178 )
1179 ) {
bf53cadf 1180 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1181 }
802cfa13
JB
1182 try {
1183 switch (commandPayload.requestedMessage) {
c60ed4b8 1184 case OCPP16MessageTrigger.BootNotification:
802cfa13 1185 setTimeout(() => {
08f130a0 1186 chargingStation.ocppRequestService
f7f98c68 1187 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1188 chargingStation,
6a8b180d 1189 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1190 chargingStation.bootNotificationRequest,
6a8b180d 1191 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1192 )
8bfbc743
JB
1193 .then((response) => {
1194 chargingStation.bootNotificationResponse = response;
ae711c83 1195 })
e7aeea18
JB
1196 .catch(() => {
1197 /* This is intentional */
1198 });
802cfa13 1199 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1200 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1201 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1202 setTimeout(() => {
08f130a0 1203 chargingStation.ocppRequestService
f7f98c68 1204 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1205 chargingStation,
ef6fa3fb
JB
1206 OCPP16RequestCommand.HEARTBEAT,
1207 null,
1208 {
1209 triggerMessage: true,
1210 }
1211 )
e7aeea18
JB
1212 .catch(() => {
1213 /* This is intentional */
1214 });
802cfa13 1215 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1216 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1217 case OCPP16MessageTrigger.StatusNotification:
dc661702
JB
1218 setTimeout(() => {
1219 if (commandPayload?.connectorId) {
08f130a0 1220 chargingStation.ocppRequestService
dc661702 1221 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1222 chargingStation,
dc661702
JB
1223 OCPP16RequestCommand.STATUS_NOTIFICATION,
1224 {
1225 connectorId: commandPayload.connectorId,
1226 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1227 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1228 },
1229 {
1230 triggerMessage: true,
1231 }
1232 )
1233 .catch(() => {
1234 /* This is intentional */
1235 });
1236 } else {
08f130a0
JB
1237 for (const connectorId of chargingStation.connectors.keys()) {
1238 chargingStation.ocppRequestService
dc661702
JB
1239 .requestHandler<
1240 OCPP16StatusNotificationRequest,
1241 OCPP16StatusNotificationResponse
1242 >(
08f130a0 1243 chargingStation,
dc661702
JB
1244 OCPP16RequestCommand.STATUS_NOTIFICATION,
1245 {
1246 connectorId,
1247 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1248 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1249 },
1250 {
1251 triggerMessage: true,
1252 }
1253 )
1254 .catch(() => {
1255 /* This is intentional */
1256 });
1257 }
1258 }
1259 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1260 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1261 default:
bf53cadf 1262 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1263 }
1264 } catch (error) {
e7aeea18 1265 return this.handleIncomingRequestError(
08f130a0 1266 chargingStation,
e7aeea18
JB
1267 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1268 error as Error,
bf53cadf 1269 { errorResponse: OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1270 );
802cfa13
JB
1271 }
1272 }
77b95a89
JB
1273
1274 private handleRequestDataTransfer(
1275 chargingStation: ChargingStation,
1276 commandPayload: OCPP16DataTransferRequest
1277 ): OCPP16DataTransferResponse {
1278 try {
1279 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1280 return {
1281 status: OCPP16DataTransferStatus.ACCEPTED,
1282 };
1283 }
1284 return {
1285 status: OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID,
1286 };
1287 } catch (error) {
1288 return this.handleIncomingRequestError(
1289 chargingStation,
1290 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1291 error as Error,
bf53cadf 1292 { errorResponse: OCPPConstants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1293 );
1294 }
1295 }
c0560973 1296}