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