UI protocol: move Insomnia requests collection to its own directory
[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) &&
ada189a8 306 ChargingStationUtils.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 }
7f7b65ca 443 if (!configuration.visible) {
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 }
08f130a0 572 chargingStation.setChargingProfile(
e7aeea18
JB
573 commandPayload.connectorId,
574 commandPayload.csChargingProfiles
575 );
576 logger.debug(
08f130a0 577 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7
JB
578 commandPayload.connectorId
579 }, dump their stack: %j`,
08f130a0 580 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
e7aeea18 581 );
c0560973
JB
582 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
583 }
584
e7aeea18 585 private handleRequestClearChargingProfile(
08f130a0 586 chargingStation: ChargingStation,
e7aeea18
JB
587 commandPayload: ClearChargingProfileRequest
588 ): ClearChargingProfileResponse {
370ae4ee
JB
589 if (
590 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 591 chargingStation,
370ae4ee
JB
592 OCPP16SupportedFeatureProfiles.SmartCharging,
593 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
594 )
595 ) {
68cb8b91
JB
596 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
597 }
08f130a0 598 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
658e2d16 599 if (!connectorStatus) {
e7aeea18 600 logger.error(
08f130a0 601 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
e7aeea18
JB
602 commandPayload.connectorId
603 }`
604 );
c0560973
JB
605 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
606 }
658e2d16
JB
607 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
608 connectorStatus.chargingProfiles = [];
e7aeea18 609 logger.debug(
08f130a0 610 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7
JB
611 commandPayload.connectorId
612 }, dump their stack: %j`,
658e2d16 613 connectorStatus.chargingProfiles
e7aeea18 614 );
c0560973
JB
615 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
616 }
617 if (!commandPayload.connectorId) {
618 let clearedCP = false;
08f130a0
JB
619 for (const connectorId of chargingStation.connectors.keys()) {
620 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
621 chargingStation
e7aeea18
JB
622 .getConnectorStatus(connectorId)
623 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
624 let clearCurrentCP = false;
625 if (chargingProfile.chargingProfileId === commandPayload.id) {
626 clearCurrentCP = true;
627 }
628 if (
629 !commandPayload.chargingProfilePurpose &&
630 chargingProfile.stackLevel === commandPayload.stackLevel
631 ) {
632 clearCurrentCP = true;
633 }
634 if (
635 !chargingProfile.stackLevel &&
636 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
637 ) {
638 clearCurrentCP = true;
639 }
640 if (
641 chargingProfile.stackLevel === commandPayload.stackLevel &&
642 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
643 ) {
644 clearCurrentCP = true;
645 }
646 if (clearCurrentCP) {
ccb1d6e9 647 connectorStatus.chargingProfiles.splice(index, 1);
e7aeea18 648 logger.debug(
08f130a0 649 `${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
ad8537a7
JB
650 commandPayload.connectorId
651 }, dump their stack: %j`,
658e2d16 652 connectorStatus.chargingProfiles
e7aeea18
JB
653 );
654 clearedCP = true;
655 }
656 });
c0560973
JB
657 }
658 }
659 if (clearedCP) {
660 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
661 }
662 }
663 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
664 }
665
e7aeea18 666 private async handleRequestChangeAvailability(
08f130a0 667 chargingStation: ChargingStation,
e7aeea18
JB
668 commandPayload: ChangeAvailabilityRequest
669 ): Promise<ChangeAvailabilityResponse> {
c0560973 670 const connectorId: number = commandPayload.connectorId;
08f130a0 671 if (!chargingStation.getConnectorStatus(connectorId)) {
e7aeea18 672 logger.error(
08f130a0 673 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 674 );
c0560973
JB
675 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
676 }
e7aeea18
JB
677 const chargePointStatus: OCPP16ChargePointStatus =
678 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
679 ? OCPP16ChargePointStatus.AVAILABLE
680 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973
JB
681 if (connectorId === 0) {
682 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 683 for (const id of chargingStation.connectors.keys()) {
5e3cb728 684 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
c0560973
JB
685 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
686 }
08f130a0 687 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
c0560973 688 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 689 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
690 OCPP16StatusNotificationRequest,
691 OCPP16StatusNotificationResponse
08f130a0 692 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
693 connectorId: id,
694 status: chargePointStatus,
695 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
696 });
08f130a0 697 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
698 }
699 }
700 return response;
e7aeea18
JB
701 } else if (
702 connectorId > 0 &&
08f130a0
JB
703 (chargingStation.getConnectorStatus(0).availability === OCPP16AvailabilityType.OPERATIVE ||
704 (chargingStation.getConnectorStatus(0).availability ===
e7aeea18
JB
705 OCPP16AvailabilityType.INOPERATIVE &&
706 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
707 ) {
5e3cb728 708 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 709 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
c0560973
JB
710 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
711 }
08f130a0
JB
712 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
713 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
714 OCPP16StatusNotificationRequest,
715 OCPP16StatusNotificationResponse
08f130a0 716 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
717 connectorId,
718 status: chargePointStatus,
719 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
720 });
08f130a0 721 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
c0560973
JB
722 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
723 }
724 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
725 }
726
e7aeea18 727 private async handleRequestRemoteStartTransaction(
08f130a0 728 chargingStation: ChargingStation,
e7aeea18
JB
729 commandPayload: RemoteStartTransactionRequest
730 ): Promise<DefaultResponse> {
658e2d16 731 const transactionConnectorId = commandPayload.connectorId;
08f130a0 732 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
a7fc8211 733 if (transactionConnectorId) {
91a4f151
JB
734 const remoteStartTransactionLogMsg =
735 chargingStation.logPrefix() +
736 ' Transaction remotely STARTED on ' +
737 chargingStation.stationInfo.chargingStationId +
738 '#' +
739 transactionConnectorId.toString() +
740 " for idTag '" +
741 commandPayload.idTag +
742 "'";
08f130a0 743 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
744 OCPP16StatusNotificationRequest,
745 OCPP16StatusNotificationResponse
08f130a0 746 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
747 connectorId: transactionConnectorId,
748 status: OCPP16ChargePointStatus.PREPARING,
749 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
750 });
658e2d16 751 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
08f130a0 752 if (chargingStation.isChargingStationAvailable() && connectorStatus) {
e060fe58 753 // Check if authorized
08f130a0 754 if (chargingStation.getAuthorizeRemoteTxRequests()) {
a7fc8211 755 let authorized = false;
e7aeea18 756 if (
08f130a0
JB
757 chargingStation.getLocalAuthListEnabled() &&
758 chargingStation.hasAuthorizedTags() &&
9d7484a4
JB
759 chargingStation.authorizedTagsCache
760 .getAuthorizedTags(
761 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
762 )
763 .find((value) => value === commandPayload.idTag)
e7aeea18 764 ) {
658e2d16
JB
765 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
766 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 767 authorized = true;
03ebf4c1 768 } else if (chargingStation.getMustAuthorizeAtRemoteStart()) {
658e2d16 769 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 770 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 771 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
772 OCPP16AuthorizeRequest,
773 OCPP16AuthorizeResponse
08f130a0 774 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
775 idTag: commandPayload.idTag,
776 });
a7fc8211
JB
777 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
778 authorized = true;
a7fc8211 779 }
71068fb9 780 } else {
e7aeea18 781 logger.warn(
08f130a0 782 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 783 );
a7fc8211
JB
784 }
785 if (authorized) {
786 // Authorization successful, start transaction
e7aeea18
JB
787 if (
788 this.setRemoteStartTransactionChargingProfile(
08f130a0 789 chargingStation,
e7aeea18
JB
790 transactionConnectorId,
791 commandPayload.chargingProfile
792 )
793 ) {
658e2d16 794 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
795 if (
796 (
08f130a0 797 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
798 OCPP16StartTransactionRequest,
799 OCPP16StartTransactionResponse
08f130a0 800 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
801 connectorId: transactionConnectorId,
802 idTag: commandPayload.idTag,
803 })
e7aeea18
JB
804 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
805 ) {
91a4f151 806 logger.debug(remoteStartTransactionLogMsg);
e060fe58
JB
807 return Constants.OCPP_RESPONSE_ACCEPTED;
808 }
e7aeea18 809 return this.notifyRemoteStartTransactionRejected(
08f130a0 810 chargingStation,
e7aeea18
JB
811 transactionConnectorId,
812 commandPayload.idTag
813 );
e060fe58 814 }
e7aeea18 815 return this.notifyRemoteStartTransactionRejected(
08f130a0 816 chargingStation,
e7aeea18
JB
817 transactionConnectorId,
818 commandPayload.idTag
819 );
a7fc8211 820 }
e7aeea18 821 return this.notifyRemoteStartTransactionRejected(
08f130a0 822 chargingStation,
e7aeea18
JB
823 transactionConnectorId,
824 commandPayload.idTag
825 );
36f6a92e 826 }
a7fc8211 827 // No authorization check required, start transaction
e7aeea18
JB
828 if (
829 this.setRemoteStartTransactionChargingProfile(
08f130a0 830 chargingStation,
e7aeea18
JB
831 transactionConnectorId,
832 commandPayload.chargingProfile
833 )
834 ) {
658e2d16 835 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
836 if (
837 (
08f130a0 838 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
839 OCPP16StartTransactionRequest,
840 OCPP16StartTransactionResponse
08f130a0 841 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
842 connectorId: transactionConnectorId,
843 idTag: commandPayload.idTag,
844 })
e7aeea18
JB
845 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
846 ) {
91a4f151 847 logger.debug(remoteStartTransactionLogMsg);
e060fe58
JB
848 return Constants.OCPP_RESPONSE_ACCEPTED;
849 }
e7aeea18 850 return this.notifyRemoteStartTransactionRejected(
08f130a0 851 chargingStation,
e7aeea18
JB
852 transactionConnectorId,
853 commandPayload.idTag
854 );
e060fe58 855 }
e7aeea18 856 return this.notifyRemoteStartTransactionRejected(
08f130a0 857 chargingStation,
e7aeea18
JB
858 transactionConnectorId,
859 commandPayload.idTag
860 );
c0560973 861 }
e7aeea18 862 return this.notifyRemoteStartTransactionRejected(
08f130a0 863 chargingStation,
e7aeea18
JB
864 transactionConnectorId,
865 commandPayload.idTag
866 );
c0560973 867 }
08f130a0
JB
868 return this.notifyRemoteStartTransactionRejected(
869 chargingStation,
870 transactionConnectorId,
871 commandPayload.idTag
872 );
a7fc8211
JB
873 }
874
e7aeea18 875 private async notifyRemoteStartTransactionRejected(
08f130a0 876 chargingStation: ChargingStation,
e7aeea18
JB
877 connectorId: number,
878 idTag: string
879 ): Promise<DefaultResponse> {
880 if (
08f130a0 881 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
e7aeea18 882 ) {
08f130a0 883 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
884 OCPP16StatusNotificationRequest,
885 OCPP16StatusNotificationResponse
08f130a0 886 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
887 connectorId,
888 status: OCPP16ChargePointStatus.AVAILABLE,
889 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
890 });
08f130a0 891 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 892 }
e7aeea18 893 logger.warn(
08f130a0 894 chargingStation.logPrefix() +
e7aeea18
JB
895 ' Remote starting transaction REJECTED on connector Id ' +
896 connectorId.toString() +
91a4f151 897 ", idTag '" +
e7aeea18 898 idTag +
91a4f151 899 "', availability '" +
08f130a0 900 chargingStation.getConnectorStatus(connectorId).availability +
91a4f151
JB
901 "', status '" +
902 chargingStation.getConnectorStatus(connectorId).status +
903 "'"
e7aeea18 904 );
c0560973
JB
905 return Constants.OCPP_RESPONSE_REJECTED;
906 }
907
e7aeea18 908 private setRemoteStartTransactionChargingProfile(
08f130a0 909 chargingStation: ChargingStation,
e7aeea18
JB
910 connectorId: number,
911 cp: OCPP16ChargingProfile
912 ): boolean {
a7fc8211 913 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
08f130a0 914 chargingStation.setChargingProfile(connectorId, cp);
e7aeea18 915 logger.debug(
08f130a0
JB
916 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
917 chargingStation.getConnectorStatus(connectorId).chargingProfiles
e7aeea18 918 );
a7fc8211
JB
919 return true;
920 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 921 logger.warn(
08f130a0 922 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
923 cp.chargingProfilePurpose
924 } charging profile(s) at remote start transaction`
925 );
a7fc8211 926 return false;
e060fe58
JB
927 } else if (!cp) {
928 return true;
a7fc8211
JB
929 }
930 }
931
e7aeea18 932 private async handleRequestRemoteStopTransaction(
08f130a0 933 chargingStation: ChargingStation,
e7aeea18
JB
934 commandPayload: RemoteStopTransactionRequest
935 ): Promise<DefaultResponse> {
c0560973 936 const transactionId = commandPayload.transactionId;
08f130a0 937 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
938 if (
939 connectorId > 0 &&
08f130a0 940 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 941 ) {
08f130a0 942 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
943 OCPP16StatusNotificationRequest,
944 OCPP16StatusNotificationResponse
08f130a0 945 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
946 connectorId,
947 status: OCPP16ChargePointStatus.FINISHING,
948 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
949 });
08f130a0 950 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
5e3cb728
JB
951 const stopResponse = await chargingStation.stopTransactionOnConnector(
952 connectorId,
a65319ba 953 OCPP16StopTransactionReason.REMOTE
5e3cb728
JB
954 );
955 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
956 return Constants.OCPP_RESPONSE_ACCEPTED;
ef6fa3fb 957 }
5e3cb728 958 return Constants.OCPP_RESPONSE_REJECTED;
c0560973
JB
959 }
960 }
44b9b577 961 logger.warn(
08f130a0 962 chargingStation.logPrefix() +
e7aeea18
JB
963 ' Trying to remote stop a non existing transaction ' +
964 transactionId.toString()
965 );
c0560973
JB
966 return Constants.OCPP_RESPONSE_REJECTED;
967 }
47e22477 968
e7aeea18 969 private async handleRequestGetDiagnostics(
08f130a0 970 chargingStation: ChargingStation,
e7aeea18
JB
971 commandPayload: GetDiagnosticsRequest
972 ): Promise<GetDiagnosticsResponse> {
68cb8b91 973 if (
370ae4ee 974 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 975 chargingStation,
370ae4ee
JB
976 OCPP16SupportedFeatureProfiles.FirmwareManagement,
977 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
978 )
68cb8b91 979 ) {
68cb8b91
JB
980 return Constants.OCPP_RESPONSE_EMPTY;
981 }
e7aeea18 982 logger.debug(
08f130a0 983 chargingStation.logPrefix() +
e7aeea18
JB
984 ' ' +
985 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS +
986 ' request received: %j',
987 commandPayload
988 );
a3868ec4 989 const uri = new URL(commandPayload.location);
47e22477
JB
990 if (uri.protocol.startsWith('ftp:')) {
991 let ftpClient: Client;
992 try {
e7aeea18 993 const logFiles = fs
0d8140bd 994 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
e7aeea18
JB
995 .filter((file) => file.endsWith('.log'))
996 .map((file) => path.join('./', file));
08f130a0 997 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
47e22477
JB
998 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
999 ftpClient = new Client();
1000 const accessResponse = await ftpClient.access({
1001 host: uri.host,
e8191622
JB
1002 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1003 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1004 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1005 });
1006 let uploadResponse: FTPResponse;
1007 if (accessResponse.code === 220) {
1008 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1009 ftpClient.trackProgress(async (info) => {
e7aeea18 1010 logger.info(
08f130a0 1011 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1012 info.bytes / 1024
1013 } bytes transferred from diagnostics archive ${info.name}`
1014 );
08f130a0 1015 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1016 DiagnosticsStatusNotificationRequest,
1017 DiagnosticsStatusNotificationResponse
08f130a0 1018 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1019 status: OCPP16DiagnosticsStatus.Uploading,
1020 });
47e22477 1021 });
e7aeea18 1022 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1023 path.join(
1024 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1025 diagnosticsArchive
1026 ),
e7aeea18
JB
1027 uri.pathname + diagnosticsArchive
1028 );
47e22477 1029 if (uploadResponse.code === 226) {
08f130a0 1030 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1031 DiagnosticsStatusNotificationRequest,
1032 DiagnosticsStatusNotificationResponse
08f130a0 1033 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1034 status: OCPP16DiagnosticsStatus.Uploaded,
1035 });
47e22477
JB
1036 if (ftpClient) {
1037 ftpClient.close();
1038 }
1039 return { fileName: diagnosticsArchive };
1040 }
e7aeea18
JB
1041 throw new OCPPError(
1042 ErrorType.GENERIC_ERROR,
1043 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1044 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1045 }`,
1046 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1047 );
47e22477 1048 }
e7aeea18
JB
1049 throw new OCPPError(
1050 ErrorType.GENERIC_ERROR,
1051 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1052 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1053 }`,
1054 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1055 );
47e22477 1056 } catch (error) {
08f130a0 1057 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1058 DiagnosticsStatusNotificationRequest,
1059 DiagnosticsStatusNotificationResponse
08f130a0 1060 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1061 status: OCPP16DiagnosticsStatus.UploadFailed,
1062 });
47e22477
JB
1063 if (ftpClient) {
1064 ftpClient.close();
1065 }
e7aeea18 1066 return this.handleIncomingRequestError(
08f130a0 1067 chargingStation,
e7aeea18
JB
1068 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1069 error as Error,
1070 { errorResponse: Constants.OCPP_RESPONSE_EMPTY }
1071 );
47e22477
JB
1072 }
1073 } else {
e7aeea18 1074 logger.error(
08f130a0 1075 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1076 uri.protocol
1077 } to transfer the diagnostic logs archive`
1078 );
08f130a0 1079 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
1080 DiagnosticsStatusNotificationRequest,
1081 DiagnosticsStatusNotificationResponse
08f130a0 1082 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1083 status: OCPP16DiagnosticsStatus.UploadFailed,
1084 });
47e22477
JB
1085 return Constants.OCPP_RESPONSE_EMPTY;
1086 }
1087 }
802cfa13 1088
e7aeea18 1089 private handleRequestTriggerMessage(
08f130a0 1090 chargingStation: ChargingStation,
e7aeea18
JB
1091 commandPayload: OCPP16TriggerMessageRequest
1092 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1093 if (
1094 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1095 chargingStation,
370ae4ee
JB
1096 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1097 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1098 )
1099 ) {
68cb8b91
JB
1100 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1101 }
dc661702
JB
1102 // TODO: factor out the check on connector id
1103 if (commandPayload?.connectorId < 0) {
1104 logger.warn(
08f130a0 1105 `${chargingStation.logPrefix()} ${
dc661702
JB
1106 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1107 } incoming request received with invalid connectorId ${commandPayload.connectorId}`
1108 );
1109 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1110 }
802cfa13
JB
1111 try {
1112 switch (commandPayload.requestedMessage) {
1113 case MessageTrigger.BootNotification:
1114 setTimeout(() => {
08f130a0 1115 chargingStation.ocppRequestService
f7f98c68 1116 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1117 chargingStation,
6a8b180d
JB
1118 OCPP16RequestCommand.BOOT_NOTIFICATION,
1119 {
0a03f36c
JB
1120 chargePointModel: chargingStation.bootNotificationRequest.chargePointModel,
1121 chargePointVendor: chargingStation.bootNotificationRequest.chargePointVendor,
6a8b180d 1122 chargeBoxSerialNumber:
0a03f36c
JB
1123 chargingStation.bootNotificationRequest.chargeBoxSerialNumber,
1124 firmwareVersion: chargingStation.bootNotificationRequest.firmwareVersion,
6a8b180d 1125 chargePointSerialNumber:
0a03f36c
JB
1126 chargingStation.bootNotificationRequest.chargePointSerialNumber,
1127 iccid: chargingStation.bootNotificationRequest.iccid,
1128 imsi: chargingStation.bootNotificationRequest.imsi,
1129 meterSerialNumber: chargingStation.bootNotificationRequest.meterSerialNumber,
1130 meterType: chargingStation.bootNotificationRequest.meterType,
6a8b180d
JB
1131 },
1132 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1133 )
ae711c83 1134 .then((value) => {
08f130a0 1135 chargingStation.bootNotificationResponse = value;
ae711c83 1136 })
e7aeea18
JB
1137 .catch(() => {
1138 /* This is intentional */
1139 });
802cfa13
JB
1140 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1141 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1142 case MessageTrigger.Heartbeat:
1143 setTimeout(() => {
08f130a0 1144 chargingStation.ocppRequestService
f7f98c68 1145 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1146 chargingStation,
ef6fa3fb
JB
1147 OCPP16RequestCommand.HEARTBEAT,
1148 null,
1149 {
1150 triggerMessage: true,
1151 }
1152 )
e7aeea18
JB
1153 .catch(() => {
1154 /* This is intentional */
1155 });
802cfa13
JB
1156 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1157 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
dc661702
JB
1158 case MessageTrigger.StatusNotification:
1159 setTimeout(() => {
1160 if (commandPayload?.connectorId) {
08f130a0 1161 chargingStation.ocppRequestService
dc661702 1162 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1163 chargingStation,
dc661702
JB
1164 OCPP16RequestCommand.STATUS_NOTIFICATION,
1165 {
1166 connectorId: commandPayload.connectorId,
1167 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1168 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1169 },
1170 {
1171 triggerMessage: true,
1172 }
1173 )
1174 .catch(() => {
1175 /* This is intentional */
1176 });
1177 } else {
08f130a0
JB
1178 for (const connectorId of chargingStation.connectors.keys()) {
1179 chargingStation.ocppRequestService
dc661702
JB
1180 .requestHandler<
1181 OCPP16StatusNotificationRequest,
1182 OCPP16StatusNotificationResponse
1183 >(
08f130a0 1184 chargingStation,
dc661702
JB
1185 OCPP16RequestCommand.STATUS_NOTIFICATION,
1186 {
1187 connectorId,
1188 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1189 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1190 },
1191 {
1192 triggerMessage: true,
1193 }
1194 )
1195 .catch(() => {
1196 /* This is intentional */
1197 });
1198 }
1199 }
1200 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1201 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13
JB
1202 default:
1203 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1204 }
1205 } catch (error) {
e7aeea18 1206 return this.handleIncomingRequestError(
08f130a0 1207 chargingStation,
e7aeea18
JB
1208 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1209 error as Error,
1210 { errorResponse: Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1211 );
802cfa13
JB
1212 }
1213 }
c0560973 1214}