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