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