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