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