refactor: cleanup eslint configuration
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
CommitLineData
a19b897d 1// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
c8eeb62b 2
66a7748d 3import type { JSONSchemaType } from 'ajv'
ef9e3b33
JB
4import {
5 addSeconds,
6 areIntervalsOverlapping,
7 differenceInSeconds,
4c3f6c20 8 type Interval,
ef9e3b33
JB
9 isAfter,
10 isBefore,
66a7748d
JB
11 isWithinInterval
12} from 'date-fns'
130783a7 13
90aceaf6
JB
14import {
15 type ChargingStation,
16 hasFeatureProfile,
66a7748d
JB
17 hasReservationExpired
18} from '../../../charging-station/index.js'
e7aeea18 19import {
563e40ce 20 type ConfigurationKey,
d19b10a8 21 type GenericResponse,
268a74bb 22 type JsonType,
d19b10a8 23 OCPP16AuthorizationStatus,
66a7748d 24 type OCPP16AvailabilityType,
366f75f6
JB
25 type OCPP16ChangeAvailabilityResponse,
26 OCPP16ChargePointStatus,
268a74bb 27 type OCPP16ChargingProfile,
ef9e3b33 28 type OCPP16ChargingSchedule,
41f3983a 29 type OCPP16ClearChargingProfileRequest,
268a74bb 30 type OCPP16IncomingRequestCommand,
27782dbc 31 type OCPP16MeterValue,
41f3983a
JB
32 OCPP16MeterValueContext,
33 OCPP16MeterValueUnit,
66a7748d 34 type OCPP16RequestCommand,
268a74bb 35 OCPP16StandardParametersKey,
d19b10a8 36 OCPP16StopTransactionReason,
268a74bb 37 type OCPP16SupportedFeatureProfiles,
66a7748d
JB
38 OCPPVersion
39} from '../../../types/index.js'
95dab6cf 40import { convertToDate, isNotEmptyArray, logger, roundTo } from '../../../utils/index.js'
66a7748d 41import { OCPPServiceUtils } from '../OCPPServiceUtils.js'
4c3f6c20 42import { OCPP16Constants } from './OCPP16Constants.js'
6ed92bc1 43
7bc31f9c 44export class OCPP16ServiceUtils extends OCPPServiceUtils {
66a7748d 45 public static checkFeatureProfile (
370ae4ee
JB
46 chargingStation: ChargingStation,
47 featureProfile: OCPP16SupportedFeatureProfiles,
66a7748d 48 command: OCPP16RequestCommand | OCPP16IncomingRequestCommand
370ae4ee 49 ): boolean {
66a7748d 50 if (hasFeatureProfile(chargingStation, featureProfile) === false) {
370ae4ee
JB
51 logger.warn(
52 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
53 OCPP16StandardParametersKey.SupportedFeatureProfiles
66a7748d
JB
54 } in configuration`
55 )
56 return false
370ae4ee 57 }
66a7748d 58 return true
370ae4ee
JB
59 }
60
66a7748d 61 public static buildTransactionBeginMeterValue (
e7aeea18
JB
62 chargingStation: ChargingStation,
63 connectorId: number,
5199f9fd 64 meterStart: number | undefined
e7aeea18 65 ): OCPP16MeterValue {
fd0c36fa 66 const meterValue: OCPP16MeterValue = {
c38f0ced 67 timestamp: new Date(),
66a7748d
JB
68 sampledValue: []
69 }
9ccca265 70 // Energy.Active.Import.Register measurand (default)
ed3d2808 71 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 72 chargingStation,
66a7748d
JB
73 connectorId
74 )
41f3983a 75 const unitDivider =
66a7748d 76 sampledValueTemplate?.unit === OCPP16MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
e7aeea18
JB
77 meterValue.sampledValue.push(
78 OCPP16ServiceUtils.buildSampledValue(
66a7748d 79 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
e1d9a0f4 80 sampledValueTemplate!,
9bf0ef23 81 roundTo((meterStart ?? 0) / unitDivider, 4),
66a7748d
JB
82 OCPP16MeterValueContext.TRANSACTION_BEGIN
83 )
84 )
85 return meterValue
fd0c36fa
JB
86 }
87
66a7748d 88 public static buildTransactionDataMeterValues (
e7aeea18 89 transactionBeginMeterValue: OCPP16MeterValue,
66a7748d 90 transactionEndMeterValue: OCPP16MeterValue
e7aeea18 91 ): OCPP16MeterValue[] {
66a7748d
JB
92 const meterValues: OCPP16MeterValue[] = []
93 meterValues.push(transactionBeginMeterValue)
94 meterValues.push(transactionEndMeterValue)
95 return meterValues
fd0c36fa 96 }
7bc31f9c 97
d19b10a8
JB
98 public static remoteStopTransaction = async (
99 chargingStation: ChargingStation,
66a7748d 100 connectorId: number
d19b10a8
JB
101 ): Promise<GenericResponse> => {
102 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
103 chargingStation,
104 connectorId,
66a7748d
JB
105 OCPP16ChargePointStatus.Finishing
106 )
d19b10a8
JB
107 const stopResponse = await chargingStation.stopTransactionOnConnector(
108 connectorId,
66a7748d
JB
109 OCPP16StopTransactionReason.REMOTE
110 )
d19b10a8 111 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d 112 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
d19b10a8 113 }
66a7748d
JB
114 return OCPP16Constants.OCPP_RESPONSE_REJECTED
115 }
d19b10a8 116
366f75f6
JB
117 public static changeAvailability = async (
118 chargingStation: ChargingStation,
225e32b0 119 connectorIds: number[],
366f75f6 120 chargePointStatus: OCPP16ChargePointStatus,
66a7748d 121 availabilityType: OCPP16AvailabilityType
366f75f6 122 ): Promise<OCPP16ChangeAvailabilityResponse> => {
66a7748d 123 const responses: OCPP16ChangeAvailabilityResponse[] = []
225e32b0
JB
124 for (const connectorId of connectorIds) {
125 let response: OCPP16ChangeAvailabilityResponse =
66a7748d
JB
126 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
127 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
128 const connectorStatus = chargingStation.getConnectorStatus(connectorId)!
5199f9fd 129 if (connectorStatus.transactionStarted === true) {
66a7748d 130 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
225e32b0 131 }
66a7748d 132 connectorStatus.availability = availabilityType
225e32b0
JB
133 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
134 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
135 chargingStation,
136 connectorId,
66a7748d
JB
137 chargePointStatus
138 )
225e32b0 139 }
66a7748d 140 responses.push(response)
366f75f6 141 }
3b0ed034 142 if (responses.includes(OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED)) {
66a7748d 143 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
366f75f6 144 }
66a7748d
JB
145 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
146 }
366f75f6 147
66a7748d 148 public static setChargingProfile (
ed3d2808
JB
149 chargingStation: ChargingStation,
150 connectorId: number,
66a7748d 151 cp: OCPP16ChargingProfile
ed3d2808 152 ): void {
aa63c9b7 153 if (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles == null) {
ed3d2808 154 logger.error(
66a7748d
JB
155 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
156 )
157 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
158 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []
ed3d2808 159 }
66a7748d 160 if (!Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808 161 logger.error(
66a7748d
JB
162 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type deferred initialization`
163 )
164 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
165 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []
ed3d2808 166 }
3423c8a5
JB
167 cp.chargingSchedule.startSchedule = convertToDate(cp.chargingSchedule.startSchedule)
168 cp.validFrom = convertToDate(cp.validFrom)
169 cp.validTo = convertToDate(cp.validTo)
66a7748d 170 let cpReplaced = false
9bf0ef23 171 if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
2466918c 172 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5199f9fd 173 for (const [index, chargingProfile] of chargingStation
2466918c
JB
174 .getConnectorStatus(connectorId)!
175 .chargingProfiles!.entries()) {
5199f9fd
JB
176 if (
177 chargingProfile.chargingProfileId === cp.chargingProfileId ||
178 (chargingProfile.stackLevel === cp.stackLevel &&
179 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
180 ) {
181 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
182 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp
183 cpReplaced = true
184 }
185 }
ed3d2808 186 }
66a7748d 187 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
ed3d2808
JB
188 }
189
73d87be1
JB
190 public static clearChargingProfiles = (
191 chargingStation: ChargingStation,
41f3983a 192 commandPayload: OCPP16ClearChargingProfileRequest,
66a7748d 193 chargingProfiles: OCPP16ChargingProfile[] | undefined
73d87be1 194 ): boolean => {
66a7748d
JB
195 const { id, chargingProfilePurpose, stackLevel } = commandPayload
196 let clearedCP = false
73d87be1 197 if (isNotEmptyArray(chargingProfiles)) {
5dc7c990 198 chargingProfiles.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
66a7748d 199 let clearCurrentCP = false
0d1f33ba 200 if (chargingProfile.chargingProfileId === id) {
66a7748d 201 clearCurrentCP = true
73d87be1 202 }
66a7748d
JB
203 if (chargingProfilePurpose == null && chargingProfile.stackLevel === stackLevel) {
204 clearCurrentCP = true
73d87be1 205 }
66a7748d
JB
206 if (
207 stackLevel == null &&
208 chargingProfile.chargingProfilePurpose === chargingProfilePurpose
209 ) {
210 clearCurrentCP = true
73d87be1
JB
211 }
212 if (
0d1f33ba
JB
213 chargingProfile.stackLevel === stackLevel &&
214 chargingProfile.chargingProfilePurpose === chargingProfilePurpose
73d87be1 215 ) {
66a7748d 216 clearCurrentCP = true
73d87be1
JB
217 }
218 if (clearCurrentCP) {
66a7748d 219 chargingProfiles.splice(index, 1)
73d87be1
JB
220 logger.debug(
221 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
66a7748d
JB
222 chargingProfile
223 )
224 clearedCP = true
73d87be1 225 }
66a7748d 226 })
73d87be1 227 }
66a7748d
JB
228 return clearedCP
229 }
73d87be1 230
ef9e3b33 231 public static composeChargingSchedules = (
4abf6441
JB
232 chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
233 chargingScheduleLower: OCPP16ChargingSchedule | undefined,
66a7748d 234 compositeInterval: Interval
ef9e3b33 235 ): OCPP16ChargingSchedule | undefined => {
66a7748d
JB
236 if (chargingScheduleHigher == null && chargingScheduleLower == null) {
237 return undefined
ef9e3b33 238 }
66a7748d
JB
239 if (chargingScheduleHigher != null && chargingScheduleLower == null) {
240 return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval)
ef9e3b33 241 }
66a7748d
JB
242 if (chargingScheduleHigher == null && chargingScheduleLower != null) {
243 return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval)
ef9e3b33 244 }
4abf6441 245 const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined =
66a7748d
JB
246 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
247 OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval)
4abf6441 248 const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined =
66a7748d
JB
249 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
250 OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval)
4abf6441 251 const compositeChargingScheduleHigherInterval: Interval = {
66a7748d 252 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 253 start: compositeChargingScheduleHigher!.startSchedule!,
ef9e3b33 254 end: addSeconds(
66a7748d 255 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 256 compositeChargingScheduleHigher!.startSchedule!,
66a7748d
JB
257 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
258 compositeChargingScheduleHigher!.duration!
259 )
260 }
4abf6441 261 const compositeChargingScheduleLowerInterval: Interval = {
66a7748d 262 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 263 start: compositeChargingScheduleLower!.startSchedule!,
ef9e3b33 264 end: addSeconds(
66a7748d 265 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 266 compositeChargingScheduleLower!.startSchedule!,
66a7748d
JB
267 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
268 compositeChargingScheduleLower!.duration!
269 )
270 }
4abf6441
JB
271 const higherFirst = isBefore(
272 compositeChargingScheduleHigherInterval.start,
66a7748d
JB
273 compositeChargingScheduleLowerInterval.start
274 )
ef9e3b33
JB
275 if (
276 !areIntervalsOverlapping(
4abf6441 277 compositeChargingScheduleHigherInterval,
66a7748d 278 compositeChargingScheduleLowerInterval
ef9e3b33
JB
279 )
280 ) {
281 return {
4abf6441 282 ...compositeChargingScheduleLower,
66a7748d 283 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441
JB
284 ...compositeChargingScheduleHigher!,
285 startSchedule: higherFirst
286 ? (compositeChargingScheduleHigherInterval.start as Date)
287 : (compositeChargingScheduleLowerInterval.start as Date),
288 duration: higherFirst
289 ? differenceInSeconds(
66a7748d
JB
290 compositeChargingScheduleLowerInterval.end,
291 compositeChargingScheduleHigherInterval.start
292 )
4abf6441 293 : differenceInSeconds(
66a7748d
JB
294 compositeChargingScheduleHigherInterval.end,
295 compositeChargingScheduleLowerInterval.start
296 ),
4abf6441 297 chargingSchedulePeriod: [
66a7748d 298 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
a974c8e4 299 ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map(schedulePeriod => {
4abf6441
JB
300 return {
301 ...schedulePeriod,
302 startPeriod: higherFirst
303 ? 0
304 : schedulePeriod.startPeriod +
305 differenceInSeconds(
306 compositeChargingScheduleHigherInterval.start,
66a7748d
JB
307 compositeChargingScheduleLowerInterval.start
308 )
309 }
4abf6441 310 }),
66a7748d 311 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
a974c8e4 312 ...compositeChargingScheduleLower!.chargingSchedulePeriod.map(schedulePeriod => {
4abf6441
JB
313 return {
314 ...schedulePeriod,
315 startPeriod: higherFirst
316 ? schedulePeriod.startPeriod +
317 differenceInSeconds(
318 compositeChargingScheduleLowerInterval.start,
66a7748d 319 compositeChargingScheduleHigherInterval.start
4abf6441 320 )
66a7748d
JB
321 : 0
322 }
323 })
324 ].sort((a, b) => a.startPeriod - b.startPeriod)
325 }
ef9e3b33 326 }
4abf6441
JB
327 return {
328 ...compositeChargingScheduleLower,
66a7748d 329 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441
JB
330 ...compositeChargingScheduleHigher!,
331 startSchedule: higherFirst
332 ? (compositeChargingScheduleHigherInterval.start as Date)
333 : (compositeChargingScheduleLowerInterval.start as Date),
334 duration: higherFirst
335 ? differenceInSeconds(
66a7748d
JB
336 compositeChargingScheduleLowerInterval.end,
337 compositeChargingScheduleHigherInterval.start
338 )
4abf6441 339 : differenceInSeconds(
66a7748d
JB
340 compositeChargingScheduleHigherInterval.end,
341 compositeChargingScheduleLowerInterval.start
342 ),
4abf6441 343 chargingSchedulePeriod: [
66a7748d 344 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
a974c8e4 345 ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map(schedulePeriod => {
4abf6441
JB
346 return {
347 ...schedulePeriod,
348 startPeriod: higherFirst
349 ? 0
350 : schedulePeriod.startPeriod +
351 differenceInSeconds(
352 compositeChargingScheduleHigherInterval.start,
66a7748d
JB
353 compositeChargingScheduleLowerInterval.start
354 )
355 }
4abf6441 356 }),
66a7748d 357 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 358 ...compositeChargingScheduleLower!.chargingSchedulePeriod
c4ab56ba 359 .filter((schedulePeriod, index) => {
4abf6441
JB
360 if (
361 higherFirst &&
362 isWithinInterval(
363 addSeconds(
364 compositeChargingScheduleLowerInterval.start,
66a7748d 365 schedulePeriod.startPeriod
4abf6441
JB
366 ),
367 {
368 start: compositeChargingScheduleLowerInterval.start,
66a7748d
JB
369 end: compositeChargingScheduleHigherInterval.end
370 }
4abf6441
JB
371 )
372 ) {
66a7748d 373 return false
4abf6441 374 }
c4ab56ba
JB
375 if (
376 higherFirst &&
66a7748d 377 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
c4ab56ba
JB
378 index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 &&
379 !isWithinInterval(
380 addSeconds(
381 compositeChargingScheduleLowerInterval.start,
66a7748d 382 schedulePeriod.startPeriod
c4ab56ba
JB
383 ),
384 {
385 start: compositeChargingScheduleLowerInterval.start,
66a7748d
JB
386 end: compositeChargingScheduleHigherInterval.end
387 }
c4ab56ba
JB
388 ) &&
389 isWithinInterval(
390 addSeconds(
391 compositeChargingScheduleLowerInterval.start,
66a7748d
JB
392 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
393 compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod
c4ab56ba
JB
394 ),
395 {
396 start: compositeChargingScheduleLowerInterval.start,
66a7748d
JB
397 end: compositeChargingScheduleHigherInterval.end
398 }
c4ab56ba
JB
399 )
400 ) {
66a7748d 401 return false
c4ab56ba 402 }
4abf6441
JB
403 if (
404 !higherFirst &&
405 isWithinInterval(
406 addSeconds(
407 compositeChargingScheduleLowerInterval.start,
66a7748d 408 schedulePeriod.startPeriod
4abf6441
JB
409 ),
410 {
411 start: compositeChargingScheduleHigherInterval.start,
66a7748d
JB
412 end: compositeChargingScheduleLowerInterval.end
413 }
4abf6441
JB
414 )
415 ) {
66a7748d 416 return false
4abf6441 417 }
66a7748d 418 return true
4abf6441 419 })
0e14e1d4
JB
420 .map((schedulePeriod, index) => {
421 if (index === 0 && schedulePeriod.startPeriod !== 0) {
66a7748d 422 schedulePeriod.startPeriod = 0
0e14e1d4 423 }
4abf6441
JB
424 return {
425 ...schedulePeriod,
426 startPeriod: higherFirst
427 ? schedulePeriod.startPeriod +
428 differenceInSeconds(
429 compositeChargingScheduleLowerInterval.start,
66a7748d 430 compositeChargingScheduleHigherInterval.start
4abf6441 431 )
66a7748d
JB
432 : 0
433 }
434 })
435 ].sort((a, b) => a.startPeriod - b.startPeriod)
436 }
437 }
ef9e3b33 438
563e40ce
JB
439 public static isConfigurationKeyVisible (key: ConfigurationKey): boolean {
440 if (key.visible == null) {
d0ed7db9 441 return true
563e40ce
JB
442 }
443 return key.visible
444 }
445
90aceaf6
JB
446 public static hasReservation = (
447 chargingStation: ChargingStation,
448 connectorId: number,
66a7748d 449 idTag: string
90aceaf6 450 ): boolean => {
66a7748d
JB
451 const connectorReservation = chargingStation.getReservationBy('connectorId', connectorId)
452 const chargingStationReservation = chargingStation.getReservationBy('connectorId', 0)
90aceaf6
JB
453 if (
454 (chargingStation.getConnectorStatus(connectorId)?.status ===
455 OCPP16ChargePointStatus.Reserved &&
66a7748d 456 connectorReservation != null &&
56563a3c 457 !hasReservationExpired(connectorReservation) &&
5199f9fd 458 connectorReservation.idTag === idTag) ||
90aceaf6 459 (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved &&
66a7748d 460 chargingStationReservation != null &&
56563a3c 461 !hasReservationExpired(chargingStationReservation) &&
5199f9fd 462 chargingStationReservation.idTag === idTag)
90aceaf6 463 ) {
88499f52
JB
464 logger.debug(
465 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
66a7748d
JB
466 connectorReservation ?? chargingStationReservation
467 )
468 return true
90aceaf6 469 }
66a7748d
JB
470 return false
471 }
90aceaf6 472
1b271a54
JB
473 public static parseJsonSchemaFile<T extends JsonType>(
474 relativePath: string,
475 moduleName?: string,
66a7748d 476 methodName?: string
1b271a54 477 ): JSONSchemaType<T> {
7164966d 478 return super.parseJsonSchemaFile<T>(
51022aa0 479 relativePath,
1b271a54
JB
480 OCPPVersion.VERSION_16,
481 moduleName,
66a7748d
JB
482 methodName
483 )
130783a7
JB
484 }
485
66a7748d 486 private static readonly composeChargingSchedule = (
ef9e3b33 487 chargingSchedule: OCPP16ChargingSchedule,
66a7748d 488 compositeInterval: Interval
ef9e3b33
JB
489 ): OCPP16ChargingSchedule | undefined => {
490 const chargingScheduleInterval: Interval = {
66a7748d 491 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ef9e3b33 492 start: chargingSchedule.startSchedule!,
66a7748d
JB
493 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
494 end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!)
495 }
d632062f 496 if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
66a7748d 497 chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod)
d632062f 498 if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
ef9e3b33
JB
499 return {
500 ...chargingSchedule,
d632062f
JB
501 startSchedule: compositeInterval.start as Date,
502 duration: differenceInSeconds(
503 chargingScheduleInterval.end,
66a7748d 504 compositeInterval.start as Date
d632062f 505 ),
0e14e1d4
JB
506 chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod
507 .filter((schedulePeriod, index) => {
ef9e3b33
JB
508 if (
509 isWithinInterval(
aafba9d8 510 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
66a7748d 511 compositeInterval
ef9e3b33
JB
512 )
513 ) {
66a7748d 514 return true
ef9e3b33
JB
515 }
516 if (
517 index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
518 !isWithinInterval(
519 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
66a7748d 520 compositeInterval
ef9e3b33
JB
521 ) &&
522 isWithinInterval(
523 addSeconds(
524 chargingScheduleInterval.start,
66a7748d 525 chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod
ef9e3b33 526 ),
66a7748d 527 compositeInterval
ef9e3b33
JB
528 )
529 ) {
66a7748d 530 return true
ef9e3b33 531 }
66a7748d 532 return false
0e14e1d4
JB
533 })
534 .map((schedulePeriod, index) => {
535 if (index === 0 && schedulePeriod.startPeriod !== 0) {
66a7748d 536 schedulePeriod.startPeriod = 0
0e14e1d4 537 }
66a7748d
JB
538 return schedulePeriod
539 })
540 }
ef9e3b33 541 }
d632062f 542 if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
ef9e3b33
JB
543 return {
544 ...chargingSchedule,
d632062f
JB
545 duration: differenceInSeconds(
546 compositeInterval.end as Date,
66a7748d 547 chargingScheduleInterval.start
d632062f 548 ),
a974c8e4 549 chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter(schedulePeriod =>
ef9e3b33 550 isWithinInterval(
aafba9d8 551 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
66a7748d
JB
552 compositeInterval
553 )
554 )
555 }
ef9e3b33 556 }
66a7748d 557 return chargingSchedule
ef9e3b33 558 }
66a7748d 559 }
6ed92bc1 560}