refactor: cleanup loops over object keys
[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
JB
168 moduleName,
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
JB
176 moduleName,
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
JB
184 moduleName,
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
JB
192 moduleName,
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
JB
200 moduleName,
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
JB
208 moduleName,
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
JB
216 moduleName,
217 'constructor'
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
JB
224 moduleName,
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
JB
232 moduleName,
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
JB
240 moduleName,
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
JB
248 moduleName,
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
JB
256 moduleName,
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
JB
264 moduleName,
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
JB
272 moduleName,
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
JB
280 moduleName,
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,
289 'constructor'
290 ),
291 ],
292 [
293 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
294 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
295 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
296 moduleName,
297 'constructor'
298 ),
299 ],
b52c969d 300 ]);
31f59c6d
JB
301 this.validatePayload = this.validatePayload.bind(this) as (
302 chargingStation: ChargingStation,
303 commandName: OCPP16IncomingRequestCommand,
304 commandPayload: JsonType
305 ) => boolean;
58144adb
JB
306 }
307
f7f98c68 308 public async incomingRequestHandler(
08f130a0 309 chargingStation: ChargingStation,
e7aeea18
JB
310 messageId: string,
311 commandName: OCPP16IncomingRequestCommand,
5cc4b63b 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,
326 2
327 )} while the charging station is in pending state on the central server`,
7369e417
JB
328 commandName,
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,
346 commandPayload
347 );
124f3553
JB
348 } catch (error) {
349 // Log
6c8f5d90 350 logger.error(
66dd3447
JB
351 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler:
352 Handle incoming request error:`,
6c8f5d90
JB
353 error
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,
364 2
365 )}`,
7369e417
JB
366 commandName,
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,
376 2
377 )} while the charging station is not registered on the central server.`,
7369e417
JB
378 commandName,
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,
387 commandName
388 );
c0560973
JB
389 }
390
9c5c4195
JB
391 private validatePayload(
392 chargingStation: ChargingStation,
393 commandName: OCPP16IncomingRequestCommand,
394 commandPayload: JsonType
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),
401 commandPayload
402 );
403 }
404 logger.warn(
66dd3447
JB
405 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found
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,
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,
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
9bf0ef23 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,
e7aeea18
JB
435 commandPayload: UnlockConnectorRequest
436 ): Promise<UnlockConnectorResponse> {
c0560973 437 const connectorId = commandPayload.connectorId;
a14022a2 438 if (chargingStation.hasConnector(connectorId) === false) {
c60ed4b8 439 logger.error(
66dd3447
JB
440 `${chargingStation.logPrefix()} Trying to unlock a non existing
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(
2585c6e9 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,
454 OCPP16StopTransactionReason.UNLOCK_COMMAND
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,
4ecff7ce
JB
464 OCPP16ChargePointStatus.Available
465 );
d8b1fab1 466 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
c0560973
JB
467 }
468
e7aeea18 469 private handleRequestGetConfiguration(
08f130a0 470 chargingStation: ChargingStation,
e7aeea18
JB
471 commandPayload: GetConfigurationRequest
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
JB
493 key,
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,
e7aeea18
JB
521 commandPayload: ChangeConfigurationRequest
522 ): ChangeConfigurationResponse {
17ac262c
JB
523 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
524 chargingStation,
525 commandPayload.key,
526 true
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,
539 true
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
JB
547 OCPP16StandardParametersKey.HeartbeatInterval,
548 commandPayload.value
549 );
c0560973
JB
550 triggerHeartbeatRestart = true;
551 }
552 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
17ac262c
JB
553 ChargingStationConfigurationUtils.setConfigurationKeyValue(
554 chargingStation,
e7aeea18
JB
555 OCPP16StandardParametersKey.HeartBeatInterval,
556 commandPayload.value
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,
e7aeea18
JB
575 commandPayload: SetChargingProfileRequest
576 ): SetChargingProfileResponse {
370ae4ee 577 if (
1789ba2c 578 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 579 chargingStation,
370ae4ee
JB
580 OCPP16SupportedFeatureProfiles.SmartCharging,
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
JB
588 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
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
JB
608 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
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
JB
615 commandPayload.connectorId,
616 commandPayload.csChargingProfiles
617 );
618 logger.debug(
08f130a0 619 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
ad8537a7 620 commandPayload.connectorId
ad67a158
JB
621 }: %j`,
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,
629 commandPayload: OCPP16GetCompositeScheduleRequest
630 ): OCPP16GetCompositeScheduleResponse {
631 if (
632 OCPP16ServiceUtils.checkFeatureProfile(
633 chargingStation,
634 OCPP16SupportedFeatureProfiles.SmartCharging,
635 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
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
JB
642 `${chargingStation.logPrefix()} Trying to get composite schedule to a
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,
e7aeea18
JB
676 commandPayload: ClearChargingProfileRequest
677 ): ClearChargingProfileResponse {
370ae4ee 678 if (
a36bad10 679 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 680 chargingStation,
370ae4ee
JB
681 OCPP16SupportedFeatureProfiles.SmartCharging,
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
JB
689 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
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(
4334db72
JB
697 chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles
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
ad67a158 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
JB
739 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
740 chargingProfile
e7aeea18
JB
741 );
742 clearedCP = true;
743 }
4334db72
JB
744 }
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,
e7aeea18
JB
768 commandPayload: ChangeAvailabilityRequest
769 ): Promise<ChangeAvailabilityResponse> {
c0560973 770 const connectorId: number = commandPayload.connectorId;
a14022a2 771 if (chargingStation.hasConnector(connectorId) === false) {
e7aeea18 772 logger.error(
66dd3447
JB
773 `${chargingStation.logPrefix()} Trying to change the availability of a
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,
794 chargePointStatus
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,
4ecff7ce
JB
824 chargePointStatus
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,
e7aeea18 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,
66dd3447 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,
66dd3447 862 idTag
649287f8
JB
863 );
864 }
66dd3447
JB
865 const remoteStartTransactionLogMsg = `
866 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
649287f8 867 chargingStation.stationInfo.chargingStationId
66dd3447 868 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
649287f8
JB
869 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
870 chargingStation,
871 transactionConnectorId,
872 OCPP16ChargePointStatus.Preparing
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,
885 chargingProfile
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,
896 reservedOnConnectorZero ? 0 : transactionConnectorId
897 );
898 startTransactionPayload.reservationId = reservation.id;
899 await chargingStation.removeReservation(
900 reservation,
901 ReservationTerminationReason.TRANSACTION_STARTED
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,
66dd3447 918 idTag
e7aeea18 919 );
c0560973 920 }
e7aeea18 921 return this.notifyRemoteStartTransactionRejected(
08f130a0 922 chargingStation,
e7aeea18 923 transactionConnectorId,
66dd3447 924 idTag
e7aeea18 925 );
c0560973 926 }
649287f8
JB
927 // No authorization check required, start transaction
928 if (
929 this.setRemoteStartTransactionChargingProfile(
930 chargingStation,
931 transactionConnectorId,
66dd3447 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,
66dd3447 953 idTag
649287f8
JB
954 );
955 }
08f130a0
JB
956 return this.notifyRemoteStartTransactionRejected(
957 chargingStation,
958 transactionConnectorId,
66dd3447 959 idTag
08f130a0 960 );
a7fc8211
JB
961 }
962
e7aeea18 963 private async notifyRemoteStartTransactionRejected(
08f130a0 964 chargingStation: ChargingStation,
e7aeea18
JB
965 connectorId: number,
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,
4ecff7ce
JB
974 OCPP16ChargePointStatus.Available
975 );
e060fe58 976 }
e7aeea18 977 logger.warn(
66dd3447
JB
978 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
979 ${connectorId.toString()}, idTag '${idTag}', availability '${
72092cfc
JB
980 chargingStation.getConnectorStatus(connectorId)?.availability
981 }', 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
JB
988 connectorId: number,
989 cp: OCPP16ChargingProfile
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`,
ad67a158 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
JB
1002 cp.chargingProfilePurpose
1003 } charging profile(s) at remote start transaction`
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,
e7aeea18 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,
1020 OCPP16ChargePointStatus.Finishing
1021 );
1022 const stopResponse = await chargingStation.stopTransactionOnConnector(
1023 connectorId,
1024 OCPP16StopTransactionReason.REMOTE
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
JB
1052 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1053 ${transactionId.toString()}`
e7aeea18 1054 );
d8b1fab1 1055 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
c0560973 1056 }
47e22477 1057
b03df580
JB
1058 private handleRequestUpdateFirmware(
1059 chargingStation: ChargingStation,
1060 commandPayload: OCPP16UpdateFirmwareRequest
1061 ): OCPP16UpdateFirmwareResponse {
1062 if (
1063 OCPP16ServiceUtils.checkFeatureProfile(
1064 chargingStation,
1065 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1066 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1067 ) === false
1068 ) {
5d280aae 1069 logger.warn(
66dd3447
JB
1070 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
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
JB
1080 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
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,
1094 chargingStation
59b6ed8d 1095 ).catch(Constants.EMPTY_FUNCTION);
c9a4f9ea
JB
1096 } else {
1097 setTimeout(() => {
507a34c1
JB
1098 this.runInAsyncScope(
1099 this.updateFirmwareSimulation.bind(this) as (
1100 this: OCPP16IncomingRequestService,
1101 ...args: any[]
1102 ) => Promise<void>,
1103 this,
1104 chargingStation
1105 ).catch(Constants.EMPTY_FUNCTION);
72092cfc 1106 }, retrieveDate?.getTime() - now);
c9a4f9ea 1107 }
d8b1fab1 1108 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
c9a4f9ea
JB
1109 }
1110
62340a29 1111 private async updateFirmwareSimulation(
c9a4f9ea 1112 chargingStation: ChargingStation,
90293abb
JB
1113 maxDelay = 30,
1114 minDelay = 15
c9a4f9ea 1115 ): Promise<void> {
fba11dc6 1116 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1bf29f5b
JB
1117 return;
1118 }
ded57f02
JB
1119 if (chargingStation.hasEvses) {
1120 for (const [evseId, evseStatus] of chargingStation.evses) {
1121 if (evseId > 0) {
1122 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1123 if (connectorStatus?.transactionStarted === false) {
1124 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1125 chargingStation,
1126 connectorId,
1127 OCPP16ChargePointStatus.Unavailable
1128 );
1129 }
1130 }
1131 }
1132 }
1133 } else {
1134 for (const connectorId of chargingStation.connectors.keys()) {
1135 if (
1136 connectorId > 0 &&
1137 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1138 ) {
1139 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1140 chargingStation,
1141 connectorId,
1142 OCPP16ChargePointStatus.Unavailable
1143 );
1144 }
c9a4f9ea
JB
1145 }
1146 }
93f0c2c8
JB
1147 await chargingStation.ocppRequestService.requestHandler<
1148 OCPP16FirmwareStatusNotificationRequest,
1149 OCPP16FirmwareStatusNotificationResponse
1150 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1151 status: OCPP16FirmwareStatus.Downloading,
1152 });
1153 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
5d280aae 1154 if (
93f0c2c8
JB
1155 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1156 OCPP16FirmwareStatus.DownloadFailed
5d280aae 1157 ) {
9bf0ef23 1158 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
5d280aae
JB
1159 await chargingStation.ocppRequestService.requestHandler<
1160 OCPP16FirmwareStatusNotificationRequest,
1161 OCPP16FirmwareStatusNotificationResponse
1162 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
15748260 1163 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
5d280aae 1164 });
93f0c2c8
JB
1165 chargingStation.stationInfo.firmwareStatus =
1166 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
5d280aae
JB
1167 return;
1168 }
9bf0ef23 1169 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
c9a4f9ea
JB
1170 await chargingStation.ocppRequestService.requestHandler<
1171 OCPP16FirmwareStatusNotificationRequest,
1172 OCPP16FirmwareStatusNotificationResponse
1173 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1174 status: OCPP16FirmwareStatus.Downloaded,
1175 });
1176 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
380ccc42 1177 let wasTransactionsStarted = false;
62340a29
JB
1178 let transactionsStarted: boolean;
1179 do {
ded57f02
JB
1180 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1181 if (runningTransactions > 0) {
62340a29
JB
1182 const waitTime = 15 * 1000;
1183 logger.debug(
66dd3447
JB
1184 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1185 ${runningTransactions} transaction(s) in progress, waiting ${
62340a29
JB
1186 waitTime / 1000
1187 } seconds before continuing firmware update simulation`
1188 );
9bf0ef23 1189 await sleep(waitTime);
62340a29 1190 transactionsStarted = true;
380ccc42 1191 wasTransactionsStarted = true;
62340a29 1192 } else {
ded57f02
JB
1193 if (chargingStation.hasEvses) {
1194 for (const [evseId, evseStatus] of chargingStation.evses) {
1195 if (evseId > 0) {
1196 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1197 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1198 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1199 chargingStation,
1200 connectorId,
1201 OCPP16ChargePointStatus.Unavailable
1202 );
1203 }
1204 }
1205 }
1206 }
1207 } else {
1208 for (const connectorId of chargingStation.connectors.keys()) {
1209 if (
1210 connectorId > 0 &&
1211 chargingStation.getConnectorStatus(connectorId)?.status !==
1212 OCPP16ChargePointStatus.Unavailable
1213 ) {
1214 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1215 chargingStation,
1216 connectorId,
1217 OCPP16ChargePointStatus.Unavailable
1218 );
1219 }
62340a29
JB
1220 }
1221 }
1222 transactionsStarted = false;
1223 }
1224 } while (transactionsStarted);
9bf0ef23 1225 !wasTransactionsStarted && (await sleep(getRandomInteger(maxDelay, minDelay) * 1000));
fba11dc6 1226 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1bf29f5b
JB
1227 return;
1228 }
c9a4f9ea
JB
1229 await chargingStation.ocppRequestService.requestHandler<
1230 OCPP16FirmwareStatusNotificationRequest,
1231 OCPP16FirmwareStatusNotificationResponse
1232 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1233 status: OCPP16FirmwareStatus.Installing,
1234 });
1235 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
93f0c2c8
JB
1236 if (
1237 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1238 OCPP16FirmwareStatus.InstallationFailed
1239 ) {
9bf0ef23 1240 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
93f0c2c8
JB
1241 await chargingStation.ocppRequestService.requestHandler<
1242 OCPP16FirmwareStatusNotificationRequest,
1243 OCPP16FirmwareStatusNotificationResponse
1244 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1245 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1246 });
1247 chargingStation.stationInfo.firmwareStatus =
1248 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1249 return;
1250 }
15748260 1251 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
9bf0ef23 1252 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
5d280aae
JB
1253 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1254 }
b03df580
JB
1255 }
1256
e7aeea18 1257 private async handleRequestGetDiagnostics(
08f130a0 1258 chargingStation: ChargingStation,
e7aeea18
JB
1259 commandPayload: GetDiagnosticsRequest
1260 ): Promise<GetDiagnosticsResponse> {
68cb8b91 1261 if (
1789ba2c 1262 OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1263 chargingStation,
370ae4ee
JB
1264 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1265 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1789ba2c 1266 ) === false
68cb8b91 1267 ) {
90293abb 1268 logger.warn(
66dd3447
JB
1269 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1270 Cannot get diagnostics: feature profile not supported`
90293abb 1271 );
d8b1fab1 1272 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
68cb8b91 1273 }
a3868ec4 1274 const uri = new URL(commandPayload.location);
47e22477
JB
1275 if (uri.protocol.startsWith('ftp:')) {
1276 let ftpClient: Client;
1277 try {
d972af76 1278 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
72092cfc 1279 .filter((file) => file.endsWith('.log'))
d972af76 1280 .map((file) => join('./', file));
44eb6026 1281 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
d972af76 1282 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
47e22477
JB
1283 ftpClient = new Client();
1284 const accessResponse = await ftpClient.access({
1285 host: uri.host,
9bf0ef23
JB
1286 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1287 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1288 ...(isNotEmptyString(uri.password) && { password: uri.password }),
47e22477
JB
1289 });
1290 let uploadResponse: FTPResponse;
1291 if (accessResponse.code === 220) {
72092cfc 1292 ftpClient.trackProgress((info) => {
e7aeea18 1293 logger.info(
08f130a0 1294 `${chargingStation.logPrefix()} ${
e7aeea18
JB
1295 info.bytes / 1024
1296 } bytes transferred from diagnostics archive ${info.name}`
1297 );
6a8329b4
JB
1298 chargingStation.ocppRequestService
1299 .requestHandler<
1300 OCPP16DiagnosticsStatusNotificationRequest,
1301 OCPP16DiagnosticsStatusNotificationResponse
1302 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1303 status: OCPP16DiagnosticsStatus.Uploading,
1304 })
72092cfc 1305 .catch((error) => {
6a8329b4 1306 logger.error(
66dd3447
JB
1307 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1308 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
6a8329b4
JB
1309 error
1310 );
1311 });
47e22477 1312 });
e7aeea18 1313 uploadResponse = await ftpClient.uploadFrom(
d972af76 1314 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
14ecae6a 1315 `${uri.pathname}${diagnosticsArchive}`
e7aeea18 1316 );
47e22477 1317 if (uploadResponse.code === 226) {
08f130a0 1318 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1319 OCPP16DiagnosticsStatusNotificationRequest,
1320 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1321 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1322 status: OCPP16DiagnosticsStatus.Uploaded,
1323 });
47e22477
JB
1324 if (ftpClient) {
1325 ftpClient.close();
1326 }
1327 return { fileName: diagnosticsArchive };
1328 }
e7aeea18
JB
1329 throw new OCPPError(
1330 ErrorType.GENERIC_ERROR,
1331 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1332 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18
JB
1333 }`,
1334 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1335 );
47e22477 1336 }
e7aeea18
JB
1337 throw new OCPPError(
1338 ErrorType.GENERIC_ERROR,
1339 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
44eb6026 1340 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
e7aeea18
JB
1341 }`,
1342 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1343 );
47e22477 1344 } catch (error) {
08f130a0 1345 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1346 OCPP16DiagnosticsStatusNotificationRequest,
1347 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1348 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1349 status: OCPP16DiagnosticsStatus.UploadFailed,
1350 });
47e22477
JB
1351 if (ftpClient) {
1352 ftpClient.close();
1353 }
e7aeea18 1354 return this.handleIncomingRequestError(
08f130a0 1355 chargingStation,
e7aeea18
JB
1356 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1357 error as Error,
d8b1fab1 1358 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
e7aeea18 1359 );
47e22477
JB
1360 }
1361 } else {
e7aeea18 1362 logger.error(
08f130a0 1363 `${chargingStation.logPrefix()} Unsupported protocol ${
e7aeea18
JB
1364 uri.protocol
1365 } to transfer the diagnostic logs archive`
1366 );
08f130a0 1367 await chargingStation.ocppRequestService.requestHandler<
c9a4f9ea
JB
1368 OCPP16DiagnosticsStatusNotificationRequest,
1369 OCPP16DiagnosticsStatusNotificationResponse
08f130a0 1370 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
ef6fa3fb
JB
1371 status: OCPP16DiagnosticsStatus.UploadFailed,
1372 });
d8b1fab1 1373 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
47e22477
JB
1374 }
1375 }
802cfa13 1376
e7aeea18 1377 private handleRequestTriggerMessage(
08f130a0 1378 chargingStation: ChargingStation,
e7aeea18
JB
1379 commandPayload: OCPP16TriggerMessageRequest
1380 ): OCPP16TriggerMessageResponse {
370ae4ee
JB
1381 if (
1382 !OCPP16ServiceUtils.checkFeatureProfile(
08f130a0 1383 chargingStation,
370ae4ee
JB
1384 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1385 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
c60ed4b8
JB
1386 ) ||
1387 !OCPP16ServiceUtils.isMessageTriggerSupported(
1388 chargingStation,
1389 commandPayload.requestedMessage
370ae4ee
JB
1390 )
1391 ) {
d8b1fab1 1392 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
68cb8b91 1393 }
c60ed4b8 1394 if (
4caa7e67 1395 !OCPP16ServiceUtils.isConnectorIdValid(
c60ed4b8
JB
1396 chargingStation,
1397 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1398 commandPayload.connectorId
1399 )
1400 ) {
d8b1fab1 1401 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
dc661702 1402 }
802cfa13
JB
1403 try {
1404 switch (commandPayload.requestedMessage) {
c60ed4b8 1405 case OCPP16MessageTrigger.BootNotification:
802cfa13 1406 setTimeout(() => {
08f130a0 1407 chargingStation.ocppRequestService
f7f98c68 1408 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
08f130a0 1409 chargingStation,
6a8b180d 1410 OCPP16RequestCommand.BOOT_NOTIFICATION,
8bfbc743 1411 chargingStation.bootNotificationRequest,
6a8b180d 1412 { skipBufferingOnError: true, triggerMessage: true }
e7aeea18 1413 )
72092cfc 1414 .then((response) => {
8bfbc743 1415 chargingStation.bootNotificationResponse = response;
ae711c83 1416 })
59b6ed8d 1417 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1418 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1419 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1420 case OCPP16MessageTrigger.Heartbeat:
802cfa13 1421 setTimeout(() => {
08f130a0 1422 chargingStation.ocppRequestService
f7f98c68 1423 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
08f130a0 1424 chargingStation,
ef6fa3fb
JB
1425 OCPP16RequestCommand.HEARTBEAT,
1426 null,
1427 {
1428 triggerMessage: true,
1429 }
1430 )
59b6ed8d 1431 .catch(Constants.EMPTY_FUNCTION);
d8b1fab1
JB
1432 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1433 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
c60ed4b8 1434 case OCPP16MessageTrigger.StatusNotification:
dc661702 1435 setTimeout(() => {
9bf0ef23 1436 if (!isNullOrUndefined(commandPayload?.connectorId)) {
08f130a0 1437 chargingStation.ocppRequestService
dc661702 1438 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
08f130a0 1439 chargingStation,
dc661702
JB
1440 OCPP16RequestCommand.STATUS_NOTIFICATION,
1441 {
1442 connectorId: commandPayload.connectorId,
1443 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
72092cfc 1444 status: chargingStation.getConnectorStatus(commandPayload.connectorId)?.status,
dc661702
JB
1445 },
1446 {
1447 triggerMessage: true,
1448 }
1449 )
59b6ed8d 1450 .catch(Constants.EMPTY_FUNCTION);
dc661702 1451 } else {
ded57f02
JB
1452 // eslint-disable-next-line no-lonely-if
1453 if (chargingStation.hasEvses) {
1454 for (const evseStatus of chargingStation.evses.values()) {
1455 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1456 chargingStation.ocppRequestService
1457 .requestHandler<
1458 OCPP16StatusNotificationRequest,
1459 OCPP16StatusNotificationResponse
1460 >(
1461 chargingStation,
1462 OCPP16RequestCommand.STATUS_NOTIFICATION,
1463 {
1464 connectorId,
1465 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1466 status: connectorStatus.status,
1467 },
1468 {
1469 triggerMessage: true,
1470 }
1471 )
1472 .catch(Constants.EMPTY_FUNCTION);
1473 }
1474 }
1475 } else {
1476 for (const connectorId of chargingStation.connectors.keys()) {
1477 chargingStation.ocppRequestService
1478 .requestHandler<
1479 OCPP16StatusNotificationRequest,
1480 OCPP16StatusNotificationResponse
1481 >(
1482 chargingStation,
1483 OCPP16RequestCommand.STATUS_NOTIFICATION,
1484 {
1485 connectorId,
1486 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1487 status: chargingStation.getConnectorStatus(connectorId)?.status,
1488 },
1489 {
1490 triggerMessage: true,
1491 }
1492 )
1493 .catch(Constants.EMPTY_FUNCTION);
1494 }
dc661702
JB
1495 }
1496 }
d8b1fab1
JB
1497 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1498 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
802cfa13 1499 default:
d8b1fab1 1500 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
802cfa13
JB
1501 }
1502 } catch (error) {
e7aeea18 1503 return this.handleIncomingRequestError(
08f130a0 1504 chargingStation,
e7aeea18
JB
1505 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1506 error as Error,
d8b1fab1 1507 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
e7aeea18 1508 );
802cfa13
JB
1509 }
1510 }
77b95a89
JB
1511
1512 private handleRequestDataTransfer(
1513 chargingStation: ChargingStation,
1514 commandPayload: OCPP16DataTransferRequest
1515 ): OCPP16DataTransferResponse {
1516 try {
1517 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
b63b4a73 1518 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
77b95a89 1519 }
b63b4a73 1520 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
77b95a89
JB
1521 } catch (error) {
1522 return this.handleIncomingRequestError(
1523 chargingStation,
1524 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1525 error as Error,
d8b1fab1 1526 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
77b95a89
JB
1527 );
1528 }
1529 }
24578c31
JB
1530
1531 private async handleRequestReserveNow(
1532 chargingStation: ChargingStation,
1533 commandPayload: OCPP16ReserveNowRequest
1534 ): Promise<OCPP16ReserveNowResponse> {
66dd3447
JB
1535 if (
1536 !OCPP16ServiceUtils.checkFeatureProfile(
1537 chargingStation,
1538 OCPP16SupportedFeatureProfiles.Reservation,
1539 OCPP16IncomingRequestCommand.RESERVE_NOW
1540 )
1541 ) {
178956d8 1542 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
66dd3447 1543 }
24578c31 1544 const { reservationId, idTag, connectorId } = commandPayload;
24578c31
JB
1545 let response: OCPP16ReserveNowResponse;
1546 try {
66dd3447 1547 if (!chargingStation.isConnectorAvailable(connectorId) && connectorId > 0) {
178956d8 1548 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1549 }
66dd3447 1550 if (connectorId === 0 && !chargingStation.getReservationOnConnectorId0Enabled()) {
178956d8 1551 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31 1552 }
66dd3447 1553 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
178956d8 1554 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
24578c31
JB
1555 }
1556 switch (chargingStation.getConnectorStatus(connectorId).status) {
178956d8
JB
1557 case OCPP16ChargePointStatus.Faulted:
1558 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
24578c31 1559 break;
178956d8
JB
1560 case OCPP16ChargePointStatus.Preparing:
1561 case OCPP16ChargePointStatus.Charging:
1562 case OCPP16ChargePointStatus.SuspendedEV:
1563 case OCPP16ChargePointStatus.SuspendedEVSE:
1564 case OCPP16ChargePointStatus.Finishing:
1565 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
24578c31 1566 break;
178956d8
JB
1567 case OCPP16ChargePointStatus.Unavailable:
1568 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
24578c31 1569 break;
178956d8 1570 case OCPP16ChargePointStatus.Reserved:
66dd3447 1571 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
178956d8 1572 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
24578c31
JB
1573 break;
1574 }
1575 // eslint-disable-next-line no-fallthrough
1576 default:
66dd3447 1577 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
178956d8 1578 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
d193a949
JB
1579 break;
1580 }
1581 await chargingStation.addReservation({
1582 id: commandPayload.reservationId,
1583 ...commandPayload,
1584 });
178956d8 1585 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
24578c31
JB
1586 break;
1587 }
1588 return response;
1589 } catch (error) {
178956d8 1590 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.Available;
24578c31
JB
1591 return this.handleIncomingRequestError(
1592 chargingStation,
1593 OCPP16IncomingRequestCommand.RESERVE_NOW,
1594 error as Error,
178956d8 1595 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
24578c31
JB
1596 );
1597 }
1598 }
1599
1600 private async handleRequestCancelReservation(
1601 chargingStation: ChargingStation,
1602 commandPayload: OCPP16CancelReservationRequest
b1f1b0f6 1603 ): Promise<GenericResponse> {
66dd3447
JB
1604 if (
1605 !OCPP16ServiceUtils.checkFeatureProfile(
1606 chargingStation,
1607 OCPP16SupportedFeatureProfiles.Reservation,
1608 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1609 )
1610 ) {
178956d8 1611 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
66dd3447 1612 }
24578c31 1613 try {
d193a949
JB
1614 const { reservationId } = commandPayload;
1615 const [exists, reservation] = chargingStation.doesReservationExists({ id: reservationId });
24578c31
JB
1616 if (!exists) {
1617 logger.error(
66dd3447
JB
1618 `${chargingStation.logPrefix()} Reservation with ID ${reservationId}
1619 does not exist on charging station`
24578c31 1620 );
178956d8 1621 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
24578c31 1622 }
ec9f36cc
JB
1623 await chargingStation.removeReservation(
1624 reservation,
1625 ReservationTerminationReason.RESERVATION_CANCELED
1626 );
178956d8 1627 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
24578c31
JB
1628 } catch (error) {
1629 return this.handleIncomingRequestError(
1630 chargingStation,
1631 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1632 error as Error,
178956d8 1633 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
24578c31 1634 );
24578c31
JB
1635 }
1636 }
c0560973 1637}