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' | |
563e40ce | 41 | import { cloneObject, 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, | |
66a7748d | 64 | meterStart: number |
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)! | |
225e32b0 | 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)) { |
ed3d2808 JB |
169 | chargingStation |
170 | .getConnectorStatus(connectorId) | |
72092cfc | 171 | ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { |
ed3d2808 JB |
172 | if ( |
173 | chargingProfile.chargingProfileId === cp.chargingProfileId || | |
174 | (chargingProfile.stackLevel === cp.stackLevel && | |
175 | chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose) | |
176 | ) { | |
66a7748d JB |
177 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
178 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp | |
179 | cpReplaced = true | |
ed3d2808 | 180 | } |
66a7748d | 181 | }) |
ed3d2808 | 182 | } |
66a7748d | 183 | !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp) |
ed3d2808 JB |
184 | } |
185 | ||
73d87be1 JB |
186 | public static clearChargingProfiles = ( |
187 | chargingStation: ChargingStation, | |
41f3983a | 188 | commandPayload: OCPP16ClearChargingProfileRequest, |
66a7748d | 189 | chargingProfiles: OCPP16ChargingProfile[] | undefined |
73d87be1 | 190 | ): boolean => { |
66a7748d JB |
191 | const { id, chargingProfilePurpose, stackLevel } = commandPayload |
192 | let clearedCP = false | |
73d87be1 JB |
193 | if (isNotEmptyArray(chargingProfiles)) { |
194 | chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { | |
66a7748d | 195 | let clearCurrentCP = false |
0d1f33ba | 196 | if (chargingProfile.chargingProfileId === id) { |
66a7748d | 197 | clearCurrentCP = true |
73d87be1 | 198 | } |
66a7748d JB |
199 | if (chargingProfilePurpose == null && chargingProfile.stackLevel === stackLevel) { |
200 | clearCurrentCP = true | |
73d87be1 | 201 | } |
66a7748d JB |
202 | if ( |
203 | stackLevel == null && | |
204 | chargingProfile.chargingProfilePurpose === chargingProfilePurpose | |
205 | ) { | |
206 | clearCurrentCP = true | |
73d87be1 JB |
207 | } |
208 | if ( | |
0d1f33ba JB |
209 | chargingProfile.stackLevel === stackLevel && |
210 | chargingProfile.chargingProfilePurpose === chargingProfilePurpose | |
73d87be1 | 211 | ) { |
66a7748d | 212 | clearCurrentCP = true |
73d87be1 JB |
213 | } |
214 | if (clearCurrentCP) { | |
66a7748d | 215 | chargingProfiles.splice(index, 1) |
73d87be1 JB |
216 | logger.debug( |
217 | `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`, | |
66a7748d JB |
218 | chargingProfile |
219 | ) | |
220 | clearedCP = true | |
73d87be1 | 221 | } |
66a7748d | 222 | }) |
73d87be1 | 223 | } |
66a7748d JB |
224 | return clearedCP |
225 | } | |
73d87be1 | 226 | |
ef9e3b33 | 227 | public static composeChargingSchedules = ( |
4abf6441 JB |
228 | chargingScheduleHigher: OCPP16ChargingSchedule | undefined, |
229 | chargingScheduleLower: OCPP16ChargingSchedule | undefined, | |
66a7748d | 230 | compositeInterval: Interval |
ef9e3b33 | 231 | ): OCPP16ChargingSchedule | undefined => { |
66a7748d JB |
232 | if (chargingScheduleHigher == null && chargingScheduleLower == null) { |
233 | return undefined | |
ef9e3b33 | 234 | } |
66a7748d JB |
235 | if (chargingScheduleHigher != null && chargingScheduleLower == null) { |
236 | return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval) | |
ef9e3b33 | 237 | } |
66a7748d JB |
238 | if (chargingScheduleHigher == null && chargingScheduleLower != null) { |
239 | return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval) | |
ef9e3b33 | 240 | } |
4abf6441 | 241 | const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined = |
66a7748d JB |
242 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
243 | OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval) | |
4abf6441 | 244 | const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined = |
66a7748d JB |
245 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
246 | OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval) | |
4abf6441 | 247 | const compositeChargingScheduleHigherInterval: Interval = { |
66a7748d | 248 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 249 | start: compositeChargingScheduleHigher!.startSchedule!, |
ef9e3b33 | 250 | end: addSeconds( |
66a7748d | 251 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 252 | compositeChargingScheduleHigher!.startSchedule!, |
66a7748d JB |
253 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
254 | compositeChargingScheduleHigher!.duration! | |
255 | ) | |
256 | } | |
4abf6441 | 257 | const compositeChargingScheduleLowerInterval: Interval = { |
66a7748d | 258 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 259 | start: compositeChargingScheduleLower!.startSchedule!, |
ef9e3b33 | 260 | end: addSeconds( |
66a7748d | 261 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 262 | compositeChargingScheduleLower!.startSchedule!, |
66a7748d JB |
263 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
264 | compositeChargingScheduleLower!.duration! | |
265 | ) | |
266 | } | |
4abf6441 JB |
267 | const higherFirst = isBefore( |
268 | compositeChargingScheduleHigherInterval.start, | |
66a7748d JB |
269 | compositeChargingScheduleLowerInterval.start |
270 | ) | |
ef9e3b33 JB |
271 | if ( |
272 | !areIntervalsOverlapping( | |
4abf6441 | 273 | compositeChargingScheduleHigherInterval, |
66a7748d | 274 | compositeChargingScheduleLowerInterval |
ef9e3b33 JB |
275 | ) |
276 | ) { | |
277 | return { | |
4abf6441 | 278 | ...compositeChargingScheduleLower, |
66a7748d | 279 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
280 | ...compositeChargingScheduleHigher!, |
281 | startSchedule: higherFirst | |
282 | ? (compositeChargingScheduleHigherInterval.start as Date) | |
283 | : (compositeChargingScheduleLowerInterval.start as Date), | |
284 | duration: higherFirst | |
285 | ? differenceInSeconds( | |
66a7748d JB |
286 | compositeChargingScheduleLowerInterval.end, |
287 | compositeChargingScheduleHigherInterval.start | |
288 | ) | |
4abf6441 | 289 | : differenceInSeconds( |
66a7748d JB |
290 | compositeChargingScheduleHigherInterval.end, |
291 | compositeChargingScheduleLowerInterval.start | |
292 | ), | |
4abf6441 | 293 | chargingSchedulePeriod: [ |
66a7748d | 294 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
295 | ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { |
296 | return { | |
297 | ...schedulePeriod, | |
298 | startPeriod: higherFirst | |
299 | ? 0 | |
300 | : schedulePeriod.startPeriod + | |
301 | differenceInSeconds( | |
302 | compositeChargingScheduleHigherInterval.start, | |
66a7748d JB |
303 | compositeChargingScheduleLowerInterval.start |
304 | ) | |
305 | } | |
4abf6441 | 306 | }), |
66a7748d | 307 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
308 | ...compositeChargingScheduleLower!.chargingSchedulePeriod.map((schedulePeriod) => { |
309 | return { | |
310 | ...schedulePeriod, | |
311 | startPeriod: higherFirst | |
312 | ? schedulePeriod.startPeriod + | |
313 | differenceInSeconds( | |
314 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 315 | compositeChargingScheduleHigherInterval.start |
4abf6441 | 316 | ) |
66a7748d JB |
317 | : 0 |
318 | } | |
319 | }) | |
320 | ].sort((a, b) => a.startPeriod - b.startPeriod) | |
321 | } | |
ef9e3b33 | 322 | } |
4abf6441 JB |
323 | return { |
324 | ...compositeChargingScheduleLower, | |
66a7748d | 325 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
326 | ...compositeChargingScheduleHigher!, |
327 | startSchedule: higherFirst | |
328 | ? (compositeChargingScheduleHigherInterval.start as Date) | |
329 | : (compositeChargingScheduleLowerInterval.start as Date), | |
330 | duration: higherFirst | |
331 | ? differenceInSeconds( | |
66a7748d JB |
332 | compositeChargingScheduleLowerInterval.end, |
333 | compositeChargingScheduleHigherInterval.start | |
334 | ) | |
4abf6441 | 335 | : differenceInSeconds( |
66a7748d JB |
336 | compositeChargingScheduleHigherInterval.end, |
337 | compositeChargingScheduleLowerInterval.start | |
338 | ), | |
4abf6441 | 339 | chargingSchedulePeriod: [ |
66a7748d | 340 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 JB |
341 | ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { |
342 | return { | |
343 | ...schedulePeriod, | |
344 | startPeriod: higherFirst | |
345 | ? 0 | |
346 | : schedulePeriod.startPeriod + | |
347 | differenceInSeconds( | |
348 | compositeChargingScheduleHigherInterval.start, | |
66a7748d JB |
349 | compositeChargingScheduleLowerInterval.start |
350 | ) | |
351 | } | |
4abf6441 | 352 | }), |
66a7748d | 353 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
4abf6441 | 354 | ...compositeChargingScheduleLower!.chargingSchedulePeriod |
c4ab56ba | 355 | .filter((schedulePeriod, index) => { |
4abf6441 JB |
356 | if ( |
357 | higherFirst && | |
358 | isWithinInterval( | |
359 | addSeconds( | |
360 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 361 | schedulePeriod.startPeriod |
4abf6441 JB |
362 | ), |
363 | { | |
364 | start: compositeChargingScheduleLowerInterval.start, | |
66a7748d JB |
365 | end: compositeChargingScheduleHigherInterval.end |
366 | } | |
4abf6441 JB |
367 | ) |
368 | ) { | |
66a7748d | 369 | return false |
4abf6441 | 370 | } |
c4ab56ba JB |
371 | if ( |
372 | higherFirst && | |
66a7748d | 373 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
c4ab56ba JB |
374 | index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 && |
375 | !isWithinInterval( | |
376 | addSeconds( | |
377 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 378 | schedulePeriod.startPeriod |
c4ab56ba JB |
379 | ), |
380 | { | |
381 | start: compositeChargingScheduleLowerInterval.start, | |
66a7748d JB |
382 | end: compositeChargingScheduleHigherInterval.end |
383 | } | |
c4ab56ba JB |
384 | ) && |
385 | isWithinInterval( | |
386 | addSeconds( | |
387 | compositeChargingScheduleLowerInterval.start, | |
66a7748d JB |
388 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
389 | compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod | |
c4ab56ba JB |
390 | ), |
391 | { | |
392 | start: compositeChargingScheduleLowerInterval.start, | |
66a7748d JB |
393 | end: compositeChargingScheduleHigherInterval.end |
394 | } | |
c4ab56ba JB |
395 | ) |
396 | ) { | |
66a7748d | 397 | return false |
c4ab56ba | 398 | } |
4abf6441 JB |
399 | if ( |
400 | !higherFirst && | |
401 | isWithinInterval( | |
402 | addSeconds( | |
403 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 404 | schedulePeriod.startPeriod |
4abf6441 JB |
405 | ), |
406 | { | |
407 | start: compositeChargingScheduleHigherInterval.start, | |
66a7748d JB |
408 | end: compositeChargingScheduleLowerInterval.end |
409 | } | |
4abf6441 JB |
410 | ) |
411 | ) { | |
66a7748d | 412 | return false |
4abf6441 | 413 | } |
66a7748d | 414 | return true |
4abf6441 | 415 | }) |
0e14e1d4 JB |
416 | .map((schedulePeriod, index) => { |
417 | if (index === 0 && schedulePeriod.startPeriod !== 0) { | |
66a7748d | 418 | schedulePeriod.startPeriod = 0 |
0e14e1d4 | 419 | } |
4abf6441 JB |
420 | return { |
421 | ...schedulePeriod, | |
422 | startPeriod: higherFirst | |
423 | ? schedulePeriod.startPeriod + | |
424 | differenceInSeconds( | |
425 | compositeChargingScheduleLowerInterval.start, | |
66a7748d | 426 | compositeChargingScheduleHigherInterval.start |
4abf6441 | 427 | ) |
66a7748d JB |
428 | : 0 |
429 | } | |
430 | }) | |
431 | ].sort((a, b) => a.startPeriod - b.startPeriod) | |
432 | } | |
433 | } | |
ef9e3b33 | 434 | |
563e40ce JB |
435 | public static isConfigurationKeyVisible (key: ConfigurationKey): boolean { |
436 | if (key.visible == null) { | |
437 | key = cloneObject(key) | |
438 | key.visible = true | |
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) && |
56563a3c | 455 | connectorReservation?.idTag === idTag) || |
90aceaf6 | 456 | (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved && |
66a7748d | 457 | chargingStationReservation != null && |
56563a3c JB |
458 | !hasReservationExpired(chargingStationReservation) && |
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 | } |