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