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