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