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