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