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