Commit | Line | Data |
---|---|---|
edd13439 | 1 | // Partial Copyright Jerome Benoit. 2021-2023. 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 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 | } |