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