Cleanup shared helpers structure
[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 (
08f130a0
JB
284 chargingStation.getOcppStrictCompliance() &&
285 chargingStation.isInPendingState() &&
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 (
08f130a0
JB
301 chargingStation.isRegistered() ||
302 (!chargingStation.getOcppStrictCompliance() && chargingStation.isInUnknownState())
e7aeea18 303 ) {
65554cc3
JB
304 if (
305 this.incomingRequestHandlers.has(commandName) &&
ed3d2808 306 OCPP16ServiceUtils.isIncomingRequestCommandSupported(commandName, chargingStation)
65554cc3 307 ) {
124f3553 308 try {
9c5c4195 309 this.validatePayload(chargingStation, commandName, commandPayload);
c75a6675 310 // Call the method to build the response
08f130a0
JB
311 response = await this.incomingRequestHandlers.get(commandName)(
312 chargingStation,
313 commandPayload
314 );
124f3553
JB
315 } catch (error) {
316 // Log
6c8f5d90
JB
317 logger.error(
318 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
319 error
320 );
124f3553
JB
321 throw error;
322 }
323 } else {
324 // Throw exception
e7aeea18
JB
325 throw new OCPPError(
326 ErrorType.NOT_IMPLEMENTED,
e3018bc4 327 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
e7aeea18
JB
328 commandPayload,
329 null,
330 2
331 )}`,
7369e417
JB
332 commandName,
333 commandPayload
e7aeea18 334 );
c0560973
JB
335 }
336 } else {
e7aeea18
JB
337 throw new OCPPError(
338 ErrorType.SECURITY_ERROR,
e3018bc4 339 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18
JB
340 commandPayload,
341 null,
342 2
343 )} while the charging station is not registered on the central server.`,
7369e417
JB
344 commandName,
345 commandPayload
e7aeea18 346 );
c0560973 347 }
c75a6675 348 // Send the built response
08f130a0
JB
349 await chargingStation.ocppRequestService.sendResponse(
350 chargingStation,
351 messageId,
352 response,
353 commandName
354 );
c0560973
JB
355 }
356
9c5c4195
JB
357 private validatePayload(
358 chargingStation: ChargingStation,
359 commandName: OCPP16IncomingRequestCommand,
360 commandPayload: JsonType
361 ): boolean {
362 if (this.jsonSchemas.has(commandName)) {
363 return this.validateIncomingRequestPayload(
364 chargingStation,
365 commandName,
366 this.jsonSchemas.get(commandName),
367 commandPayload
368 );
369 }
370 logger.warn(
ee307db5 371 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
9c5c4195
JB
372 );
373 return false;
374 }
375
c0560973 376 // Simulate charging station restart
08f130a0
JB
377 private handleRequestReset(
378 chargingStation: ChargingStation,
379 commandPayload: ResetRequest
380 ): DefaultResponse {
71623267
JB
381 // eslint-disable-next-line @typescript-eslint/no-misused-promises
382 setImmediate(async (): Promise<void> => {
60ddad53 383 await chargingStation.reset((commandPayload.type + 'Reset') as OCPP16StopTransactionReason);
c0560973 384 });
e7aeea18 385 logger.info(
08f130a0 386 `${chargingStation.logPrefix()} ${
e7aeea18
JB
387 commandPayload.type
388 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
08f130a0 389 chargingStation.stationInfo.resetTime
e7aeea18
JB
390 )}`
391 );
c0560973
JB
392 return Constants.OCPP_RESPONSE_ACCEPTED;
393 }
394
b52c969d 395 private handleRequestClearCache(): DefaultResponse {
c0560973
JB
396 return Constants.OCPP_RESPONSE_ACCEPTED;
397 }
398
e7aeea18 399 private async handleRequestUnlockConnector(
08f130a0 400 chargingStation: ChargingStation,
e7aeea18
JB
401 commandPayload: UnlockConnectorRequest
402 ): Promise<UnlockConnectorResponse> {
c0560973
JB
403 const connectorId = commandPayload.connectorId;
404 if (connectorId === 0) {
e7aeea18 405 logger.error(
08f130a0 406 chargingStation.logPrefix() + ' Trying to unlock connector ' + connectorId.toString()
e7aeea18 407 );
c0560973
JB
408 return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
409 }
5e3cb728
JB
410 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
411 const stopResponse = await chargingStation.stopTransactionOnConnector(
412 connectorId,
413 OCPP16StopTransactionReason.UNLOCK_COMMAND
414 );
c0560973
JB
415 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
416 return Constants.OCPP_RESPONSE_UNLOCKED;
417 }
418 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
419 }
08f130a0 420 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
421 OCPP16StatusNotificationRequest,
422 OCPP16StatusNotificationResponse
08f130a0 423 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
424 connectorId,
425 status: OCPP16ChargePointStatus.AVAILABLE,
426 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
427 });
08f130a0 428 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
429 return Constants.OCPP_RESPONSE_UNLOCKED;
430 }
431
e7aeea18 432 private handleRequestGetConfiguration(
08f130a0 433 chargingStation: ChargingStation,
e7aeea18
JB
434 commandPayload: GetConfigurationRequest
435 ): GetConfigurationResponse {
c0560973
JB
436 const configurationKey: OCPPConfigurationKey[] = [];
437 const unknownKey: string[] = [];
a723e7e9 438 if (Utils.isEmptyArray(commandPayload.key) === true) {
08f130a0 439 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
a723e7e9 440 if (Utils.isUndefined(configuration.visible) === true) {
7f7b65ca 441 configuration.visible = true;
c0560973 442 }
da8629bb 443 if (configuration.visible === false) {
c0560973
JB
444 continue;
445 }
446 configurationKey.push({
7f7b65ca
JB
447 key: configuration.key,
448 readonly: configuration.readonly,
449 value: configuration.value,
c0560973
JB
450 });
451 }
452 } else {
453 for (const key of commandPayload.key) {
17ac262c
JB
454 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
455 chargingStation,
456 key
457 );
c0560973 458 if (keyFound) {
a723e7e9 459 if (Utils.isUndefined(keyFound.visible) === true) {
c0560973
JB
460 keyFound.visible = true;
461 }
a723e7e9 462 if (keyFound.visible === false) {
c0560973
JB
463 continue;
464 }
465 configurationKey.push({
466 key: keyFound.key,
467 readonly: keyFound.readonly,
468 value: keyFound.value,
469 });
470 } else {
471 unknownKey.push(key);
472 }
473 }
474 }
475 return {
476 configurationKey,
477 unknownKey,
478 };
479 }
480
e7aeea18 481 private handleRequestChangeConfiguration(
08f130a0 482 chargingStation: ChargingStation,
e7aeea18
JB
483 commandPayload: ChangeConfigurationRequest
484 ): ChangeConfigurationResponse {
17ac262c
JB
485 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
486 chargingStation,
487 commandPayload.key,
488 true
489 );
c0560973
JB
490 if (!keyToChange) {
491 return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
492 } else if (keyToChange && keyToChange.readonly) {
493 return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
494 } else if (keyToChange && !keyToChange.readonly) {
c0560973 495 let valueChanged = false;
a95873d8 496 if (keyToChange.value !== commandPayload.value) {
17ac262c
JB
497 ChargingStationConfigurationUtils.setConfigurationKeyValue(
498 chargingStation,
499 commandPayload.key,
500 commandPayload.value,
501 true
502 );
c0560973
JB
503 valueChanged = true;
504 }
505 let triggerHeartbeatRestart = false;
506 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
17ac262c
JB
507 ChargingStationConfigurationUtils.setConfigurationKeyValue(
508 chargingStation,
e7aeea18
JB
509 OCPP16StandardParametersKey.HeartbeatInterval,
510 commandPayload.value
511 );
c0560973
JB
512 triggerHeartbeatRestart = true;
513 }
514 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
515 ChargingStationConfigurationUtils.setConfigurationKeyValue(
516 chargingStation,
e7aeea18
JB
517 OCPP16StandardParametersKey.HeartBeatInterval,
518 commandPayload.value
519 );
c0560973
JB
520 triggerHeartbeatRestart = true;
521 }
522 if (triggerHeartbeatRestart) {
08f130a0 523 chargingStation.restartHeartbeat();
c0560973
JB
524 }
525 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
08f130a0 526 chargingStation.restartWebSocketPing();
c0560973
JB
527 }
528 if (keyToChange.reboot) {
529 return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
530 }
531 return Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
532 }
533 }
534
e7aeea18 535 private handleRequestSetChargingProfile(
08f130a0 536 chargingStation: ChargingStation,
e7aeea18
JB
537 commandPayload: SetChargingProfileRequest
538 ): SetChargingProfileResponse {
370ae4ee
JB
539 if (
540 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 541 chargingStation,
370ae4ee
JB
542 OCPP16SupportedFeatureProfiles.SmartCharging,
543 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
544 )
545 ) {
68cb8b91
JB
546 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
547 }
08f130a0 548 if (!chargingStation.getConnectorStatus(commandPayload.connectorId)) {
e7aeea18 549 logger.error(
08f130a0 550 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
e7aeea18
JB
551 commandPayload.connectorId
552 }`
553 );
c0560973
JB
554 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
555 }
e7aeea18
JB
556 if (
557 commandPayload.csChargingProfiles.chargingProfilePurpose ===
558 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
559 commandPayload.connectorId !== 0
560 ) {
c0560973
JB
561 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
562 }
e7aeea18
JB
563 if (
564 commandPayload.csChargingProfiles.chargingProfilePurpose ===
565 ChargingProfilePurposeType.TX_PROFILE &&
566 (commandPayload.connectorId === 0 ||
5e3cb728
JB
567 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
568 false)
e7aeea18 569 ) {
c0560973
JB
570 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
571 }
ed3d2808 572 OCPP16ServiceUtils.setChargingProfile(
7cb5b17f 573 chargingStation,
e7aeea18
JB
574 commandPayload.connectorId,
575 commandPayload.csChargingProfiles
576 );
577 logger.debug(
08f130a0 578 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7
JB
579 commandPayload.connectorId
580 }, dump their stack: %j`,
08f130a0 581 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
e7aeea18 582 );
c0560973
JB
583 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
584 }
585
e7aeea18 586 private handleRequestClearChargingProfile(
08f130a0 587 chargingStation: ChargingStation,
e7aeea18
JB
588 commandPayload: ClearChargingProfileRequest
589 ): ClearChargingProfileResponse {
370ae4ee
JB
590 if (
591 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 592 chargingStation,
370ae4ee
JB
593 OCPP16SupportedFeatureProfiles.SmartCharging,
594 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
595 )
596 ) {
68cb8b91
JB
597 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
598 }
08f130a0 599 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
658e2d16 600 if (!connectorStatus) {
e7aeea18 601 logger.error(
08f130a0 602 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
e7aeea18
JB
603 commandPayload.connectorId
604 }`
605 );
c0560973
JB
606 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
607 }
658e2d16
JB
608 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
609 connectorStatus.chargingProfiles = [];
e7aeea18 610 logger.debug(
08f130a0 611 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7
JB
612 commandPayload.connectorId
613 }, dump their stack: %j`,
658e2d16 614 connectorStatus.chargingProfiles
e7aeea18 615 );
c0560973
JB
616 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
617 }
618 if (!commandPayload.connectorId) {
619 let clearedCP = false;
08f130a0
JB
620 for (const connectorId of chargingStation.connectors.keys()) {
621 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
622 chargingStation
e7aeea18
JB
623 .getConnectorStatus(connectorId)
624 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
625 let clearCurrentCP = false;
626 if (chargingProfile.chargingProfileId === commandPayload.id) {
627 clearCurrentCP = true;
628 }
629 if (
630 !commandPayload.chargingProfilePurpose &&
631 chargingProfile.stackLevel === commandPayload.stackLevel
632 ) {
633 clearCurrentCP = true;
634 }
635 if (
636 !chargingProfile.stackLevel &&
637 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
638 ) {
639 clearCurrentCP = true;
640 }
641 if (
642 chargingProfile.stackLevel === commandPayload.stackLevel &&
643 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
644 ) {
645 clearCurrentCP = true;
646 }
647 if (clearCurrentCP) {
ccb1d6e9 648 connectorStatus.chargingProfiles.splice(index, 1);
e7aeea18 649 logger.debug(
08f130a0 650 `${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
ad8537a7
JB
651 commandPayload.connectorId
652 }, dump their stack: %j`,
658e2d16 653 connectorStatus.chargingProfiles
e7aeea18
JB
654 );
655 clearedCP = true;
656 }
657 });
c0560973
JB
658 }
659 }
660 if (clearedCP) {
661 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
662 }
663 }
664 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
665 }
666
e7aeea18 667 private async handleRequestChangeAvailability(
08f130a0 668 chargingStation: ChargingStation,
e7aeea18
JB
669 commandPayload: ChangeAvailabilityRequest
670 ): Promise<ChangeAvailabilityResponse> {
c0560973 671 const connectorId: number = commandPayload.connectorId;
08f130a0 672 if (!chargingStation.getConnectorStatus(connectorId)) {
e7aeea18 673 logger.error(
08f130a0 674 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 675 );
c0560973
JB
676 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
677 }
e7aeea18
JB
678 const chargePointStatus: OCPP16ChargePointStatus =
679 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
680 ? OCPP16ChargePointStatus.AVAILABLE
681 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973
JB
682 if (connectorId === 0) {
683 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 684 for (const id of chargingStation.connectors.keys()) {
5e3cb728 685 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
c0560973
JB
686 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
687 }
08f130a0 688 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
c0560973 689 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 690 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
691 OCPP16StatusNotificationRequest,
692 OCPP16StatusNotificationResponse
08f130a0 693 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
694 connectorId: id,
695 status: chargePointStatus,
696 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
697 });
08f130a0 698 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
699 }
700 }
701 return response;
e7aeea18
JB
702 } else if (
703 connectorId > 0 &&
08f130a0
JB
704 (chargingStation.getConnectorStatus(0).availability === OCPP16AvailabilityType.OPERATIVE ||
705 (chargingStation.getConnectorStatus(0).availability ===
e7aeea18
JB
706 OCPP16AvailabilityType.INOPERATIVE &&
707 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
708 ) {
5e3cb728 709 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 710 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
c0560973
JB
711 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
712 }
08f130a0
JB
713 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
714 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
715 OCPP16StatusNotificationRequest,
716 OCPP16StatusNotificationResponse
08f130a0 717 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
718 connectorId,
719 status: chargePointStatus,
720 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
721 });
08f130a0 722 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
c0560973
JB
723 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
724 }
725 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
726 }
727
e7aeea18 728 private async handleRequestRemoteStartTransaction(
08f130a0 729 chargingStation: ChargingStation,
e7aeea18
JB
730 commandPayload: RemoteStartTransactionRequest
731 ): Promise<DefaultResponse> {
658e2d16 732 const transactionConnectorId = commandPayload.connectorId;
08f130a0 733 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
a7fc8211 734 if (transactionConnectorId) {
91a4f151
JB
735 const remoteStartTransactionLogMsg =
736 chargingStation.logPrefix() +
737 ' Transaction remotely STARTED on ' +
738 chargingStation.stationInfo.chargingStationId +
739 '#' +
740 transactionConnectorId.toString() +
741 " for idTag '" +
742 commandPayload.idTag +
743 "'";
08f130a0 744 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
745 OCPP16StatusNotificationRequest,
746 OCPP16StatusNotificationResponse
08f130a0 747 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
748 connectorId: transactionConnectorId,
749 status: OCPP16ChargePointStatus.PREPARING,
750 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
751 });
658e2d16 752 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
08f130a0 753 if (chargingStation.isChargingStationAvailable() && connectorStatus) {
e060fe58 754 // Check if authorized
08f130a0 755 if (chargingStation.getAuthorizeRemoteTxRequests()) {
a7fc8211 756 let authorized = false;
e7aeea18 757 if (
08f130a0
JB
758 chargingStation.getLocalAuthListEnabled() &&
759 chargingStation.hasAuthorizedTags() &&
9d7484a4
JB
760 chargingStation.authorizedTagsCache
761 .getAuthorizedTags(
762 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
763 )
764 .find((value) => value === commandPayload.idTag)
e7aeea18 765 ) {
658e2d16
JB
766 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
767 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 768 authorized = true;
03ebf4c1 769 } else if (chargingStation.getMustAuthorizeAtRemoteStart()) {
658e2d16 770 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 771 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 772 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
773 OCPP16AuthorizeRequest,
774 OCPP16AuthorizeResponse
08f130a0 775 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
776 idTag: commandPayload.idTag,
777 });
a7fc8211
JB
778 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
779 authorized = true;
a7fc8211 780 }
71068fb9 781 } else {
e7aeea18 782 logger.warn(
08f130a0 783 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 784 );
a7fc8211
JB
785 }
786 if (authorized) {
787 // Authorization successful, start transaction
e7aeea18
JB
788 if (
789 this.setRemoteStartTransactionChargingProfile(
08f130a0 790 chargingStation,
e7aeea18
JB
791 transactionConnectorId,
792 commandPayload.chargingProfile
793 )
794 ) {
658e2d16 795 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
796 if (
797 (
08f130a0 798 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
799 OCPP16StartTransactionRequest,
800 OCPP16StartTransactionResponse
08f130a0 801 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
802 connectorId: transactionConnectorId,
803 idTag: commandPayload.idTag,
804 })
e7aeea18
JB
805 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
806 ) {
91a4f151 807 logger.debug(remoteStartTransactionLogMsg);
e060fe58
JB
808 return Constants.OCPP_RESPONSE_ACCEPTED;
809 }
e7aeea18 810 return this.notifyRemoteStartTransactionRejected(
08f130a0 811 chargingStation,
e7aeea18
JB
812 transactionConnectorId,
813 commandPayload.idTag
814 );
e060fe58 815 }
e7aeea18 816 return this.notifyRemoteStartTransactionRejected(
08f130a0 817 chargingStation,
e7aeea18
JB
818 transactionConnectorId,
819 commandPayload.idTag
820 );
a7fc8211 821 }
e7aeea18 822 return this.notifyRemoteStartTransactionRejected(
08f130a0 823 chargingStation,
e7aeea18
JB
824 transactionConnectorId,
825 commandPayload.idTag
826 );
36f6a92e 827 }
a7fc8211 828 // No authorization check required, start transaction
e7aeea18
JB
829 if (
830 this.setRemoteStartTransactionChargingProfile(
08f130a0 831 chargingStation,
e7aeea18
JB
832 transactionConnectorId,
833 commandPayload.chargingProfile
834 )
835 ) {
658e2d16 836 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
837 if (
838 (
08f130a0 839 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
840 OCPP16StartTransactionRequest,
841 OCPP16StartTransactionResponse
08f130a0 842 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
843 connectorId: transactionConnectorId,
844 idTag: commandPayload.idTag,
845 })
e7aeea18
JB
846 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
847 ) {
91a4f151 848 logger.debug(remoteStartTransactionLogMsg);
e060fe58
JB
849 return Constants.OCPP_RESPONSE_ACCEPTED;
850 }
e7aeea18 851 return this.notifyRemoteStartTransactionRejected(
08f130a0 852 chargingStation,
e7aeea18
JB
853 transactionConnectorId,
854 commandPayload.idTag
855 );
e060fe58 856 }
e7aeea18 857 return this.notifyRemoteStartTransactionRejected(
08f130a0 858 chargingStation,
e7aeea18
JB
859 transactionConnectorId,
860 commandPayload.idTag
861 );
c0560973 862 }
e7aeea18 863 return this.notifyRemoteStartTransactionRejected(
08f130a0 864 chargingStation,
e7aeea18
JB
865 transactionConnectorId,
866 commandPayload.idTag
867 );
c0560973 868 }
08f130a0
JB
869 return this.notifyRemoteStartTransactionRejected(
870 chargingStation,
871 transactionConnectorId,
872 commandPayload.idTag
873 );
a7fc8211
JB
874 }
875
e7aeea18 876 private async notifyRemoteStartTransactionRejected(
08f130a0 877 chargingStation: ChargingStation,
e7aeea18
JB
878 connectorId: number,
879 idTag: string
880 ): Promise<DefaultResponse> {
881 if (
08f130a0 882 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
e7aeea18 883 ) {
08f130a0 884 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
885 OCPP16StatusNotificationRequest,
886 OCPP16StatusNotificationResponse
08f130a0 887 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
888 connectorId,
889 status: OCPP16ChargePointStatus.AVAILABLE,
890 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
891 });
08f130a0 892 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 893 }
e7aeea18 894 logger.warn(
08f130a0 895 chargingStation.logPrefix() +
e7aeea18
JB
896 ' Remote starting transaction REJECTED on connector Id ' +
897 connectorId.toString() +
91a4f151 898 ", idTag '" +
e7aeea18 899 idTag +
91a4f151 900 "', availability '" +
08f130a0 901 chargingStation.getConnectorStatus(connectorId).availability +
91a4f151
JB
902 "', status '" +
903 chargingStation.getConnectorStatus(connectorId).status +
904 "'"
e7aeea18 905 );
c0560973
JB
906 return Constants.OCPP_RESPONSE_REJECTED;
907 }
908
e7aeea18 909 private setRemoteStartTransactionChargingProfile(
08f130a0 910 chargingStation: ChargingStation,
e7aeea18
JB
911 connectorId: number,
912 cp: OCPP16ChargingProfile
913 ): boolean {
a7fc8211 914 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
ed3d2808 915 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
e7aeea18 916 logger.debug(
08f130a0
JB
917 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
918 chargingStation.getConnectorStatus(connectorId).chargingProfiles
e7aeea18 919 );
a7fc8211
JB
920 return true;
921 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 922 logger.warn(
08f130a0 923 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
924 cp.chargingProfilePurpose
925 } charging profile(s) at remote start transaction`
926 );
a7fc8211 927 return false;
e060fe58
JB
928 } else if (!cp) {
929 return true;
a7fc8211
JB
930 }
931 }
932
e7aeea18 933 private async handleRequestRemoteStopTransaction(
08f130a0 934 chargingStation: ChargingStation,
e7aeea18
JB
935 commandPayload: RemoteStopTransactionRequest
936 ): Promise<DefaultResponse> {
c0560973 937 const transactionId = commandPayload.transactionId;
08f130a0 938 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
939 if (
940 connectorId > 0 &&
08f130a0 941 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 942 ) {
08f130a0 943 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
944 OCPP16StatusNotificationRequest,
945 OCPP16StatusNotificationResponse
08f130a0 946 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
947 connectorId,
948 status: OCPP16ChargePointStatus.FINISHING,
949 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
950 });
08f130a0 951 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
5e3cb728
JB
952 const stopResponse = await chargingStation.stopTransactionOnConnector(
953 connectorId,
a65319ba 954 OCPP16StopTransactionReason.REMOTE
5e3cb728
JB
955 );
956 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
957 return Constants.OCPP_RESPONSE_ACCEPTED;
ef6fa3fb 958 }
5e3cb728 959 return Constants.OCPP_RESPONSE_REJECTED;
c0560973
JB
960 }
961 }
44b9b577 962 logger.warn(
08f130a0 963 chargingStation.logPrefix() +
e7aeea18
JB
964 ' Trying to remote stop a non existing transaction ' +
965 transactionId.toString()
966 );
c0560973
JB
967 return Constants.OCPP_RESPONSE_REJECTED;
968 }
47e22477 969
e7aeea18 970 private async handleRequestGetDiagnostics(
08f130a0 971 chargingStation: ChargingStation,
e7aeea18
JB
972 commandPayload: GetDiagnosticsRequest
973 ): Promise<GetDiagnosticsResponse> {
68cb8b91 974 if (
370ae4ee 975 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 976 chargingStation,
370ae4ee
JB
977 OCPP16SupportedFeatureProfiles.FirmwareManagement,
978 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
979 )
68cb8b91 980 ) {
68cb8b91
JB
981 return Constants.OCPP_RESPONSE_EMPTY;
982 }
e7aeea18 983 logger.debug(
08f130a0 984 chargingStation.logPrefix() +
e7aeea18
JB
985 ' ' +
986 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS +
987 ' request received: %j',
988 commandPayload
989 );
a3868ec4 990 const uri = new URL(commandPayload.location);
47e22477
JB
991 if (uri.protocol.startsWith('ftp:')) {
992 let ftpClient: Client;
993 try {
e7aeea18 994 const logFiles = fs
0d8140bd 995 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
996 .filter((file) => file.endsWith('.log'))
997 .map((file) => path.join('./', file));
08f130a0 998 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
999 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1000 ftpClient = new Client();
1001 const accessResponse = await ftpClient.access({
1002 host: uri.host,
e8191622
JB
1003 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1004 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1005 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1006 });
1007 let uploadResponse: FTPResponse;
1008 if (accessResponse.code === 220) {
1009 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1010 ftpClient.trackProgress(async (info) => {
e7aeea18 1011 logger.info(
08f130a0 1012 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1013 info.bytes / 1024
1014 } bytes transferred from diagnostics archive ${info.name}`
1015 );
08f130a0 1016 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1017 DiagnosticsStatusNotificationRequest,
1018 DiagnosticsStatusNotificationResponse
08f130a0 1019 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1020 status: OCPP16DiagnosticsStatus.Uploading,
1021 });
47e22477 1022 });
e7aeea18 1023 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1024 path.join(
1025 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1026 diagnosticsArchive
1027 ),
e7aeea18
JB
1028 uri.pathname + diagnosticsArchive
1029 );
47e22477 1030 if (uploadResponse.code === 226) {
08f130a0 1031 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1032 DiagnosticsStatusNotificationRequest,
1033 DiagnosticsStatusNotificationResponse
08f130a0 1034 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1035 status: OCPP16DiagnosticsStatus.Uploaded,
1036 });
47e22477
JB
1037 if (ftpClient) {
1038 ftpClient.close();
1039 }
1040 return { fileName: diagnosticsArchive };
1041 }
e7aeea18
JB
1042 throw new OCPPError(
1043 ErrorType.GENERIC_ERROR,
1044 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1045 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1046 }`,
1047 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1048 );
47e22477 1049 }
e7aeea18
JB
1050 throw new OCPPError(
1051 ErrorType.GENERIC_ERROR,
1052 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1053 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1054 }`,
1055 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1056 );
47e22477 1057 } catch (error) {
08f130a0 1058 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1059 DiagnosticsStatusNotificationRequest,
1060 DiagnosticsStatusNotificationResponse
08f130a0 1061 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1062 status: OCPP16DiagnosticsStatus.UploadFailed,
1063 });
47e22477
JB
1064 if (ftpClient) {
1065 ftpClient.close();
1066 }
e7aeea18 1067 return this.handleIncomingRequestError(
08f130a0 1068 chargingStation,
e7aeea18
JB
1069 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1070 error as Error,
1071 { errorResponse: Constants.OCPP_RESPONSE_EMPTY }
1072 );
47e22477
JB
1073 }
1074 } else {
e7aeea18 1075 logger.error(
08f130a0 1076 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1077 uri.protocol
1078 } to transfer the diagnostic logs archive`
1079 );
08f130a0 1080 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1081 DiagnosticsStatusNotificationRequest,
1082 DiagnosticsStatusNotificationResponse
08f130a0 1083 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1084 status: OCPP16DiagnosticsStatus.UploadFailed,
1085 });
47e22477
JB
1086 return Constants.OCPP_RESPONSE_EMPTY;
1087 }
1088 }
802cfa13 1089
e7aeea18 1090 private handleRequestTriggerMessage(
08f130a0 1091 chargingStation: ChargingStation,
e7aeea18
JB
1092 commandPayload: OCPP16TriggerMessageRequest
1093 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1094 if (
1095 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1096 chargingStation,
370ae4ee
JB
1097 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1098 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1099 )
1100 ) {
68cb8b91
JB
1101 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1102 }
dc661702
JB
1103 // TODO: factor out the check on connector id
1104 if (commandPayload?.connectorId < 0) {
1105 logger.warn(
08f130a0 1106 `${chargingStation.logPrefix()} ${
dc661702
JB
1107 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1108 } incoming request received with invalid connectorId ${commandPayload.connectorId}`
1109 );
1110 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1111 }
802cfa13
JB
1112 try {
1113 switch (commandPayload.requestedMessage) {
1114 case MessageTrigger.BootNotification:
1115 setTimeout(() => {
08f130a0 1116 chargingStation.ocppRequestService
f7f98c68 1117 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1118 chargingStation,
6a8b180d 1119 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1120 chargingStation.bootNotificationRequest,
6a8b180d 1121 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1122 )
8bfbc743
JB
1123 .then((response) => {
1124 chargingStation.bootNotificationResponse = response;
ae711c83 1125 })
e7aeea18
JB
1126 .catch(() => {
1127 /* This is intentional */
1128 });
802cfa13
JB
1129 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1130 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1131 case MessageTrigger.Heartbeat:
1132 setTimeout(() => {
08f130a0 1133 chargingStation.ocppRequestService
f7f98c68 1134 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1135 chargingStation,
ef6fa3fb
JB
1136 OCPP16RequestCommand.HEARTBEAT,
1137 null,
1138 {
1139 triggerMessage: true,
1140 }
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;
dc661702
JB
1147 case MessageTrigger.StatusNotification:
1148 setTimeout(() => {
1149 if (commandPayload?.connectorId) {
08f130a0 1150 chargingStation.ocppRequestService
dc661702 1151 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1152 chargingStation,
dc661702
JB
1153 OCPP16RequestCommand.STATUS_NOTIFICATION,
1154 {
1155 connectorId: commandPayload.connectorId,
1156 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1157 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1158 },
1159 {
1160 triggerMessage: true,
1161 }
1162 )
1163 .catch(() => {
1164 /* This is intentional */
1165 });
1166 } else {
08f130a0
JB
1167 for (const connectorId of chargingStation.connectors.keys()) {
1168 chargingStation.ocppRequestService
dc661702
JB
1169 .requestHandler<
1170 OCPP16StatusNotificationRequest,
1171 OCPP16StatusNotificationResponse
1172 >(
08f130a0 1173 chargingStation,
dc661702
JB
1174 OCPP16RequestCommand.STATUS_NOTIFICATION,
1175 {
1176 connectorId,
1177 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1178 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1179 },
1180 {
1181 triggerMessage: true,
1182 }
1183 )
1184 .catch(() => {
1185 /* This is intentional */
1186 });
1187 }
1188 }
1189 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1190 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13
JB
1191 default:
1192 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1193 }
1194 } catch (error) {
e7aeea18 1195 return this.handleIncomingRequestError(
08f130a0 1196 chargingStation,
e7aeea18
JB
1197 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1198 error as Error,
1199 { errorResponse: Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1200 );
802cfa13
JB
1201 }
1202 }
c0560973 1203}