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