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