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