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