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