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