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