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