refactor: improve log messages
[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,
0eb666db 31 prepareChargingProfileKind,
90aceaf6 32 removeExpiredReservations,
f2d5e3d9 33 setConfigurationKeyValue,
2896e06d 34} from '../../../charging-station';
268a74bb 35import { OCPPError } from '../../../exception';
e7aeea18 36import {
27782dbc 37 type ChangeConfigurationRequest,
268a74bb 38 type ChangeConfigurationResponse,
27782dbc 39 type ClearChargingProfileRequest,
268a74bb
JB
40 type ClearChargingProfileResponse,
41 ErrorType,
42 type GenericResponse,
41189456 43 GenericStatus,
27782dbc 44 type GetConfigurationRequest,
268a74bb 45 type GetConfigurationResponse,
27782dbc 46 type GetDiagnosticsRequest,
268a74bb
JB
47 type GetDiagnosticsResponse,
48 type IncomingRequestHandler,
49 type JsonObject,
50 type JsonType,
51 OCPP16AuthorizationStatus,
e7aeea18 52 OCPP16AvailabilityType,
27782dbc 53 type OCPP16BootNotificationRequest,
268a74bb 54 type OCPP16BootNotificationResponse,
66dd3447 55 type OCPP16CancelReservationRequest,
366f75f6
JB
56 type OCPP16ChangeAvailabilityRequest,
57 type OCPP16ChangeAvailabilityResponse,
268a74bb
JB
58 OCPP16ChargePointErrorCode,
59 OCPP16ChargePointStatus,
60 type OCPP16ChargingProfile,
0ac97927 61 OCPP16ChargingProfilePurposeType,
491dad29 62 OCPP16ChargingRateUnitType,
41189456 63 type OCPP16ChargingSchedule,
491dad29 64 type OCPP16ChargingSchedulePeriod,
27782dbc
JB
65 type OCPP16ClearCacheRequest,
66 type OCPP16DataTransferRequest,
268a74bb 67 type OCPP16DataTransferResponse,
77b95a89 68 OCPP16DataTransferVendorId,
268a74bb 69 OCPP16DiagnosticsStatus,
c9a4f9ea 70 type OCPP16DiagnosticsStatusNotificationRequest,
268a74bb 71 type OCPP16DiagnosticsStatusNotificationResponse,
c9a4f9ea
JB
72 OCPP16FirmwareStatus,
73 type OCPP16FirmwareStatusNotificationRequest,
268a74bb 74 type OCPP16FirmwareStatusNotificationResponse,
41189456
JB
75 type OCPP16GetCompositeScheduleRequest,
76 type OCPP16GetCompositeScheduleResponse,
27782dbc 77 type OCPP16HeartbeatRequest,
268a74bb 78 type OCPP16HeartbeatResponse,
e7aeea18 79 OCPP16IncomingRequestCommand,
c60ed4b8 80 OCPP16MessageTrigger,
94a464f9 81 OCPP16RequestCommand,
66dd3447
JB
82 type OCPP16ReserveNowRequest,
83 type OCPP16ReserveNowResponse,
268a74bb
JB
84 OCPP16StandardParametersKey,
85 type OCPP16StartTransactionRequest,
86 type OCPP16StartTransactionResponse,
27782dbc 87 type OCPP16StatusNotificationRequest,
268a74bb
JB
88 type OCPP16StatusNotificationResponse,
89 OCPP16StopTransactionReason,
90 OCPP16SupportedFeatureProfiles,
27782dbc 91 type OCPP16TriggerMessageRequest,
268a74bb 92 type OCPP16TriggerMessageResponse,
27782dbc 93 type OCPP16UpdateFirmwareRequest,
268a74bb
JB
94 type OCPP16UpdateFirmwareResponse,
95 type OCPPConfigurationKey,
96 OCPPVersion,
27782dbc
JB
97 type RemoteStartTransactionRequest,
98 type RemoteStopTransactionRequest,
66dd3447 99 ReservationTerminationReason,
27782dbc
JB
100 type ResetRequest,
101 type SetChargingProfileRequest,
27782dbc 102 type SetChargingProfileResponse,
268a74bb 103 type UnlockConnectorRequest,
27782dbc 104 type UnlockConnectorResponse,
268a74bb 105} from '../../../types';
9bf0ef23
JB
106import {
107 Constants,
ad490d5f 108 cloneObject,
9bf0ef23
JB
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 };
bbb55ee4 707 const storedChargingProfiles: OCPP16ChargingProfile[] = cloneObject<OCPP16ChargingProfile[]>(
ad490d5f
JB
708 (connectorStatus?.chargingProfiles ?? []).concat(
709 chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [],
710 ),
bbb55ee4
JB
711 ).sort((a, b) => b.stackLevel - a.stackLevel);
712 const chargingProfiles: OCPP16ChargingProfile[] = [];
713 for (const storedChargingProfile of storedChargingProfiles) {
b3d7d654 714 if (
ad490d5f 715 connectorStatus?.transactionStarted &&
bbb55ee4 716 isNullOrUndefined(storedChargingProfile.chargingSchedule?.startSchedule)
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 }
bbb55ee4 726 if (!isDate(storedChargingProfile.chargingSchedule?.startSchedule)) {
ad490d5f
JB
727 logger.warn(
728 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
bbb55ee4 729 storedChargingProfile.chargingProfileId
ad490d5f
JB
730 } startSchedule property is not a Date object. Trying to convert it to a Date object`,
731 );
bbb55ee4
JB
732 storedChargingProfile.chargingSchedule.startSchedule = convertToDate(
733 storedChargingProfile.chargingSchedule?.startSchedule,
ad490d5f
JB
734 )!;
735 }
0eb666db
JB
736 if (
737 !prepareChargingProfileKind(
738 connectorStatus,
bbb55ee4 739 storedChargingProfile,
0eb666db
JB
740 interval.start as Date,
741 chargingStation.logPrefix(),
742 )
743 ) {
744 continue;
b3d7d654 745 }
41189456 746 if (
ad490d5f 747 !canProceedChargingProfile(
bbb55ee4 748 storedChargingProfile,
ad490d5f
JB
749 interval.start as Date,
750 chargingStation.logPrefix(),
b3d7d654 751 )
41189456 752 ) {
ad490d5f
JB
753 continue;
754 }
755 // Add active charging profiles into chargingProfiles array
756 if (
bbb55ee4 757 isValidTime(storedChargingProfile.chargingSchedule?.startSchedule) &&
acfa5fd1
JB
758 isWithinInterval(storedChargingProfile.chargingSchedule.startSchedule!, interval)
759 ) {
760 if (isEmptyArray(chargingProfiles)) {
761 if (
762 isAfter(
763 addSeconds(
764 storedChargingProfile.chargingSchedule.startSchedule!,
765 storedChargingProfile.chargingSchedule.duration!,
766 ),
767 interval.end,
768 )
769 ) {
770 storedChargingProfile.chargingSchedule.chargingSchedulePeriod =
771 storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter(
772 (schedulePeriod) =>
773 isWithinInterval(
774 addSeconds(
775 storedChargingProfile.chargingSchedule.startSchedule!,
776 schedulePeriod.startPeriod,
777 )!,
778 interval,
779 ),
780 );
781 storedChargingProfile.chargingSchedule.duration = differenceInSeconds(
782 interval.end,
bbb55ee4 783 storedChargingProfile.chargingSchedule.startSchedule!,
acfa5fd1
JB
784 );
785 }
786 chargingProfiles.push(storedChargingProfile);
787 } else if (isNotEmptyArray(chargingProfiles)) {
788 const chargingProfilesInterval: Interval = {
789 start: min(
790 chargingProfiles.map(
791 (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime,
792 ),
793 ),
794 end: max(
795 chargingProfiles.map(
796 (chargingProfile) =>
797 addSeconds(
798 chargingProfile.chargingSchedule.startSchedule!,
799 chargingProfile.chargingSchedule.duration!,
800 ) ?? minTime,
bbb55ee4 801 ),
acfa5fd1
JB
802 ),
803 };
804 let addChargingProfile = false;
805 if (
806 isBefore(interval.start, chargingProfilesInterval.start) &&
807 isBefore(
808 storedChargingProfile.chargingSchedule.startSchedule!,
809 chargingProfilesInterval.start,
810 )
811 ) {
812 // Remove charging schedule periods that are after the start of the active profiles interval
813 storedChargingProfile.chargingSchedule.chargingSchedulePeriod =
814 storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter(
815 (schedulePeriod) =>
816 isWithinInterval(
817 addSeconds(
818 storedChargingProfile.chargingSchedule.startSchedule!,
819 schedulePeriod.startPeriod,
820 ),
821 {
822 start: interval.start,
823 end: chargingProfilesInterval.start,
824 },
825 ),
826 );
827 addChargingProfile = true;
828 }
829 if (
830 isBefore(chargingProfilesInterval.end, interval.end) &&
831 isAfter(
832 addSeconds(
bbb55ee4 833 storedChargingProfile.chargingSchedule.startSchedule!,
acfa5fd1
JB
834 storedChargingProfile.chargingSchedule.duration!,
835 ),
836 chargingProfilesInterval.end,
837 )
838 ) {
839 // Remove charging schedule periods that are before the end of the active profiles interval
840 // FIXME: can lead to a gap in the charging schedule: chargingProfilesInterval.end -> first matching schedulePeriod.startPeriod
841 storedChargingProfile.chargingSchedule.chargingSchedulePeriod =
842 storedChargingProfile.chargingSchedule.chargingSchedulePeriod.filter(
843 (schedulePeriod) =>
844 isWithinInterval(
845 addSeconds(
846 storedChargingProfile.chargingSchedule.startSchedule!,
847 schedulePeriod.startPeriod,
848 ),
849 {
850 start: chargingProfilesInterval.end,
851 end: interval.end,
852 },
bbb55ee4 853 ),
acfa5fd1
JB
854 );
855 addChargingProfile = true;
856 }
857 addChargingProfile && chargingProfiles.push(storedChargingProfile);
858 }
41189456
JB
859 }
860 }
491dad29
JB
861 const compositeScheduleStart: Date = min(
862 chargingProfiles.map(
863 (chargingProfile) => chargingProfile.chargingSchedule.startSchedule ?? maxTime,
ad490d5f 864 ),
491dad29
JB
865 );
866 const compositeScheduleDuration: number = Math.max(
acfa5fd1
JB
867 ...chargingProfiles.map((chargingProfile) =>
868 isNaN(chargingProfile.chargingSchedule.duration!)
869 ? -Infinity
870 : chargingProfile.chargingSchedule.duration!,
ad490d5f 871 ),
491dad29 872 );
491dad29
JB
873 const compositeSchedulePeriods: OCPP16ChargingSchedulePeriod[] = chargingProfiles
874 .map((chargingProfile) => chargingProfile.chargingSchedule.chargingSchedulePeriod)
875 .reduce(
876 (accumulator, value) =>
877 accumulator.concat(value).sort((a, b) => a.startPeriod - b.startPeriod),
878 [],
879 );
880 const compositeSchedule: OCPP16ChargingSchedule = {
881 startSchedule: compositeScheduleStart,
882 duration: compositeScheduleDuration,
ad490d5f
JB
883 chargingRateUnit: chargingProfiles.every(
884 (chargingProfile) =>
491dad29 885 chargingProfile.chargingSchedule.chargingRateUnit === OCPP16ChargingRateUnitType.AMPERE,
ad490d5f 886 )
491dad29 887 ? OCPP16ChargingRateUnitType.AMPERE
ad490d5f
JB
888 : chargingProfiles.every(
889 (chargingProfile) =>
491dad29 890 chargingProfile.chargingSchedule.chargingRateUnit === OCPP16ChargingRateUnitType.WATT,
ad490d5f 891 )
491dad29
JB
892 ? OCPP16ChargingRateUnitType.WATT
893 : OCPP16ChargingRateUnitType.AMPERE,
894 chargingSchedulePeriod: compositeSchedulePeriods,
ad490d5f 895 minChargeRate: Math.min(
acfa5fd1
JB
896 ...chargingProfiles.map((chargingProfile) =>
897 isNaN(chargingProfile.chargingSchedule.minChargeRate!)
898 ? Infinity
899 : chargingProfile.chargingSchedule.minChargeRate!,
ad490d5f
JB
900 ),
901 ),
902 };
41189456
JB
903 return {
904 status: GenericStatus.Accepted,
ad490d5f 905 scheduleStart: compositeSchedule.startSchedule!,
0d1f33ba 906 connectorId,
41189456
JB
907 chargingSchedule: compositeSchedule,
908 };
909 }
910
e7aeea18 911 private handleRequestClearChargingProfile(
08f130a0 912 chargingStation: ChargingStation,
5edd8ba0 913 commandPayload: ClearChargingProfileRequest,
e7aeea18 914 ): ClearChargingProfileResponse {
370ae4ee 915 if (
a36bad10 916 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 917 chargingStation,
370ae4ee 918 OCPP16SupportedFeatureProfiles.SmartCharging,
5edd8ba0 919 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
a36bad10 920 ) === false
370ae4ee 921 ) {
d8b1fab1 922 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
68cb8b91 923 }
0d1f33ba
JB
924 const { connectorId } = commandPayload;
925 if (chargingStation.hasConnector(connectorId!) === false) {
e7aeea18 926 logger.error(
66dd3447 927 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
0d1f33ba 928 a non existing connector id ${connectorId}`,
e7aeea18 929 );
d8b1fab1 930 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973 931 }
d812bdcb 932 if (
0d1f33ba
JB
933 !isNullOrUndefined(connectorId) &&
934 isNotEmptyArray(chargingStation.getConnectorStatus(connectorId!)?.chargingProfiles)
d812bdcb 935 ) {
0d1f33ba 936 chargingStation.getConnectorStatus(connectorId!)!.chargingProfiles = [];
e7aeea18 937 logger.debug(
0d1f33ba 938 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`,
e7aeea18 939 );
d8b1fab1 940 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973 941 }
0d1f33ba 942 if (isNullOrUndefined(connectorId)) {
c0560973 943 let clearedCP = false;
4334db72
JB
944 if (chargingStation.hasEvses) {
945 for (const evseStatus of chargingStation.evses.values()) {
946 for (const connectorStatus of evseStatus.connectors.values()) {
73d87be1
JB
947 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
948 chargingStation,
949 commandPayload,
950 connectorStatus.chargingProfiles,
951 );
4334db72
JB
952 }
953 }
954 } else {
0d1f33ba 955 for (const id of chargingStation.connectors.keys()) {
73d87be1
JB
956 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
957 chargingStation,
958 commandPayload,
0d1f33ba 959 chargingStation.getConnectorStatus(id)?.chargingProfiles,
73d87be1 960 );
c0560973
JB
961 }
962 }
963 if (clearedCP) {
d8b1fab1 964 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
965 }
966 }
d8b1fab1 967 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973
JB
968 }
969
e7aeea18 970 private async handleRequestChangeAvailability(
08f130a0 971 chargingStation: ChargingStation,
366f75f6
JB
972 commandPayload: OCPP16ChangeAvailabilityRequest,
973 ): Promise<OCPP16ChangeAvailabilityResponse> {
0d1f33ba 974 const { connectorId, type } = commandPayload;
a14022a2 975 if (chargingStation.hasConnector(connectorId) === false) {
e7aeea18 976 logger.error(
66dd3447 977 `${chargingStation.logPrefix()} Trying to change the availability of a
5edd8ba0 978 non existing connector id ${connectorId.toString()}`,
e7aeea18 979 );
d8b1fab1 980 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973 981 }
e7aeea18 982 const chargePointStatus: OCPP16ChargePointStatus =
0d1f33ba 983 type === OCPP16AvailabilityType.Operative
721646e9
JB
984 ? OCPP16ChargePointStatus.Available
985 : OCPP16ChargePointStatus.Unavailable;
c0560973 986 if (connectorId === 0) {
366f75f6 987 let response: OCPP16ChangeAvailabilityResponse;
ded57f02
JB
988 if (chargingStation.hasEvses) {
989 for (const evseStatus of chargingStation.evses.values()) {
366f75f6
JB
990 response = await OCPP16ServiceUtils.changeAvailability(
991 chargingStation,
225e32b0 992 [...evseStatus.connectors.keys()],
366f75f6 993 chargePointStatus,
0d1f33ba 994 type,
366f75f6 995 );
ded57f02 996 }
225e32b0
JB
997 } else {
998 response = await OCPP16ServiceUtils.changeAvailability(
999 chargingStation,
1000 [...chargingStation.connectors.keys()],
1001 chargePointStatus,
0d1f33ba 1002 type,
225e32b0 1003 );
c0560973 1004 }
366f75f6 1005 return response!;
e7aeea18
JB
1006 } else if (
1007 connectorId > 0 &&
56eb297e
JB
1008 (chargingStation.isChargingStationAvailable() === true ||
1009 (chargingStation.isChargingStationAvailable() === false &&
0d1f33ba 1010 type === OCPP16AvailabilityType.Inoperative))
e7aeea18 1011 ) {
5e3cb728 1012 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
0d1f33ba 1013 chargingStation.getConnectorStatus(connectorId)!.availability = type;
d8b1fab1 1014 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 1015 }
0d1f33ba 1016 chargingStation.getConnectorStatus(connectorId)!.availability = type;
4ecff7ce
JB
1017 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1018 chargingStation,
ef6fa3fb 1019 connectorId,
5edd8ba0 1020 chargePointStatus,
4ecff7ce 1021 );
d8b1fab1 1022 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 1023 }
d8b1fab1 1024 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
1025 }
1026
e7aeea18 1027 private async handleRequestRemoteStartTransaction(
08f130a0 1028 chargingStation: ChargingStation,
5edd8ba0 1029 commandPayload: RemoteStartTransactionRequest,
f03e1042 1030 ): Promise<GenericResponse> {
66dd3447 1031 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
649287f8
JB
1032 if (chargingStation.hasConnector(transactionConnectorId) === false) {
1033 return this.notifyRemoteStartTransactionRejected(
4ecff7ce
JB
1034 chargingStation,
1035 transactionConnectorId,
5edd8ba0 1036 idTag,
4ecff7ce 1037 );
649287f8
JB
1038 }
1039 if (
d193a949
JB
1040 !chargingStation.isChargingStationAvailable() ||
1041 !chargingStation.isConnectorAvailable(transactionConnectorId)
649287f8
JB
1042 ) {
1043 return this.notifyRemoteStartTransactionRejected(
1044 chargingStation,
1045 transactionConnectorId,
5edd8ba0 1046 idTag,
649287f8
JB
1047 );
1048 }
66dd3447
JB
1049 const remoteStartTransactionLogMsg = `
1050 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
5edd8ba0
JB
1051 chargingStation.stationInfo.chargingStationId
1052 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
649287f8
JB
1053 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1054 chargingStation,
1055 transactionConnectorId,
5edd8ba0 1056 OCPP16ChargePointStatus.Preparing,
649287f8 1057 );
e1d9a0f4 1058 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
d984c13f
JB
1059 // Authorization check required
1060 if (
1061 chargingStation.getAuthorizeRemoteTxRequests() === true &&
66dd3447
JB
1062 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
1063 ) {
1064 // Authorization successful, start transaction
1065 if (
1066 this.setRemoteStartTransactionChargingProfile(
1067 chargingStation,
1068 transactionConnectorId,
e1d9a0f4 1069 chargingProfile!,
66dd3447
JB
1070 ) === true
1071 ) {
1072 connectorStatus.transactionRemoteStarted = true;
e7aeea18 1073 if (
66dd3447
JB
1074 (
1075 await chargingStation.ocppRequestService.requestHandler<
1076 OCPP16StartTransactionRequest,
1077 OCPP16StartTransactionResponse
d984c13f
JB
1078 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
1079 connectorId: transactionConnectorId,
1080 idTag,
1081 })
66dd3447 1082 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
e7aeea18 1083 ) {
eb79c525 1084 logger.debug(remoteStartTransactionLogMsg);
66dd3447 1085 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
e060fe58 1086 }
e7aeea18 1087 return this.notifyRemoteStartTransactionRejected(
08f130a0 1088 chargingStation,
e7aeea18 1089 transactionConnectorId,
5edd8ba0 1090 idTag,
e7aeea18 1091 );
c0560973 1092 }
e7aeea18 1093 return this.notifyRemoteStartTransactionRejected(
08f130a0 1094 chargingStation,
e7aeea18 1095 transactionConnectorId,
5edd8ba0 1096 idTag,
e7aeea18 1097 );
c0560973 1098 }
649287f8
JB
1099 // No authorization check required, start transaction
1100 if (
1101 this.setRemoteStartTransactionChargingProfile(
1102 chargingStation,
1103 transactionConnectorId,
e1d9a0f4 1104 chargingProfile!,
649287f8
JB
1105 ) === true
1106 ) {
1107 connectorStatus.transactionRemoteStarted = true;
1108 if (
1109 (
1110 await chargingStation.ocppRequestService.requestHandler<
1111 OCPP16StartTransactionRequest,
1112 OCPP16StartTransactionResponse
1113 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
1114 connectorId: transactionConnectorId,
66dd3447 1115 idTag,
649287f8
JB
1116 })
1117 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
1118 ) {
eb79c525 1119 logger.debug(remoteStartTransactionLogMsg);
649287f8
JB
1120 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
1121 }
1122 return this.notifyRemoteStartTransactionRejected(
1123 chargingStation,
1124 transactionConnectorId,
5edd8ba0 1125 idTag,
649287f8
JB
1126 );
1127 }
08f130a0
JB
1128 return this.notifyRemoteStartTransactionRejected(
1129 chargingStation,
1130 transactionConnectorId,
5edd8ba0 1131 idTag,
08f130a0 1132 );
a7fc8211
JB
1133 }
1134
e7aeea18 1135 private async notifyRemoteStartTransactionRejected(
08f130a0 1136 chargingStation: ChargingStation,
e7aeea18 1137 connectorId: number,
5edd8ba0 1138 idTag: string,
f03e1042 1139 ): Promise<GenericResponse> {
e7aeea18 1140 if (
721646e9 1141 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
e7aeea18 1142 ) {
4ecff7ce
JB
1143 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1144 chargingStation,
ef6fa3fb 1145 connectorId,
5edd8ba0 1146 OCPP16ChargePointStatus.Available,
4ecff7ce 1147 );
e060fe58 1148 }
e7aeea18 1149 logger.warn(
66dd3447 1150 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
5edd8ba0
JB
1151 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
1152 connectorId,
1153 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
e7aeea18 1154 );
d8b1fab1 1155 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973
JB
1156 }
1157
e7aeea18 1158 private setRemoteStartTransactionChargingProfile(
08f130a0 1159 chargingStation: ChargingStation,
e7aeea18 1160 connectorId: number,
55f2ab60 1161 chargingProfile: OCPP16ChargingProfile,
e7aeea18 1162 ): boolean {
55f2ab60
JB
1163 if (
1164 chargingProfile &&
1165 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
1166 ) {
1167 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
e7aeea18 1168 logger.debug(
66dd3447
JB
1169 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
1170 on connector id ${connectorId}: %j`,
55f2ab60 1171 chargingProfile,
e7aeea18 1172 );
a7fc8211 1173 return true;
55f2ab60
JB
1174 } else if (
1175 chargingProfile &&
1176 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
1177 ) {
e7aeea18 1178 logger.warn(
08f130a0 1179 `${chargingStation.logPrefix()} Not allowed to set ${
55f2ab60 1180 chargingProfile.chargingProfilePurpose
5edd8ba0 1181 } charging profile(s) at remote start transaction`,
e7aeea18 1182 );
a7fc8211
JB
1183 return false;
1184 }
e1d9a0f4 1185 return true;
a7fc8211
JB
1186 }
1187
e7aeea18 1188 private async handleRequestRemoteStopTransaction(
08f130a0 1189 chargingStation: ChargingStation,
5edd8ba0 1190 commandPayload: RemoteStopTransactionRequest,
f03e1042 1191 ): Promise<GenericResponse> {
0d1f33ba 1192 const { transactionId } = commandPayload;
ded57f02
JB
1193 if (chargingStation.hasEvses) {
1194 for (const [evseId, evseStatus] of chargingStation.evses) {
1195 if (evseId > 0) {
1196 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1197 if (connectorStatus.transactionId === transactionId) {
d19b10a8 1198 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
ded57f02
JB
1199 }
1200 }
1201 }
1202 }
1203 } else {
1204 for (const connectorId of chargingStation.connectors.keys()) {
1205 if (
1206 connectorId > 0 &&
1207 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1208 ) {
d19b10a8 1209 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
ef6fa3fb 1210 }
c0560973
JB
1211 }
1212 }
44b9b577 1213 logger.warn(
56563a3c 1214 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id
5edd8ba0 1215 ${transactionId.toString()}`,
e7aeea18 1216 );
d8b1fab1 1217 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973 1218 }
47e22477 1219
b03df580
JB
1220 private handleRequestUpdateFirmware(
1221 chargingStation: ChargingStation,
5edd8ba0 1222 commandPayload: OCPP16UpdateFirmwareRequest,
b03df580
JB
1223 ): OCPP16UpdateFirmwareResponse {
1224 if (
1225 OCPP16ServiceUtils.checkFeatureProfile(
1226 chargingStation,
1227 OCPP16SupportedFeatureProfiles.FirmwareManagement,
5edd8ba0 1228 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
b03df580
JB
1229 ) === false
1230 ) {
5d280aae 1231 logger.warn(
66dd3447 1232 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
5edd8ba0 1233 Cannot simulate firmware update: feature profile not supported`,
5d280aae 1234 );
d8b1fab1 1235 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
5d280aae 1236 }
0d1f33ba 1237 let { retrieveDate } = commandPayload;
5d280aae 1238 if (
9bf0ef23 1239 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
5d280aae
JB
1240 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1241 ) {
1242 logger.warn(
66dd3447 1243 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
5edd8ba0 1244 Cannot simulate firmware update: firmware update is already in progress`,
5d280aae 1245 );
d8b1fab1 1246 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
b03df580 1247 }
0d1f33ba 1248 retrieveDate = convertToDate(retrieveDate)!;
2c7bdc61 1249 const now = Date.now();
72092cfc 1250 if (retrieveDate?.getTime() <= now) {
27f08ad3 1251 this.runInAsyncScope(
62340a29 1252 this.updateFirmwareSimulation.bind(this) as (
27f08ad3 1253 this: OCPP16IncomingRequestService,
e843aa40 1254 ...args: unknown[]
27f08ad3
JB
1255 ) => Promise<void>,
1256 this,
5edd8ba0 1257 chargingStation,
59b6ed8d 1258 ).catch(Constants.EMPTY_FUNCTION);
c9a4f9ea 1259 } else {
5edd8ba0
JB
1260 setTimeout(
1261 () => {
1262 this.runInAsyncScope(
1263 this.updateFirmwareSimulation.bind(this) as (
1264 this: OCPP16IncomingRequestService,
e843aa40 1265 ...args: unknown[]
5edd8ba0
JB
1266 ) => Promise<void>,
1267 this,
1268 chargingStation,
1269 ).catch(Constants.EMPTY_FUNCTION);
1270 },
1271 retrieveDate?.getTime() - now,
1272 );
c9a4f9ea 1273 }
d8b1fab1 1274 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
c9a4f9ea
JB
1275 }
1276
62340a29 1277 private async updateFirmwareSimulation(
c9a4f9ea 1278 chargingStation: ChargingStation,
90293abb 1279 maxDelay = 30,
5edd8ba0 1280 minDelay = 15,
c9a4f9ea 1281 ): Promise<void> {
fba11dc6 1282 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1bf29f5b
JB
1283 return;
1284 }
ded57f02
JB
1285 if (chargingStation.hasEvses) {
1286 for (const [evseId, evseStatus] of chargingStation.evses) {
1287 if (evseId > 0) {
1288 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1289 if (connectorStatus?.transactionStarted === false) {
1290 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1291 chargingStation,
1292 connectorId,
5edd8ba0 1293 OCPP16ChargePointStatus.Unavailable,
ded57f02
JB
1294 );
1295 }
1296 }
1297 }
1298 }
1299 } else {
1300 for (const connectorId of chargingStation.connectors.keys()) {
1301 if (
1302 connectorId > 0 &&
1303 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1304 ) {
1305 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1306 chargingStation,
1307 connectorId,
5edd8ba0 1308 OCPP16ChargePointStatus.Unavailable,
ded57f02
JB
1309 );
1310 }
c9a4f9ea
JB
1311 }
1312 }
93f0c2c8
JB
1313 await chargingStation.ocppRequestService.requestHandler<
1314 OCPP16FirmwareStatusNotificationRequest,
1315 OCPP16FirmwareStatusNotificationResponse
1316 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1317 status: OCPP16FirmwareStatus.Downloading,
1318 });
1319 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
5d280aae 1320 if (
93f0c2c8
JB
1321 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1322 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1323 ) {
be4c6702 1324 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
5d280aae
JB
1325 await chargingStation.ocppRequestService.requestHandler<
1326 OCPP16FirmwareStatusNotificationRequest,
1327 OCPP16FirmwareStatusNotificationResponse
1328 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
15748260 1329 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
5d280aae 1330 });
93f0c2c8
JB
1331 chargingStation.stationInfo.firmwareStatus =
1332 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
5d280aae
JB
1333 return;
1334 }
be4c6702 1335 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
c9a4f9ea
JB
1336 await chargingStation.ocppRequestService.requestHandler<
1337 OCPP16FirmwareStatusNotificationRequest,
1338 OCPP16FirmwareStatusNotificationResponse
1339 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1340 status: OCPP16FirmwareStatus.Downloaded,
1341 });
1342 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
380ccc42 1343 let wasTransactionsStarted = false;
62340a29
JB
1344 let transactionsStarted: boolean;
1345 do {
ded57f02
JB
1346 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1347 if (runningTransactions > 0) {
be4c6702 1348 const waitTime = secondsToMilliseconds(15);
62340a29 1349 logger.debug(
66dd3447 1350 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
be4c6702
JB
1351 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1352 waitTime,
1353 )} before continuing firmware update simulation`,
62340a29 1354 );
9bf0ef23 1355 await sleep(waitTime);
62340a29 1356 transactionsStarted = true;
380ccc42 1357 wasTransactionsStarted = true;
62340a29 1358 } else {
ded57f02
JB
1359 if (chargingStation.hasEvses) {
1360 for (const [evseId, evseStatus] of chargingStation.evses) {
1361 if (evseId > 0) {
1362 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1363 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1364 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1365 chargingStation,
1366 connectorId,
5edd8ba0 1367 OCPP16ChargePointStatus.Unavailable,
ded57f02
JB
1368 );
1369 }
1370 }
1371 }
1372 }
1373 } else {
1374 for (const connectorId of chargingStation.connectors.keys()) {
1375 if (
1376 connectorId > 0 &&
1377 chargingStation.getConnectorStatus(connectorId)?.status !==
1378 OCPP16ChargePointStatus.Unavailable
1379 ) {
1380 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1381 chargingStation,
1382 connectorId,
5edd8ba0 1383 OCPP16ChargePointStatus.Unavailable,
ded57f02
JB
1384 );
1385 }
62340a29
JB
1386 }
1387 }
1388 transactionsStarted = false;
1389 }
1390 } while (transactionsStarted);
be4c6702
JB
1391 !wasTransactionsStarted &&
1392 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
fba11dc6 1393 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1bf29f5b
JB
1394 return;
1395 }
c9a4f9ea
JB
1396 await chargingStation.ocppRequestService.requestHandler<
1397 OCPP16FirmwareStatusNotificationRequest,
1398 OCPP16FirmwareStatusNotificationResponse
1399 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1400 status: OCPP16FirmwareStatus.Installing,
1401 });
1402 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
93f0c2c8
JB
1403 if (
1404 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1405 OCPP16FirmwareStatus.InstallationFailed
1406 ) {
be4c6702 1407 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
93f0c2c8
JB
1408 await chargingStation.ocppRequestService.requestHandler<
1409 OCPP16FirmwareStatusNotificationRequest,
1410 OCPP16FirmwareStatusNotificationResponse
1411 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1412 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1413 });
1414 chargingStation.stationInfo.firmwareStatus =
1415 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1416 return;
1417 }
15748260 1418 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
be4c6702 1419 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
5d280aae
JB
1420 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1421 }
b03df580
JB
1422 }
1423
e7aeea18 1424 private async handleRequestGetDiagnostics(
08f130a0 1425 chargingStation: ChargingStation,
5edd8ba0 1426 commandPayload: GetDiagnosticsRequest,
e7aeea18 1427 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1428 if (
1789ba2c 1429 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1430 chargingStation,
370ae4ee 1431 OCPP16SupportedFeatureProfiles.FirmwareManagement,
5edd8ba0 1432 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1789ba2c 1433 ) === false
68cb8b91 1434 ) {
90293abb 1435 logger.warn(
66dd3447 1436 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
5edd8ba0 1437 Cannot get diagnostics: feature profile not supported`,
90293abb 1438 );
d8b1fab1 1439 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
68cb8b91 1440 }
0d1f33ba
JB
1441 const { location } = commandPayload;
1442 const uri = new URL(location);
47e22477 1443 if (uri.protocol.startsWith('ftp:')) {
e1d9a0f4 1444 let ftpClient: Client | undefined;
47e22477 1445 try {
d972af76 1446 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
72092cfc 1447 .filter((file) => file.endsWith('.log'))
d972af76 1448 .map((file) => join('./', file));
44eb6026 1449 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
d972af76 1450 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
47e22477
JB
1451 ftpClient = new Client();
1452 const accessResponse = await ftpClient.access({
1453 host: uri.host,
9bf0ef23
JB
1454 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1455 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1456 ...(isNotEmptyString(uri.password) && { password: uri.password }),
47e22477 1457 });
e1d9a0f4 1458 let uploadResponse: FTPResponse | undefined;
47e22477 1459 if (accessResponse.code === 220) {
72092cfc 1460 ftpClient.trackProgress((info) => {
e7aeea18 1461 logger.info(
56563a3c 1462 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1463 info.bytes / 1024
5edd8ba0 1464 } bytes transferred from diagnostics archive ${info.name}`,
e7aeea18 1465 );
6a8329b4
JB
1466 chargingStation.ocppRequestService
1467 .requestHandler<
1468 OCPP16DiagnosticsStatusNotificationRequest,
1469 OCPP16DiagnosticsStatusNotificationResponse
1470 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1471 status: OCPP16DiagnosticsStatus.Uploading,
1472 })
72092cfc 1473 .catch((error) => {
6a8329b4 1474 logger.error(
66dd3447
JB
1475 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1476 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
5edd8ba0 1477 error,
6a8329b4
JB
1478 );
1479 });
47e22477 1480 });
e7aeea18 1481 uploadResponse = await ftpClient.uploadFrom(
d972af76 1482 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
5edd8ba0 1483 `${uri.pathname}${diagnosticsArchive}`,
e7aeea18 1484 );
47e22477 1485 if (uploadResponse.code === 226) {
08f130a0 1486 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1487 OCPP16DiagnosticsStatusNotificationRequest,
1488 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1489 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1490 status: OCPP16DiagnosticsStatus.Uploaded,
1491 });
47e22477
JB
1492 if (ftpClient) {
1493 ftpClient.close();
1494 }
1495 return { fileName: diagnosticsArchive };
1496 }
e7aeea18
JB
1497 throw new OCPPError(
1498 ErrorType.GENERIC_ERROR,
1499 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1500 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18 1501 }`,
5edd8ba0 1502 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
e7aeea18 1503 );
47e22477 1504 }
e7aeea18
JB
1505 throw new OCPPError(
1506 ErrorType.GENERIC_ERROR,
1507 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1508 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18 1509 }`,
5edd8ba0 1510 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
e7aeea18 1511 );
47e22477 1512 } catch (error) {
08f130a0 1513 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1514 OCPP16DiagnosticsStatusNotificationRequest,
1515 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1516 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1517 status: OCPP16DiagnosticsStatus.UploadFailed,
1518 });
47e22477
JB
1519 if (ftpClient) {
1520 ftpClient.close();
1521 }
e1d9a0f4 1522 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1523 chargingStation,
e7aeea18
JB
1524 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1525 error as Error,
5edd8ba0 1526 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
e1d9a0f4 1527 )!;
47e22477
JB
1528 }
1529 } else {
e7aeea18 1530 logger.error(
08f130a0 1531 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1532 uri.protocol
5edd8ba0 1533 } to transfer the diagnostic logs archive`,
e7aeea18 1534 );
08f130a0 1535 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1536 OCPP16DiagnosticsStatusNotificationRequest,
1537 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1538 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1539 status: OCPP16DiagnosticsStatus.UploadFailed,
1540 });
d8b1fab1 1541 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1542 }
1543 }
802cfa13 1544
e7aeea18 1545 private handleRequestTriggerMessage(
08f130a0 1546 chargingStation: ChargingStation,
5edd8ba0 1547 commandPayload: OCPP16TriggerMessageRequest,
e7aeea18 1548 ): OCPP16TriggerMessageResponse {
0d1f33ba 1549 const { requestedMessage, connectorId } = commandPayload;
370ae4ee
JB
1550 if (
1551 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1552 chargingStation,
370ae4ee 1553 OCPP16SupportedFeatureProfiles.RemoteTrigger,
5edd8ba0 1554 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
c60ed4b8 1555 ) ||
0d1f33ba 1556 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1557 ) {
d8b1fab1 1558 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1559 }
c60ed4b8 1560 if (
4caa7e67 1561 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1562 chargingStation,
1563 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
0d1f33ba 1564 connectorId!,
c60ed4b8
JB
1565 )
1566 ) {
d8b1fab1 1567 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1568 }
802cfa13 1569 try {
0d1f33ba 1570 switch (requestedMessage) {
c60ed4b8 1571 case OCPP16MessageTrigger.BootNotification:
802cfa13 1572 setTimeout(() => {
08f130a0 1573 chargingStation.ocppRequestService
f7f98c68 1574 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1575 chargingStation,
6a8b180d 1576 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1577 chargingStation.bootNotificationRequest,
5edd8ba0 1578 { skipBufferingOnError: true, triggerMessage: true },
e7aeea18 1579 )
72092cfc 1580 .then((response) => {
8bfbc743 1581 chargingStation.bootNotificationResponse = response;
ae711c83 1582 })
59b6ed8d 1583 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1584 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1585 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1586 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1587 setTimeout(() => {
08f130a0 1588 chargingStation.ocppRequestService
f7f98c68 1589 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1590 chargingStation,
ef6fa3fb
JB
1591 OCPP16RequestCommand.HEARTBEAT,
1592 null,
1593 {
1594 triggerMessage: true,
5edd8ba0 1595 },
ef6fa3fb 1596 )
59b6ed8d 1597 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1598 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1599 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1600 case OCPP16MessageTrigger.StatusNotification:
dc661702 1601 setTimeout(() => {
0d1f33ba 1602 if (!isNullOrUndefined(connectorId)) {
08f130a0 1603 chargingStation.ocppRequestService
dc661702 1604 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1605 chargingStation,
dc661702
JB
1606 OCPP16RequestCommand.STATUS_NOTIFICATION,
1607 {
0d1f33ba 1608 connectorId,
dc661702 1609 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
0d1f33ba 1610 status: chargingStation.getConnectorStatus(connectorId!)?.status,
dc661702
JB
1611 },
1612 {
1613 triggerMessage: true,
5edd8ba0 1614 },
dc661702 1615 )
59b6ed8d 1616 .catch(Constants.EMPTY_FUNCTION);
dc661702 1617 } else {
ded57f02
JB
1618 // eslint-disable-next-line no-lonely-if
1619 if (chargingStation.hasEvses) {
1620 for (const evseStatus of chargingStation.evses.values()) {
0d1f33ba 1621 for (const [id, connectorStatus] of evseStatus.connectors) {
ded57f02
JB
1622 chargingStation.ocppRequestService
1623 .requestHandler<
1624 OCPP16StatusNotificationRequest,
1625 OCPP16StatusNotificationResponse
1626 >(
1627 chargingStation,
1628 OCPP16RequestCommand.STATUS_NOTIFICATION,
1629 {
0d1f33ba 1630 connectorId: id,
ded57f02
JB
1631 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1632 status: connectorStatus.status,
1633 },
1634 {
1635 triggerMessage: true,
5edd8ba0 1636 },
ded57f02
JB
1637 )
1638 .catch(Constants.EMPTY_FUNCTION);
1639 }
1640 }
1641 } else {
0d1f33ba 1642 for (const id of chargingStation.connectors.keys()) {
ded57f02
JB
1643 chargingStation.ocppRequestService
1644 .requestHandler<
1645 OCPP16StatusNotificationRequest,
1646 OCPP16StatusNotificationResponse
1647 >(
1648 chargingStation,
1649 OCPP16RequestCommand.STATUS_NOTIFICATION,
1650 {
0d1f33ba 1651 connectorId: id,
ded57f02 1652 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
0d1f33ba 1653 status: chargingStation.getConnectorStatus(id)?.status,
ded57f02
JB
1654 },
1655 {
1656 triggerMessage: true,
5edd8ba0 1657 },
ded57f02
JB
1658 )
1659 .catch(Constants.EMPTY_FUNCTION);
1660 }
dc661702
JB
1661 }
1662 }
d8b1fab1
JB
1663 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1664 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1665 default:
d8b1fab1 1666 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1667 }
1668 } catch (error) {
e1d9a0f4 1669 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
08f130a0 1670 chargingStation,
e7aeea18
JB
1671 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1672 error as Error,
5edd8ba0 1673 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
e1d9a0f4 1674 )!;
802cfa13
JB
1675 }
1676 }
77b95a89
JB
1677
1678 private handleRequestDataTransfer(
1679 chargingStation: ChargingStation,
5edd8ba0 1680 commandPayload: OCPP16DataTransferRequest,
77b95a89 1681 ): OCPP16DataTransferResponse {
0d1f33ba 1682 const { vendorId } = commandPayload;
77b95a89 1683 try {
0d1f33ba 1684 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
b63b4a73 1685 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
77b95a89 1686 }
b63b4a73 1687 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
77b95a89 1688 } catch (error) {
e1d9a0f4 1689 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1690 chargingStation,
1691 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1692 error as Error,
5edd8ba0 1693 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
e1d9a0f4 1694 )!;
77b95a89
JB
1695 }
1696 }
24578c31
JB
1697
1698 private async handleRequestReserveNow(
1699 chargingStation: ChargingStation,
5edd8ba0 1700 commandPayload: OCPP16ReserveNowRequest,
24578c31 1701 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1702 if (
1703 !OCPP16ServiceUtils.checkFeatureProfile(
1704 chargingStation,
1705 OCPP16SupportedFeatureProfiles.Reservation,
5edd8ba0 1706 OCPP16IncomingRequestCommand.RESERVE_NOW,
66dd3447
JB
1707 )
1708 ) {
178956d8 1709 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
66dd3447 1710 }
24578c31 1711 const { reservationId, idTag, connectorId } = commandPayload;
24578c31
JB
1712 let response: OCPP16ReserveNowResponse;
1713 try {
d984c13f 1714 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
178956d8 1715 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1716 }
10e8c3e1 1717 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
178956d8 1718 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1719 }
66dd3447 1720 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
178956d8 1721 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1722 }
90aceaf6 1723 await removeExpiredReservations(chargingStation);
e1d9a0f4 1724 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
178956d8
JB
1725 case OCPP16ChargePointStatus.Faulted:
1726 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
24578c31 1727 break;
178956d8
JB
1728 case OCPP16ChargePointStatus.Preparing:
1729 case OCPP16ChargePointStatus.Charging:
1730 case OCPP16ChargePointStatus.SuspendedEV:
1731 case OCPP16ChargePointStatus.SuspendedEVSE:
1732 case OCPP16ChargePointStatus.Finishing:
1733 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
24578c31 1734 break;
178956d8
JB
1735 case OCPP16ChargePointStatus.Unavailable:
1736 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
24578c31 1737 break;
178956d8 1738 case OCPP16ChargePointStatus.Reserved:
66dd3447 1739 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
178956d8 1740 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
24578c31
JB
1741 break;
1742 }
1743 // eslint-disable-next-line no-fallthrough
1744 default:
66dd3447 1745 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
178956d8 1746 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
d193a949
JB
1747 break;
1748 }
1749 await chargingStation.addReservation({
1750 id: commandPayload.reservationId,
1751 ...commandPayload,
1752 });
178956d8 1753 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
24578c31
JB
1754 break;
1755 }
1756 return response;
1757 } catch (error) {
e1d9a0f4
JB
1758 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1759 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1760 chargingStation,
1761 OCPP16IncomingRequestCommand.RESERVE_NOW,
1762 error as Error,
5edd8ba0 1763 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
e1d9a0f4 1764 )!;
24578c31
JB
1765 }
1766 }
1767
1768 private async handleRequestCancelReservation(
1769 chargingStation: ChargingStation,
5edd8ba0 1770 commandPayload: OCPP16CancelReservationRequest,
b1f1b0f6 1771 ): Promise<GenericResponse> {
66dd3447
JB
1772 if (
1773 !OCPP16ServiceUtils.checkFeatureProfile(
1774 chargingStation,
1775 OCPP16SupportedFeatureProfiles.Reservation,
5edd8ba0 1776 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
66dd3447
JB
1777 )
1778 ) {
178956d8 1779 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
66dd3447 1780 }
24578c31 1781 try {
d193a949 1782 const { reservationId } = commandPayload;
2ca0ea90 1783 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
af4339e1 1784 if (isUndefined(reservation)) {
90aceaf6
JB
1785 logger.debug(
1786 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
5edd8ba0 1787 does not exist on charging station`,
24578c31 1788 );
178956d8 1789 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
24578c31 1790 }
ec9f36cc 1791 await chargingStation.removeReservation(
e1d9a0f4 1792 reservation!,
5edd8ba0 1793 ReservationTerminationReason.RESERVATION_CANCELED,
ec9f36cc 1794 );
178956d8 1795 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
24578c31 1796 } catch (error) {
e1d9a0f4 1797 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1798 chargingStation,
1799 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1800 error as Error,
5edd8ba0 1801 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
e1d9a0f4 1802 )!;
24578c31
JB
1803 }
1804 }
c0560973 1805}