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