feat: add initial support for evse definition in template
[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';
d8b1fab1 85import { OCPP16Constants, OCPP16ServiceUtils, 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 );
d8b1fab1 389 return OCPP16Constants.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(
2585c6e9 399 `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId.toString()}`
c60ed4b8 400 );
d8b1fab1 401 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c60ed4b8 402 }
c0560973 403 if (connectorId === 0) {
e7aeea18 404 logger.error(
2585c6e9 405 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`
e7aeea18 406 );
d8b1fab1 407 return OCPP16Constants.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) {
d8b1fab1 415 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
c0560973 416 }
d8b1fab1 417 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED;
c0560973 418 }
4ecff7ce
JB
419 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
420 chargingStation,
ef6fa3fb 421 connectorId,
4ecff7ce
JB
422 OCPP16ChargePointStatus.Available
423 );
d8b1fab1 424 return OCPP16Constants.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) {
d8b1fab1 487 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
c0560973 488 } else if (keyToChange && keyToChange.readonly) {
d8b1fab1 489 return OCPP16Constants.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) {
d8b1fab1 525 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
c0560973 526 }
d8b1fab1 527 return OCPP16Constants.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 ) {
d8b1fab1 542 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
68cb8b91 543 }
1789ba2c 544 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 545 logger.error(
2585c6e9 546 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${
e7aeea18
JB
547 commandPayload.connectorId
548 }`
549 );
d8b1fab1 550 return OCPP16Constants.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 ) {
d8b1fab1 557 return OCPP16Constants.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 );
d8b1fab1 571 return OCPP16Constants.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 );
d8b1fab1 584 return OCPP16Constants.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 ) {
d8b1fab1 598 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456
JB
599 }
600 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
601 logger.error(
2585c6e9 602 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${
41189456
JB
603 commandPayload.connectorId
604 }`
605 );
d8b1fab1 606 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456
JB
607 }
608 if (
609 Utils.isEmptyArray(
610 chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles
611 )
612 ) {
d8b1fab1 613 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456
JB
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 ) {
d8b1fab1 648 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
68cb8b91 649 }
1789ba2c 650 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 651 logger.error(
2585c6e9 652 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${
e7aeea18
JB
653 commandPayload.connectorId
654 }`
655 );
d8b1fab1 656 return OCPP16Constants.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 );
d8b1fab1 669 return OCPP16Constants.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) {
d8b1fab1 714 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
715 }
716 }
d8b1fab1 717 return OCPP16Constants.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(
2585c6e9 727 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId.toString()}`
e7aeea18 728 );
d8b1fab1 729 return OCPP16Constants.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) {
d8b1fab1
JB
736 let response: ChangeAvailabilityResponse =
737 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 738 for (const id of chargingStation.connectors.keys()) {
5e3cb728 739 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
d8b1fab1 740 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 741 }
08f130a0 742 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
d8b1fab1 743 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
4ecff7ce
JB
744 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
745 chargingStation,
746 id,
747 chargePointStatus
748 );
c0560973
JB
749 }
750 }
751 return response;
e7aeea18
JB
752 } else if (
753 connectorId > 0 &&
56eb297e
JB
754 (chargingStation.isChargingStationAvailable() === true ||
755 (chargingStation.isChargingStationAvailable() === false &&
e7aeea18
JB
756 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
757 ) {
5e3cb728 758 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 759 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
d8b1fab1 760 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 761 }
08f130a0 762 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
4ecff7ce
JB
763 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
764 chargingStation,
ef6fa3fb 765 connectorId,
4ecff7ce
JB
766 chargePointStatus
767 );
d8b1fab1 768 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 769 }
d8b1fab1 770 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
771 }
772
e7aeea18 773 private async handleRequestRemoteStartTransaction(
08f130a0 774 chargingStation: ChargingStation,
e7aeea18 775 commandPayload: RemoteStartTransactionRequest
f03e1042 776 ): Promise<GenericResponse> {
658e2d16 777 const transactionConnectorId = commandPayload.connectorId;
1789ba2c 778 if (chargingStation.connectors.has(transactionConnectorId) === true) {
44eb6026
JB
779 const remoteStartTransactionLogMsg = `${chargingStation.logPrefix()} Transaction remotely STARTED on ${
780 chargingStation.stationInfo.chargingStationId
781 }#${transactionConnectorId.toString()} for idTag '${commandPayload.idTag}'`;
4ecff7ce
JB
782 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
783 chargingStation,
784 transactionConnectorId,
785 OCPP16ChargePointStatus.Preparing
786 );
1789ba2c 787 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
62340a29
JB
788 if (
789 chargingStation.isChargingStationAvailable() === true &&
790 chargingStation.isConnectorAvailable(transactionConnectorId) === true
791 ) {
e060fe58 792 // Check if authorized
1789ba2c 793 if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
a7fc8211 794 let authorized = false;
e7aeea18 795 if (
1789ba2c 796 chargingStation.getLocalAuthListEnabled() === true &&
f911a4af 797 chargingStation.hasIdTags() === true &&
5a2a53cf 798 Utils.isNotEmptyString(
f911a4af 799 chargingStation.idTagsCache
e302df1d 800 .getIdTags(ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo))
d812bdcb
JB
801 ?.find((idTag) => idTag === commandPayload.idTag)
802 )
e7aeea18 803 ) {
658e2d16
JB
804 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
805 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 806 authorized = true;
1789ba2c 807 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
658e2d16 808 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 809 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 810 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
811 OCPP16AuthorizeRequest,
812 OCPP16AuthorizeResponse
08f130a0 813 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
814 idTag: commandPayload.idTag,
815 });
a7fc8211
JB
816 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
817 authorized = true;
a7fc8211 818 }
71068fb9 819 } else {
e7aeea18 820 logger.warn(
08f130a0 821 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 822 );
a7fc8211 823 }
1789ba2c 824 if (authorized === true) {
a7fc8211 825 // Authorization successful, start transaction
e7aeea18
JB
826 if (
827 this.setRemoteStartTransactionChargingProfile(
08f130a0 828 chargingStation,
e7aeea18
JB
829 transactionConnectorId,
830 commandPayload.chargingProfile
1789ba2c 831 ) === true
e7aeea18 832 ) {
658e2d16 833 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
834 if (
835 (
08f130a0 836 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
837 OCPP16StartTransactionRequest,
838 OCPP16StartTransactionResponse
08f130a0 839 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
840 connectorId: transactionConnectorId,
841 idTag: commandPayload.idTag,
842 })
e7aeea18
JB
843 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
844 ) {
91a4f151 845 logger.debug(remoteStartTransactionLogMsg);
d8b1fab1 846 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
e060fe58 847 }
e7aeea18 848 return this.notifyRemoteStartTransactionRejected(
08f130a0 849 chargingStation,
e7aeea18
JB
850 transactionConnectorId,
851 commandPayload.idTag
852 );
e060fe58 853 }
e7aeea18 854 return this.notifyRemoteStartTransactionRejected(
08f130a0 855 chargingStation,
e7aeea18
JB
856 transactionConnectorId,
857 commandPayload.idTag
858 );
a7fc8211 859 }
e7aeea18 860 return this.notifyRemoteStartTransactionRejected(
08f130a0 861 chargingStation,
e7aeea18
JB
862 transactionConnectorId,
863 commandPayload.idTag
864 );
36f6a92e 865 }
a7fc8211 866 // No authorization check required, start transaction
e7aeea18
JB
867 if (
868 this.setRemoteStartTransactionChargingProfile(
08f130a0 869 chargingStation,
e7aeea18
JB
870 transactionConnectorId,
871 commandPayload.chargingProfile
1789ba2c 872 ) === true
e7aeea18 873 ) {
658e2d16 874 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
875 if (
876 (
08f130a0 877 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
878 OCPP16StartTransactionRequest,
879 OCPP16StartTransactionResponse
08f130a0 880 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
881 connectorId: transactionConnectorId,
882 idTag: commandPayload.idTag,
883 })
e7aeea18
JB
884 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
885 ) {
91a4f151 886 logger.debug(remoteStartTransactionLogMsg);
d8b1fab1 887 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
e060fe58 888 }
e7aeea18 889 return this.notifyRemoteStartTransactionRejected(
08f130a0 890 chargingStation,
e7aeea18
JB
891 transactionConnectorId,
892 commandPayload.idTag
893 );
e060fe58 894 }
e7aeea18 895 return this.notifyRemoteStartTransactionRejected(
08f130a0 896 chargingStation,
e7aeea18
JB
897 transactionConnectorId,
898 commandPayload.idTag
899 );
c0560973 900 }
e7aeea18 901 return this.notifyRemoteStartTransactionRejected(
08f130a0 902 chargingStation,
e7aeea18
JB
903 transactionConnectorId,
904 commandPayload.idTag
905 );
c0560973 906 }
08f130a0
JB
907 return this.notifyRemoteStartTransactionRejected(
908 chargingStation,
909 transactionConnectorId,
910 commandPayload.idTag
911 );
a7fc8211
JB
912 }
913
e7aeea18 914 private async notifyRemoteStartTransactionRejected(
08f130a0 915 chargingStation: ChargingStation,
e7aeea18
JB
916 connectorId: number,
917 idTag: string
f03e1042 918 ): Promise<GenericResponse> {
e7aeea18 919 if (
721646e9 920 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
e7aeea18 921 ) {
4ecff7ce
JB
922 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
923 chargingStation,
ef6fa3fb 924 connectorId,
4ecff7ce
JB
925 OCPP16ChargePointStatus.Available
926 );
e060fe58 927 }
e7aeea18 928 logger.warn(
2585c6e9 929 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id ${connectorId.toString()}, idTag '${idTag}', availability '${
72092cfc
JB
930 chargingStation.getConnectorStatus(connectorId)?.availability
931 }', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`
e7aeea18 932 );
d8b1fab1 933 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973
JB
934 }
935
e7aeea18 936 private setRemoteStartTransactionChargingProfile(
08f130a0 937 chargingStation: ChargingStation,
e7aeea18
JB
938 connectorId: number,
939 cp: OCPP16ChargingProfile
940 ): boolean {
0ac97927 941 if (cp && cp.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
ed3d2808 942 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
e7aeea18 943 logger.debug(
ad67a158
JB
944 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
945 cp
e7aeea18 946 );
a7fc8211 947 return true;
0ac97927 948 } else if (cp && cp.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 949 logger.warn(
08f130a0 950 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
951 cp.chargingProfilePurpose
952 } charging profile(s) at remote start transaction`
953 );
a7fc8211 954 return false;
e060fe58
JB
955 } else if (!cp) {
956 return true;
a7fc8211
JB
957 }
958 }
959
e7aeea18 960 private async handleRequestRemoteStopTransaction(
08f130a0 961 chargingStation: ChargingStation,
e7aeea18 962 commandPayload: RemoteStopTransactionRequest
f03e1042 963 ): Promise<GenericResponse> {
c0560973 964 const transactionId = commandPayload.transactionId;
08f130a0 965 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
966 if (
967 connectorId > 0 &&
08f130a0 968 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 969 ) {
4ecff7ce
JB
970 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
971 chargingStation,
ef6fa3fb 972 connectorId,
4ecff7ce
JB
973 OCPP16ChargePointStatus.Finishing
974 );
5e3cb728
JB
975 const stopResponse = await chargingStation.stopTransactionOnConnector(
976 connectorId,
a65319ba 977 OCPP16StopTransactionReason.REMOTE
5e3cb728
JB
978 );
979 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
d8b1fab1 980 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
ef6fa3fb 981 }
d8b1fab1 982 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973
JB
983 }
984 }
44b9b577 985 logger.warn(
44eb6026 986 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction ${transactionId.toString()}`
e7aeea18 987 );
d8b1fab1 988 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973 989 }
47e22477 990
b03df580
JB
991 private handleRequestUpdateFirmware(
992 chargingStation: ChargingStation,
993 commandPayload: OCPP16UpdateFirmwareRequest
994 ): OCPP16UpdateFirmwareResponse {
995 if (
996 OCPP16ServiceUtils.checkFeatureProfile(
997 chargingStation,
998 OCPP16SupportedFeatureProfiles.FirmwareManagement,
999 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1000 ) === false
1001 ) {
5d280aae 1002 logger.warn(
90293abb 1003 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
5d280aae 1004 );
d8b1fab1 1005 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
5d280aae
JB
1006 }
1007 if (
1008 !Utils.isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1009 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1010 ) {
1011 logger.warn(
90293abb 1012 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
5d280aae 1013 );
d8b1fab1 1014 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
b03df580 1015 }
c9a4f9ea 1016 const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate);
2c7bdc61 1017 const now = Date.now();
72092cfc 1018 if (retrieveDate?.getTime() <= now) {
27f08ad3 1019 this.runInAsyncScope(
62340a29 1020 this.updateFirmwareSimulation.bind(this) as (
27f08ad3
JB
1021 this: OCPP16IncomingRequestService,
1022 ...args: any[]
1023 ) => Promise<void>,
1024 this,
1025 chargingStation
59b6ed8d 1026 ).catch(Constants.EMPTY_FUNCTION);
c9a4f9ea
JB
1027 } else {
1028 setTimeout(() => {
62340a29 1029 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION);
72092cfc 1030 }, retrieveDate?.getTime() - now);
c9a4f9ea 1031 }
d8b1fab1 1032 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
c9a4f9ea
JB
1033 }
1034
62340a29 1035 private async updateFirmwareSimulation(
c9a4f9ea 1036 chargingStation: ChargingStation,
90293abb
JB
1037 maxDelay = 30,
1038 minDelay = 15
c9a4f9ea 1039 ): Promise<void> {
1bf29f5b
JB
1040 if (
1041 ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) ===
1042 false
1043 ) {
1044 return;
1045 }
c9a4f9ea
JB
1046 for (const connectorId of chargingStation.connectors.keys()) {
1047 if (
1048 connectorId > 0 &&
72092cfc 1049 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
c9a4f9ea 1050 ) {
4ecff7ce
JB
1051 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1052 chargingStation,
c9a4f9ea 1053 connectorId,
4ecff7ce
JB
1054 OCPP16ChargePointStatus.Unavailable
1055 );
c9a4f9ea
JB
1056 }
1057 }
93f0c2c8
JB
1058 await chargingStation.ocppRequestService.requestHandler<
1059 OCPP16FirmwareStatusNotificationRequest,
1060 OCPP16FirmwareStatusNotificationResponse
1061 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1062 status: OCPP16FirmwareStatus.Downloading,
1063 });
1064 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
5d280aae 1065 if (
93f0c2c8
JB
1066 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1067 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1068 ) {
93f0c2c8 1069 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
5d280aae
JB
1070 await chargingStation.ocppRequestService.requestHandler<
1071 OCPP16FirmwareStatusNotificationRequest,
1072 OCPP16FirmwareStatusNotificationResponse
1073 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
15748260 1074 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
5d280aae 1075 });
93f0c2c8
JB
1076 chargingStation.stationInfo.firmwareStatus =
1077 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
5d280aae
JB
1078 return;
1079 }
90293abb 1080 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
c9a4f9ea
JB
1081 await chargingStation.ocppRequestService.requestHandler<
1082 OCPP16FirmwareStatusNotificationRequest,
1083 OCPP16FirmwareStatusNotificationResponse
1084 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1085 status: OCPP16FirmwareStatus.Downloaded,
1086 });
1087 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
380ccc42 1088 let wasTransactionsStarted = false;
62340a29
JB
1089 let transactionsStarted: boolean;
1090 do {
1091 let trxCount = 0;
1092 for (const connectorId of chargingStation.connectors.keys()) {
1093 if (
1094 connectorId > 0 &&
1095 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true
1096 ) {
1097 trxCount++;
1098 }
1099 }
1100 if (trxCount > 0) {
1101 const waitTime = 15 * 1000;
1102 logger.debug(
1103 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${trxCount} transaction(s) in progress, waiting ${
1104 waitTime / 1000
1105 } seconds before continuing firmware update simulation`
1106 );
1107 await Utils.sleep(waitTime);
1108 transactionsStarted = true;
380ccc42 1109 wasTransactionsStarted = true;
62340a29
JB
1110 } else {
1111 for (const connectorId of chargingStation.connectors.keys()) {
1112 if (
1113 connectorId > 0 &&
1114 chargingStation.getConnectorStatus(connectorId)?.status !==
1115 OCPP16ChargePointStatus.Unavailable
1116 ) {
4ecff7ce
JB
1117 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1118 chargingStation,
62340a29 1119 connectorId,
4ecff7ce
JB
1120 OCPP16ChargePointStatus.Unavailable
1121 );
62340a29
JB
1122 }
1123 }
1124 transactionsStarted = false;
1125 }
1126 } while (transactionsStarted);
380ccc42
JB
1127 !wasTransactionsStarted &&
1128 (await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000));
1bf29f5b
JB
1129 if (
1130 ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) ===
1131 false
1132 ) {
1133 return;
1134 }
c9a4f9ea
JB
1135 await chargingStation.ocppRequestService.requestHandler<
1136 OCPP16FirmwareStatusNotificationRequest,
1137 OCPP16FirmwareStatusNotificationResponse
1138 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1139 status: OCPP16FirmwareStatus.Installing,
1140 });
1141 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
93f0c2c8
JB
1142 if (
1143 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1144 OCPP16FirmwareStatus.InstallationFailed
1145 ) {
1146 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
1147 await chargingStation.ocppRequestService.requestHandler<
1148 OCPP16FirmwareStatusNotificationRequest,
1149 OCPP16FirmwareStatusNotificationResponse
1150 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1151 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1152 });
1153 chargingStation.stationInfo.firmwareStatus =
1154 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1155 return;
1156 }
15748260 1157 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
90293abb 1158 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
5d280aae
JB
1159 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1160 }
b03df580
JB
1161 }
1162
e7aeea18 1163 private async handleRequestGetDiagnostics(
08f130a0 1164 chargingStation: ChargingStation,
e7aeea18
JB
1165 commandPayload: GetDiagnosticsRequest
1166 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1167 if (
1789ba2c 1168 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1169 chargingStation,
370ae4ee
JB
1170 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1171 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1172 ) === false
68cb8b91 1173 ) {
90293abb
JB
1174 logger.warn(
1175 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1176 );
d8b1fab1 1177 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
68cb8b91 1178 }
a3868ec4 1179 const uri = new URL(commandPayload.location);
47e22477
JB
1180 if (uri.protocol.startsWith('ftp:')) {
1181 let ftpClient: Client;
1182 try {
e7aeea18 1183 const logFiles = fs
0d8140bd 1184 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
72092cfc
JB
1185 .filter((file) => file.endsWith('.log'))
1186 .map((file) => path.join('./', file));
44eb6026 1187 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
47e22477
JB
1188 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1189 ftpClient = new Client();
1190 const accessResponse = await ftpClient.access({
1191 host: uri.host,
5a2a53cf
JB
1192 ...(Utils.isNotEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1193 ...(Utils.isNotEmptyString(uri.username) && { user: uri.username }),
1194 ...(Utils.isNotEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1195 });
1196 let uploadResponse: FTPResponse;
1197 if (accessResponse.code === 220) {
72092cfc 1198 ftpClient.trackProgress((info) => {
e7aeea18 1199 logger.info(
08f130a0 1200 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1201 info.bytes / 1024
1202 } bytes transferred from diagnostics archive ${info.name}`
1203 );
6a8329b4
JB
1204 chargingStation.ocppRequestService
1205 .requestHandler<
1206 OCPP16DiagnosticsStatusNotificationRequest,
1207 OCPP16DiagnosticsStatusNotificationResponse
1208 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1209 status: OCPP16DiagnosticsStatus.Uploading,
1210 })
72092cfc 1211 .catch((error) => {
6a8329b4
JB
1212 logger.error(
1213 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1214 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1215 }'`,
1216 error
1217 );
1218 });
47e22477 1219 });
e7aeea18 1220 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1221 path.join(
1222 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1223 diagnosticsArchive
1224 ),
14ecae6a 1225 `${uri.pathname}${diagnosticsArchive}`
e7aeea18 1226 );
47e22477 1227 if (uploadResponse.code === 226) {
08f130a0 1228 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1229 OCPP16DiagnosticsStatusNotificationRequest,
1230 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1231 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1232 status: OCPP16DiagnosticsStatus.Uploaded,
1233 });
47e22477
JB
1234 if (ftpClient) {
1235 ftpClient.close();
1236 }
1237 return { fileName: diagnosticsArchive };
1238 }
e7aeea18
JB
1239 throw new OCPPError(
1240 ErrorType.GENERIC_ERROR,
1241 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1242 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18
JB
1243 }`,
1244 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1245 );
47e22477 1246 }
e7aeea18
JB
1247 throw new OCPPError(
1248 ErrorType.GENERIC_ERROR,
1249 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1250 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18
JB
1251 }`,
1252 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1253 );
47e22477 1254 } catch (error) {
08f130a0 1255 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1256 OCPP16DiagnosticsStatusNotificationRequest,
1257 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1258 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1259 status: OCPP16DiagnosticsStatus.UploadFailed,
1260 });
47e22477
JB
1261 if (ftpClient) {
1262 ftpClient.close();
1263 }
e7aeea18 1264 return this.handleIncomingRequestError(
08f130a0 1265 chargingStation,
e7aeea18
JB
1266 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1267 error as Error,
d8b1fab1 1268 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
e7aeea18 1269 );
47e22477
JB
1270 }
1271 } else {
e7aeea18 1272 logger.error(
08f130a0 1273 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1274 uri.protocol
1275 } to transfer the diagnostic logs archive`
1276 );
08f130a0 1277 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1278 OCPP16DiagnosticsStatusNotificationRequest,
1279 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1280 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1281 status: OCPP16DiagnosticsStatus.UploadFailed,
1282 });
d8b1fab1 1283 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1284 }
1285 }
802cfa13 1286
e7aeea18 1287 private handleRequestTriggerMessage(
08f130a0 1288 chargingStation: ChargingStation,
e7aeea18
JB
1289 commandPayload: OCPP16TriggerMessageRequest
1290 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1291 if (
1292 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1293 chargingStation,
370ae4ee
JB
1294 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1295 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1296 ) ||
1297 !OCPP16ServiceUtils.isMessageTriggerSupported(
1298 chargingStation,
1299 commandPayload.requestedMessage
370ae4ee
JB
1300 )
1301 ) {
d8b1fab1 1302 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1303 }
c60ed4b8 1304 if (
4caa7e67 1305 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1306 chargingStation,
1307 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1308 commandPayload.connectorId
1309 )
1310 ) {
d8b1fab1 1311 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1312 }
802cfa13
JB
1313 try {
1314 switch (commandPayload.requestedMessage) {
c60ed4b8 1315 case OCPP16MessageTrigger.BootNotification:
802cfa13 1316 setTimeout(() => {
08f130a0 1317 chargingStation.ocppRequestService
f7f98c68 1318 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1319 chargingStation,
6a8b180d 1320 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1321 chargingStation.bootNotificationRequest,
6a8b180d 1322 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1323 )
72092cfc 1324 .then((response) => {
8bfbc743 1325 chargingStation.bootNotificationResponse = response;
ae711c83 1326 })
59b6ed8d 1327 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1328 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1329 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1330 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1331 setTimeout(() => {
08f130a0 1332 chargingStation.ocppRequestService
f7f98c68 1333 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1334 chargingStation,
ef6fa3fb
JB
1335 OCPP16RequestCommand.HEARTBEAT,
1336 null,
1337 {
1338 triggerMessage: true,
1339 }
1340 )
59b6ed8d 1341 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1342 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1343 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1344 case OCPP16MessageTrigger.StatusNotification:
dc661702 1345 setTimeout(() => {
d812bdcb 1346 if (!Utils.isNullOrUndefined(commandPayload?.connectorId)) {
08f130a0 1347 chargingStation.ocppRequestService
dc661702 1348 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1349 chargingStation,
dc661702
JB
1350 OCPP16RequestCommand.STATUS_NOTIFICATION,
1351 {
1352 connectorId: commandPayload.connectorId,
1353 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
72092cfc 1354 status: chargingStation.getConnectorStatus(commandPayload.connectorId)?.status,
dc661702
JB
1355 },
1356 {
1357 triggerMessage: true,
1358 }
1359 )
59b6ed8d 1360 .catch(Constants.EMPTY_FUNCTION);
dc661702 1361 } else {
08f130a0
JB
1362 for (const connectorId of chargingStation.connectors.keys()) {
1363 chargingStation.ocppRequestService
dc661702
JB
1364 .requestHandler<
1365 OCPP16StatusNotificationRequest,
1366 OCPP16StatusNotificationResponse
1367 >(
08f130a0 1368 chargingStation,
dc661702
JB
1369 OCPP16RequestCommand.STATUS_NOTIFICATION,
1370 {
1371 connectorId,
1372 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
72092cfc 1373 status: chargingStation.getConnectorStatus(connectorId)?.status,
dc661702
JB
1374 },
1375 {
1376 triggerMessage: true,
1377 }
1378 )
59b6ed8d 1379 .catch(Constants.EMPTY_FUNCTION);
dc661702
JB
1380 }
1381 }
d8b1fab1
JB
1382 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1383 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1384 default:
d8b1fab1 1385 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1386 }
1387 } catch (error) {
e7aeea18 1388 return this.handleIncomingRequestError(
08f130a0 1389 chargingStation,
e7aeea18
JB
1390 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1391 error as Error,
d8b1fab1 1392 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1393 );
802cfa13
JB
1394 }
1395 }
77b95a89
JB
1396
1397 private handleRequestDataTransfer(
1398 chargingStation: ChargingStation,
1399 commandPayload: OCPP16DataTransferRequest
1400 ): OCPP16DataTransferResponse {
1401 try {
1402 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1403 return {
1404 status: OCPP16DataTransferStatus.ACCEPTED,
1405 };
1406 }
1407 return {
1408 status: OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID,
1409 };
1410 } catch (error) {
1411 return this.handleIncomingRequestError(
1412 chargingStation,
1413 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1414 error as Error,
d8b1fab1 1415 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1416 );
1417 }
1418 }
c0560973 1419}