Fix start charging stations and connectors status handling
[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 {
71623267
JB
381 // eslint-disable-next-line @typescript-eslint/no-misused-promises
382 setImmediate(async (): Promise<void> => {
60ddad53 383 await chargingStation.reset((commandPayload.type + 'Reset') as OCPP16StopTransactionReason);
c0560973 384 });
e7aeea18 385 logger.info(
08f130a0 386 `${chargingStation.logPrefix()} ${
e7aeea18
JB
387 commandPayload.type
388 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
08f130a0 389 chargingStation.stationInfo.resetTime
e7aeea18
JB
390 )}`
391 );
c0560973
JB
392 return Constants.OCPP_RESPONSE_ACCEPTED;
393 }
394
b52c969d 395 private handleRequestClearCache(): DefaultResponse {
c0560973
JB
396 return Constants.OCPP_RESPONSE_ACCEPTED;
397 }
398
e7aeea18 399 private async handleRequestUnlockConnector(
08f130a0 400 chargingStation: ChargingStation,
e7aeea18
JB
401 commandPayload: UnlockConnectorRequest
402 ): Promise<UnlockConnectorResponse> {
c0560973
JB
403 const connectorId = commandPayload.connectorId;
404 if (connectorId === 0) {
e7aeea18 405 logger.error(
08f130a0 406 chargingStation.logPrefix() + ' Trying to unlock connector ' + connectorId.toString()
e7aeea18 407 );
c0560973
JB
408 return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
409 }
5e3cb728
JB
410 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
411 const stopResponse = await chargingStation.stopTransactionOnConnector(
412 connectorId,
413 OCPP16StopTransactionReason.UNLOCK_COMMAND
414 );
c0560973
JB
415 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
416 return Constants.OCPP_RESPONSE_UNLOCKED;
417 }
418 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
419 }
08f130a0 420 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
421 OCPP16StatusNotificationRequest,
422 OCPP16StatusNotificationResponse
08f130a0 423 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
424 connectorId,
425 status: OCPP16ChargePointStatus.AVAILABLE,
426 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
427 });
08f130a0 428 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
429 return Constants.OCPP_RESPONSE_UNLOCKED;
430 }
431
e7aeea18 432 private handleRequestGetConfiguration(
08f130a0 433 chargingStation: ChargingStation,
e7aeea18
JB
434 commandPayload: GetConfigurationRequest
435 ): GetConfigurationResponse {
c0560973
JB
436 const configurationKey: OCPPConfigurationKey[] = [];
437 const unknownKey: string[] = [];
a723e7e9 438 if (Utils.isEmptyArray(commandPayload.key) === true) {
08f130a0 439 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
a723e7e9 440 if (Utils.isUndefined(configuration.visible) === true) {
7f7b65ca 441 configuration.visible = true;
c0560973 442 }
da8629bb 443 if (configuration.visible === false) {
c0560973
JB
444 continue;
445 }
446 configurationKey.push({
7f7b65ca
JB
447 key: configuration.key,
448 readonly: configuration.readonly,
449 value: configuration.value,
c0560973
JB
450 });
451 }
452 } else {
453 for (const key of commandPayload.key) {
17ac262c
JB
454 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
455 chargingStation,
456 key
457 );
c0560973 458 if (keyFound) {
a723e7e9 459 if (Utils.isUndefined(keyFound.visible) === true) {
c0560973
JB
460 keyFound.visible = true;
461 }
a723e7e9 462 if (keyFound.visible === false) {
c0560973
JB
463 continue;
464 }
465 configurationKey.push({
466 key: keyFound.key,
467 readonly: keyFound.readonly,
468 value: keyFound.value,
469 });
470 } else {
471 unknownKey.push(key);
472 }
473 }
474 }
475 return {
476 configurationKey,
477 unknownKey,
478 };
479 }
480
e7aeea18 481 private handleRequestChangeConfiguration(
08f130a0 482 chargingStation: ChargingStation,
e7aeea18
JB
483 commandPayload: ChangeConfigurationRequest
484 ): ChangeConfigurationResponse {
17ac262c
JB
485 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
486 chargingStation,
487 commandPayload.key,
488 true
489 );
c0560973
JB
490 if (!keyToChange) {
491 return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
492 } else if (keyToChange && keyToChange.readonly) {
493 return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
494 } else if (keyToChange && !keyToChange.readonly) {
c0560973 495 let valueChanged = false;
a95873d8 496 if (keyToChange.value !== commandPayload.value) {
17ac262c
JB
497 ChargingStationConfigurationUtils.setConfigurationKeyValue(
498 chargingStation,
499 commandPayload.key,
500 commandPayload.value,
501 true
502 );
c0560973
JB
503 valueChanged = true;
504 }
505 let triggerHeartbeatRestart = false;
506 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
17ac262c
JB
507 ChargingStationConfigurationUtils.setConfigurationKeyValue(
508 chargingStation,
e7aeea18
JB
509 OCPP16StandardParametersKey.HeartbeatInterval,
510 commandPayload.value
511 );
c0560973
JB
512 triggerHeartbeatRestart = true;
513 }
514 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
515 ChargingStationConfigurationUtils.setConfigurationKeyValue(
516 chargingStation,
e7aeea18
JB
517 OCPP16StandardParametersKey.HeartBeatInterval,
518 commandPayload.value
519 );
c0560973
JB
520 triggerHeartbeatRestart = true;
521 }
522 if (triggerHeartbeatRestart) {
08f130a0 523 chargingStation.restartHeartbeat();
c0560973
JB
524 }
525 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
08f130a0 526 chargingStation.restartWebSocketPing();
c0560973
JB
527 }
528 if (keyToChange.reboot) {
529 return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
530 }
531 return Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
532 }
533 }
534
e7aeea18 535 private handleRequestSetChargingProfile(
08f130a0 536 chargingStation: ChargingStation,
e7aeea18
JB
537 commandPayload: SetChargingProfileRequest
538 ): SetChargingProfileResponse {
370ae4ee
JB
539 if (
540 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 541 chargingStation,
370ae4ee
JB
542 OCPP16SupportedFeatureProfiles.SmartCharging,
543 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
544 )
545 ) {
68cb8b91
JB
546 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
547 }
08f130a0 548 if (!chargingStation.getConnectorStatus(commandPayload.connectorId)) {
e7aeea18 549 logger.error(
08f130a0 550 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
e7aeea18
JB
551 commandPayload.connectorId
552 }`
553 );
c0560973
JB
554 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
555 }
e7aeea18
JB
556 if (
557 commandPayload.csChargingProfiles.chargingProfilePurpose ===
558 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
559 commandPayload.connectorId !== 0
560 ) {
c0560973
JB
561 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
562 }
e7aeea18
JB
563 if (
564 commandPayload.csChargingProfiles.chargingProfilePurpose ===
565 ChargingProfilePurposeType.TX_PROFILE &&
566 (commandPayload.connectorId === 0 ||
5e3cb728
JB
567 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
568 false)
e7aeea18 569 ) {
c0560973
JB
570 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
571 }
ed3d2808 572 OCPP16ServiceUtils.setChargingProfile(
7cb5b17f 573 chargingStation,
e7aeea18
JB
574 commandPayload.connectorId,
575 commandPayload.csChargingProfiles
576 );
577 logger.debug(
08f130a0 578 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7
JB
579 commandPayload.connectorId
580 }, dump their stack: %j`,
08f130a0 581 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
e7aeea18 582 );
c0560973
JB
583 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
584 }
585
e7aeea18 586 private handleRequestClearChargingProfile(
08f130a0 587 chargingStation: ChargingStation,
e7aeea18
JB
588 commandPayload: ClearChargingProfileRequest
589 ): ClearChargingProfileResponse {
370ae4ee
JB
590 if (
591 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 592 chargingStation,
370ae4ee
JB
593 OCPP16SupportedFeatureProfiles.SmartCharging,
594 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
595 )
596 ) {
68cb8b91
JB
597 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
598 }
08f130a0 599 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
658e2d16 600 if (!connectorStatus) {
e7aeea18 601 logger.error(
08f130a0 602 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
e7aeea18
JB
603 commandPayload.connectorId
604 }`
605 );
c0560973
JB
606 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
607 }
658e2d16
JB
608 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
609 connectorStatus.chargingProfiles = [];
e7aeea18 610 logger.debug(
08f130a0 611 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7
JB
612 commandPayload.connectorId
613 }, dump their stack: %j`,
658e2d16 614 connectorStatus.chargingProfiles
e7aeea18 615 );
c0560973
JB
616 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
617 }
618 if (!commandPayload.connectorId) {
619 let clearedCP = false;
08f130a0
JB
620 for (const connectorId of chargingStation.connectors.keys()) {
621 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
622 chargingStation
e7aeea18
JB
623 .getConnectorStatus(connectorId)
624 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
625 let clearCurrentCP = false;
626 if (chargingProfile.chargingProfileId === commandPayload.id) {
627 clearCurrentCP = true;
628 }
629 if (
630 !commandPayload.chargingProfilePurpose &&
631 chargingProfile.stackLevel === commandPayload.stackLevel
632 ) {
633 clearCurrentCP = true;
634 }
635 if (
636 !chargingProfile.stackLevel &&
637 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
638 ) {
639 clearCurrentCP = true;
640 }
641 if (
642 chargingProfile.stackLevel === commandPayload.stackLevel &&
643 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
644 ) {
645 clearCurrentCP = true;
646 }
647 if (clearCurrentCP) {
ccb1d6e9 648 connectorStatus.chargingProfiles.splice(index, 1);
e7aeea18 649 logger.debug(
08f130a0 650 `${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
ad8537a7
JB
651 commandPayload.connectorId
652 }, dump their stack: %j`,
658e2d16 653 connectorStatus.chargingProfiles
e7aeea18
JB
654 );
655 clearedCP = true;
656 }
657 });
c0560973
JB
658 }
659 }
660 if (clearedCP) {
661 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
662 }
663 }
664 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
665 }
666
e7aeea18 667 private async handleRequestChangeAvailability(
08f130a0 668 chargingStation: ChargingStation,
e7aeea18
JB
669 commandPayload: ChangeAvailabilityRequest
670 ): Promise<ChangeAvailabilityResponse> {
c0560973 671 const connectorId: number = commandPayload.connectorId;
08f130a0 672 if (!chargingStation.getConnectorStatus(connectorId)) {
e7aeea18 673 logger.error(
08f130a0 674 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 675 );
c0560973
JB
676 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
677 }
e7aeea18
JB
678 const chargePointStatus: OCPP16ChargePointStatus =
679 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
680 ? OCPP16ChargePointStatus.AVAILABLE
681 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973
JB
682 if (connectorId === 0) {
683 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 684 for (const id of chargingStation.connectors.keys()) {
5e3cb728 685 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
c0560973
JB
686 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
687 }
08f130a0 688 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
c0560973 689 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 690 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
691 OCPP16StatusNotificationRequest,
692 OCPP16StatusNotificationResponse
08f130a0 693 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
694 connectorId: id,
695 status: chargePointStatus,
696 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
697 });
08f130a0 698 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
699 }
700 }
701 return response;
e7aeea18
JB
702 } else if (
703 connectorId > 0 &&
56eb297e
JB
704 (chargingStation.isChargingStationAvailable() === true ||
705 (chargingStation.isChargingStationAvailable() === false &&
e7aeea18
JB
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) {
ed3d2808 914 OCPP16ServiceUtils.setChargingProfile(chargingStation, 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 1118 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1119 chargingStation.bootNotificationRequest,
6a8b180d 1120 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1121 )
8bfbc743
JB
1122 .then((response) => {
1123 chargingStation.bootNotificationResponse = response;
ae711c83 1124 })
e7aeea18
JB
1125 .catch(() => {
1126 /* This is intentional */
1127 });
802cfa13
JB
1128 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1129 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1130 case MessageTrigger.Heartbeat:
1131 setTimeout(() => {
08f130a0 1132 chargingStation.ocppRequestService
f7f98c68 1133 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1134 chargingStation,
ef6fa3fb
JB
1135 OCPP16RequestCommand.HEARTBEAT,
1136 null,
1137 {
1138 triggerMessage: true,
1139 }
1140 )
e7aeea18
JB
1141 .catch(() => {
1142 /* This is intentional */
1143 });
802cfa13
JB
1144 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1145 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
dc661702
JB
1146 case MessageTrigger.StatusNotification:
1147 setTimeout(() => {
1148 if (commandPayload?.connectorId) {
08f130a0 1149 chargingStation.ocppRequestService
dc661702 1150 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1151 chargingStation,
dc661702
JB
1152 OCPP16RequestCommand.STATUS_NOTIFICATION,
1153 {
1154 connectorId: commandPayload.connectorId,
1155 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1156 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
dc661702
JB
1157 },
1158 {
1159 triggerMessage: true,
1160 }
1161 )
1162 .catch(() => {
1163 /* This is intentional */
1164 });
1165 } else {
08f130a0
JB
1166 for (const connectorId of chargingStation.connectors.keys()) {
1167 chargingStation.ocppRequestService
dc661702
JB
1168 .requestHandler<
1169 OCPP16StatusNotificationRequest,
1170 OCPP16StatusNotificationResponse
1171 >(
08f130a0 1172 chargingStation,
dc661702
JB
1173 OCPP16RequestCommand.STATUS_NOTIFICATION,
1174 {
1175 connectorId,
1176 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
08f130a0 1177 status: chargingStation.getConnectorStatus(connectorId).status,
dc661702
JB
1178 },
1179 {
1180 triggerMessage: true,
1181 }
1182 )
1183 .catch(() => {
1184 /* This is intentional */
1185 });
1186 }
1187 }
1188 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1189 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13
JB
1190 default:
1191 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1192 }
1193 } catch (error) {
e7aeea18 1194 return this.handleIncomingRequestError(
08f130a0 1195 chargingStation,
e7aeea18
JB
1196 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1197 error as Error,
1198 { errorResponse: Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1199 );
802cfa13
JB
1200 }
1201 }
c0560973 1202}