Commit | Line | Data |
---|---|---|
a19b897d | 1 | // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved. |
c8eeb62b | 2 | |
66a7748d | 3 | import type { JSONSchemaType } from 'ajv' |
ef9e3b33 | 4 | import { |
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 | 14 | import { OCPP16Constants } from './OCPP16Constants.js' |
90aceaf6 JB |
15 | import { |
16 | type ChargingStation, | |
17 | hasFeatureProfile, | |
66a7748d JB |
18 | hasReservationExpired |
19 | } from '../../../charging-station/index.js' | |
e7aeea18 | 20 | import { |
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 | 41 | import { convertToDate, isNotEmptyArray, logger, roundTo } from '../../../utils/index.js' |
66a7748d | 42 | import { OCPPServiceUtils } from '../OCPPServiceUtils.js' |
6ed92bc1 | 43 | |
7bc31f9c | 44 | export 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 | 200 | if (isNotEmptyArray(chargingProfiles)) { |
5dc7c990 | 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 | } |