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 { |
d19b10a8 | 21 | type GenericResponse, |
268a74bb | 22 | type JsonType, |
d19b10a8 | 23 | OCPP16AuthorizationStatus, |
66a7748d | 24 | type OCPP16AvailabilityType, |
366f75f6 JB |
25 | type OCPP16ChangeAvailabilityResponse, |
26 | OCPP16ChargePointStatus, | |
268a74bb | 27 | type OCPP16ChargingProfile, |
ef9e3b33 | 28 | type OCPP16ChargingSchedule, |
41f3983a | 29 | type OCPP16ClearChargingProfileRequest, |
268a74bb | 30 | type OCPP16IncomingRequestCommand, |
27782dbc | 31 | type OCPP16MeterValue, |
41f3983a JB |
32 | OCPP16MeterValueContext, |
33 | OCPP16MeterValueUnit, | |
66a7748d | 34 | type OCPP16RequestCommand, |
268a74bb | 35 | OCPP16StandardParametersKey, |
d19b10a8 | 36 | OCPP16StopTransactionReason, |
268a74bb | 37 | type OCPP16SupportedFeatureProfiles, |
66a7748d JB |
38 | OCPPVersion |
39 | } from '../../../types/index.js' | |
aa63c9b7 | 40 | import { isNotEmptyArray, logger, roundTo } from '../../../utils/index.js' |
66a7748d | 41 | import { OCPPServiceUtils } from '../OCPPServiceUtils.js' |
6ed92bc1 | 42 | |
7bc31f9c | 43 | export class OCPP16ServiceUtils extends OCPPServiceUtils { |
66a7748d | 44 | public static checkFeatureProfile ( |
370ae4ee JB |
45 | chargingStation: ChargingStation, |
46 | featureProfile: OCPP16SupportedFeatureProfiles, | |
66a7748d | 47 | command: OCPP16RequestCommand | OCPP16IncomingRequestCommand |
370ae4ee | 48 | ): boolean { |
66a7748d | 49 | if (hasFeatureProfile(chargingStation, featureProfile) === false) { |
370ae4ee JB |
50 | logger.warn( |
51 | `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${ | |
52 | OCPP16StandardParametersKey.SupportedFeatureProfiles | |
66a7748d JB |
53 | } in configuration` |
54 | ) | |
55 | return false | |
370ae4ee | 56 | } |
66a7748d | 57 | return true |
370ae4ee JB |
58 | } |
59 | ||
66a7748d | 60 | public static buildTransactionBeginMeterValue ( |
e7aeea18 JB |
61 | chargingStation: ChargingStation, |
62 | connectorId: number, | |
66a7748d | 63 | meterStart: number |
e7aeea18 | 64 | ): OCPP16MeterValue { |
fd0c36fa | 65 | const meterValue: OCPP16MeterValue = { |
c38f0ced | 66 | timestamp: new Date(), |
66a7748d JB |
67 | sampledValue: [] |
68 | } | |
9ccca265 | 69 | // Energy.Active.Import.Register measurand (default) |
ed3d2808 | 70 | const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 71 | chargingStation, |
66a7748d JB |
72 | connectorId |
73 | ) | |
41f3983a | 74 | const unitDivider = |
66a7748d | 75 | sampledValueTemplate?.unit === OCPP16MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1 |
e7aeea18 JB |
76 | meterValue.sampledValue.push( |
77 | OCPP16ServiceUtils.buildSampledValue( | |
66a7748d | 78 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
e1d9a0f4 | 79 | sampledValueTemplate!, |
9bf0ef23 | 80 | roundTo((meterStart ?? 0) / unitDivider, 4), |
66a7748d JB |
81 | OCPP16MeterValueContext.TRANSACTION_BEGIN |
82 | ) | |
83 | ) | |
84 | return meterValue | |
fd0c36fa JB |
85 | } |
86 | ||
66a7748d | 87 | public static buildTransactionDataMeterValues ( |
e7aeea18 | 88 | transactionBeginMeterValue: OCPP16MeterValue, |
66a7748d | 89 | transactionEndMeterValue: OCPP16MeterValue |
e7aeea18 | 90 | ): OCPP16MeterValue[] { |
66a7748d JB |
91 | const meterValues: OCPP16MeterValue[] = [] |
92 | meterValues.push(transactionBeginMeterValue) | |
93 | meterValues.push(transactionEndMeterValue) | |
94 | return meterValues | |
fd0c36fa | 95 | } |
7bc31f9c | 96 | |
d19b10a8 JB |
97 | public static remoteStopTransaction = async ( |
98 | chargingStation: ChargingStation, | |
66a7748d | 99 | connectorId: number |
d19b10a8 JB |
100 | ): Promise<GenericResponse> => { |
101 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( | |
102 | chargingStation, | |
103 | connectorId, | |
66a7748d JB |
104 | OCPP16ChargePointStatus.Finishing |
105 | ) | |
d19b10a8 JB |
106 | const stopResponse = await chargingStation.stopTransactionOnConnector( |
107 | connectorId, | |
66a7748d JB |
108 | OCPP16StopTransactionReason.REMOTE |
109 | ) | |
d19b10a8 | 110 | if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { |
66a7748d | 111 | return OCPP16Constants.OCPP_RESPONSE_ACCEPTED |
d19b10a8 | 112 | } |
66a7748d JB |
113 | return OCPP16Constants.OCPP_RESPONSE_REJECTED |
114 | } | |
d19b10a8 | 115 | |
366f75f6 JB |
116 | public static changeAvailability = async ( |
117 | chargingStation: ChargingStation, | |
225e32b0 | 118 | connectorIds: number[], |
366f75f6 | 119 | chargePointStatus: OCPP16ChargePointStatus, |
66a7748d | 120 | availabilityType: OCPP16AvailabilityType |
366f75f6 | 121 | ): Promise<OCPP16ChangeAvailabilityResponse> => { |
66a7748d | 122 | const responses: OCPP16ChangeAvailabilityResponse[] = [] |
225e32b0 JB |
123 | for (const connectorId of connectorIds) { |
124 | let response: OCPP16ChangeAvailabilityResponse = | |
66a7748d JB |
125 | OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED |
126 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
127 | const connectorStatus = chargingStation.getConnectorStatus(connectorId)! | |
225e32b0 | 128 | if (connectorStatus?.transactionStarted === true) { |
66a7748d | 129 | response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED |
225e32b0 | 130 | } |
66a7748d | 131 | connectorStatus.availability = availabilityType |
225e32b0 JB |
132 | if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) { |
133 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( | |
134 | chargingStation, | |
135 | connectorId, | |
66a7748d JB |
136 | chargePointStatus |
137 | ) | |
225e32b0 | 138 | } |
66a7748d | 139 | responses.push(response) |
366f75f6 | 140 | } |
3b0ed034 | 141 | if (responses.includes(OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED)) { |
66a7748d | 142 | return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED |
366f75f6 | 143 | } |
66a7748d JB |
144 | return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED |
145 | } | |
366f75f6 | 146 | |
66a7748d | 147 | public static setChargingProfile ( |
ed3d2808 JB |
148 | chargingStation: ChargingStation, |
149 | connectorId: number, | |
66a7748d | 150 | cp: OCPP16ChargingProfile |
ed3d2808 | 151 | ): void { |
aa63c9b7 | 152 | if (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles == null) { |
ed3d2808 | 153 | logger.error( |
66a7748d JB |
154 | `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization` |
155 | ) | |
156 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
157 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [] | |
ed3d2808 | 158 | } |
66a7748d | 159 | if (!Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) { |
ed3d2808 | 160 | logger.error( |
66a7748d JB |
161 | `${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` |
162 | ) | |
163 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
164 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [] | |
ed3d2808 | 165 | } |
66a7748d | 166 | let cpReplaced = false |
9bf0ef23 | 167 | if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) { |
ed3d2808 JB |
168 | chargingStation |
169 | .getConnectorStatus(connectorId) | |
72092cfc | 170 | ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { |
ed3d2808 JB |
171 | if ( |
172 | chargingProfile.chargingProfileId === cp.chargingProfileId || | |
173 | (chargingProfile.stackLevel === cp.stackLevel && | |
174 | chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose) | |
175 | ) { | |
66a7748d JB |
176 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
177 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp | |
178 | cpReplaced = true | |
ed3d2808 | 179 | } |
66a7748d | 180 | }) |
ed3d2808 | 181 | } |
66a7748d | 182 | !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp) |
ed3d2808 JB |
183 | } |
184 | ||
73d87be1 JB |
185 | public static clearChargingProfiles = ( |
186 | chargingStation: ChargingStation, | |
41f3983a | 187 | commandPayload: OCPP16ClearChargingProfileRequest, |
66a7748d | 188 | chargingProfiles: OCPP16ChargingProfile[] | undefined |
73d87be1 | 189 | ): boolean => { |
66a7748d JB |
190 | const { id, chargingProfilePurpose, stackLevel } = commandPayload |
191 | let clearedCP = false | |
73d87be1 JB |
192 | if (isNotEmptyArray(chargingProfiles)) { |
193 | chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { | |
66a7748d | 194 | let clearCurrentCP = false |
0d1f33ba | 195 | if (chargingProfile.chargingProfileId === id) { |
66a7748d | 196 | clearCurrentCP = true |
73d87be1 | 197 | } |
66a7748d JB |
198 | if (chargingProfilePurpose == null && chargingProfile.stackLevel === stackLevel) { |
199 | clearCurrentCP = true | |
73d87be1 | 200 | } |
66a7748d JB |
201 | if ( |
202 | stackLevel == null && | |
203 | chargingProfile.chargingProfilePurpose === chargingProfilePurpose | |
204 | ) { | |
205 | clearCurrentCP = true | |
73d87be1 JB |
206 | } |
207 | if ( | |
0d1f33ba JB |
208 | chargingProfile.stackLevel === stackLevel && |
209 | chargingProfile.chargingProfilePurpose === chargingProfilePurpose | |
73d87be1 | 210 | ) { |
66a7748d | 211 | clearCurrentCP = true |
73d87be1 JB |
212 | } |
213 | if (clearCurrentCP) { | |
66a7748d | 214 | chargingProfiles.splice(index, 1) |
73d87be1 JB |
215 | logger.debug( |
216 | `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`, | |
66a7748d JB |
217 | chargingProfile |
218 | ) | |
219 | clearedCP = true | |
73d87be1 | 220 | } |
66a7748d | 221 | }) |
73d87be1 | 222 | } |
66a7748d JB |
223 | return clearedCP |
224 | } | |
73d87be1 | 225 | |
ef9e3b33 | 226 | public static composeChargingSchedules = ( |
4abf6441 JB |
227 | chargingScheduleHigher: OCPP16ChargingSchedule | undefined, |
228 | chargingScheduleLower: OCPP16ChargingSchedule | undefined, | |
66a7748d | 229 | compositeInterval: Interval |
ef9e3b33 | 230 | ): OCPP16ChargingSchedule | undefined => { |
66a7748d JB |
231 | if (chargingScheduleHigher == null && chargingScheduleLower == null) { |
232 | return undefined | |
ef9e3b33 | 233 | } |
66a7748d JB |
234 | if (chargingScheduleHigher != null && chargingScheduleLower == null) { |
235 | return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval) | |
ef9e3b33 | 236 | } |
66a7748d JB |
237 | if (chargingScheduleHigher == null && chargingScheduleLower != null) { |
238 | return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval) | |
ef9e3b33 | 239 | } |
4abf6441 | 240 | const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined = |
66a7748d JB |
241 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
242 | OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval) | |
4abf6441 | 243 | const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined = |
66a7748d JB |
244 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
245 | OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval) | |
4abf6441 | 246 | const compositeChargingScheduleHigherInterval: Interval = { |
66a7748d | 247 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 248 | start: compositeChargingScheduleHigher!.startSchedule!, |
ef9e3b33 | 249 | end: addSeconds( |
66a7748d | 250 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 251 | compositeChargingScheduleHigher!.startSchedule!, |
66a7748d JB |
252 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
253 | compositeChargingScheduleHigher!.duration! | |
254 | ) | |
255 | } | |
4abf6441 | 256 | const compositeChargingScheduleLowerInterval: Interval = { |
66a7748d | 257 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 258 | start: compositeChargingScheduleLower!.startSchedule!, |
ef9e3b33 | 259 | end: addSeconds( |
66a7748d | 260 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 261 | compositeChargingScheduleLower!.startSchedule!, |
66a7748d JB |
262 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
263 | compositeChargingScheduleLower!.duration! | |
264 | ) | |
265 | } | |
4abf6441 JB |
266 | const higherFirst = isBefore( |
267 | compositeChargingScheduleHigherInterval.start, | |
66a7748d JB |
268 | compositeChargingScheduleLowerInterval.start |
269 | ) | |
ef9e3b33 JB |
270 | if ( |
271 | !areIntervalsOverlapping( | |
4abf6441 | 272 | compositeChargingScheduleHigherInterval, |
66a7748d | 273 | compositeChargingScheduleLowerInterval |
ef9e3b33 JB |
274 | ) |
275 | ) { | |
276 | return { | |
4abf6441 | 277 | ...compositeChargingScheduleLower, |
66a7748d | 278 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
279 | ...compositeChargingScheduleHigher!, |
280 | startSchedule: higherFirst | |
281 | ? (compositeChargingScheduleHigherInterval.start as Date) | |
282 | : (compositeChargingScheduleLowerInterval.start as Date), | |
283 | duration: higherFirst | |
284 | ? differenceInSeconds( | |
66a7748d JB |
285 | compositeChargingScheduleLowerInterval.end, |
286 | compositeChargingScheduleHigherInterval.start | |
287 | ) | |
4abf6441 | 288 | : differenceInSeconds( |
66a7748d JB |
289 | compositeChargingScheduleHigherInterval.end, |
290 | compositeChargingScheduleLowerInterval.start | |
291 | ), | |
4abf6441 | 292 | chargingSchedulePeriod: [ |
66a7748d | 293 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
294 | ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { |
295 | return { | |
296 | ...schedulePeriod, | |
297 | startPeriod: higherFirst | |
298 | ? 0 | |
299 | : schedulePeriod.startPeriod + | |
300 | differenceInSeconds( | |
301 | compositeChargingScheduleHigherInterval.start, | |
66a7748d JB |
302 | compositeChargingScheduleLowerInterval.start |
303 | ) | |
304 | } | |
4abf6441 | 305 | }), |
66a7748d | 306 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
307 | ...compositeChargingScheduleLower!.chargingSchedulePeriod.map((schedulePeriod) => { |
308 | return { | |
309 | ...schedulePeriod, | |
310 | startPeriod: higherFirst | |
311 | ? schedulePeriod.startPeriod + | |
312 | differenceInSeconds( | |
313 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 314 | compositeChargingScheduleHigherInterval.start |
4abf6441 | 315 | ) |
66a7748d JB |
316 | : 0 |
317 | } | |
318 | }) | |
319 | ].sort((a, b) => a.startPeriod - b.startPeriod) | |
320 | } | |
ef9e3b33 | 321 | } |
4abf6441 JB |
322 | return { |
323 | ...compositeChargingScheduleLower, | |
66a7748d | 324 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
325 | ...compositeChargingScheduleHigher!, |
326 | startSchedule: higherFirst | |
327 | ? (compositeChargingScheduleHigherInterval.start as Date) | |
328 | : (compositeChargingScheduleLowerInterval.start as Date), | |
329 | duration: higherFirst | |
330 | ? differenceInSeconds( | |
66a7748d JB |
331 | compositeChargingScheduleLowerInterval.end, |
332 | compositeChargingScheduleHigherInterval.start | |
333 | ) | |
4abf6441 | 334 | : differenceInSeconds( |
66a7748d JB |
335 | compositeChargingScheduleHigherInterval.end, |
336 | compositeChargingScheduleLowerInterval.start | |
337 | ), | |
4abf6441 | 338 | chargingSchedulePeriod: [ |
66a7748d | 339 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
340 | ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { |
341 | return { | |
342 | ...schedulePeriod, | |
343 | startPeriod: higherFirst | |
344 | ? 0 | |
345 | : schedulePeriod.startPeriod + | |
346 | differenceInSeconds( | |
347 | compositeChargingScheduleHigherInterval.start, | |
66a7748d JB |
348 | compositeChargingScheduleLowerInterval.start |
349 | ) | |
350 | } | |
4abf6441 | 351 | }), |
66a7748d | 352 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 353 | ...compositeChargingScheduleLower!.chargingSchedulePeriod |
c4ab56ba | 354 | .filter((schedulePeriod, index) => { |
4abf6441 JB |
355 | if ( |
356 | higherFirst && | |
357 | isWithinInterval( | |
358 | addSeconds( | |
359 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 360 | schedulePeriod.startPeriod |
4abf6441 JB |
361 | ), |
362 | { | |
363 | start: compositeChargingScheduleLowerInterval.start, | |
66a7748d JB |
364 | end: compositeChargingScheduleHigherInterval.end |
365 | } | |
4abf6441 JB |
366 | ) |
367 | ) { | |
66a7748d | 368 | return false |
4abf6441 | 369 | } |
c4ab56ba JB |
370 | if ( |
371 | higherFirst && | |
66a7748d | 372 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
c4ab56ba JB |
373 | index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 && |
374 | !isWithinInterval( | |
375 | addSeconds( | |
376 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 377 | schedulePeriod.startPeriod |
c4ab56ba JB |
378 | ), |
379 | { | |
380 | start: compositeChargingScheduleLowerInterval.start, | |
66a7748d JB |
381 | end: compositeChargingScheduleHigherInterval.end |
382 | } | |
c4ab56ba JB |
383 | ) && |
384 | isWithinInterval( | |
385 | addSeconds( | |
386 | compositeChargingScheduleLowerInterval.start, | |
66a7748d JB |
387 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
388 | compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod | |
c4ab56ba JB |
389 | ), |
390 | { | |
391 | start: compositeChargingScheduleLowerInterval.start, | |
66a7748d JB |
392 | end: compositeChargingScheduleHigherInterval.end |
393 | } | |
c4ab56ba JB |
394 | ) |
395 | ) { | |
66a7748d | 396 | return false |
c4ab56ba | 397 | } |
4abf6441 JB |
398 | if ( |
399 | !higherFirst && | |
400 | isWithinInterval( | |
401 | addSeconds( | |
402 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 403 | schedulePeriod.startPeriod |
4abf6441 JB |
404 | ), |
405 | { | |
406 | start: compositeChargingScheduleHigherInterval.start, | |
66a7748d JB |
407 | end: compositeChargingScheduleLowerInterval.end |
408 | } | |
4abf6441 JB |
409 | ) |
410 | ) { | |
66a7748d | 411 | return false |
4abf6441 | 412 | } |
66a7748d | 413 | return true |
4abf6441 | 414 | }) |
0e14e1d4 JB |
415 | .map((schedulePeriod, index) => { |
416 | if (index === 0 && schedulePeriod.startPeriod !== 0) { | |
66a7748d | 417 | schedulePeriod.startPeriod = 0 |
0e14e1d4 | 418 | } |
4abf6441 JB |
419 | return { |
420 | ...schedulePeriod, | |
421 | startPeriod: higherFirst | |
422 | ? schedulePeriod.startPeriod + | |
423 | differenceInSeconds( | |
424 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 425 | compositeChargingScheduleHigherInterval.start |
4abf6441 | 426 | ) |
66a7748d JB |
427 | : 0 |
428 | } | |
429 | }) | |
430 | ].sort((a, b) => a.startPeriod - b.startPeriod) | |
431 | } | |
432 | } | |
ef9e3b33 | 433 | |
90aceaf6 JB |
434 | public static hasReservation = ( |
435 | chargingStation: ChargingStation, | |
436 | connectorId: number, | |
66a7748d | 437 | idTag: string |
90aceaf6 | 438 | ): boolean => { |
66a7748d JB |
439 | const connectorReservation = chargingStation.getReservationBy('connectorId', connectorId) |
440 | const chargingStationReservation = chargingStation.getReservationBy('connectorId', 0) | |
90aceaf6 JB |
441 | if ( |
442 | (chargingStation.getConnectorStatus(connectorId)?.status === | |
443 | OCPP16ChargePointStatus.Reserved && | |
66a7748d | 444 | connectorReservation != null && |
56563a3c | 445 | !hasReservationExpired(connectorReservation) && |
56563a3c | 446 | connectorReservation?.idTag === idTag) || |
90aceaf6 | 447 | (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved && |
66a7748d | 448 | chargingStationReservation != null && |
56563a3c JB |
449 | !hasReservationExpired(chargingStationReservation) && |
450 | chargingStationReservation?.idTag === idTag) | |
90aceaf6 | 451 | ) { |
88499f52 JB |
452 | logger.debug( |
453 | `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`, | |
66a7748d JB |
454 | connectorReservation ?? chargingStationReservation |
455 | ) | |
456 | return true | |
90aceaf6 | 457 | } |
66a7748d JB |
458 | return false |
459 | } | |
90aceaf6 | 460 | |
1b271a54 JB |
461 | public static parseJsonSchemaFile<T extends JsonType>( |
462 | relativePath: string, | |
463 | moduleName?: string, | |
66a7748d | 464 | methodName?: string |
1b271a54 | 465 | ): JSONSchemaType<T> { |
7164966d | 466 | return super.parseJsonSchemaFile<T>( |
51022aa0 | 467 | relativePath, |
1b271a54 JB |
468 | OCPPVersion.VERSION_16, |
469 | moduleName, | |
66a7748d JB |
470 | methodName |
471 | ) | |
130783a7 JB |
472 | } |
473 | ||
66a7748d | 474 | private static readonly composeChargingSchedule = ( |
ef9e3b33 | 475 | chargingSchedule: OCPP16ChargingSchedule, |
66a7748d | 476 | compositeInterval: Interval |
ef9e3b33 JB |
477 | ): OCPP16ChargingSchedule | undefined => { |
478 | const chargingScheduleInterval: Interval = { | |
66a7748d | 479 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
ef9e3b33 | 480 | start: chargingSchedule.startSchedule!, |
66a7748d JB |
481 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
482 | end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!) | |
483 | } | |
d632062f | 484 | if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) { |
66a7748d | 485 | chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod) |
d632062f | 486 | if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) { |
ef9e3b33 JB |
487 | return { |
488 | ...chargingSchedule, | |
d632062f JB |
489 | startSchedule: compositeInterval.start as Date, |
490 | duration: differenceInSeconds( | |
491 | chargingScheduleInterval.end, | |
66a7748d | 492 | compositeInterval.start as Date |
d632062f | 493 | ), |
0e14e1d4 JB |
494 | chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod |
495 | .filter((schedulePeriod, index) => { | |
ef9e3b33 JB |
496 | if ( |
497 | isWithinInterval( | |
66a7748d | 498 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
ef9e3b33 | 499 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!, |
66a7748d | 500 | compositeInterval |
ef9e3b33 JB |
501 | ) |
502 | ) { | |
66a7748d | 503 | return true |
ef9e3b33 JB |
504 | } |
505 | if ( | |
506 | index < chargingSchedule.chargingSchedulePeriod.length - 1 && | |
507 | !isWithinInterval( | |
508 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod), | |
66a7748d | 509 | compositeInterval |
ef9e3b33 JB |
510 | ) && |
511 | isWithinInterval( | |
512 | addSeconds( | |
513 | chargingScheduleInterval.start, | |
66a7748d | 514 | chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod |
ef9e3b33 | 515 | ), |
66a7748d | 516 | compositeInterval |
ef9e3b33 JB |
517 | ) |
518 | ) { | |
66a7748d | 519 | return true |
ef9e3b33 | 520 | } |
66a7748d | 521 | return false |
0e14e1d4 JB |
522 | }) |
523 | .map((schedulePeriod, index) => { | |
524 | if (index === 0 && schedulePeriod.startPeriod !== 0) { | |
66a7748d | 525 | schedulePeriod.startPeriod = 0 |
0e14e1d4 | 526 | } |
66a7748d JB |
527 | return schedulePeriod |
528 | }) | |
529 | } | |
ef9e3b33 | 530 | } |
d632062f | 531 | if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) { |
ef9e3b33 JB |
532 | return { |
533 | ...chargingSchedule, | |
d632062f JB |
534 | duration: differenceInSeconds( |
535 | compositeInterval.end as Date, | |
66a7748d | 536 | chargingScheduleInterval.start |
d632062f | 537 | ), |
ef9e3b33 JB |
538 | chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter((schedulePeriod) => |
539 | isWithinInterval( | |
66a7748d | 540 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
ef9e3b33 | 541 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!, |
66a7748d JB |
542 | compositeInterval |
543 | ) | |
544 | ) | |
545 | } | |
ef9e3b33 | 546 | } |
66a7748d | 547 | return chargingSchedule |
ef9e3b33 | 548 | } |
66a7748d | 549 | } |
6ed92bc1 | 550 | } |