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