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