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