refactor: split Utils static methods class into functions
[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
d972af76
JB
3import { createWriteStream, readdirSync } from 'node:fs';
4import { dirname, join, resolve } from 'node:path';
130783a7 5import { URL, fileURLToPath } from 'node:url';
8114d10e 6
6c1761d4 7import type { JSONSchemaType } from 'ajv';
27782dbc 8import { Client, type FTPResponse } from 'basic-ftp';
d972af76 9import { create } from 'tar';
8114d10e 10
4c3c0d59
JB
11import { OCPP16Constants } from './OCPP16Constants';
12import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
2896e06d
JB
13import {
14 type ChargingStation,
15 ChargingStationConfigurationUtils,
16 ChargingStationUtils,
17} from '../../../charging-station';
268a74bb 18import { OCPPError } from '../../../exception';
e7aeea18 19import {
27782dbc 20 type ChangeAvailabilityRequest,
268a74bb 21 type ChangeAvailabilityResponse,
27782dbc 22 type ChangeConfigurationRequest,
268a74bb 23 type ChangeConfigurationResponse,
27782dbc 24 type ClearChargingProfileRequest,
268a74bb 25 type ClearChargingProfileResponse,
4334db72 26 type ConnectorStatus,
268a74bb
JB
27 ErrorType,
28 type GenericResponse,
41189456 29 GenericStatus,
27782dbc 30 type GetConfigurationRequest,
268a74bb 31 type GetConfigurationResponse,
27782dbc 32 type GetDiagnosticsRequest,
268a74bb
JB
33 type GetDiagnosticsResponse,
34 type IncomingRequestHandler,
35 type JsonObject,
36 type JsonType,
37 OCPP16AuthorizationStatus,
e7aeea18 38 OCPP16AvailabilityType,
27782dbc 39 type OCPP16BootNotificationRequest,
268a74bb 40 type OCPP16BootNotificationResponse,
66dd3447 41 type OCPP16CancelReservationRequest,
268a74bb
JB
42 OCPP16ChargePointErrorCode,
43 OCPP16ChargePointStatus,
44 type OCPP16ChargingProfile,
0ac97927 45 OCPP16ChargingProfilePurposeType,
41189456 46 type OCPP16ChargingSchedule,
27782dbc
JB
47 type OCPP16ClearCacheRequest,
48 type OCPP16DataTransferRequest,
268a74bb 49 type OCPP16DataTransferResponse,
77b95a89 50 OCPP16DataTransferVendorId,
268a74bb 51 OCPP16DiagnosticsStatus,
c9a4f9ea 52 type OCPP16DiagnosticsStatusNotificationRequest,
268a74bb 53 type OCPP16DiagnosticsStatusNotificationResponse,
c9a4f9ea
JB
54 OCPP16FirmwareStatus,
55 type OCPP16FirmwareStatusNotificationRequest,
268a74bb 56 type OCPP16FirmwareStatusNotificationResponse,
41189456
JB
57 type OCPP16GetCompositeScheduleRequest,
58 type OCPP16GetCompositeScheduleResponse,
27782dbc 59 type OCPP16HeartbeatRequest,
268a74bb 60 type OCPP16HeartbeatResponse,
e7aeea18 61 OCPP16IncomingRequestCommand,
c60ed4b8 62 OCPP16MessageTrigger,
94a464f9 63 OCPP16RequestCommand,
66dd3447
JB
64 type OCPP16ReserveNowRequest,
65 type OCPP16ReserveNowResponse,
268a74bb
JB
66 OCPP16StandardParametersKey,
67 type OCPP16StartTransactionRequest,
68 type OCPP16StartTransactionResponse,
27782dbc 69 type OCPP16StatusNotificationRequest,
268a74bb
JB
70 type OCPP16StatusNotificationResponse,
71 OCPP16StopTransactionReason,
72 OCPP16SupportedFeatureProfiles,
27782dbc 73 type OCPP16TriggerMessageRequest,
268a74bb 74 type OCPP16TriggerMessageResponse,
27782dbc 75 type OCPP16UpdateFirmwareRequest,
268a74bb
JB
76 type OCPP16UpdateFirmwareResponse,
77 type OCPPConfigurationKey,
78 OCPPVersion,
27782dbc
JB
79 type RemoteStartTransactionRequest,
80 type RemoteStopTransactionRequest,
66dd3447
JB
81 ReservationFilterKey,
82 ReservationTerminationReason,
27782dbc
JB
83 type ResetRequest,
84 type SetChargingProfileRequest,
27782dbc 85 type SetChargingProfileResponse,
66dd3447 86 type StartTransactionRequest,
268a74bb 87 type UnlockConnectorRequest,
27782dbc 88 type UnlockConnectorResponse,
268a74bb 89} from '../../../types';
9bf0ef23
JB
90import {
91 Constants,
92 convertToDate,
93 convertToInt,
94 formatDurationMilliSeconds,
95 getRandomInteger,
96 isEmptyArray,
97 isNotEmptyArray,
98 isNotEmptyString,
99 isNullOrUndefined,
100 isUndefined,
101 logger,
102 sleep,
103} from '../../../utils';
4c3c0d59 104import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService';
c0560973 105
2a115f87 106const moduleName = 'OCPP16IncomingRequestService';
909dcf2d 107
268a74bb 108export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
b3fc3ff5 109 protected jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
58144adb
JB
110 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
111
08f130a0 112 public constructor() {
b768993d
JB
113 // if (new.target?.name === moduleName) {
114 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
115 // }
d270cc87 116 super(OCPPVersion.VERSION_16);
58144adb
JB
117 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
118 [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)],
119 [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
120 [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)],
e7aeea18
JB
121 [
122 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
123 this.handleRequestGetConfiguration.bind(this),
124 ],
125 [
126 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
127 this.handleRequestChangeConfiguration.bind(this),
128 ],
41189456
JB
129 [
130 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
131 this.handleRequestGetCompositeSchedule.bind(this),
132 ],
e7aeea18
JB
133 [
134 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
135 this.handleRequestSetChargingProfile.bind(this),
136 ],
137 [
138 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
139 this.handleRequestClearChargingProfile.bind(this),
140 ],
141 [
142 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
143 this.handleRequestChangeAvailability.bind(this),
144 ],
145 [
146 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
147 this.handleRequestRemoteStartTransaction.bind(this),
148 ],
149 [
150 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
151 this.handleRequestRemoteStopTransaction.bind(this),
152 ],
734d790d 153 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
e7aeea18 154 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
77b95a89 155 [OCPP16IncomingRequestCommand.DATA_TRANSFER, this.handleRequestDataTransfer.bind(this)],
c9a4f9ea 156 [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)],
d193a949
JB
157 [OCPP16IncomingRequestCommand.RESERVE_NOW, this.handleRequestReserveNow.bind(this)],
158 [
159 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
160 this.handleRequestCancelReservation.bind(this),
161 ],
58144adb 162 ]);
b52c969d
JB
163 this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
164 [
165 OCPP16IncomingRequestCommand.RESET,
130783a7 166 OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
51022aa0 167 'assets/json-schemas/ocpp/1.6/Reset.json',
1b271a54
JB
168 moduleName,
169 'constructor'
130783a7 170 ),
b52c969d
JB
171 ],
172 [
173 OCPP16IncomingRequestCommand.CLEAR_CACHE,
130783a7 174 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
51022aa0 175 'assets/json-schemas/ocpp/1.6/ClearCache.json',
1b271a54
JB
176 moduleName,
177 'constructor'
e9a4164c 178 ),
b52c969d
JB
179 ],
180 [
181 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
130783a7 182 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
51022aa0 183 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
1b271a54
JB
184 moduleName,
185 'constructor'
e9a4164c 186 ),
b52c969d
JB
187 ],
188 [
189 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
130783a7 190 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
51022aa0 191 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
1b271a54
JB
192 moduleName,
193 'constructor'
e9a4164c 194 ),
b52c969d
JB
195 ],
196 [
197 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
130783a7 198 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
51022aa0 199 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
1b271a54
JB
200 moduleName,
201 'constructor'
e9a4164c 202 ),
b52c969d
JB
203 ],
204 [
205 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
130783a7 206 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
51022aa0 207 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
1b271a54
JB
208 moduleName,
209 'constructor'
e9a4164c 210 ),
b52c969d 211 ],
41189456
JB
212 [
213 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
214 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
51022aa0 215 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
41189456
JB
216 moduleName,
217 'constructor'
218 ),
219 ],
b52c969d
JB
220 [
221 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
130783a7 222 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
51022aa0 223 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
1b271a54
JB
224 moduleName,
225 'constructor'
e9a4164c 226 ),
b52c969d
JB
227 ],
228 [
229 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
130783a7 230 OCPP16ServiceUtils.parseJsonSchemaFile<ClearChargingProfileRequest>(
51022aa0 231 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
1b271a54
JB
232 moduleName,
233 'constructor'
e9a4164c 234 ),
b52c969d
JB
235 ],
236 [
237 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
130783a7 238 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeAvailabilityRequest>(
51022aa0 239 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
1b271a54
JB
240 moduleName,
241 'constructor'
e9a4164c 242 ),
b52c969d
JB
243 ],
244 [
245 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
130783a7 246 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
51022aa0 247 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
1b271a54
JB
248 moduleName,
249 'constructor'
e9a4164c 250 ),
b52c969d
JB
251 ],
252 [
253 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
130783a7 254 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
51022aa0 255 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
1b271a54
JB
256 moduleName,
257 'constructor'
e9a4164c 258 ),
b52c969d
JB
259 ],
260 [
261 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
130783a7 262 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
51022aa0 263 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
1b271a54
JB
264 moduleName,
265 'constructor'
e9a4164c 266 ),
b52c969d 267 ],
77b95a89
JB
268 [
269 OCPP16IncomingRequestCommand.DATA_TRANSFER,
130783a7 270 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
51022aa0 271 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
1b271a54
JB
272 moduleName,
273 'constructor'
e9a4164c 274 ),
77b95a89 275 ],
bfbda738
JB
276 [
277 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
130783a7 278 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
51022aa0 279 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
1b271a54
JB
280 moduleName,
281 'constructor'
e9a4164c 282 ),
bfbda738 283 ],
d193a949
JB
284 [
285 OCPP16IncomingRequestCommand.RESERVE_NOW,
286 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
287 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
288 moduleName,
289 'constructor'
290 ),
291 ],
292 [
293 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
294 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
295 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
296 moduleName,
297 'constructor'
298 ),
299 ],
b52c969d 300 ]);
31f59c6d
JB
301 this.validatePayload = this.validatePayload.bind(this) as (
302 chargingStation: ChargingStation,
303 commandName: OCPP16IncomingRequestCommand,
304 commandPayload: JsonType
305 ) => boolean;
58144adb
JB
306 }
307
f7f98c68 308 public async incomingRequestHandler(
08f130a0 309 chargingStation: ChargingStation,
e7aeea18
JB
310 messageId: string,
311 commandName: OCPP16IncomingRequestCommand,
5cc4b63b 312 commandPayload: JsonType
e7aeea18 313 ): Promise<void> {
5cc4b63b 314 let response: JsonType;
e7aeea18 315 if (
3a13fc92 316 chargingStation.getOcppStrictCompliance() === true &&
f7c2994d 317 chargingStation.inPendingState() === true &&
e7aeea18
JB
318 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
319 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
320 ) {
321 throw new OCPPError(
322 ErrorType.SECURITY_ERROR,
e3018bc4 323 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18
JB
324 commandPayload,
325 null,
326 2
327 )} while the charging station is in pending state on the central server`,
7369e417
JB
328 commandName,
329 commandPayload
e7aeea18 330 );
caad9d6b 331 }
e7aeea18 332 if (
ed6cfcff
JB
333 chargingStation.isRegistered() === true ||
334 (chargingStation.getOcppStrictCompliance() === false &&
f7c2994d 335 chargingStation.inUnknownState() === true)
e7aeea18 336 ) {
65554cc3 337 if (
ed6cfcff
JB
338 this.incomingRequestHandlers.has(commandName) === true &&
339 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
65554cc3 340 ) {
124f3553 341 try {
9c5c4195 342 this.validatePayload(chargingStation, commandName, commandPayload);
c75a6675 343 // Call the method to build the response
08f130a0
JB
344 response = await this.incomingRequestHandlers.get(commandName)(
345 chargingStation,
346 commandPayload
347 );
124f3553
JB
348 } catch (error) {
349 // Log
6c8f5d90 350 logger.error(
66dd3447
JB
351 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler:
352 Handle incoming request error:`,
6c8f5d90
JB
353 error
354 );
124f3553
JB
355 throw error;
356 }
357 } else {
358 // Throw exception
e7aeea18
JB
359 throw new OCPPError(
360 ErrorType.NOT_IMPLEMENTED,
e3018bc4 361 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
e7aeea18
JB
362 commandPayload,
363 null,
364 2
365 )}`,
7369e417
JB
366 commandName,
367 commandPayload
e7aeea18 368 );
c0560973
JB
369 }
370 } else {
e7aeea18
JB
371 throw new OCPPError(
372 ErrorType.SECURITY_ERROR,
e3018bc4 373 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
e7aeea18
JB
374 commandPayload,
375 null,
376 2
377 )} while the charging station is not registered on the central server.`,
7369e417
JB
378 commandName,
379 commandPayload
e7aeea18 380 );
c0560973 381 }
c75a6675 382 // Send the built response
08f130a0
JB
383 await chargingStation.ocppRequestService.sendResponse(
384 chargingStation,
385 messageId,
386 response,
387 commandName
388 );
c0560973
JB
389 }
390
9c5c4195
JB
391 private validatePayload(
392 chargingStation: ChargingStation,
393 commandName: OCPP16IncomingRequestCommand,
394 commandPayload: JsonType
395 ): boolean {
45988780 396 if (this.jsonSchemas.has(commandName) === true) {
9c5c4195
JB
397 return this.validateIncomingRequestPayload(
398 chargingStation,
399 commandName,
400 this.jsonSchemas.get(commandName),
401 commandPayload
402 );
403 }
404 logger.warn(
66dd3447
JB
405 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found
406 for command '${commandName}' PDU validation`
9c5c4195
JB
407 );
408 return false;
409 }
410
c0560973 411 // Simulate charging station restart
08f130a0
JB
412 private handleRequestReset(
413 chargingStation: ChargingStation,
414 commandPayload: ResetRequest
f03e1042 415 ): GenericResponse {
27f08ad3
JB
416 this.runInAsyncScope(
417 chargingStation.reset.bind(chargingStation) as (
418 this: ChargingStation,
419 ...args: any[]
420 ) => Promise<void>,
421 chargingStation,
422 `${commandPayload.type}Reset` as OCPP16StopTransactionReason
59b6ed8d 423 ).catch(Constants.EMPTY_FUNCTION);
e7aeea18 424 logger.info(
08f130a0 425 `${chargingStation.logPrefix()} ${
e7aeea18 426 commandPayload.type
66dd3447 427 } reset command received, simulating it. The station will be
9bf0ef23 428 back online in ${formatDurationMilliSeconds(chargingStation.stationInfo.resetTime)}`
e7aeea18 429 );
d8b1fab1 430 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
c0560973
JB
431 }
432
e7aeea18 433 private async handleRequestUnlockConnector(
08f130a0 434 chargingStation: ChargingStation,
e7aeea18
JB
435 commandPayload: UnlockConnectorRequest
436 ): Promise<UnlockConnectorResponse> {
c0560973 437 const connectorId = commandPayload.connectorId;
a14022a2 438 if (chargingStation.hasConnector(connectorId) === false) {
c60ed4b8 439 logger.error(
66dd3447
JB
440 `${chargingStation.logPrefix()} Trying to unlock a non existing
441 connector id ${connectorId.toString()}`
c60ed4b8 442 );
d8b1fab1 443 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c60ed4b8 444 }
c0560973 445 if (connectorId === 0) {
e7aeea18 446 logger.error(
2585c6e9 447 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`
e7aeea18 448 );
d8b1fab1 449 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c0560973 450 }
5e3cb728
JB
451 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
452 const stopResponse = await chargingStation.stopTransactionOnConnector(
453 connectorId,
454 OCPP16StopTransactionReason.UNLOCK_COMMAND
455 );
c0560973 456 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
d8b1fab1 457 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
c0560973 458 }
d8b1fab1 459 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED;
c0560973 460 }
4ecff7ce
JB
461 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
462 chargingStation,
ef6fa3fb 463 connectorId,
4ecff7ce
JB
464 OCPP16ChargePointStatus.Available
465 );
d8b1fab1 466 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
c0560973
JB
467 }
468
e7aeea18 469 private handleRequestGetConfiguration(
08f130a0 470 chargingStation: ChargingStation,
e7aeea18
JB
471 commandPayload: GetConfigurationRequest
472 ): GetConfigurationResponse {
c0560973
JB
473 const configurationKey: OCPPConfigurationKey[] = [];
474 const unknownKey: string[] = [];
9bf0ef23 475 if (isUndefined(commandPayload.key) === true) {
08f130a0 476 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
9bf0ef23 477 if (isUndefined(configuration.visible) === true) {
7f7b65ca 478 configuration.visible = true;
c0560973 479 }
da8629bb 480 if (configuration.visible === false) {
c0560973
JB
481 continue;
482 }
483 configurationKey.push({
7f7b65ca
JB
484 key: configuration.key,
485 readonly: configuration.readonly,
486 value: configuration.value,
c0560973
JB
487 });
488 }
9bf0ef23 489 } else if (isNotEmptyArray(commandPayload.key) === true) {
c0560973 490 for (const key of commandPayload.key) {
17ac262c
JB
491 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
492 chargingStation,
acda9cab
JB
493 key,
494 true
17ac262c 495 );
c0560973 496 if (keyFound) {
9bf0ef23 497 if (isUndefined(keyFound.visible) === true) {
c0560973
JB
498 keyFound.visible = true;
499 }
a723e7e9 500 if (keyFound.visible === false) {
c0560973
JB
501 continue;
502 }
503 configurationKey.push({
504 key: keyFound.key,
505 readonly: keyFound.readonly,
506 value: keyFound.value,
507 });
508 } else {
509 unknownKey.push(key);
510 }
511 }
512 }
513 return {
514 configurationKey,
515 unknownKey,
516 };
517 }
518
e7aeea18 519 private handleRequestChangeConfiguration(
08f130a0 520 chargingStation: ChargingStation,
e7aeea18
JB
521 commandPayload: ChangeConfigurationRequest
522 ): ChangeConfigurationResponse {
17ac262c
JB
523 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
524 chargingStation,
525 commandPayload.key,
526 true
527 );
c0560973 528 if (!keyToChange) {
d8b1fab1 529 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
bd5d98e0 530 } else if (keyToChange?.readonly === true) {
d8b1fab1 531 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
bd5d98e0 532 } else if (keyToChange?.readonly === false) {
c0560973 533 let valueChanged = false;
a95873d8 534 if (keyToChange.value !== commandPayload.value) {
17ac262c
JB
535 ChargingStationConfigurationUtils.setConfigurationKeyValue(
536 chargingStation,
537 commandPayload.key,
538 commandPayload.value,
539 true
540 );
c0560973
JB
541 valueChanged = true;
542 }
543 let triggerHeartbeatRestart = false;
544 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
17ac262c
JB
545 ChargingStationConfigurationUtils.setConfigurationKeyValue(
546 chargingStation,
e7aeea18
JB
547 OCPP16StandardParametersKey.HeartbeatInterval,
548 commandPayload.value
549 );
c0560973
JB
550 triggerHeartbeatRestart = true;
551 }
552 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
553 ChargingStationConfigurationUtils.setConfigurationKeyValue(
554 chargingStation,
e7aeea18
JB
555 OCPP16StandardParametersKey.HeartBeatInterval,
556 commandPayload.value
557 );
c0560973
JB
558 triggerHeartbeatRestart = true;
559 }
560 if (triggerHeartbeatRestart) {
08f130a0 561 chargingStation.restartHeartbeat();
c0560973
JB
562 }
563 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
08f130a0 564 chargingStation.restartWebSocketPing();
c0560973
JB
565 }
566 if (keyToChange.reboot) {
d8b1fab1 567 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
c0560973 568 }
d8b1fab1 569 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
c0560973
JB
570 }
571 }
572
e7aeea18 573 private handleRequestSetChargingProfile(
08f130a0 574 chargingStation: ChargingStation,
e7aeea18
JB
575 commandPayload: SetChargingProfileRequest
576 ): SetChargingProfileResponse {
370ae4ee 577 if (
1789ba2c 578 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 579 chargingStation,
370ae4ee
JB
580 OCPP16SupportedFeatureProfiles.SmartCharging,
581 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
1789ba2c 582 ) === false
370ae4ee 583 ) {
d8b1fab1 584 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
68cb8b91 585 }
a14022a2 586 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
e7aeea18 587 logger.error(
66dd3447
JB
588 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
589 non existing connector id ${commandPayload.connectorId}`
e7aeea18 590 );
d8b1fab1 591 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 592 }
e7aeea18
JB
593 if (
594 commandPayload.csChargingProfiles.chargingProfilePurpose ===
0ac97927 595 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
e7aeea18
JB
596 commandPayload.connectorId !== 0
597 ) {
d8b1fab1 598 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 599 }
e7aeea18
JB
600 if (
601 commandPayload.csChargingProfiles.chargingProfilePurpose ===
0ac97927 602 OCPP16ChargingProfilePurposeType.TX_PROFILE &&
e7aeea18 603 (commandPayload.connectorId === 0 ||
5e3cb728
JB
604 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
605 false)
e7aeea18 606 ) {
db0af086 607 logger.error(
66dd3447
JB
608 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
609 on connector ${commandPayload.connectorId} without a started transaction`
db0af086 610 );
d8b1fab1 611 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 612 }
ed3d2808 613 OCPP16ServiceUtils.setChargingProfile(
7cb5b17f 614 chargingStation,
e7aeea18
JB
615 commandPayload.connectorId,
616 commandPayload.csChargingProfiles
617 );
618 logger.debug(
08f130a0 619 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7 620 commandPayload.connectorId
ad67a158
JB
621 }: %j`,
622 commandPayload.csChargingProfiles
e7aeea18 623 );
d8b1fab1 624 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
625 }
626
41189456
JB
627 private handleRequestGetCompositeSchedule(
628 chargingStation: ChargingStation,
629 commandPayload: OCPP16GetCompositeScheduleRequest
630 ): OCPP16GetCompositeScheduleResponse {
631 if (
632 OCPP16ServiceUtils.checkFeatureProfile(
633 chargingStation,
634 OCPP16SupportedFeatureProfiles.SmartCharging,
635 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
636 ) === false
637 ) {
d8b1fab1 638 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456 639 }
a14022a2 640 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
41189456 641 logger.error(
66dd3447
JB
642 `${chargingStation.logPrefix()} Trying to get composite schedule to a
643 non existing connector id ${commandPayload.connectorId}`
41189456 644 );
d8b1fab1 645 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456
JB
646 }
647 if (
9bf0ef23 648 isEmptyArray(chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles)
41189456 649 ) {
d8b1fab1 650 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456
JB
651 }
652 const startDate = new Date();
653 const endDate = new Date(startDate.getTime() + commandPayload.duration * 1000);
654 let compositeSchedule: OCPP16ChargingSchedule;
655 for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId)
656 .chargingProfiles) {
657 // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc.
658 if (
659 chargingProfile.chargingSchedule?.startSchedule >= startDate &&
660 chargingProfile.chargingSchedule?.startSchedule <= endDate
661 ) {
662 compositeSchedule = chargingProfile.chargingSchedule;
663 break;
664 }
665 }
666 return {
667 status: GenericStatus.Accepted,
668 scheduleStart: compositeSchedule?.startSchedule,
669 connectorId: commandPayload.connectorId,
670 chargingSchedule: compositeSchedule,
671 };
672 }
673
e7aeea18 674 private handleRequestClearChargingProfile(
08f130a0 675 chargingStation: ChargingStation,
e7aeea18
JB
676 commandPayload: ClearChargingProfileRequest
677 ): ClearChargingProfileResponse {
370ae4ee 678 if (
a36bad10 679 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 680 chargingStation,
370ae4ee
JB
681 OCPP16SupportedFeatureProfiles.SmartCharging,
682 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
a36bad10 683 ) === false
370ae4ee 684 ) {
d8b1fab1 685 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
68cb8b91 686 }
a14022a2 687 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
e7aeea18 688 logger.error(
66dd3447
JB
689 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
690 a non existing connector id ${commandPayload.connectorId}`
e7aeea18 691 );
d8b1fab1 692 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973 693 }
d812bdcb 694 if (
9bf0ef23
JB
695 !isNullOrUndefined(commandPayload.connectorId) &&
696 isNotEmptyArray(
4334db72
JB
697 chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles
698 )
d812bdcb 699 ) {
4334db72 700 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles = [];
e7aeea18 701 logger.debug(
08f130a0 702 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
ad8537a7 703 commandPayload.connectorId
ad67a158 704 }`
e7aeea18 705 );
d8b1fab1 706 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973 707 }
9bf0ef23 708 if (isNullOrUndefined(commandPayload.connectorId)) {
c0560973 709 let clearedCP = false;
4334db72 710 const clearChargingProfiles = (connectorStatus: ConnectorStatus) => {
9bf0ef23 711 if (isNotEmptyArray(connectorStatus?.chargingProfiles)) {
4334db72
JB
712 connectorStatus?.chargingProfiles?.forEach(
713 (chargingProfile: OCPP16ChargingProfile, index: number) => {
e7aeea18
JB
714 let clearCurrentCP = false;
715 if (chargingProfile.chargingProfileId === commandPayload.id) {
716 clearCurrentCP = true;
717 }
718 if (
719 !commandPayload.chargingProfilePurpose &&
720 chargingProfile.stackLevel === commandPayload.stackLevel
721 ) {
722 clearCurrentCP = true;
723 }
724 if (
725 !chargingProfile.stackLevel &&
726 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
727 ) {
728 clearCurrentCP = true;
729 }
730 if (
731 chargingProfile.stackLevel === commandPayload.stackLevel &&
732 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
733 ) {
734 clearCurrentCP = true;
735 }
736 if (clearCurrentCP) {
72092cfc 737 connectorStatus?.chargingProfiles?.splice(index, 1);
e7aeea18 738 logger.debug(
ad67a158
JB
739 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
740 chargingProfile
e7aeea18
JB
741 );
742 clearedCP = true;
743 }
4334db72
JB
744 }
745 );
746 }
747 };
748 if (chargingStation.hasEvses) {
749 for (const evseStatus of chargingStation.evses.values()) {
750 for (const connectorStatus of evseStatus.connectors.values()) {
751 clearChargingProfiles(connectorStatus);
752 }
753 }
754 } else {
755 for (const connectorId of chargingStation.connectors.keys()) {
756 clearChargingProfiles(chargingStation.getConnectorStatus(connectorId));
c0560973
JB
757 }
758 }
759 if (clearedCP) {
d8b1fab1 760 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
761 }
762 }
d8b1fab1 763 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973
JB
764 }
765
e7aeea18 766 private async handleRequestChangeAvailability(
08f130a0 767 chargingStation: ChargingStation,
e7aeea18
JB
768 commandPayload: ChangeAvailabilityRequest
769 ): Promise<ChangeAvailabilityResponse> {
c0560973 770 const connectorId: number = commandPayload.connectorId;
a14022a2 771 if (chargingStation.hasConnector(connectorId) === false) {
e7aeea18 772 logger.error(
66dd3447
JB
773 `${chargingStation.logPrefix()} Trying to change the availability of a
774 non existing connector id ${connectorId.toString()}`
e7aeea18 775 );
d8b1fab1 776 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973 777 }
e7aeea18 778 const chargePointStatus: OCPP16ChargePointStatus =
0d6f335f 779 commandPayload.type === OCPP16AvailabilityType.Operative
721646e9
JB
780 ? OCPP16ChargePointStatus.Available
781 : OCPP16ChargePointStatus.Unavailable;
c0560973 782 if (connectorId === 0) {
d8b1fab1
JB
783 let response: ChangeAvailabilityResponse =
784 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
ded57f02
JB
785 const changeAvailability = async (id: number, connectorStatus: ConnectorStatus) => {
786 if (connectorStatus?.transactionStarted === true) {
d8b1fab1 787 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 788 }
ded57f02 789 connectorStatus.availability = commandPayload.type;
d8b1fab1 790 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
4ecff7ce
JB
791 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
792 chargingStation,
793 id,
794 chargePointStatus
795 );
c0560973 796 }
ded57f02
JB
797 };
798 if (chargingStation.hasEvses) {
799 for (const evseStatus of chargingStation.evses.values()) {
800 for (const [id, connectorStatus] of evseStatus.connectors) {
801 await changeAvailability(id, connectorStatus);
802 }
803 }
804 } else {
805 for (const id of chargingStation.connectors.keys()) {
806 await changeAvailability(id, chargingStation.getConnectorStatus(id));
807 }
c0560973
JB
808 }
809 return response;
e7aeea18
JB
810 } else if (
811 connectorId > 0 &&
56eb297e
JB
812 (chargingStation.isChargingStationAvailable() === true ||
813 (chargingStation.isChargingStationAvailable() === false &&
0d6f335f 814 commandPayload.type === OCPP16AvailabilityType.Inoperative))
e7aeea18 815 ) {
5e3cb728 816 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
08f130a0 817 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
d8b1fab1 818 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 819 }
08f130a0 820 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
4ecff7ce
JB
821 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
822 chargingStation,
ef6fa3fb 823 connectorId,
4ecff7ce
JB
824 chargePointStatus
825 );
d8b1fab1 826 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 827 }
d8b1fab1 828 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
829 }
830
e7aeea18 831 private async handleRequestRemoteStartTransaction(
08f130a0 832 chargingStation: ChargingStation,
e7aeea18 833 commandPayload: RemoteStartTransactionRequest
f03e1042 834 ): Promise<GenericResponse> {
66dd3447 835 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
d193a949 836 const reserved =
24578c31
JB
837 chargingStation.getConnectorStatus(transactionConnectorId).status ===
838 OCPP16ChargePointStatus.Reserved;
66dd3447
JB
839 const reservedOnConnectorZero =
840 chargingStation.getConnectorStatus(0).status === OCPP16ChargePointStatus.Reserved;
d193a949 841 if (
66dd3447
JB
842 (reserved &&
843 !chargingStation.validateIncomingRequestWithReservation(transactionConnectorId, idTag)) ||
844 (reservedOnConnectorZero && !chargingStation.validateIncomingRequestWithReservation(0, idTag))
d193a949
JB
845 ) {
846 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
847 }
649287f8
JB
848 if (chargingStation.hasConnector(transactionConnectorId) === false) {
849 return this.notifyRemoteStartTransactionRejected(
4ecff7ce
JB
850 chargingStation,
851 transactionConnectorId,
66dd3447 852 idTag
4ecff7ce 853 );
649287f8
JB
854 }
855 if (
d193a949
JB
856 !chargingStation.isChargingStationAvailable() ||
857 !chargingStation.isConnectorAvailable(transactionConnectorId)
649287f8
JB
858 ) {
859 return this.notifyRemoteStartTransactionRejected(
860 chargingStation,
861 transactionConnectorId,
66dd3447 862 idTag
649287f8
JB
863 );
864 }
66dd3447
JB
865 const remoteStartTransactionLogMsg = `
866 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
649287f8 867 chargingStation.stationInfo.chargingStationId
66dd3447 868 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
649287f8
JB
869 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
870 chargingStation,
871 transactionConnectorId,
872 OCPP16ChargePointStatus.Preparing
873 );
874 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
875 // Check if authorized
66dd3447
JB
876 if (
877 chargingStation.getAuthorizeRemoteTxRequests() &&
878 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
879 ) {
880 // Authorization successful, start transaction
881 if (
882 this.setRemoteStartTransactionChargingProfile(
883 chargingStation,
884 transactionConnectorId,
885 chargingProfile
886 ) === true
887 ) {
888 connectorStatus.transactionRemoteStarted = true;
889 const startTransactionPayload: Partial<StartTransactionRequest> = {
890 connectorId: transactionConnectorId,
178956d8 891 idTag,
66dd3447
JB
892 };
893 if (reserved || reservedOnConnectorZero) {
894 const reservation = chargingStation.getReservationBy(
895 ReservationFilterKey.CONNECTOR_ID,
896 reservedOnConnectorZero ? 0 : transactionConnectorId
897 );
898 startTransactionPayload.reservationId = reservation.id;
899 await chargingStation.removeReservation(
900 reservation,
901 ReservationTerminationReason.TRANSACTION_STARTED
902 );
903 }
e7aeea18 904 if (
66dd3447
JB
905 (
906 await chargingStation.ocppRequestService.requestHandler<
907 OCPP16StartTransactionRequest,
908 OCPP16StartTransactionResponse
909 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, startTransactionPayload)
910 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
e7aeea18 911 ) {
eb79c525 912 logger.debug(remoteStartTransactionLogMsg);
66dd3447 913 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
e060fe58 914 }
e7aeea18 915 return this.notifyRemoteStartTransactionRejected(
08f130a0 916 chargingStation,
e7aeea18 917 transactionConnectorId,
66dd3447 918 idTag
e7aeea18 919 );
c0560973 920 }
e7aeea18 921 return this.notifyRemoteStartTransactionRejected(
08f130a0 922 chargingStation,
e7aeea18 923 transactionConnectorId,
66dd3447 924 idTag
e7aeea18 925 );
c0560973 926 }
649287f8
JB
927 // No authorization check required, start transaction
928 if (
929 this.setRemoteStartTransactionChargingProfile(
930 chargingStation,
931 transactionConnectorId,
66dd3447 932 chargingProfile
649287f8
JB
933 ) === true
934 ) {
935 connectorStatus.transactionRemoteStarted = true;
936 if (
937 (
938 await chargingStation.ocppRequestService.requestHandler<
939 OCPP16StartTransactionRequest,
940 OCPP16StartTransactionResponse
941 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
942 connectorId: transactionConnectorId,
66dd3447 943 idTag,
649287f8
JB
944 })
945 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
946 ) {
eb79c525 947 logger.debug(remoteStartTransactionLogMsg);
649287f8
JB
948 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
949 }
950 return this.notifyRemoteStartTransactionRejected(
951 chargingStation,
952 transactionConnectorId,
66dd3447 953 idTag
649287f8
JB
954 );
955 }
08f130a0
JB
956 return this.notifyRemoteStartTransactionRejected(
957 chargingStation,
958 transactionConnectorId,
66dd3447 959 idTag
08f130a0 960 );
a7fc8211
JB
961 }
962
e7aeea18 963 private async notifyRemoteStartTransactionRejected(
08f130a0 964 chargingStation: ChargingStation,
e7aeea18
JB
965 connectorId: number,
966 idTag: string
f03e1042 967 ): Promise<GenericResponse> {
e7aeea18 968 if (
721646e9 969 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
e7aeea18 970 ) {
4ecff7ce
JB
971 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
972 chargingStation,
ef6fa3fb 973 connectorId,
4ecff7ce
JB
974 OCPP16ChargePointStatus.Available
975 );
e060fe58 976 }
e7aeea18 977 logger.warn(
66dd3447
JB
978 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
979 ${connectorId.toString()}, idTag '${idTag}', availability '${
72092cfc
JB
980 chargingStation.getConnectorStatus(connectorId)?.availability
981 }', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`
e7aeea18 982 );
d8b1fab1 983 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973
JB
984 }
985
e7aeea18 986 private setRemoteStartTransactionChargingProfile(
08f130a0 987 chargingStation: ChargingStation,
e7aeea18
JB
988 connectorId: number,
989 cp: OCPP16ChargingProfile
990 ): boolean {
0ac97927 991 if (cp && cp.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
ed3d2808 992 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
e7aeea18 993 logger.debug(
66dd3447
JB
994 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
995 on connector id ${connectorId}: %j`,
ad67a158 996 cp
e7aeea18 997 );
a7fc8211 998 return true;
0ac97927 999 } else if (cp && cp.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18 1000 logger.warn(
08f130a0 1001 `${chargingStation.logPrefix()} Not allowed to set ${
e7aeea18
JB
1002 cp.chargingProfilePurpose
1003 } charging profile(s) at remote start transaction`
1004 );
a7fc8211 1005 return false;
e060fe58
JB
1006 } else if (!cp) {
1007 return true;
a7fc8211
JB
1008 }
1009 }
1010
e7aeea18 1011 private async handleRequestRemoteStopTransaction(
08f130a0 1012 chargingStation: ChargingStation,
e7aeea18 1013 commandPayload: RemoteStopTransactionRequest
f03e1042 1014 ): Promise<GenericResponse> {
c0560973 1015 const transactionId = commandPayload.transactionId;
ded57f02
JB
1016 const remoteStopTransaction = async (connectorId: number): Promise<GenericResponse> => {
1017 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1018 chargingStation,
1019 connectorId,
1020 OCPP16ChargePointStatus.Finishing
1021 );
1022 const stopResponse = await chargingStation.stopTransactionOnConnector(
1023 connectorId,
1024 OCPP16StopTransactionReason.REMOTE
1025 );
1026 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
1027 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
1028 }
1029 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1030 };
1031 if (chargingStation.hasEvses) {
1032 for (const [evseId, evseStatus] of chargingStation.evses) {
1033 if (evseId > 0) {
1034 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1035 if (connectorStatus.transactionId === transactionId) {
1036 return remoteStopTransaction(connectorId);
1037 }
1038 }
1039 }
1040 }
1041 } else {
1042 for (const connectorId of chargingStation.connectors.keys()) {
1043 if (
1044 connectorId > 0 &&
1045 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1046 ) {
1047 return remoteStopTransaction(connectorId);
ef6fa3fb 1048 }
c0560973
JB
1049 }
1050 }
44b9b577 1051 logger.warn(
66dd3447
JB
1052 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1053 ${transactionId.toString()}`
e7aeea18 1054 );
d8b1fab1 1055 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973 1056 }
47e22477 1057
b03df580
JB
1058 private handleRequestUpdateFirmware(
1059 chargingStation: ChargingStation,
1060 commandPayload: OCPP16UpdateFirmwareRequest
1061 ): OCPP16UpdateFirmwareResponse {
1062 if (
1063 OCPP16ServiceUtils.checkFeatureProfile(
1064 chargingStation,
1065 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1066 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1067 ) === false
1068 ) {
5d280aae 1069 logger.warn(
66dd3447
JB
1070 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1071 Cannot simulate firmware update: feature profile not supported`
5d280aae 1072 );
d8b1fab1 1073 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
5d280aae
JB
1074 }
1075 if (
9bf0ef23 1076 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
5d280aae
JB
1077 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1078 ) {
1079 logger.warn(
66dd3447
JB
1080 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1081 Cannot simulate firmware update: firmware update is already in progress`
5d280aae 1082 );
d8b1fab1 1083 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
b03df580 1084 }
9bf0ef23 1085 const retrieveDate = convertToDate(commandPayload.retrieveDate);
2c7bdc61 1086 const now = Date.now();
72092cfc 1087 if (retrieveDate?.getTime() <= now) {
27f08ad3 1088 this.runInAsyncScope(
62340a29 1089 this.updateFirmwareSimulation.bind(this) as (
27f08ad3
JB
1090 this: OCPP16IncomingRequestService,
1091 ...args: any[]
1092 ) => Promise<void>,
1093 this,
1094 chargingStation
59b6ed8d 1095 ).catch(Constants.EMPTY_FUNCTION);
c9a4f9ea
JB
1096 } else {
1097 setTimeout(() => {
507a34c1
JB
1098 this.runInAsyncScope(
1099 this.updateFirmwareSimulation.bind(this) as (
1100 this: OCPP16IncomingRequestService,
1101 ...args: any[]
1102 ) => Promise<void>,
1103 this,
1104 chargingStation
1105 ).catch(Constants.EMPTY_FUNCTION);
72092cfc 1106 }, retrieveDate?.getTime() - now);
c9a4f9ea 1107 }
d8b1fab1 1108 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
c9a4f9ea
JB
1109 }
1110
62340a29 1111 private async updateFirmwareSimulation(
c9a4f9ea 1112 chargingStation: ChargingStation,
90293abb
JB
1113 maxDelay = 30,
1114 minDelay = 15
c9a4f9ea 1115 ): Promise<void> {
1bf29f5b
JB
1116 if (
1117 ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) ===
1118 false
1119 ) {
1120 return;
1121 }
ded57f02
JB
1122 if (chargingStation.hasEvses) {
1123 for (const [evseId, evseStatus] of chargingStation.evses) {
1124 if (evseId > 0) {
1125 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1126 if (connectorStatus?.transactionStarted === false) {
1127 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1128 chargingStation,
1129 connectorId,
1130 OCPP16ChargePointStatus.Unavailable
1131 );
1132 }
1133 }
1134 }
1135 }
1136 } else {
1137 for (const connectorId of chargingStation.connectors.keys()) {
1138 if (
1139 connectorId > 0 &&
1140 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1141 ) {
1142 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1143 chargingStation,
1144 connectorId,
1145 OCPP16ChargePointStatus.Unavailable
1146 );
1147 }
c9a4f9ea
JB
1148 }
1149 }
93f0c2c8
JB
1150 await chargingStation.ocppRequestService.requestHandler<
1151 OCPP16FirmwareStatusNotificationRequest,
1152 OCPP16FirmwareStatusNotificationResponse
1153 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1154 status: OCPP16FirmwareStatus.Downloading,
1155 });
1156 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
5d280aae 1157 if (
93f0c2c8
JB
1158 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1159 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1160 ) {
9bf0ef23 1161 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
5d280aae
JB
1162 await chargingStation.ocppRequestService.requestHandler<
1163 OCPP16FirmwareStatusNotificationRequest,
1164 OCPP16FirmwareStatusNotificationResponse
1165 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
15748260 1166 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
5d280aae 1167 });
93f0c2c8
JB
1168 chargingStation.stationInfo.firmwareStatus =
1169 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
5d280aae
JB
1170 return;
1171 }
9bf0ef23 1172 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
c9a4f9ea
JB
1173 await chargingStation.ocppRequestService.requestHandler<
1174 OCPP16FirmwareStatusNotificationRequest,
1175 OCPP16FirmwareStatusNotificationResponse
1176 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1177 status: OCPP16FirmwareStatus.Downloaded,
1178 });
1179 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
380ccc42 1180 let wasTransactionsStarted = false;
62340a29
JB
1181 let transactionsStarted: boolean;
1182 do {
ded57f02
JB
1183 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1184 if (runningTransactions > 0) {
62340a29
JB
1185 const waitTime = 15 * 1000;
1186 logger.debug(
66dd3447
JB
1187 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1188 ${runningTransactions} transaction(s) in progress, waiting ${
62340a29
JB
1189 waitTime / 1000
1190 } seconds before continuing firmware update simulation`
1191 );
9bf0ef23 1192 await sleep(waitTime);
62340a29 1193 transactionsStarted = true;
380ccc42 1194 wasTransactionsStarted = true;
62340a29 1195 } else {
ded57f02
JB
1196 if (chargingStation.hasEvses) {
1197 for (const [evseId, evseStatus] of chargingStation.evses) {
1198 if (evseId > 0) {
1199 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1200 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1201 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1202 chargingStation,
1203 connectorId,
1204 OCPP16ChargePointStatus.Unavailable
1205 );
1206 }
1207 }
1208 }
1209 }
1210 } else {
1211 for (const connectorId of chargingStation.connectors.keys()) {
1212 if (
1213 connectorId > 0 &&
1214 chargingStation.getConnectorStatus(connectorId)?.status !==
1215 OCPP16ChargePointStatus.Unavailable
1216 ) {
1217 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1218 chargingStation,
1219 connectorId,
1220 OCPP16ChargePointStatus.Unavailable
1221 );
1222 }
62340a29
JB
1223 }
1224 }
1225 transactionsStarted = false;
1226 }
1227 } while (transactionsStarted);
9bf0ef23 1228 !wasTransactionsStarted && (await sleep(getRandomInteger(maxDelay, minDelay) * 1000));
1bf29f5b
JB
1229 if (
1230 ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) ===
1231 false
1232 ) {
1233 return;
1234 }
c9a4f9ea
JB
1235 await chargingStation.ocppRequestService.requestHandler<
1236 OCPP16FirmwareStatusNotificationRequest,
1237 OCPP16FirmwareStatusNotificationResponse
1238 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1239 status: OCPP16FirmwareStatus.Installing,
1240 });
1241 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
93f0c2c8
JB
1242 if (
1243 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1244 OCPP16FirmwareStatus.InstallationFailed
1245 ) {
9bf0ef23 1246 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
93f0c2c8
JB
1247 await chargingStation.ocppRequestService.requestHandler<
1248 OCPP16FirmwareStatusNotificationRequest,
1249 OCPP16FirmwareStatusNotificationResponse
1250 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1251 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1252 });
1253 chargingStation.stationInfo.firmwareStatus =
1254 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1255 return;
1256 }
15748260 1257 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
9bf0ef23 1258 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
5d280aae
JB
1259 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1260 }
b03df580
JB
1261 }
1262
e7aeea18 1263 private async handleRequestGetDiagnostics(
08f130a0 1264 chargingStation: ChargingStation,
e7aeea18
JB
1265 commandPayload: GetDiagnosticsRequest
1266 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1267 if (
1789ba2c 1268 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1269 chargingStation,
370ae4ee
JB
1270 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1271 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1272 ) === false
68cb8b91 1273 ) {
90293abb 1274 logger.warn(
66dd3447
JB
1275 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1276 Cannot get diagnostics: feature profile not supported`
90293abb 1277 );
d8b1fab1 1278 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
68cb8b91 1279 }
a3868ec4 1280 const uri = new URL(commandPayload.location);
47e22477
JB
1281 if (uri.protocol.startsWith('ftp:')) {
1282 let ftpClient: Client;
1283 try {
d972af76 1284 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
72092cfc 1285 .filter((file) => file.endsWith('.log'))
d972af76 1286 .map((file) => join('./', file));
44eb6026 1287 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
d972af76 1288 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
47e22477
JB
1289 ftpClient = new Client();
1290 const accessResponse = await ftpClient.access({
1291 host: uri.host,
9bf0ef23
JB
1292 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1293 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1294 ...(isNotEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1295 });
1296 let uploadResponse: FTPResponse;
1297 if (accessResponse.code === 220) {
72092cfc 1298 ftpClient.trackProgress((info) => {
e7aeea18 1299 logger.info(
08f130a0 1300 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1301 info.bytes / 1024
1302 } bytes transferred from diagnostics archive ${info.name}`
1303 );
6a8329b4
JB
1304 chargingStation.ocppRequestService
1305 .requestHandler<
1306 OCPP16DiagnosticsStatusNotificationRequest,
1307 OCPP16DiagnosticsStatusNotificationResponse
1308 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1309 status: OCPP16DiagnosticsStatus.Uploading,
1310 })
72092cfc 1311 .catch((error) => {
6a8329b4 1312 logger.error(
66dd3447
JB
1313 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1314 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
6a8329b4
JB
1315 error
1316 );
1317 });
47e22477 1318 });
e7aeea18 1319 uploadResponse = await ftpClient.uploadFrom(
d972af76 1320 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
14ecae6a 1321 `${uri.pathname}${diagnosticsArchive}`
e7aeea18 1322 );
47e22477 1323 if (uploadResponse.code === 226) {
08f130a0 1324 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1325 OCPP16DiagnosticsStatusNotificationRequest,
1326 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1327 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1328 status: OCPP16DiagnosticsStatus.Uploaded,
1329 });
47e22477
JB
1330 if (ftpClient) {
1331 ftpClient.close();
1332 }
1333 return { fileName: diagnosticsArchive };
1334 }
e7aeea18
JB
1335 throw new OCPPError(
1336 ErrorType.GENERIC_ERROR,
1337 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1338 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18
JB
1339 }`,
1340 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1341 );
47e22477 1342 }
e7aeea18
JB
1343 throw new OCPPError(
1344 ErrorType.GENERIC_ERROR,
1345 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1346 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18
JB
1347 }`,
1348 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1349 );
47e22477 1350 } catch (error) {
08f130a0 1351 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1352 OCPP16DiagnosticsStatusNotificationRequest,
1353 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1354 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1355 status: OCPP16DiagnosticsStatus.UploadFailed,
1356 });
47e22477
JB
1357 if (ftpClient) {
1358 ftpClient.close();
1359 }
e7aeea18 1360 return this.handleIncomingRequestError(
08f130a0 1361 chargingStation,
e7aeea18
JB
1362 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1363 error as Error,
d8b1fab1 1364 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
e7aeea18 1365 );
47e22477
JB
1366 }
1367 } else {
e7aeea18 1368 logger.error(
08f130a0 1369 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1370 uri.protocol
1371 } to transfer the diagnostic logs archive`
1372 );
08f130a0 1373 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1374 OCPP16DiagnosticsStatusNotificationRequest,
1375 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1376 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1377 status: OCPP16DiagnosticsStatus.UploadFailed,
1378 });
d8b1fab1 1379 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1380 }
1381 }
802cfa13 1382
e7aeea18 1383 private handleRequestTriggerMessage(
08f130a0 1384 chargingStation: ChargingStation,
e7aeea18
JB
1385 commandPayload: OCPP16TriggerMessageRequest
1386 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1387 if (
1388 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1389 chargingStation,
370ae4ee
JB
1390 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1391 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1392 ) ||
1393 !OCPP16ServiceUtils.isMessageTriggerSupported(
1394 chargingStation,
1395 commandPayload.requestedMessage
370ae4ee
JB
1396 )
1397 ) {
d8b1fab1 1398 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1399 }
c60ed4b8 1400 if (
4caa7e67 1401 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1402 chargingStation,
1403 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1404 commandPayload.connectorId
1405 )
1406 ) {
d8b1fab1 1407 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1408 }
802cfa13
JB
1409 try {
1410 switch (commandPayload.requestedMessage) {
c60ed4b8 1411 case OCPP16MessageTrigger.BootNotification:
802cfa13 1412 setTimeout(() => {
08f130a0 1413 chargingStation.ocppRequestService
f7f98c68 1414 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1415 chargingStation,
6a8b180d 1416 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1417 chargingStation.bootNotificationRequest,
6a8b180d 1418 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1419 )
72092cfc 1420 .then((response) => {
8bfbc743 1421 chargingStation.bootNotificationResponse = response;
ae711c83 1422 })
59b6ed8d 1423 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1424 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1425 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1426 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1427 setTimeout(() => {
08f130a0 1428 chargingStation.ocppRequestService
f7f98c68 1429 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1430 chargingStation,
ef6fa3fb
JB
1431 OCPP16RequestCommand.HEARTBEAT,
1432 null,
1433 {
1434 triggerMessage: true,
1435 }
1436 )
59b6ed8d 1437 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1438 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1439 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1440 case OCPP16MessageTrigger.StatusNotification:
dc661702 1441 setTimeout(() => {
9bf0ef23 1442 if (!isNullOrUndefined(commandPayload?.connectorId)) {
08f130a0 1443 chargingStation.ocppRequestService
dc661702 1444 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1445 chargingStation,
dc661702
JB
1446 OCPP16RequestCommand.STATUS_NOTIFICATION,
1447 {
1448 connectorId: commandPayload.connectorId,
1449 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
72092cfc 1450 status: chargingStation.getConnectorStatus(commandPayload.connectorId)?.status,
dc661702
JB
1451 },
1452 {
1453 triggerMessage: true,
1454 }
1455 )
59b6ed8d 1456 .catch(Constants.EMPTY_FUNCTION);
dc661702 1457 } else {
ded57f02
JB
1458 // eslint-disable-next-line no-lonely-if
1459 if (chargingStation.hasEvses) {
1460 for (const evseStatus of chargingStation.evses.values()) {
1461 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1462 chargingStation.ocppRequestService
1463 .requestHandler<
1464 OCPP16StatusNotificationRequest,
1465 OCPP16StatusNotificationResponse
1466 >(
1467 chargingStation,
1468 OCPP16RequestCommand.STATUS_NOTIFICATION,
1469 {
1470 connectorId,
1471 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1472 status: connectorStatus.status,
1473 },
1474 {
1475 triggerMessage: true,
1476 }
1477 )
1478 .catch(Constants.EMPTY_FUNCTION);
1479 }
1480 }
1481 } else {
1482 for (const connectorId of chargingStation.connectors.keys()) {
1483 chargingStation.ocppRequestService
1484 .requestHandler<
1485 OCPP16StatusNotificationRequest,
1486 OCPP16StatusNotificationResponse
1487 >(
1488 chargingStation,
1489 OCPP16RequestCommand.STATUS_NOTIFICATION,
1490 {
1491 connectorId,
1492 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1493 status: chargingStation.getConnectorStatus(connectorId)?.status,
1494 },
1495 {
1496 triggerMessage: true,
1497 }
1498 )
1499 .catch(Constants.EMPTY_FUNCTION);
1500 }
dc661702
JB
1501 }
1502 }
d8b1fab1
JB
1503 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1504 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1505 default:
d8b1fab1 1506 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1507 }
1508 } catch (error) {
e7aeea18 1509 return this.handleIncomingRequestError(
08f130a0 1510 chargingStation,
e7aeea18
JB
1511 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1512 error as Error,
d8b1fab1 1513 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1514 );
802cfa13
JB
1515 }
1516 }
77b95a89
JB
1517
1518 private handleRequestDataTransfer(
1519 chargingStation: ChargingStation,
1520 commandPayload: OCPP16DataTransferRequest
1521 ): OCPP16DataTransferResponse {
1522 try {
1523 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
b63b4a73 1524 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
77b95a89 1525 }
b63b4a73 1526 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
77b95a89
JB
1527 } catch (error) {
1528 return this.handleIncomingRequestError(
1529 chargingStation,
1530 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1531 error as Error,
d8b1fab1 1532 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1533 );
1534 }
1535 }
24578c31
JB
1536
1537 private async handleRequestReserveNow(
1538 chargingStation: ChargingStation,
1539 commandPayload: OCPP16ReserveNowRequest
1540 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1541 if (
1542 !OCPP16ServiceUtils.checkFeatureProfile(
1543 chargingStation,
1544 OCPP16SupportedFeatureProfiles.Reservation,
1545 OCPP16IncomingRequestCommand.RESERVE_NOW
1546 )
1547 ) {
178956d8 1548 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
66dd3447 1549 }
24578c31 1550 const { reservationId, idTag, connectorId } = commandPayload;
24578c31
JB
1551 let response: OCPP16ReserveNowResponse;
1552 try {
66dd3447 1553 if (!chargingStation.isConnectorAvailable(connectorId) && connectorId > 0) {
178956d8 1554 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1555 }
66dd3447 1556 if (connectorId === 0 && !chargingStation.getReservationOnConnectorId0Enabled()) {
178956d8 1557 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1558 }
66dd3447 1559 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
178956d8 1560 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31
JB
1561 }
1562 switch (chargingStation.getConnectorStatus(connectorId).status) {
178956d8
JB
1563 case OCPP16ChargePointStatus.Faulted:
1564 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
24578c31 1565 break;
178956d8
JB
1566 case OCPP16ChargePointStatus.Preparing:
1567 case OCPP16ChargePointStatus.Charging:
1568 case OCPP16ChargePointStatus.SuspendedEV:
1569 case OCPP16ChargePointStatus.SuspendedEVSE:
1570 case OCPP16ChargePointStatus.Finishing:
1571 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
24578c31 1572 break;
178956d8
JB
1573 case OCPP16ChargePointStatus.Unavailable:
1574 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
24578c31 1575 break;
178956d8 1576 case OCPP16ChargePointStatus.Reserved:
66dd3447 1577 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
178956d8 1578 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
24578c31
JB
1579 break;
1580 }
1581 // eslint-disable-next-line no-fallthrough
1582 default:
66dd3447 1583 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
178956d8 1584 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
d193a949
JB
1585 break;
1586 }
1587 await chargingStation.addReservation({
1588 id: commandPayload.reservationId,
1589 ...commandPayload,
1590 });
178956d8 1591 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
24578c31
JB
1592 break;
1593 }
1594 return response;
1595 } catch (error) {
178956d8 1596 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.Available;
24578c31
JB
1597 return this.handleIncomingRequestError(
1598 chargingStation,
1599 OCPP16IncomingRequestCommand.RESERVE_NOW,
1600 error as Error,
178956d8 1601 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
24578c31
JB
1602 );
1603 }
1604 }
1605
1606 private async handleRequestCancelReservation(
1607 chargingStation: ChargingStation,
1608 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1609 ): Promise<GenericResponse> {
66dd3447
JB
1610 if (
1611 !OCPP16ServiceUtils.checkFeatureProfile(
1612 chargingStation,
1613 OCPP16SupportedFeatureProfiles.Reservation,
1614 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1615 )
1616 ) {
178956d8 1617 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
66dd3447 1618 }
24578c31 1619 try {
d193a949
JB
1620 const { reservationId } = commandPayload;
1621 const [exists, reservation] = chargingStation.doesReservationExists({ id: reservationId });
24578c31
JB
1622 if (!exists) {
1623 logger.error(
66dd3447
JB
1624 `${chargingStation.logPrefix()} Reservation with ID ${reservationId}
1625 does not exist on charging station`
24578c31 1626 );
178956d8 1627 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
24578c31 1628 }
ec9f36cc
JB
1629 await chargingStation.removeReservation(
1630 reservation,
1631 ReservationTerminationReason.RESERVATION_CANCELED
1632 );
178956d8 1633 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
24578c31
JB
1634 } catch (error) {
1635 return this.handleIncomingRequestError(
1636 chargingStation,
1637 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1638 error as Error,
178956d8 1639 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
24578c31 1640 );
24578c31
JB
1641 }
1642 }
c0560973 1643}