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