docs: refine bug report issue template, take 2
[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
5edd8ba0 467 connector id ${connectorId.toString()}`,
c60ed4b8 468 );
d8b1fab1 469 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c60ed4b8 470 }
c0560973 471 if (connectorId === 0) {
e7aeea18 472 logger.error(
5edd8ba0 473 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`,
e7aeea18 474 );
d8b1fab1 475 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
c0560973 476 }
5e3cb728
JB
477 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
478 const stopResponse = await chargingStation.stopTransactionOnConnector(
479 connectorId,
5edd8ba0 480 OCPP16StopTransactionReason.UNLOCK_COMMAND,
5e3cb728 481 );
c0560973 482 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
d8b1fab1 483 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
c0560973 484 }
d8b1fab1 485 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED;
c0560973 486 }
4ecff7ce
JB
487 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
488 chargingStation,
ef6fa3fb 489 connectorId,
5edd8ba0 490 OCPP16ChargePointStatus.Available,
4ecff7ce 491 );
d8b1fab1 492 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
c0560973
JB
493 }
494
e7aeea18 495 private handleRequestGetConfiguration(
08f130a0 496 chargingStation: ChargingStation,
5edd8ba0 497 commandPayload: GetConfigurationRequest,
e7aeea18 498 ): GetConfigurationResponse {
0d1f33ba 499 const { key } = commandPayload;
c0560973
JB
500 const configurationKey: OCPPConfigurationKey[] = [];
501 const unknownKey: string[] = [];
0d1f33ba 502 if (isUndefined(key) === true) {
f568f368 503 for (const configuration of chargingStation.ocppConfiguration!.configurationKey!) {
9bf0ef23 504 if (isUndefined(configuration.visible) === true) {
7f7b65ca 505 configuration.visible = true;
c0560973 506 }
da8629bb 507 if (configuration.visible === false) {
c0560973
JB
508 continue;
509 }
510 configurationKey.push({
7f7b65ca
JB
511 key: configuration.key,
512 readonly: configuration.readonly,
513 value: configuration.value,
c0560973
JB
514 });
515 }
0d1f33ba
JB
516 } else if (isNotEmptyArray(key) === true) {
517 for (const k of key!) {
518 const keyFound = getConfigurationKey(chargingStation, k, true);
c0560973 519 if (keyFound) {
9bf0ef23 520 if (isUndefined(keyFound.visible) === true) {
c0560973
JB
521 keyFound.visible = true;
522 }
a723e7e9 523 if (keyFound.visible === false) {
c0560973
JB
524 continue;
525 }
526 configurationKey.push({
527 key: keyFound.key,
528 readonly: keyFound.readonly,
529 value: keyFound.value,
530 });
531 } else {
0d1f33ba 532 unknownKey.push(k);
c0560973
JB
533 }
534 }
535 }
536 return {
537 configurationKey,
538 unknownKey,
539 };
540 }
541
e7aeea18 542 private handleRequestChangeConfiguration(
08f130a0 543 chargingStation: ChargingStation,
5edd8ba0 544 commandPayload: ChangeConfigurationRequest,
e7aeea18 545 ): ChangeConfigurationResponse {
0d1f33ba
JB
546 const { key, value } = commandPayload;
547 const keyToChange = getConfigurationKey(chargingStation, key, true);
e1d9a0f4 548 if (keyToChange?.readonly === true) {
d8b1fab1 549 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
bd5d98e0 550 } else if (keyToChange?.readonly === false) {
c0560973 551 let valueChanged = false;
0d1f33ba
JB
552 if (keyToChange.value !== value) {
553 setConfigurationKeyValue(chargingStation, key, value, true);
c0560973
JB
554 valueChanged = true;
555 }
556 let triggerHeartbeatRestart = false;
e1d9a0f4
JB
557 if (
558 (keyToChange.key as OCPP16StandardParametersKey) ===
559 OCPP16StandardParametersKey.HeartBeatInterval &&
560 valueChanged
561 ) {
f2d5e3d9 562 setConfigurationKeyValue(
17ac262c 563 chargingStation,
e7aeea18 564 OCPP16StandardParametersKey.HeartbeatInterval,
0d1f33ba 565 value,
e7aeea18 566 );
c0560973
JB
567 triggerHeartbeatRestart = true;
568 }
e1d9a0f4
JB
569 if (
570 (keyToChange.key as OCPP16StandardParametersKey) ===
571 OCPP16StandardParametersKey.HeartbeatInterval &&
572 valueChanged
573 ) {
f2d5e3d9 574 setConfigurationKeyValue(
17ac262c 575 chargingStation,
e7aeea18 576 OCPP16StandardParametersKey.HeartBeatInterval,
0d1f33ba 577 value,
e7aeea18 578 );
c0560973
JB
579 triggerHeartbeatRestart = true;
580 }
581 if (triggerHeartbeatRestart) {
08f130a0 582 chargingStation.restartHeartbeat();
c0560973 583 }
e1d9a0f4
JB
584 if (
585 (keyToChange.key as OCPP16StandardParametersKey) ===
586 OCPP16StandardParametersKey.WebSocketPingInterval &&
587 valueChanged
588 ) {
08f130a0 589 chargingStation.restartWebSocketPing();
c0560973
JB
590 }
591 if (keyToChange.reboot) {
d8b1fab1 592 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
c0560973 593 }
d8b1fab1 594 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
c0560973 595 }
e1d9a0f4 596 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
c0560973
JB
597 }
598
e7aeea18 599 private handleRequestSetChargingProfile(
08f130a0 600 chargingStation: ChargingStation,
5edd8ba0 601 commandPayload: SetChargingProfileRequest,
e7aeea18 602 ): SetChargingProfileResponse {
370ae4ee 603 if (
1789ba2c 604 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 605 chargingStation,
370ae4ee 606 OCPP16SupportedFeatureProfiles.SmartCharging,
5edd8ba0 607 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
1789ba2c 608 ) === false
370ae4ee 609 ) {
d8b1fab1 610 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
68cb8b91 611 }
0d1f33ba
JB
612 const { connectorId, csChargingProfiles } = commandPayload;
613 if (chargingStation.hasConnector(connectorId) === false) {
e7aeea18 614 logger.error(
66dd3447 615 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
0d1f33ba 616 non existing connector id ${connectorId}`,
e7aeea18 617 );
d8b1fab1 618 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 619 }
e7aeea18 620 if (
0d1f33ba 621 csChargingProfiles.chargingProfilePurpose ===
0ac97927 622 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
0d1f33ba 623 connectorId !== 0
e7aeea18 624 ) {
d8b1fab1 625 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 626 }
e7aeea18 627 if (
0d1f33ba
JB
628 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
629 (connectorId === 0 ||
630 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false)
e7aeea18 631 ) {
db0af086 632 logger.error(
66dd3447 633 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
0d1f33ba 634 on connector ${connectorId} without a started transaction`,
db0af086 635 );
d8b1fab1 636 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
c0560973 637 }
0d1f33ba 638 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles);
e7aeea18 639 logger.debug(
0d1f33ba
JB
640 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
641 csChargingProfiles,
e7aeea18 642 );
d8b1fab1 643 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
644 }
645
41189456
JB
646 private handleRequestGetCompositeSchedule(
647 chargingStation: ChargingStation,
5edd8ba0 648 commandPayload: OCPP16GetCompositeScheduleRequest,
41189456
JB
649 ): OCPP16GetCompositeScheduleResponse {
650 if (
651 OCPP16ServiceUtils.checkFeatureProfile(
652 chargingStation,
653 OCPP16SupportedFeatureProfiles.SmartCharging,
e1d9a0f4 654 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
41189456
JB
655 ) === false
656 ) {
d8b1fab1 657 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456 658 }
b3d7d654 659 const { connectorId, duration, chargingRateUnit } = commandPayload;
0d1f33ba 660 if (chargingStation.hasConnector(connectorId) === false) {
41189456 661 logger.error(
66dd3447 662 `${chargingStation.logPrefix()} Trying to get composite schedule to a
0d1f33ba 663 non existing connector id ${connectorId}`,
41189456 664 );
d8b1fab1 665 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456 666 }
b3d7d654
JB
667 if (connectorId === 0) {
668 logger.error(
669 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`,
670 );
671 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
672 }
673 if (chargingRateUnit) {
bbb55ee4
JB
674 logger.warn(
675 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`,
b3d7d654 676 );
b3d7d654 677 }
0eb666db 678 const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
ad490d5f
JB
679 if (
680 isEmptyArray(
681 connectorStatus?.chargingProfiles &&
682 isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles),
683 )
684 ) {
d8b1fab1 685 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456 686 }
ad490d5f 687 const currentDate = new Date();
ef9e3b33 688 const compositeScheduleInterval: Interval = {
ad490d5f
JB
689 start: currentDate,
690 end: addSeconds(currentDate, duration),
d372f6da 691 };
6fc0c6f3 692 // Get charging profiles sorted by connector id then stack level
ef9e3b33 693 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
6fc0c6f3
JB
694 chargingStation,
695 connectorId,
696 );
ef9e3b33
JB
697 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined;
698 let compositeSchedule: OCPP16ChargingSchedule | undefined;
699 for (const chargingProfile of chargingProfiles) {
b3d7d654 700 if (
ef9e3b33 701 isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
da332e70 702 connectorStatus?.transactionStarted
b3d7d654 703 ) {
ad490d5f
JB
704 logger.debug(
705 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
ef9e3b33 706 chargingProfile.chargingProfileId
ad490d5f 707 } has no startSchedule defined. Trying to set it to the connector current transaction start date`,
b3d7d654 708 );
ad490d5f 709 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
ef9e3b33 710 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart;
ad490d5f 711 }
da332e70 712 if (
ef9e3b33
JB
713 !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
714 !isDate(chargingProfile.chargingSchedule?.startSchedule)
715 ) {
716 logger.warn(
717 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
718 chargingProfile.chargingProfileId
719 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`,
720 );
721 chargingProfile.chargingSchedule.startSchedule = convertToDate(
722 chargingProfile.chargingSchedule?.startSchedule,
723 )!;
724 }
725 if (
726 !isNullOrUndefined(chargingProfile.chargingSchedule?.startSchedule) &&
727 isNullOrUndefined(chargingProfile.chargingSchedule?.duration)
da332e70
JB
728 ) {
729 logger.debug(
ef9e3b33
JB
730 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
731 chargingProfile.chargingProfileId
da332e70
JB
732 } has no duration defined and will be set to the maximum time allowed`,
733 );
734 // OCPP specifies that if duration is not defined, it should be infinite
ef9e3b33 735 chargingProfile.chargingSchedule.duration = differenceInSeconds(
da332e70 736 maxTime,
ef9e3b33 737 chargingProfile.chargingSchedule.startSchedule!,
da332e70
JB
738 );
739 }
0eb666db
JB
740 if (
741 !prepareChargingProfileKind(
742 connectorStatus,
ef9e3b33
JB
743 chargingProfile,
744 compositeScheduleInterval.start as Date,
0eb666db
JB
745 chargingStation.logPrefix(),
746 )
747 ) {
748 continue;
b3d7d654 749 }
41189456 750 if (
ad490d5f 751 !canProceedChargingProfile(
ef9e3b33
JB
752 chargingProfile,
753 compositeScheduleInterval.start as Date,
ad490d5f 754 chargingStation.logPrefix(),
b3d7d654 755 )
41189456 756 ) {
ad490d5f
JB
757 continue;
758 }
ef9e3b33
JB
759 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
760 previousCompositeSchedule,
761 chargingProfile.chargingSchedule,
762 compositeScheduleInterval,
491dad29 763 );
ef9e3b33
JB
764 previousCompositeSchedule = compositeSchedule;
765 }
766 if (compositeSchedule) {
767 return {
768 status: GenericStatus.Accepted,
769 scheduleStart: compositeSchedule.startSchedule!,
770 connectorId,
771 chargingSchedule: compositeSchedule,
772 };
773 }
774 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
41189456
JB
775 }
776
e7aeea18 777 private handleRequestClearChargingProfile(
08f130a0 778 chargingStation: ChargingStation,
5edd8ba0 779 commandPayload: ClearChargingProfileRequest,
e7aeea18 780 ): ClearChargingProfileResponse {
370ae4ee 781 if (
a36bad10 782 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 783 chargingStation,
370ae4ee 784 OCPP16SupportedFeatureProfiles.SmartCharging,
5edd8ba0 785 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
a36bad10 786 ) === false
370ae4ee 787 ) {
d8b1fab1 788 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
68cb8b91 789 }
0d1f33ba
JB
790 const { connectorId } = commandPayload;
791 if (chargingStation.hasConnector(connectorId!) === false) {
e7aeea18 792 logger.error(
66dd3447 793 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
0d1f33ba 794 a non existing connector id ${connectorId}`,
e7aeea18 795 );
d8b1fab1 796 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973 797 }
d812bdcb 798 if (
0d1f33ba
JB
799 !isNullOrUndefined(connectorId) &&
800 isNotEmptyArray(chargingStation.getConnectorStatus(connectorId!)?.chargingProfiles)
d812bdcb 801 ) {
0d1f33ba 802 chargingStation.getConnectorStatus(connectorId!)!.chargingProfiles = [];
e7aeea18 803 logger.debug(
0d1f33ba 804 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`,
e7aeea18 805 );
d8b1fab1 806 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973 807 }
0d1f33ba 808 if (isNullOrUndefined(connectorId)) {
c0560973 809 let clearedCP = false;
4334db72
JB
810 if (chargingStation.hasEvses) {
811 for (const evseStatus of chargingStation.evses.values()) {
812 for (const connectorStatus of evseStatus.connectors.values()) {
73d87be1
JB
813 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
814 chargingStation,
815 commandPayload,
816 connectorStatus.chargingProfiles,
817 );
4334db72
JB
818 }
819 }
820 } else {
0d1f33ba 821 for (const id of chargingStation.connectors.keys()) {
73d87be1
JB
822 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
823 chargingStation,
824 commandPayload,
0d1f33ba 825 chargingStation.getConnectorStatus(id)?.chargingProfiles,
73d87be1 826 );
c0560973
JB
827 }
828 }
829 if (clearedCP) {
d8b1fab1 830 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
c0560973
JB
831 }
832 }
d8b1fab1 833 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
c0560973
JB
834 }
835
e7aeea18 836 private async handleRequestChangeAvailability(
08f130a0 837 chargingStation: ChargingStation,
366f75f6
JB
838 commandPayload: OCPP16ChangeAvailabilityRequest,
839 ): Promise<OCPP16ChangeAvailabilityResponse> {
0d1f33ba 840 const { connectorId, type } = commandPayload;
a14022a2 841 if (chargingStation.hasConnector(connectorId) === false) {
e7aeea18 842 logger.error(
66dd3447 843 `${chargingStation.logPrefix()} Trying to change the availability of a
5edd8ba0 844 non existing connector id ${connectorId.toString()}`,
e7aeea18 845 );
d8b1fab1 846 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973 847 }
e7aeea18 848 const chargePointStatus: OCPP16ChargePointStatus =
0d1f33ba 849 type === OCPP16AvailabilityType.Operative
721646e9
JB
850 ? OCPP16ChargePointStatus.Available
851 : OCPP16ChargePointStatus.Unavailable;
c0560973 852 if (connectorId === 0) {
366f75f6 853 let response: OCPP16ChangeAvailabilityResponse;
ded57f02
JB
854 if (chargingStation.hasEvses) {
855 for (const evseStatus of chargingStation.evses.values()) {
366f75f6
JB
856 response = await OCPP16ServiceUtils.changeAvailability(
857 chargingStation,
225e32b0 858 [...evseStatus.connectors.keys()],
366f75f6 859 chargePointStatus,
0d1f33ba 860 type,
366f75f6 861 );
ded57f02 862 }
225e32b0
JB
863 } else {
864 response = await OCPP16ServiceUtils.changeAvailability(
865 chargingStation,
866 [...chargingStation.connectors.keys()],
867 chargePointStatus,
0d1f33ba 868 type,
225e32b0 869 );
c0560973 870 }
366f75f6 871 return response!;
e7aeea18
JB
872 } else if (
873 connectorId > 0 &&
56eb297e
JB
874 (chargingStation.isChargingStationAvailable() === true ||
875 (chargingStation.isChargingStationAvailable() === false &&
0d1f33ba 876 type === OCPP16AvailabilityType.Inoperative))
e7aeea18 877 ) {
5e3cb728 878 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
0d1f33ba 879 chargingStation.getConnectorStatus(connectorId)!.availability = type;
d8b1fab1 880 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
c0560973 881 }
0d1f33ba 882 chargingStation.getConnectorStatus(connectorId)!.availability = type;
4ecff7ce
JB
883 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
884 chargingStation,
ef6fa3fb 885 connectorId,
5edd8ba0 886 chargePointStatus,
4ecff7ce 887 );
d8b1fab1 888 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
c0560973 889 }
d8b1fab1 890 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
c0560973
JB
891 }
892
e7aeea18 893 private async handleRequestRemoteStartTransaction(
08f130a0 894 chargingStation: ChargingStation,
5edd8ba0 895 commandPayload: RemoteStartTransactionRequest,
f03e1042 896 ): Promise<GenericResponse> {
66dd3447 897 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
649287f8
JB
898 if (chargingStation.hasConnector(transactionConnectorId) === false) {
899 return this.notifyRemoteStartTransactionRejected(
4ecff7ce
JB
900 chargingStation,
901 transactionConnectorId,
5edd8ba0 902 idTag,
4ecff7ce 903 );
649287f8
JB
904 }
905 if (
d193a949
JB
906 !chargingStation.isChargingStationAvailable() ||
907 !chargingStation.isConnectorAvailable(transactionConnectorId)
649287f8
JB
908 ) {
909 return this.notifyRemoteStartTransactionRejected(
910 chargingStation,
911 transactionConnectorId,
5edd8ba0 912 idTag,
649287f8
JB
913 );
914 }
66dd3447
JB
915 const remoteStartTransactionLogMsg = `
916 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
5edd8ba0
JB
917 chargingStation.stationInfo.chargingStationId
918 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
649287f8
JB
919 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
920 chargingStation,
921 transactionConnectorId,
5edd8ba0 922 OCPP16ChargePointStatus.Preparing,
649287f8 923 );
e1d9a0f4 924 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
d984c13f
JB
925 // Authorization check required
926 if (
927 chargingStation.getAuthorizeRemoteTxRequests() === true &&
66dd3447
JB
928 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
929 ) {
930 // Authorization successful, start transaction
931 if (
932 this.setRemoteStartTransactionChargingProfile(
933 chargingStation,
934 transactionConnectorId,
e1d9a0f4 935 chargingProfile!,
66dd3447
JB
936 ) === true
937 ) {
938 connectorStatus.transactionRemoteStarted = true;
e7aeea18 939 if (
66dd3447
JB
940 (
941 await chargingStation.ocppRequestService.requestHandler<
942 OCPP16StartTransactionRequest,
943 OCPP16StartTransactionResponse
d984c13f
JB
944 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
945 connectorId: transactionConnectorId,
946 idTag,
947 })
66dd3447 948 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
e7aeea18 949 ) {
eb79c525 950 logger.debug(remoteStartTransactionLogMsg);
66dd3447 951 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
e060fe58 952 }
e7aeea18 953 return this.notifyRemoteStartTransactionRejected(
08f130a0 954 chargingStation,
e7aeea18 955 transactionConnectorId,
5edd8ba0 956 idTag,
e7aeea18 957 );
c0560973 958 }
e7aeea18 959 return this.notifyRemoteStartTransactionRejected(
08f130a0 960 chargingStation,
e7aeea18 961 transactionConnectorId,
5edd8ba0 962 idTag,
e7aeea18 963 );
c0560973 964 }
649287f8
JB
965 // No authorization check required, start transaction
966 if (
967 this.setRemoteStartTransactionChargingProfile(
968 chargingStation,
969 transactionConnectorId,
e1d9a0f4 970 chargingProfile!,
649287f8
JB
971 ) === true
972 ) {
973 connectorStatus.transactionRemoteStarted = true;
974 if (
975 (
976 await chargingStation.ocppRequestService.requestHandler<
977 OCPP16StartTransactionRequest,
978 OCPP16StartTransactionResponse
979 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
980 connectorId: transactionConnectorId,
66dd3447 981 idTag,
649287f8
JB
982 })
983 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
984 ) {
eb79c525 985 logger.debug(remoteStartTransactionLogMsg);
649287f8
JB
986 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
987 }
988 return this.notifyRemoteStartTransactionRejected(
989 chargingStation,
990 transactionConnectorId,
5edd8ba0 991 idTag,
649287f8
JB
992 );
993 }
08f130a0
JB
994 return this.notifyRemoteStartTransactionRejected(
995 chargingStation,
996 transactionConnectorId,
5edd8ba0 997 idTag,
08f130a0 998 );
a7fc8211
JB
999 }
1000
e7aeea18 1001 private async notifyRemoteStartTransactionRejected(
08f130a0 1002 chargingStation: ChargingStation,
e7aeea18 1003 connectorId: number,
5edd8ba0 1004 idTag: string,
f03e1042 1005 ): Promise<GenericResponse> {
e7aeea18 1006 if (
721646e9 1007 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
e7aeea18 1008 ) {
4ecff7ce
JB
1009 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1010 chargingStation,
ef6fa3fb 1011 connectorId,
5edd8ba0 1012 OCPP16ChargePointStatus.Available,
4ecff7ce 1013 );
e060fe58 1014 }
e7aeea18 1015 logger.warn(
66dd3447 1016 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
5edd8ba0
JB
1017 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
1018 connectorId,
1019 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
e7aeea18 1020 );
d8b1fab1 1021 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973
JB
1022 }
1023
e7aeea18 1024 private setRemoteStartTransactionChargingProfile(
08f130a0 1025 chargingStation: ChargingStation,
e7aeea18 1026 connectorId: number,
55f2ab60 1027 chargingProfile: OCPP16ChargingProfile,
e7aeea18 1028 ): boolean {
55f2ab60
JB
1029 if (
1030 chargingProfile &&
1031 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
1032 ) {
1033 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
e7aeea18 1034 logger.debug(
66dd3447
JB
1035 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
1036 on connector id ${connectorId}: %j`,
55f2ab60 1037 chargingProfile,
e7aeea18 1038 );
a7fc8211 1039 return true;
55f2ab60
JB
1040 } else if (
1041 chargingProfile &&
1042 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
1043 ) {
e7aeea18 1044 logger.warn(
08f130a0 1045 `${chargingStation.logPrefix()} Not allowed to set ${
55f2ab60 1046 chargingProfile.chargingProfilePurpose
5edd8ba0 1047 } charging profile(s) at remote start transaction`,
e7aeea18 1048 );
a7fc8211
JB
1049 return false;
1050 }
e1d9a0f4 1051 return true;
a7fc8211
JB
1052 }
1053
e7aeea18 1054 private async handleRequestRemoteStopTransaction(
08f130a0 1055 chargingStation: ChargingStation,
5edd8ba0 1056 commandPayload: RemoteStopTransactionRequest,
f03e1042 1057 ): Promise<GenericResponse> {
0d1f33ba 1058 const { transactionId } = commandPayload;
ded57f02
JB
1059 if (chargingStation.hasEvses) {
1060 for (const [evseId, evseStatus] of chargingStation.evses) {
1061 if (evseId > 0) {
1062 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1063 if (connectorStatus.transactionId === transactionId) {
d19b10a8 1064 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
ded57f02
JB
1065 }
1066 }
1067 }
1068 }
1069 } else {
1070 for (const connectorId of chargingStation.connectors.keys()) {
1071 if (
1072 connectorId > 0 &&
1073 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1074 ) {
d19b10a8 1075 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
ef6fa3fb 1076 }
c0560973
JB
1077 }
1078 }
44b9b577 1079 logger.warn(
56563a3c 1080 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id
5edd8ba0 1081 ${transactionId.toString()}`,
e7aeea18 1082 );
d8b1fab1 1083 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973 1084 }
47e22477 1085
b03df580
JB
1086 private handleRequestUpdateFirmware(
1087 chargingStation: ChargingStation,
5edd8ba0 1088 commandPayload: OCPP16UpdateFirmwareRequest,
b03df580
JB
1089 ): OCPP16UpdateFirmwareResponse {
1090 if (
1091 OCPP16ServiceUtils.checkFeatureProfile(
1092 chargingStation,
1093 OCPP16SupportedFeatureProfiles.FirmwareManagement,
5edd8ba0 1094 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
b03df580
JB
1095 ) === false
1096 ) {
5d280aae 1097 logger.warn(
66dd3447 1098 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
5edd8ba0 1099 Cannot simulate firmware update: feature profile not supported`,
5d280aae 1100 );
d8b1fab1 1101 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
5d280aae 1102 }
0d1f33ba 1103 let { retrieveDate } = commandPayload;
5d280aae 1104 if (
9bf0ef23 1105 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
5d280aae
JB
1106 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1107 ) {
1108 logger.warn(
66dd3447 1109 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
5edd8ba0 1110 Cannot simulate firmware update: firmware update is already in progress`,
5d280aae 1111 );
d8b1fab1 1112 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
b03df580 1113 }
0d1f33ba 1114 retrieveDate = convertToDate(retrieveDate)!;
2c7bdc61 1115 const now = Date.now();
72092cfc 1116 if (retrieveDate?.getTime() <= now) {
27f08ad3 1117 this.runInAsyncScope(
62340a29 1118 this.updateFirmwareSimulation.bind(this) as (
27f08ad3 1119 this: OCPP16IncomingRequestService,
e843aa40 1120 ...args: unknown[]
27f08ad3
JB
1121 ) => Promise<void>,
1122 this,
5edd8ba0 1123 chargingStation,
59b6ed8d 1124 ).catch(Constants.EMPTY_FUNCTION);
c9a4f9ea 1125 } else {
5edd8ba0
JB
1126 setTimeout(
1127 () => {
1128 this.runInAsyncScope(
1129 this.updateFirmwareSimulation.bind(this) as (
1130 this: OCPP16IncomingRequestService,
e843aa40 1131 ...args: unknown[]
5edd8ba0
JB
1132 ) => Promise<void>,
1133 this,
1134 chargingStation,
1135 ).catch(Constants.EMPTY_FUNCTION);
1136 },
1137 retrieveDate?.getTime() - now,
1138 );
c9a4f9ea 1139 }
d8b1fab1 1140 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
c9a4f9ea
JB
1141 }
1142
62340a29 1143 private async updateFirmwareSimulation(
c9a4f9ea 1144 chargingStation: ChargingStation,
90293abb 1145 maxDelay = 30,
5edd8ba0 1146 minDelay = 15,
c9a4f9ea 1147 ): Promise<void> {
fba11dc6 1148 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1bf29f5b
JB
1149 return;
1150 }
ded57f02
JB
1151 if (chargingStation.hasEvses) {
1152 for (const [evseId, evseStatus] of chargingStation.evses) {
1153 if (evseId > 0) {
1154 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1155 if (connectorStatus?.transactionStarted === false) {
1156 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1157 chargingStation,
1158 connectorId,
5edd8ba0 1159 OCPP16ChargePointStatus.Unavailable,
ded57f02
JB
1160 );
1161 }
1162 }
1163 }
1164 }
1165 } else {
1166 for (const connectorId of chargingStation.connectors.keys()) {
1167 if (
1168 connectorId > 0 &&
1169 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1170 ) {
1171 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1172 chargingStation,
1173 connectorId,
5edd8ba0 1174 OCPP16ChargePointStatus.Unavailable,
ded57f02
JB
1175 );
1176 }
c9a4f9ea
JB
1177 }
1178 }
93f0c2c8
JB
1179 await chargingStation.ocppRequestService.requestHandler<
1180 OCPP16FirmwareStatusNotificationRequest,
1181 OCPP16FirmwareStatusNotificationResponse
1182 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1183 status: OCPP16FirmwareStatus.Downloading,
1184 });
1185 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
5d280aae 1186 if (
93f0c2c8
JB
1187 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1188 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1189 ) {
be4c6702 1190 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
5d280aae
JB
1191 await chargingStation.ocppRequestService.requestHandler<
1192 OCPP16FirmwareStatusNotificationRequest,
1193 OCPP16FirmwareStatusNotificationResponse
1194 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
15748260 1195 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
5d280aae 1196 });
93f0c2c8
JB
1197 chargingStation.stationInfo.firmwareStatus =
1198 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
5d280aae
JB
1199 return;
1200 }
be4c6702 1201 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
c9a4f9ea
JB
1202 await chargingStation.ocppRequestService.requestHandler<
1203 OCPP16FirmwareStatusNotificationRequest,
1204 OCPP16FirmwareStatusNotificationResponse
1205 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1206 status: OCPP16FirmwareStatus.Downloaded,
1207 });
1208 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
380ccc42 1209 let wasTransactionsStarted = false;
62340a29
JB
1210 let transactionsStarted: boolean;
1211 do {
ded57f02
JB
1212 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1213 if (runningTransactions > 0) {
be4c6702 1214 const waitTime = secondsToMilliseconds(15);
62340a29 1215 logger.debug(
66dd3447 1216 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
be4c6702
JB
1217 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1218 waitTime,
1219 )} before continuing firmware update simulation`,
62340a29 1220 );
9bf0ef23 1221 await sleep(waitTime);
62340a29 1222 transactionsStarted = true;
380ccc42 1223 wasTransactionsStarted = true;
62340a29 1224 } else {
ded57f02
JB
1225 if (chargingStation.hasEvses) {
1226 for (const [evseId, evseStatus] of chargingStation.evses) {
1227 if (evseId > 0) {
1228 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1229 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1230 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1231 chargingStation,
1232 connectorId,
5edd8ba0 1233 OCPP16ChargePointStatus.Unavailable,
ded57f02
JB
1234 );
1235 }
1236 }
1237 }
1238 }
1239 } else {
1240 for (const connectorId of chargingStation.connectors.keys()) {
1241 if (
1242 connectorId > 0 &&
1243 chargingStation.getConnectorStatus(connectorId)?.status !==
1244 OCPP16ChargePointStatus.Unavailable
1245 ) {
1246 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1247 chargingStation,
1248 connectorId,
5edd8ba0 1249 OCPP16ChargePointStatus.Unavailable,
ded57f02
JB
1250 );
1251 }
62340a29
JB
1252 }
1253 }
1254 transactionsStarted = false;
1255 }
1256 } while (transactionsStarted);
be4c6702
JB
1257 !wasTransactionsStarted &&
1258 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
fba11dc6 1259 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1bf29f5b
JB
1260 return;
1261 }
c9a4f9ea
JB
1262 await chargingStation.ocppRequestService.requestHandler<
1263 OCPP16FirmwareStatusNotificationRequest,
1264 OCPP16FirmwareStatusNotificationResponse
1265 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1266 status: OCPP16FirmwareStatus.Installing,
1267 });
1268 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
93f0c2c8
JB
1269 if (
1270 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1271 OCPP16FirmwareStatus.InstallationFailed
1272 ) {
be4c6702 1273 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
93f0c2c8
JB
1274 await chargingStation.ocppRequestService.requestHandler<
1275 OCPP16FirmwareStatusNotificationRequest,
1276 OCPP16FirmwareStatusNotificationResponse
1277 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1278 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1279 });
1280 chargingStation.stationInfo.firmwareStatus =
1281 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1282 return;
1283 }
15748260 1284 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
be4c6702 1285 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
5d280aae
JB
1286 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1287 }
b03df580
JB
1288 }
1289
e7aeea18 1290 private async handleRequestGetDiagnostics(
08f130a0 1291 chargingStation: ChargingStation,
5edd8ba0 1292 commandPayload: GetDiagnosticsRequest,
e7aeea18 1293 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1294 if (
1789ba2c 1295 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1296 chargingStation,
370ae4ee 1297 OCPP16SupportedFeatureProfiles.FirmwareManagement,
5edd8ba0 1298 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1789ba2c 1299 ) === false
68cb8b91 1300 ) {
90293abb 1301 logger.warn(
66dd3447 1302 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
5edd8ba0 1303 Cannot get diagnostics: feature profile not supported`,
90293abb 1304 );
d8b1fab1 1305 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
68cb8b91 1306 }
0d1f33ba
JB
1307 const { location } = commandPayload;
1308 const uri = new URL(location);
47e22477 1309 if (uri.protocol.startsWith('ftp:')) {
e1d9a0f4 1310 let ftpClient: Client | undefined;
47e22477 1311 try {
d972af76 1312 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
72092cfc 1313 .filter((file) => file.endsWith('.log'))
d972af76 1314 .map((file) => join('./', file));
44eb6026 1315 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
d972af76 1316 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
47e22477
JB
1317 ftpClient = new Client();
1318 const accessResponse = await ftpClient.access({
1319 host: uri.host,
9bf0ef23
JB
1320 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1321 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1322 ...(isNotEmptyString(uri.password) && { password: uri.password }),
47e22477 1323 });
e1d9a0f4 1324 let uploadResponse: FTPResponse | undefined;
47e22477 1325 if (accessResponse.code === 220) {
72092cfc 1326 ftpClient.trackProgress((info) => {
e7aeea18 1327 logger.info(
56563a3c 1328 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
e7aeea18 1329 info.bytes / 1024
5edd8ba0 1330 } bytes transferred from diagnostics archive ${info.name}`,
e7aeea18 1331 );
6a8329b4
JB
1332 chargingStation.ocppRequestService
1333 .requestHandler<
1334 OCPP16DiagnosticsStatusNotificationRequest,
1335 OCPP16DiagnosticsStatusNotificationResponse
1336 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1337 status: OCPP16DiagnosticsStatus.Uploading,
1338 })
72092cfc 1339 .catch((error) => {
6a8329b4 1340 logger.error(
66dd3447
JB
1341 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1342 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
5edd8ba0 1343 error,
6a8329b4
JB
1344 );
1345 });
47e22477 1346 });
e7aeea18 1347 uploadResponse = await ftpClient.uploadFrom(
d972af76 1348 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
5edd8ba0 1349 `${uri.pathname}${diagnosticsArchive}`,
e7aeea18 1350 );
47e22477 1351 if (uploadResponse.code === 226) {
08f130a0 1352 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1353 OCPP16DiagnosticsStatusNotificationRequest,
1354 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1355 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1356 status: OCPP16DiagnosticsStatus.Uploaded,
1357 });
47e22477
JB
1358 if (ftpClient) {
1359 ftpClient.close();
1360 }
1361 return { fileName: diagnosticsArchive };
1362 }
e7aeea18
JB
1363 throw new OCPPError(
1364 ErrorType.GENERIC_ERROR,
1365 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1366 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18 1367 }`,
5edd8ba0 1368 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
e7aeea18 1369 );
47e22477 1370 }
e7aeea18
JB
1371 throw new OCPPError(
1372 ErrorType.GENERIC_ERROR,
1373 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1374 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18 1375 }`,
5edd8ba0 1376 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
e7aeea18 1377 );
47e22477 1378 } catch (error) {
08f130a0 1379 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1380 OCPP16DiagnosticsStatusNotificationRequest,
1381 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1382 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1383 status: OCPP16DiagnosticsStatus.UploadFailed,
1384 });
47e22477
JB
1385 if (ftpClient) {
1386 ftpClient.close();
1387 }
e1d9a0f4 1388 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
08f130a0 1389 chargingStation,
e7aeea18
JB
1390 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1391 error as Error,
5edd8ba0 1392 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
e1d9a0f4 1393 )!;
47e22477
JB
1394 }
1395 } else {
e7aeea18 1396 logger.error(
08f130a0 1397 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18 1398 uri.protocol
5edd8ba0 1399 } to transfer the diagnostic logs archive`,
e7aeea18 1400 );
08f130a0 1401 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1402 OCPP16DiagnosticsStatusNotificationRequest,
1403 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1404 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1405 status: OCPP16DiagnosticsStatus.UploadFailed,
1406 });
d8b1fab1 1407 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1408 }
1409 }
802cfa13 1410
e7aeea18 1411 private handleRequestTriggerMessage(
08f130a0 1412 chargingStation: ChargingStation,
5edd8ba0 1413 commandPayload: OCPP16TriggerMessageRequest,
e7aeea18 1414 ): OCPP16TriggerMessageResponse {
0d1f33ba 1415 const { requestedMessage, connectorId } = commandPayload;
370ae4ee
JB
1416 if (
1417 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1418 chargingStation,
370ae4ee 1419 OCPP16SupportedFeatureProfiles.RemoteTrigger,
5edd8ba0 1420 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
c60ed4b8 1421 ) ||
0d1f33ba 1422 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
370ae4ee 1423 ) {
d8b1fab1 1424 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1425 }
c60ed4b8 1426 if (
4caa7e67 1427 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1428 chargingStation,
1429 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
0d1f33ba 1430 connectorId!,
c60ed4b8
JB
1431 )
1432 ) {
d8b1fab1 1433 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1434 }
802cfa13 1435 try {
0d1f33ba 1436 switch (requestedMessage) {
c60ed4b8 1437 case OCPP16MessageTrigger.BootNotification:
802cfa13 1438 setTimeout(() => {
08f130a0 1439 chargingStation.ocppRequestService
f7f98c68 1440 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1441 chargingStation,
6a8b180d 1442 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1443 chargingStation.bootNotificationRequest,
5edd8ba0 1444 { skipBufferingOnError: true, triggerMessage: true },
e7aeea18 1445 )
72092cfc 1446 .then((response) => {
8bfbc743 1447 chargingStation.bootNotificationResponse = response;
ae711c83 1448 })
59b6ed8d 1449 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1450 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1451 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1452 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1453 setTimeout(() => {
08f130a0 1454 chargingStation.ocppRequestService
f7f98c68 1455 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1456 chargingStation,
ef6fa3fb
JB
1457 OCPP16RequestCommand.HEARTBEAT,
1458 null,
1459 {
1460 triggerMessage: true,
5edd8ba0 1461 },
ef6fa3fb 1462 )
59b6ed8d 1463 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1464 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1465 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1466 case OCPP16MessageTrigger.StatusNotification:
dc661702 1467 setTimeout(() => {
0d1f33ba 1468 if (!isNullOrUndefined(connectorId)) {
08f130a0 1469 chargingStation.ocppRequestService
dc661702 1470 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1471 chargingStation,
dc661702
JB
1472 OCPP16RequestCommand.STATUS_NOTIFICATION,
1473 {
0d1f33ba 1474 connectorId,
dc661702 1475 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
0d1f33ba 1476 status: chargingStation.getConnectorStatus(connectorId!)?.status,
dc661702
JB
1477 },
1478 {
1479 triggerMessage: true,
5edd8ba0 1480 },
dc661702 1481 )
59b6ed8d 1482 .catch(Constants.EMPTY_FUNCTION);
dc661702 1483 } else {
ded57f02
JB
1484 // eslint-disable-next-line no-lonely-if
1485 if (chargingStation.hasEvses) {
1486 for (const evseStatus of chargingStation.evses.values()) {
0d1f33ba 1487 for (const [id, connectorStatus] of evseStatus.connectors) {
ded57f02
JB
1488 chargingStation.ocppRequestService
1489 .requestHandler<
1490 OCPP16StatusNotificationRequest,
1491 OCPP16StatusNotificationResponse
1492 >(
1493 chargingStation,
1494 OCPP16RequestCommand.STATUS_NOTIFICATION,
1495 {
0d1f33ba 1496 connectorId: id,
ded57f02
JB
1497 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1498 status: connectorStatus.status,
1499 },
1500 {
1501 triggerMessage: true,
5edd8ba0 1502 },
ded57f02
JB
1503 )
1504 .catch(Constants.EMPTY_FUNCTION);
1505 }
1506 }
1507 } else {
0d1f33ba 1508 for (const id of chargingStation.connectors.keys()) {
ded57f02
JB
1509 chargingStation.ocppRequestService
1510 .requestHandler<
1511 OCPP16StatusNotificationRequest,
1512 OCPP16StatusNotificationResponse
1513 >(
1514 chargingStation,
1515 OCPP16RequestCommand.STATUS_NOTIFICATION,
1516 {
0d1f33ba 1517 connectorId: id,
ded57f02 1518 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
0d1f33ba 1519 status: chargingStation.getConnectorStatus(id)?.status,
ded57f02
JB
1520 },
1521 {
1522 triggerMessage: true,
5edd8ba0 1523 },
ded57f02
JB
1524 )
1525 .catch(Constants.EMPTY_FUNCTION);
1526 }
dc661702
JB
1527 }
1528 }
d8b1fab1
JB
1529 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1530 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1531 default:
d8b1fab1 1532 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1533 }
1534 } catch (error) {
e1d9a0f4 1535 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
08f130a0 1536 chargingStation,
e7aeea18
JB
1537 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1538 error as Error,
5edd8ba0 1539 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
e1d9a0f4 1540 )!;
802cfa13
JB
1541 }
1542 }
77b95a89
JB
1543
1544 private handleRequestDataTransfer(
1545 chargingStation: ChargingStation,
5edd8ba0 1546 commandPayload: OCPP16DataTransferRequest,
77b95a89 1547 ): OCPP16DataTransferResponse {
0d1f33ba 1548 const { vendorId } = commandPayload;
77b95a89 1549 try {
0d1f33ba 1550 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
b63b4a73 1551 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
77b95a89 1552 }
b63b4a73 1553 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
77b95a89 1554 } catch (error) {
e1d9a0f4 1555 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
77b95a89
JB
1556 chargingStation,
1557 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1558 error as Error,
5edd8ba0 1559 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
e1d9a0f4 1560 )!;
77b95a89
JB
1561 }
1562 }
24578c31
JB
1563
1564 private async handleRequestReserveNow(
1565 chargingStation: ChargingStation,
5edd8ba0 1566 commandPayload: OCPP16ReserveNowRequest,
24578c31 1567 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1568 if (
1569 !OCPP16ServiceUtils.checkFeatureProfile(
1570 chargingStation,
1571 OCPP16SupportedFeatureProfiles.Reservation,
5edd8ba0 1572 OCPP16IncomingRequestCommand.RESERVE_NOW,
66dd3447
JB
1573 )
1574 ) {
178956d8 1575 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
66dd3447 1576 }
24578c31 1577 const { reservationId, idTag, connectorId } = commandPayload;
24578c31
JB
1578 let response: OCPP16ReserveNowResponse;
1579 try {
d984c13f 1580 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
178956d8 1581 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1582 }
10e8c3e1 1583 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
178956d8 1584 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1585 }
66dd3447 1586 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
178956d8 1587 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1588 }
90aceaf6 1589 await removeExpiredReservations(chargingStation);
e1d9a0f4 1590 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
178956d8
JB
1591 case OCPP16ChargePointStatus.Faulted:
1592 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
24578c31 1593 break;
178956d8
JB
1594 case OCPP16ChargePointStatus.Preparing:
1595 case OCPP16ChargePointStatus.Charging:
1596 case OCPP16ChargePointStatus.SuspendedEV:
1597 case OCPP16ChargePointStatus.SuspendedEVSE:
1598 case OCPP16ChargePointStatus.Finishing:
1599 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
24578c31 1600 break;
178956d8
JB
1601 case OCPP16ChargePointStatus.Unavailable:
1602 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
24578c31 1603 break;
178956d8 1604 case OCPP16ChargePointStatus.Reserved:
66dd3447 1605 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
178956d8 1606 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
24578c31
JB
1607 break;
1608 }
1609 // eslint-disable-next-line no-fallthrough
1610 default:
66dd3447 1611 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
178956d8 1612 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
d193a949
JB
1613 break;
1614 }
1615 await chargingStation.addReservation({
1616 id: commandPayload.reservationId,
1617 ...commandPayload,
1618 });
178956d8 1619 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
24578c31
JB
1620 break;
1621 }
1622 return response;
1623 } catch (error) {
e1d9a0f4
JB
1624 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1625 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
24578c31
JB
1626 chargingStation,
1627 OCPP16IncomingRequestCommand.RESERVE_NOW,
1628 error as Error,
5edd8ba0 1629 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
e1d9a0f4 1630 )!;
24578c31
JB
1631 }
1632 }
1633
1634 private async handleRequestCancelReservation(
1635 chargingStation: ChargingStation,
5edd8ba0 1636 commandPayload: OCPP16CancelReservationRequest,
b1f1b0f6 1637 ): Promise<GenericResponse> {
66dd3447
JB
1638 if (
1639 !OCPP16ServiceUtils.checkFeatureProfile(
1640 chargingStation,
1641 OCPP16SupportedFeatureProfiles.Reservation,
5edd8ba0 1642 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
66dd3447
JB
1643 )
1644 ) {
178956d8 1645 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
66dd3447 1646 }
24578c31 1647 try {
d193a949 1648 const { reservationId } = commandPayload;
2ca0ea90 1649 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
af4339e1 1650 if (isUndefined(reservation)) {
90aceaf6
JB
1651 logger.debug(
1652 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
5edd8ba0 1653 does not exist on charging station`,
24578c31 1654 );
178956d8 1655 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
24578c31 1656 }
ec9f36cc 1657 await chargingStation.removeReservation(
e1d9a0f4 1658 reservation!,
5edd8ba0 1659 ReservationTerminationReason.RESERVATION_CANCELED,
ec9f36cc 1660 );
178956d8 1661 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
24578c31 1662 } catch (error) {
e1d9a0f4 1663 return this.handleIncomingRequestError<GenericResponse>(
24578c31
JB
1664 chargingStation,
1665 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1666 error as Error,
5edd8ba0 1667 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
e1d9a0f4 1668 )!;
24578c31
JB
1669 }
1670 }
c0560973 1671}