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