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