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