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