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