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