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