refactor: cleanup eslint disablement rule
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
CommitLineData
edd13439 1// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
c8eeb62b 2
66a7748d 3import type { JSONSchemaType } from 'ajv'
ef9e3b33 4import {
f1e3871b 5 type Interval,
ef9e3b33
JB
6 addSeconds,
7 areIntervalsOverlapping,
8 differenceInSeconds,
9 isAfter,
10 isBefore,
66a7748d
JB
11 isWithinInterval
12} from 'date-fns'
130783a7 13
66a7748d 14import { OCPP16Constants } from './OCPP16Constants.js'
90aceaf6
JB
15import {
16 type ChargingStation,
17 hasFeatureProfile,
66a7748d
JB
18 hasReservationExpired
19} from '../../../charging-station/index.js'
e7aeea18 20import {
563e40ce 21 type ConfigurationKey,
d19b10a8 22 type GenericResponse,
268a74bb 23 type JsonType,
d19b10a8 24 OCPP16AuthorizationStatus,
66a7748d 25 type OCPP16AvailabilityType,
366f75f6
JB
26 type OCPP16ChangeAvailabilityResponse,
27 OCPP16ChargePointStatus,
268a74bb 28 type OCPP16ChargingProfile,
ef9e3b33 29 type OCPP16ChargingSchedule,
41f3983a 30 type OCPP16ClearChargingProfileRequest,
268a74bb 31 type OCPP16IncomingRequestCommand,
27782dbc 32 type OCPP16MeterValue,
41f3983a
JB
33 OCPP16MeterValueContext,
34 OCPP16MeterValueUnit,
66a7748d 35 type OCPP16RequestCommand,
268a74bb 36 OCPP16StandardParametersKey,
d19b10a8 37 OCPP16StopTransactionReason,
268a74bb 38 type OCPP16SupportedFeatureProfiles,
66a7748d
JB
39 OCPPVersion
40} from '../../../types/index.js'
95dab6cf 41import { convertToDate, isNotEmptyArray, logger, roundTo } from '../../../utils/index.js'
66a7748d 42import { OCPPServiceUtils } from '../OCPPServiceUtils.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 }
95dab6cf
JB
167 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
168 cp.validFrom = convertToDate(cp.validFrom)!
169 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170 cp.validTo = convertToDate(cp.validTo)!
66a7748d 171 let cpReplaced = false
9bf0ef23 172 if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
2466918c 173 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5199f9fd 174 for (const [index, chargingProfile] of chargingStation
2466918c
JB
175 .getConnectorStatus(connectorId)!
176 .chargingProfiles!.entries()) {
5199f9fd
JB
177 if (
178 chargingProfile.chargingProfileId === cp.chargingProfileId ||
179 (chargingProfile.stackLevel === cp.stackLevel &&
180 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
181 ) {
182 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
183 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp
184 cpReplaced = true
185 }
186 }
ed3d2808 187 }
66a7748d 188 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
ed3d2808
JB
189 }
190
73d87be1
JB
191 public static clearChargingProfiles = (
192 chargingStation: ChargingStation,
41f3983a 193 commandPayload: OCPP16ClearChargingProfileRequest,
66a7748d 194 chargingProfiles: OCPP16ChargingProfile[] | undefined
73d87be1 195 ): boolean => {
66a7748d
JB
196 const { id, chargingProfilePurpose, stackLevel } = commandPayload
197 let clearedCP = false
73d87be1
JB
198 if (isNotEmptyArray(chargingProfiles)) {
199 chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
66a7748d 200 let clearCurrentCP = false
0d1f33ba 201 if (chargingProfile.chargingProfileId === id) {
66a7748d 202 clearCurrentCP = true
73d87be1 203 }
66a7748d
JB
204 if (chargingProfilePurpose == null && chargingProfile.stackLevel === stackLevel) {
205 clearCurrentCP = true
73d87be1 206 }
66a7748d
JB
207 if (
208 stackLevel == null &&
209 chargingProfile.chargingProfilePurpose === chargingProfilePurpose
210 ) {
211 clearCurrentCP = true
73d87be1
JB
212 }
213 if (
0d1f33ba
JB
214 chargingProfile.stackLevel === stackLevel &&
215 chargingProfile.chargingProfilePurpose === chargingProfilePurpose
73d87be1 216 ) {
66a7748d 217 clearCurrentCP = true
73d87be1
JB
218 }
219 if (clearCurrentCP) {
66a7748d 220 chargingProfiles.splice(index, 1)
73d87be1
JB
221 logger.debug(
222 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
66a7748d
JB
223 chargingProfile
224 )
225 clearedCP = true
73d87be1 226 }
66a7748d 227 })
73d87be1 228 }
66a7748d
JB
229 return clearedCP
230 }
73d87be1 231
ef9e3b33 232 public static composeChargingSchedules = (
4abf6441
JB
233 chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
234 chargingScheduleLower: OCPP16ChargingSchedule | undefined,
66a7748d 235 compositeInterval: Interval
ef9e3b33 236 ): OCPP16ChargingSchedule | undefined => {
66a7748d
JB
237 if (chargingScheduleHigher == null && chargingScheduleLower == null) {
238 return undefined
ef9e3b33 239 }
66a7748d
JB
240 if (chargingScheduleHigher != null && chargingScheduleLower == null) {
241 return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval)
ef9e3b33 242 }
66a7748d
JB
243 if (chargingScheduleHigher == null && chargingScheduleLower != null) {
244 return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval)
ef9e3b33 245 }
4abf6441 246 const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined =
66a7748d
JB
247 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
248 OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval)
4abf6441 249 const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined =
66a7748d
JB
250 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
251 OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval)
4abf6441 252 const compositeChargingScheduleHigherInterval: Interval = {
66a7748d 253 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 254 start: compositeChargingScheduleHigher!.startSchedule!,
ef9e3b33 255 end: addSeconds(
66a7748d 256 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 257 compositeChargingScheduleHigher!.startSchedule!,
66a7748d
JB
258 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
259 compositeChargingScheduleHigher!.duration!
260 )
261 }
4abf6441 262 const compositeChargingScheduleLowerInterval: Interval = {
66a7748d 263 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 264 start: compositeChargingScheduleLower!.startSchedule!,
ef9e3b33 265 end: addSeconds(
66a7748d 266 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 267 compositeChargingScheduleLower!.startSchedule!,
66a7748d
JB
268 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
269 compositeChargingScheduleLower!.duration!
270 )
271 }
4abf6441
JB
272 const higherFirst = isBefore(
273 compositeChargingScheduleHigherInterval.start,
66a7748d
JB
274 compositeChargingScheduleLowerInterval.start
275 )
ef9e3b33
JB
276 if (
277 !areIntervalsOverlapping(
4abf6441 278 compositeChargingScheduleHigherInterval,
66a7748d 279 compositeChargingScheduleLowerInterval
ef9e3b33
JB
280 )
281 ) {
282 return {
4abf6441 283 ...compositeChargingScheduleLower,
66a7748d 284 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441
JB
285 ...compositeChargingScheduleHigher!,
286 startSchedule: higherFirst
287 ? (compositeChargingScheduleHigherInterval.start as Date)
288 : (compositeChargingScheduleLowerInterval.start as Date),
289 duration: higherFirst
290 ? differenceInSeconds(
66a7748d
JB
291 compositeChargingScheduleLowerInterval.end,
292 compositeChargingScheduleHigherInterval.start
293 )
4abf6441 294 : differenceInSeconds(
66a7748d
JB
295 compositeChargingScheduleHigherInterval.end,
296 compositeChargingScheduleLowerInterval.start
297 ),
4abf6441 298 chargingSchedulePeriod: [
66a7748d 299 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
a974c8e4 300 ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map(schedulePeriod => {
4abf6441
JB
301 return {
302 ...schedulePeriod,
303 startPeriod: higherFirst
304 ? 0
305 : schedulePeriod.startPeriod +
306 differenceInSeconds(
307 compositeChargingScheduleHigherInterval.start,
66a7748d
JB
308 compositeChargingScheduleLowerInterval.start
309 )
310 }
4abf6441 311 }),
66a7748d 312 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
a974c8e4 313 ...compositeChargingScheduleLower!.chargingSchedulePeriod.map(schedulePeriod => {
4abf6441
JB
314 return {
315 ...schedulePeriod,
316 startPeriod: higherFirst
317 ? schedulePeriod.startPeriod +
318 differenceInSeconds(
319 compositeChargingScheduleLowerInterval.start,
66a7748d 320 compositeChargingScheduleHigherInterval.start
4abf6441 321 )
66a7748d
JB
322 : 0
323 }
324 })
325 ].sort((a, b) => a.startPeriod - b.startPeriod)
326 }
ef9e3b33 327 }
4abf6441
JB
328 return {
329 ...compositeChargingScheduleLower,
66a7748d 330 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441
JB
331 ...compositeChargingScheduleHigher!,
332 startSchedule: higherFirst
333 ? (compositeChargingScheduleHigherInterval.start as Date)
334 : (compositeChargingScheduleLowerInterval.start as Date),
335 duration: higherFirst
336 ? differenceInSeconds(
66a7748d
JB
337 compositeChargingScheduleLowerInterval.end,
338 compositeChargingScheduleHigherInterval.start
339 )
4abf6441 340 : differenceInSeconds(
66a7748d
JB
341 compositeChargingScheduleHigherInterval.end,
342 compositeChargingScheduleLowerInterval.start
343 ),
4abf6441 344 chargingSchedulePeriod: [
66a7748d 345 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
a974c8e4 346 ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map(schedulePeriod => {
4abf6441
JB
347 return {
348 ...schedulePeriod,
349 startPeriod: higherFirst
350 ? 0
351 : schedulePeriod.startPeriod +
352 differenceInSeconds(
353 compositeChargingScheduleHigherInterval.start,
66a7748d
JB
354 compositeChargingScheduleLowerInterval.start
355 )
356 }
4abf6441 357 }),
66a7748d 358 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
4abf6441 359 ...compositeChargingScheduleLower!.chargingSchedulePeriod
c4ab56ba 360 .filter((schedulePeriod, index) => {
4abf6441
JB
361 if (
362 higherFirst &&
363 isWithinInterval(
364 addSeconds(
365 compositeChargingScheduleLowerInterval.start,
66a7748d 366 schedulePeriod.startPeriod
4abf6441
JB
367 ),
368 {
369 start: compositeChargingScheduleLowerInterval.start,
66a7748d
JB
370 end: compositeChargingScheduleHigherInterval.end
371 }
4abf6441
JB
372 )
373 ) {
66a7748d 374 return false
4abf6441 375 }
c4ab56ba
JB
376 if (
377 higherFirst &&
66a7748d 378 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
c4ab56ba
JB
379 index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 &&
380 !isWithinInterval(
381 addSeconds(
382 compositeChargingScheduleLowerInterval.start,
66a7748d 383 schedulePeriod.startPeriod
c4ab56ba
JB
384 ),
385 {
386 start: compositeChargingScheduleLowerInterval.start,
66a7748d
JB
387 end: compositeChargingScheduleHigherInterval.end
388 }
c4ab56ba
JB
389 ) &&
390 isWithinInterval(
391 addSeconds(
392 compositeChargingScheduleLowerInterval.start,
66a7748d
JB
393 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
394 compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod
c4ab56ba
JB
395 ),
396 {
397 start: compositeChargingScheduleLowerInterval.start,
66a7748d
JB
398 end: compositeChargingScheduleHigherInterval.end
399 }
c4ab56ba
JB
400 )
401 ) {
66a7748d 402 return false
c4ab56ba 403 }
4abf6441
JB
404 if (
405 !higherFirst &&
406 isWithinInterval(
407 addSeconds(
408 compositeChargingScheduleLowerInterval.start,
66a7748d 409 schedulePeriod.startPeriod
4abf6441
JB
410 ),
411 {
412 start: compositeChargingScheduleHigherInterval.start,
66a7748d
JB
413 end: compositeChargingScheduleLowerInterval.end
414 }
4abf6441
JB
415 )
416 ) {
66a7748d 417 return false
4abf6441 418 }
66a7748d 419 return true
4abf6441 420 })
0e14e1d4
JB
421 .map((schedulePeriod, index) => {
422 if (index === 0 && schedulePeriod.startPeriod !== 0) {
66a7748d 423 schedulePeriod.startPeriod = 0
0e14e1d4 424 }
4abf6441
JB
425 return {
426 ...schedulePeriod,
427 startPeriod: higherFirst
428 ? schedulePeriod.startPeriod +
429 differenceInSeconds(
430 compositeChargingScheduleLowerInterval.start,
66a7748d 431 compositeChargingScheduleHigherInterval.start
4abf6441 432 )
66a7748d
JB
433 : 0
434 }
435 })
436 ].sort((a, b) => a.startPeriod - b.startPeriod)
437 }
438 }
ef9e3b33 439
563e40ce
JB
440 public static isConfigurationKeyVisible (key: ConfigurationKey): boolean {
441 if (key.visible == null) {
d0ed7db9 442 return true
563e40ce
JB
443 }
444 return key.visible
445 }
446
90aceaf6
JB
447 public static hasReservation = (
448 chargingStation: ChargingStation,
449 connectorId: number,
66a7748d 450 idTag: string
90aceaf6 451 ): boolean => {
66a7748d
JB
452 const connectorReservation = chargingStation.getReservationBy('connectorId', connectorId)
453 const chargingStationReservation = chargingStation.getReservationBy('connectorId', 0)
90aceaf6
JB
454 if (
455 (chargingStation.getConnectorStatus(connectorId)?.status ===
456 OCPP16ChargePointStatus.Reserved &&
66a7748d 457 connectorReservation != null &&
56563a3c 458 !hasReservationExpired(connectorReservation) &&
5199f9fd 459 connectorReservation.idTag === idTag) ||
90aceaf6 460 (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved &&
66a7748d 461 chargingStationReservation != null &&
56563a3c 462 !hasReservationExpired(chargingStationReservation) &&
5199f9fd 463 chargingStationReservation.idTag === idTag)
90aceaf6 464 ) {
88499f52
JB
465 logger.debug(
466 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
66a7748d
JB
467 connectorReservation ?? chargingStationReservation
468 )
469 return true
90aceaf6 470 }
66a7748d
JB
471 return false
472 }
90aceaf6 473
1b271a54
JB
474 public static parseJsonSchemaFile<T extends JsonType>(
475 relativePath: string,
476 moduleName?: string,
66a7748d 477 methodName?: string
1b271a54 478 ): JSONSchemaType<T> {
7164966d 479 return super.parseJsonSchemaFile<T>(
51022aa0 480 relativePath,
1b271a54
JB
481 OCPPVersion.VERSION_16,
482 moduleName,
66a7748d
JB
483 methodName
484 )
130783a7
JB
485 }
486
66a7748d 487 private static readonly composeChargingSchedule = (
ef9e3b33 488 chargingSchedule: OCPP16ChargingSchedule,
66a7748d 489 compositeInterval: Interval
ef9e3b33
JB
490 ): OCPP16ChargingSchedule | undefined => {
491 const chargingScheduleInterval: Interval = {
66a7748d 492 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ef9e3b33 493 start: chargingSchedule.startSchedule!,
66a7748d
JB
494 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
495 end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!)
496 }
d632062f 497 if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
66a7748d 498 chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod)
d632062f 499 if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
ef9e3b33
JB
500 return {
501 ...chargingSchedule,
d632062f
JB
502 startSchedule: compositeInterval.start as Date,
503 duration: differenceInSeconds(
504 chargingScheduleInterval.end,
66a7748d 505 compositeInterval.start as Date
d632062f 506 ),
0e14e1d4
JB
507 chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod
508 .filter((schedulePeriod, index) => {
ef9e3b33
JB
509 if (
510 isWithinInterval(
66a7748d 511 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ef9e3b33 512 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
66a7748d 513 compositeInterval
ef9e3b33
JB
514 )
515 ) {
66a7748d 516 return true
ef9e3b33
JB
517 }
518 if (
519 index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
520 !isWithinInterval(
521 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
66a7748d 522 compositeInterval
ef9e3b33
JB
523 ) &&
524 isWithinInterval(
525 addSeconds(
526 chargingScheduleInterval.start,
66a7748d 527 chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod
ef9e3b33 528 ),
66a7748d 529 compositeInterval
ef9e3b33
JB
530 )
531 ) {
66a7748d 532 return true
ef9e3b33 533 }
66a7748d 534 return false
0e14e1d4
JB
535 })
536 .map((schedulePeriod, index) => {
537 if (index === 0 && schedulePeriod.startPeriod !== 0) {
66a7748d 538 schedulePeriod.startPeriod = 0
0e14e1d4 539 }
66a7748d
JB
540 return schedulePeriod
541 })
542 }
ef9e3b33 543 }
d632062f 544 if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
ef9e3b33
JB
545 return {
546 ...chargingSchedule,
d632062f
JB
547 duration: differenceInSeconds(
548 compositeInterval.end as Date,
66a7748d 549 chargingScheduleInterval.start
d632062f 550 ),
a974c8e4 551 chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter(schedulePeriod =>
ef9e3b33 552 isWithinInterval(
66a7748d 553 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ef9e3b33 554 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
66a7748d
JB
555 compositeInterval
556 )
557 )
558 }
ef9e3b33 559 }
66a7748d 560 return chargingSchedule
ef9e3b33 561 }
66a7748d 562 }
6ed92bc1 563}