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