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