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