chore: version 1.1.94
[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[] = [];
9a15316c 428 if (Utils.isUndefined(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 }
9a15316c 442 } else if (Utils.isNotEmptyArray(commandPayload.key) === true) {
c0560973 443 for (const key of commandPayload.key) {
17ac262c
JB
444 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
445 chargingStation,
acda9cab
JB
446 key,
447 true
17ac262c 448 );
c0560973 449 if (keyFound) {
a723e7e9 450 if (Utils.isUndefined(keyFound.visible) === true) {
c0560973
JB
451 keyFound.visible = true;
452 }
a723e7e9 453 if (keyFound.visible === false) {
c0560973
JB
454 continue;
455 }
456 configurationKey.push({
457 key: keyFound.key,
458 readonly: keyFound.readonly,
459 value: keyFound.value,
460 });
461 } else {
462 unknownKey.push(key);
463 }
464 }
465 }
466 return {
467 configurationKey,
468 unknownKey,
469 };
470 }
471
e7aeea18 472 private handleRequestChangeConfiguration(
08f130a0 473 chargingStation: ChargingStation,
e7aeea18
JB
474 commandPayload: ChangeConfigurationRequest
475 ): ChangeConfigurationResponse {
17ac262c
JB
476 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
477 chargingStation,
478 commandPayload.key,
479 true
480 );
c0560973 481 if (!keyToChange) {
bf53cadf 482 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
c0560973 483 } else if (keyToChange && keyToChange.readonly) {
bf53cadf 484 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
c0560973 485 } else if (keyToChange && !keyToChange.readonly) {
c0560973 486 let valueChanged = false;
a95873d8 487 if (keyToChange.value !== commandPayload.value) {
17ac262c
JB
488 ChargingStationConfigurationUtils.setConfigurationKeyValue(
489 chargingStation,
490 commandPayload.key,
491 commandPayload.value,
492 true
493 );
c0560973
JB
494 valueChanged = true;
495 }
496 let triggerHeartbeatRestart = false;
497 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
17ac262c
JB
498 ChargingStationConfigurationUtils.setConfigurationKeyValue(
499 chargingStation,
e7aeea18
JB
500 OCPP16StandardParametersKey.HeartbeatInterval,
501 commandPayload.value
502 );
c0560973
JB
503 triggerHeartbeatRestart = true;
504 }
505 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
506 ChargingStationConfigurationUtils.setConfigurationKeyValue(
507 chargingStation,
e7aeea18
JB
508 OCPP16StandardParametersKey.HeartBeatInterval,
509 commandPayload.value
510 );
c0560973
JB
511 triggerHeartbeatRestart = true;
512 }
513 if (triggerHeartbeatRestart) {
08f130a0 514 chargingStation.restartHeartbeat();
c0560973
JB
515 }
516 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
08f130a0 517 chargingStation.restartWebSocketPing();
c0560973
JB
518 }
519 if (keyToChange.reboot) {
bf53cadf 520 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
c0560973 521 }
bf53cadf 522 return OCPPConstants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
c0560973
JB
523 }
524 }
525
e7aeea18 526 private handleRequestSetChargingProfile(
08f130a0 527 chargingStation: ChargingStation,
e7aeea18
JB
528 commandPayload: SetChargingProfileRequest
529 ): SetChargingProfileResponse {
370ae4ee 530 if (
1789ba2c 531 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 532 chargingStation,
370ae4ee
JB
533 OCPP16SupportedFeatureProfiles.SmartCharging,
534 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
1789ba2c 535 ) === false
370ae4ee 536 ) {
bf53cadf 537 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
68cb8b91 538 }
1789ba2c 539 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 540 logger.error(
08f130a0 541 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
e7aeea18
JB
542 commandPayload.connectorId
543 }`
544 );
bf53cadf 545 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 546 }
e7aeea18
JB
547 if (
548 commandPayload.csChargingProfiles.chargingProfilePurpose ===
549 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
550 commandPayload.connectorId !== 0
551 ) {
bf53cadf 552 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 553 }
e7aeea18
JB
554 if (
555 commandPayload.csChargingProfiles.chargingProfilePurpose ===
556 ChargingProfilePurposeType.TX_PROFILE &&
557 (commandPayload.connectorId === 0 ||
5e3cb728
JB
558 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
559 false)
e7aeea18 560 ) {
db0af086
JB
561 logger.error(
562 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${
563 commandPayload.connectorId
564 } without a started transaction`
565 );
bf53cadf 566 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 567 }
ed3d2808 568 OCPP16ServiceUtils.setChargingProfile(
7cb5b17f 569 chargingStation,
e7aeea18
JB
570 commandPayload.connectorId,
571 commandPayload.csChargingProfiles
572 );
573 logger.debug(
08f130a0 574 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7 575 commandPayload.connectorId
ad67a158
JB
576 }: %j`,
577 commandPayload.csChargingProfiles
e7aeea18 578 );
bf53cadf 579 return OCPPConstants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
580 }
581
e7aeea18 582 private handleRequestClearChargingProfile(
08f130a0 583 chargingStation: ChargingStation,
e7aeea18
JB
584 commandPayload: ClearChargingProfileRequest
585 ): ClearChargingProfileResponse {
370ae4ee 586 if (
a36bad10 587 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 588 chargingStation,
370ae4ee
JB
589 OCPP16SupportedFeatureProfiles.SmartCharging,
590 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
a36bad10 591 ) === false
370ae4ee 592 ) {
bf53cadf 593 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
68cb8b91 594 }
1789ba2c 595 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
e7aeea18 596 logger.error(
08f130a0 597 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
e7aeea18
JB
598 commandPayload.connectorId
599 }`
600 );
bf53cadf 601 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973 602 }
1789ba2c 603 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
d812bdcb
JB
604 if (
605 !Utils.isNullOrUndefined(commandPayload.connectorId) &&
53ac516c 606 Utils.isNotEmptyArray(connectorStatus?.chargingProfiles)
d812bdcb 607 ) {
658e2d16 608 connectorStatus.chargingProfiles = [];
e7aeea18 609 logger.debug(
08f130a0 610 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7 611 commandPayload.connectorId
ad67a158 612 }`
e7aeea18 613 );
bf53cadf 614 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973 615 }
d812bdcb 616 if (Utils.isNullOrUndefined(commandPayload.connectorId)) {
c0560973 617 let clearedCP = false;
08f130a0 618 for (const connectorId of chargingStation.connectors.keys()) {
72092cfc 619 if (
53ac516c 620 Utils.isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)
72092cfc 621 ) {
08f130a0 622 chargingStation
e7aeea18 623 .getConnectorStatus(connectorId)
72092cfc 624 ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
e7aeea18
JB
625 let clearCurrentCP = false;
626 if (chargingProfile.chargingProfileId === commandPayload.id) {
627 clearCurrentCP = true;
628 }
629 if (
630 !commandPayload.chargingProfilePurpose &&
631 chargingProfile.stackLevel === commandPayload.stackLevel
632 ) {
633 clearCurrentCP = true;
634 }
635 if (
636 !chargingProfile.stackLevel &&
637 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
638 ) {
639 clearCurrentCP = true;
640 }
641 if (
642 chargingProfile.stackLevel === commandPayload.stackLevel &&
643 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
644 ) {
645 clearCurrentCP = true;
646 }
647 if (clearCurrentCP) {
72092cfc 648 connectorStatus?.chargingProfiles?.splice(index, 1);
e7aeea18 649 logger.debug(
ad67a158
JB
650 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
651 chargingProfile
e7aeea18
JB
652 );
653 clearedCP = true;
654 }
655 });
c0560973
JB
656 }
657 }
658 if (clearedCP) {
bf53cadf 659 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
660 }
661 }
bf53cadf 662 return OCPPConstants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973
JB
663 }
664
e7aeea18 665 private async handleRequestChangeAvailability(
08f130a0 666 chargingStation: ChargingStation,
e7aeea18
JB
667 commandPayload: ChangeAvailabilityRequest
668 ): Promise<ChangeAvailabilityResponse> {
c0560973 669 const connectorId: number = commandPayload.connectorId;
c60ed4b8 670 if (chargingStation.connectors.has(connectorId) === false) {
e7aeea18 671 logger.error(
08f130a0 672 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
e7aeea18 673 );
bf53cadf 674 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973 675 }
e7aeea18
JB
676 const chargePointStatus: OCPP16ChargePointStatus =
677 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
678 ? OCPP16ChargePointStatus.AVAILABLE
679 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973 680 if (connectorId === 0) {
bf53cadf 681 let response: ChangeAvailabilityResponse = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
08f130a0 682 for (const id of chargingStation.connectors.keys()) {
5e3cb728 683 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
bf53cadf 684 response = OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 685 }
08f130a0 686 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
bf53cadf 687 if (response === OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
08f130a0 688 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
689 OCPP16StatusNotificationRequest,
690 OCPP16StatusNotificationResponse
08f130a0 691 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
692 connectorId: id,
693 status: chargePointStatus,
694 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
695 });
08f130a0 696 chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
697 }
698 }
699 return response;
e7aeea18
JB
700 } else if (
701 connectorId > 0 &&
56eb297e
JB
702 (chargingStation.isChargingStationAvailable() === true ||
703 (chargingStation.isChargingStationAvailable() === false &&
e7aeea18
JB
704 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
705 ) {
5e3cb728 706 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 707 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
bf53cadf 708 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 709 }
08f130a0
JB
710 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
711 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
712 OCPP16StatusNotificationRequest,
713 OCPP16StatusNotificationResponse
08f130a0 714 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
715 connectorId,
716 status: chargePointStatus,
717 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
718 });
08f130a0 719 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
bf53cadf 720 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 721 }
bf53cadf 722 return OCPPConstants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
723 }
724
e7aeea18 725 private async handleRequestRemoteStartTransaction(
08f130a0 726 chargingStation: ChargingStation,
e7aeea18 727 commandPayload: RemoteStartTransactionRequest
f03e1042 728 ): Promise<GenericResponse> {
658e2d16 729 const transactionConnectorId = commandPayload.connectorId;
1789ba2c 730 if (chargingStation.connectors.has(transactionConnectorId) === true) {
44eb6026
JB
731 const remoteStartTransactionLogMsg = `${chargingStation.logPrefix()} Transaction remotely STARTED on ${
732 chargingStation.stationInfo.chargingStationId
733 }#${transactionConnectorId.toString()} for idTag '${commandPayload.idTag}'`;
08f130a0 734 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
735 OCPP16StatusNotificationRequest,
736 OCPP16StatusNotificationResponse
08f130a0 737 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
738 connectorId: transactionConnectorId,
739 status: OCPP16ChargePointStatus.PREPARING,
740 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
741 });
1789ba2c 742 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
658e2d16 743 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
1789ba2c 744 if (chargingStation.isChargingStationAvailable() === true) {
e060fe58 745 // Check if authorized
1789ba2c 746 if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
a7fc8211 747 let authorized = false;
e7aeea18 748 if (
1789ba2c
JB
749 chargingStation.getLocalAuthListEnabled() === true &&
750 chargingStation.hasAuthorizedTags() === true &&
5a2a53cf 751 Utils.isNotEmptyString(
d812bdcb
JB
752 chargingStation.authorizedTagsCache
753 .getAuthorizedTags(
754 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
755 )
756 ?.find((idTag) => idTag === commandPayload.idTag)
757 )
e7aeea18 758 ) {
658e2d16
JB
759 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
760 connectorStatus.idTagLocalAuthorized = true;
36f6a92e 761 authorized = true;
1789ba2c 762 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
658e2d16 763 connectorStatus.authorizeIdTag = commandPayload.idTag;
2e3d65ae 764 const authorizeResponse: OCPP16AuthorizeResponse =
08f130a0 765 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
766 OCPP16AuthorizeRequest,
767 OCPP16AuthorizeResponse
08f130a0 768 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
ef6fa3fb
JB
769 idTag: commandPayload.idTag,
770 });
a7fc8211
JB
771 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
772 authorized = true;
a7fc8211 773 }
71068fb9 774 } else {
e7aeea18 775 logger.warn(
08f130a0 776 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
e7aeea18 777 );
a7fc8211 778 }
1789ba2c 779 if (authorized === true) {
a7fc8211 780 // Authorization successful, start transaction
e7aeea18
JB
781 if (
782 this.setRemoteStartTransactionChargingProfile(
08f130a0 783 chargingStation,
e7aeea18
JB
784 transactionConnectorId,
785 commandPayload.chargingProfile
1789ba2c 786 ) === true
e7aeea18 787 ) {
658e2d16 788 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
789 if (
790 (
08f130a0 791 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
792 OCPP16StartTransactionRequest,
793 OCPP16StartTransactionResponse
08f130a0 794 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
795 connectorId: transactionConnectorId,
796 idTag: commandPayload.idTag,
797 })
e7aeea18
JB
798 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
799 ) {
91a4f151 800 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 801 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 802 }
e7aeea18 803 return this.notifyRemoteStartTransactionRejected(
08f130a0 804 chargingStation,
e7aeea18
JB
805 transactionConnectorId,
806 commandPayload.idTag
807 );
e060fe58 808 }
e7aeea18 809 return this.notifyRemoteStartTransactionRejected(
08f130a0 810 chargingStation,
e7aeea18
JB
811 transactionConnectorId,
812 commandPayload.idTag
813 );
a7fc8211 814 }
e7aeea18 815 return this.notifyRemoteStartTransactionRejected(
08f130a0 816 chargingStation,
e7aeea18
JB
817 transactionConnectorId,
818 commandPayload.idTag
819 );
36f6a92e 820 }
a7fc8211 821 // No authorization check required, start transaction
e7aeea18
JB
822 if (
823 this.setRemoteStartTransactionChargingProfile(
08f130a0 824 chargingStation,
e7aeea18
JB
825 transactionConnectorId,
826 commandPayload.chargingProfile
1789ba2c 827 ) === true
e7aeea18 828 ) {
658e2d16 829 connectorStatus.transactionRemoteStarted = true;
e7aeea18
JB
830 if (
831 (
08f130a0 832 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
833 OCPP16StartTransactionRequest,
834 OCPP16StartTransactionResponse
08f130a0 835 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
ef6fa3fb
JB
836 connectorId: transactionConnectorId,
837 idTag: commandPayload.idTag,
838 })
e7aeea18
JB
839 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
840 ) {
91a4f151 841 logger.debug(remoteStartTransactionLogMsg);
bf53cadf 842 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
e060fe58 843 }
e7aeea18 844 return this.notifyRemoteStartTransactionRejected(
08f130a0 845 chargingStation,
e7aeea18
JB
846 transactionConnectorId,
847 commandPayload.idTag
848 );
e060fe58 849 }
e7aeea18 850 return this.notifyRemoteStartTransactionRejected(
08f130a0 851 chargingStation,
e7aeea18
JB
852 transactionConnectorId,
853 commandPayload.idTag
854 );
c0560973 855 }
e7aeea18 856 return this.notifyRemoteStartTransactionRejected(
08f130a0 857 chargingStation,
e7aeea18
JB
858 transactionConnectorId,
859 commandPayload.idTag
860 );
c0560973 861 }
08f130a0
JB
862 return this.notifyRemoteStartTransactionRejected(
863 chargingStation,
864 transactionConnectorId,
865 commandPayload.idTag
866 );
a7fc8211
JB
867 }
868
e7aeea18 869 private async notifyRemoteStartTransactionRejected(
08f130a0 870 chargingStation: ChargingStation,
e7aeea18
JB
871 connectorId: number,
872 idTag: string
f03e1042 873 ): Promise<GenericResponse> {
e7aeea18 874 if (
72092cfc 875 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.AVAILABLE
e7aeea18 876 ) {
08f130a0 877 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
878 OCPP16StatusNotificationRequest,
879 OCPP16StatusNotificationResponse
08f130a0 880 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
881 connectorId,
882 status: OCPP16ChargePointStatus.AVAILABLE,
883 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
884 });
08f130a0 885 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 886 }
e7aeea18 887 logger.warn(
44eb6026 888 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector Id ${connectorId.toString()}, idTag '${idTag}', availability '${
72092cfc
JB
889 chargingStation.getConnectorStatus(connectorId)?.availability
890 }', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`
e7aeea18 891 );
bf53cadf 892 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
893 }
894
e7aeea18 895 private setRemoteStartTransactionChargingProfile(
08f130a0 896 chargingStation: ChargingStation,
e7aeea18
JB
897 connectorId: number,
898 cp: OCPP16ChargingProfile
899 ): boolean {
a7fc8211 900 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
ed3d2808 901 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
e7aeea18 902 logger.debug(
ad67a158
JB
903 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
904 cp
e7aeea18 905 );
a7fc8211
JB
906 return true;
907 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 908 logger.warn(
08f130a0 909 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
910 cp.chargingProfilePurpose
911 } charging profile(s) at remote start transaction`
912 );
a7fc8211 913 return false;
e060fe58
JB
914 } else if (!cp) {
915 return true;
a7fc8211
JB
916 }
917 }
918
e7aeea18 919 private async handleRequestRemoteStopTransaction(
08f130a0 920 chargingStation: ChargingStation,
e7aeea18 921 commandPayload: RemoteStopTransactionRequest
f03e1042 922 ): Promise<GenericResponse> {
c0560973 923 const transactionId = commandPayload.transactionId;
08f130a0 924 for (const connectorId of chargingStation.connectors.keys()) {
e7aeea18
JB
925 if (
926 connectorId > 0 &&
08f130a0 927 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
e7aeea18 928 ) {
08f130a0 929 await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
930 OCPP16StatusNotificationRequest,
931 OCPP16StatusNotificationResponse
08f130a0 932 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
ef6fa3fb
JB
933 connectorId,
934 status: OCPP16ChargePointStatus.FINISHING,
935 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
936 });
08f130a0 937 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
5e3cb728
JB
938 const stopResponse = await chargingStation.stopTransactionOnConnector(
939 connectorId,
a65319ba 940 OCPP16StopTransactionReason.REMOTE
5e3cb728
JB
941 );
942 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
bf53cadf 943 return OCPPConstants.OCPP_RESPONSE_ACCEPTED;
ef6fa3fb 944 }
bf53cadf 945 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973
JB
946 }
947 }
44b9b577 948 logger.warn(
44eb6026 949 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction ${transactionId.toString()}`
e7aeea18 950 );
bf53cadf 951 return OCPPConstants.OCPP_RESPONSE_REJECTED;
c0560973 952 }
47e22477 953
b03df580
JB
954 private handleRequestUpdateFirmware(
955 chargingStation: ChargingStation,
956 commandPayload: OCPP16UpdateFirmwareRequest
957 ): OCPP16UpdateFirmwareResponse {
958 if (
959 OCPP16ServiceUtils.checkFeatureProfile(
960 chargingStation,
961 OCPP16SupportedFeatureProfiles.FirmwareManagement,
962 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
963 ) === false
964 ) {
5d280aae 965 logger.warn(
90293abb 966 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
5d280aae
JB
967 );
968 return OCPPConstants.OCPP_RESPONSE_EMPTY;
969 }
970 if (
971 !Utils.isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
972 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
973 ) {
974 logger.warn(
90293abb 975 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
5d280aae 976 );
bf53cadf 977 return OCPPConstants.OCPP_RESPONSE_EMPTY;
b03df580 978 }
c9a4f9ea 979 const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate);
2c7bdc61 980 const now = Date.now();
72092cfc 981 if (retrieveDate?.getTime() <= now) {
27f08ad3
JB
982 this.runInAsyncScope(
983 this.updateFirmware.bind(this) as (
984 this: OCPP16IncomingRequestService,
985 ...args: any[]
986 ) => Promise<void>,
987 this,
988 chargingStation
989 ).catch(() => {
990 /* This is intentional */
991 });
c9a4f9ea
JB
992 } else {
993 setTimeout(() => {
994 this.updateFirmware(chargingStation).catch(() => {
995 /* Intentional */
996 });
72092cfc 997 }, retrieveDate?.getTime() - now);
c9a4f9ea
JB
998 }
999 return OCPPConstants.OCPP_RESPONSE_EMPTY;
1000 }
1001
1002 private async updateFirmware(
1003 chargingStation: ChargingStation,
90293abb
JB
1004 maxDelay = 30,
1005 minDelay = 15
c9a4f9ea
JB
1006 ): Promise<void> {
1007 chargingStation.stopAutomaticTransactionGenerator();
1008 for (const connectorId of chargingStation.connectors.keys()) {
1009 if (
1010 connectorId > 0 &&
72092cfc 1011 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
c9a4f9ea
JB
1012 ) {
1013 await chargingStation.ocppRequestService.requestHandler<
1014 OCPP16StatusNotificationRequest,
1015 OCPP16StatusNotificationResponse
1016 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
1017 connectorId,
1018 status: OCPP16ChargePointStatus.UNAVAILABLE,
1019 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1020 });
3637ca2c
JB
1021 chargingStation.getConnectorStatus(connectorId).status =
1022 OCPP16ChargePointStatus.UNAVAILABLE;
c9a4f9ea
JB
1023 }
1024 }
5d280aae 1025 if (
15748260 1026 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus &&
5a2a53cf 1027 Utils.isNotEmptyString(chargingStation.stationInfo?.firmwareUpgrade?.failureStatus)
5d280aae
JB
1028 ) {
1029 await chargingStation.ocppRequestService.requestHandler<
1030 OCPP16FirmwareStatusNotificationRequest,
1031 OCPP16FirmwareStatusNotificationResponse
1032 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
15748260 1033 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
5d280aae
JB
1034 });
1035 return;
1036 }
c9a4f9ea
JB
1037 await chargingStation.ocppRequestService.requestHandler<
1038 OCPP16FirmwareStatusNotificationRequest,
1039 OCPP16FirmwareStatusNotificationResponse
1040 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1041 status: OCPP16FirmwareStatus.Downloading,
1042 });
1043 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
90293abb 1044 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
c9a4f9ea
JB
1045 await chargingStation.ocppRequestService.requestHandler<
1046 OCPP16FirmwareStatusNotificationRequest,
1047 OCPP16FirmwareStatusNotificationResponse
1048 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1049 status: OCPP16FirmwareStatus.Downloaded,
1050 });
1051 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
90293abb 1052 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
c9a4f9ea
JB
1053 await chargingStation.ocppRequestService.requestHandler<
1054 OCPP16FirmwareStatusNotificationRequest,
1055 OCPP16FirmwareStatusNotificationResponse
1056 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1057 status: OCPP16FirmwareStatus.Installing,
1058 });
1059 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
15748260 1060 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
90293abb 1061 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
5d280aae
JB
1062 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1063 }
b03df580
JB
1064 }
1065
e7aeea18 1066 private async handleRequestGetDiagnostics(
08f130a0 1067 chargingStation: ChargingStation,
e7aeea18
JB
1068 commandPayload: GetDiagnosticsRequest
1069 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1070 if (
1789ba2c 1071 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1072 chargingStation,
370ae4ee
JB
1073 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1074 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1075 ) === false
68cb8b91 1076 ) {
90293abb
JB
1077 logger.warn(
1078 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1079 );
bf53cadf 1080 return OCPPConstants.OCPP_RESPONSE_EMPTY;
68cb8b91 1081 }
a3868ec4 1082 const uri = new URL(commandPayload.location);
47e22477
JB
1083 if (uri.protocol.startsWith('ftp:')) {
1084 let ftpClient: Client;
1085 try {
e7aeea18 1086 const logFiles = fs
0d8140bd 1087 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
72092cfc
JB
1088 .filter((file) => file.endsWith('.log'))
1089 .map((file) => path.join('./', file));
44eb6026 1090 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
47e22477
JB
1091 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1092 ftpClient = new Client();
1093 const accessResponse = await ftpClient.access({
1094 host: uri.host,
5a2a53cf
JB
1095 ...(Utils.isNotEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1096 ...(Utils.isNotEmptyString(uri.username) && { user: uri.username }),
1097 ...(Utils.isNotEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1098 });
1099 let uploadResponse: FTPResponse;
1100 if (accessResponse.code === 220) {
72092cfc 1101 ftpClient.trackProgress((info) => {
e7aeea18 1102 logger.info(
08f130a0 1103 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1104 info.bytes / 1024
1105 } bytes transferred from diagnostics archive ${info.name}`
1106 );
6a8329b4
JB
1107 chargingStation.ocppRequestService
1108 .requestHandler<
1109 OCPP16DiagnosticsStatusNotificationRequest,
1110 OCPP16DiagnosticsStatusNotificationResponse
1111 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1112 status: OCPP16DiagnosticsStatus.Uploading,
1113 })
72092cfc 1114 .catch((error) => {
6a8329b4
JB
1115 logger.error(
1116 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1117 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1118 }'`,
1119 error
1120 );
1121 });
47e22477 1122 });
e7aeea18 1123 uploadResponse = await ftpClient.uploadFrom(
0d8140bd
JB
1124 path.join(
1125 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1126 diagnosticsArchive
1127 ),
14ecae6a 1128 `${uri.pathname}${diagnosticsArchive}`
e7aeea18 1129 );
47e22477 1130 if (uploadResponse.code === 226) {
08f130a0 1131 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1132 OCPP16DiagnosticsStatusNotificationRequest,
1133 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1134 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1135 status: OCPP16DiagnosticsStatus.Uploaded,
1136 });
47e22477
JB
1137 if (ftpClient) {
1138 ftpClient.close();
1139 }
1140 return { fileName: diagnosticsArchive };
1141 }
e7aeea18
JB
1142 throw new OCPPError(
1143 ErrorType.GENERIC_ERROR,
1144 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1145 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18
JB
1146 }`,
1147 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1148 );
47e22477 1149 }
e7aeea18
JB
1150 throw new OCPPError(
1151 ErrorType.GENERIC_ERROR,
1152 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1153 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18
JB
1154 }`,
1155 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1156 );
47e22477 1157 } catch (error) {
08f130a0 1158 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1159 OCPP16DiagnosticsStatusNotificationRequest,
1160 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1161 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1162 status: OCPP16DiagnosticsStatus.UploadFailed,
1163 });
47e22477
JB
1164 if (ftpClient) {
1165 ftpClient.close();
1166 }
e7aeea18 1167 return this.handleIncomingRequestError(
08f130a0 1168 chargingStation,
e7aeea18
JB
1169 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1170 error as Error,
bf53cadf 1171 { errorResponse: OCPPConstants.OCPP_RESPONSE_EMPTY }
e7aeea18 1172 );
47e22477
JB
1173 }
1174 } else {
e7aeea18 1175 logger.error(
08f130a0 1176 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1177 uri.protocol
1178 } to transfer the diagnostic logs archive`
1179 );
08f130a0 1180 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1181 OCPP16DiagnosticsStatusNotificationRequest,
1182 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1183 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1184 status: OCPP16DiagnosticsStatus.UploadFailed,
1185 });
bf53cadf 1186 return OCPPConstants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1187 }
1188 }
802cfa13 1189
e7aeea18 1190 private handleRequestTriggerMessage(
08f130a0 1191 chargingStation: ChargingStation,
e7aeea18
JB
1192 commandPayload: OCPP16TriggerMessageRequest
1193 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1194 if (
1195 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1196 chargingStation,
370ae4ee
JB
1197 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1198 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1199 ) ||
1200 !OCPP16ServiceUtils.isMessageTriggerSupported(
1201 chargingStation,
1202 commandPayload.requestedMessage
370ae4ee
JB
1203 )
1204 ) {
bf53cadf 1205 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1206 }
c60ed4b8 1207 if (
4caa7e67 1208 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1209 chargingStation,
1210 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1211 commandPayload.connectorId
1212 )
1213 ) {
bf53cadf 1214 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1215 }
802cfa13
JB
1216 try {
1217 switch (commandPayload.requestedMessage) {
c60ed4b8 1218 case OCPP16MessageTrigger.BootNotification:
802cfa13 1219 setTimeout(() => {
08f130a0 1220 chargingStation.ocppRequestService
f7f98c68 1221 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1222 chargingStation,
6a8b180d 1223 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1224 chargingStation.bootNotificationRequest,
6a8b180d 1225 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1226 )
72092cfc 1227 .then((response) => {
8bfbc743 1228 chargingStation.bootNotificationResponse = response;
ae711c83 1229 })
e7aeea18
JB
1230 .catch(() => {
1231 /* This is intentional */
1232 });
802cfa13 1233 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1234 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1235 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1236 setTimeout(() => {
08f130a0 1237 chargingStation.ocppRequestService
f7f98c68 1238 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1239 chargingStation,
ef6fa3fb
JB
1240 OCPP16RequestCommand.HEARTBEAT,
1241 null,
1242 {
1243 triggerMessage: true,
1244 }
1245 )
e7aeea18
JB
1246 .catch(() => {
1247 /* This is intentional */
1248 });
802cfa13 1249 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1250 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1251 case OCPP16MessageTrigger.StatusNotification:
dc661702 1252 setTimeout(() => {
d812bdcb 1253 if (!Utils.isNullOrUndefined(commandPayload?.connectorId)) {
08f130a0 1254 chargingStation.ocppRequestService
dc661702 1255 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1256 chargingStation,
dc661702
JB
1257 OCPP16RequestCommand.STATUS_NOTIFICATION,
1258 {
1259 connectorId: commandPayload.connectorId,
1260 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
72092cfc 1261 status: chargingStation.getConnectorStatus(commandPayload.connectorId)?.status,
dc661702
JB
1262 },
1263 {
1264 triggerMessage: true,
1265 }
1266 )
1267 .catch(() => {
1268 /* This is intentional */
1269 });
1270 } else {
08f130a0
JB
1271 for (const connectorId of chargingStation.connectors.keys()) {
1272 chargingStation.ocppRequestService
dc661702
JB
1273 .requestHandler<
1274 OCPP16StatusNotificationRequest,
1275 OCPP16StatusNotificationResponse
1276 >(
08f130a0 1277 chargingStation,
dc661702
JB
1278 OCPP16RequestCommand.STATUS_NOTIFICATION,
1279 {
1280 connectorId,
1281 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
72092cfc 1282 status: chargingStation.getConnectorStatus(connectorId)?.status,
dc661702
JB
1283 },
1284 {
1285 triggerMessage: true,
1286 }
1287 )
1288 .catch(() => {
1289 /* This is intentional */
1290 });
1291 }
1292 }
1293 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
bf53cadf 1294 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1295 default:
bf53cadf 1296 return OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1297 }
1298 } catch (error) {
e7aeea18 1299 return this.handleIncomingRequestError(
08f130a0 1300 chargingStation,
e7aeea18
JB
1301 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1302 error as Error,
bf53cadf 1303 { errorResponse: OCPPConstants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1304 );
802cfa13
JB
1305 }
1306 }
77b95a89
JB
1307
1308 private handleRequestDataTransfer(
1309 chargingStation: ChargingStation,
1310 commandPayload: OCPP16DataTransferRequest
1311 ): OCPP16DataTransferResponse {
1312 try {
1313 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1314 return {
1315 status: OCPP16DataTransferStatus.ACCEPTED,
1316 };
1317 }
1318 return {
1319 status: OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID,
1320 };
1321 } catch (error) {
1322 return this.handleIncomingRequestError(
1323 chargingStation,
1324 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1325 error as Error,
bf53cadf 1326 { errorResponse: OCPPConstants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1327 );
1328 }
1329 }
c0560973 1330}