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