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