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