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