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