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