Commit | Line | Data |
---|---|---|
edd13439 | 1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
c8eeb62b | 2 | |
130783a7 | 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, | |
11 | isWithinInterval, | |
12 | } from 'date-fns'; | |
130783a7 | 13 | |
366f75f6 | 14 | import { OCPP16Constants } from './OCPP16Constants'; |
90aceaf6 JB |
15 | import { |
16 | type ChargingStation, | |
17 | hasFeatureProfile, | |
18 | hasReservationExpired, | |
19 | } from '../../../charging-station'; | |
268a74bb | 20 | import { OCPPError } from '../../../exception'; |
e7aeea18 | 21 | import { |
73d87be1 | 22 | type ClearChargingProfileRequest, |
268a74bb JB |
23 | CurrentType, |
24 | ErrorType, | |
d19b10a8 | 25 | type GenericResponse, |
268a74bb JB |
26 | type JsonType, |
27 | type MeasurandPerPhaseSampledValueTemplates, | |
28 | type MeasurandValues, | |
e7aeea18 JB |
29 | MeterValueContext, |
30 | MeterValueLocation, | |
31 | MeterValueUnit, | |
d19b10a8 | 32 | OCPP16AuthorizationStatus, |
366f75f6 JB |
33 | OCPP16AvailabilityType, |
34 | type OCPP16ChangeAvailabilityResponse, | |
35 | OCPP16ChargePointStatus, | |
268a74bb | 36 | type OCPP16ChargingProfile, |
ef9e3b33 | 37 | type OCPP16ChargingSchedule, |
268a74bb | 38 | type OCPP16IncomingRequestCommand, |
27782dbc | 39 | type OCPP16MeterValue, |
e7aeea18 JB |
40 | OCPP16MeterValueMeasurand, |
41 | OCPP16MeterValuePhase, | |
370ae4ee | 42 | OCPP16RequestCommand, |
268a74bb JB |
43 | type OCPP16SampledValue, |
44 | OCPP16StandardParametersKey, | |
d19b10a8 | 45 | OCPP16StopTransactionReason, |
268a74bb JB |
46 | type OCPP16SupportedFeatureProfiles, |
47 | OCPPVersion, | |
48 | type SampledValueTemplate, | |
268a74bb | 49 | } from '../../../types'; |
9bf0ef23 JB |
50 | import { |
51 | ACElectricUtils, | |
52 | Constants, | |
53 | DCElectricUtils, | |
54 | convertToFloat, | |
55 | convertToInt, | |
56 | getRandomFloatFluctuatedRounded, | |
57 | getRandomFloatRounded, | |
58 | getRandomInteger, | |
59 | isNotEmptyArray, | |
5a47f72c | 60 | isNotEmptyString, |
9bf0ef23 JB |
61 | isNullOrUndefined, |
62 | isUndefined, | |
63 | logger, | |
64 | roundTo, | |
65 | } from '../../../utils'; | |
4c3c0d59 | 66 | import { OCPPServiceUtils } from '../OCPPServiceUtils'; |
6ed92bc1 | 67 | |
7bc31f9c | 68 | export class OCPP16ServiceUtils extends OCPPServiceUtils { |
370ae4ee JB |
69 | public static checkFeatureProfile( |
70 | chargingStation: ChargingStation, | |
71 | featureProfile: OCPP16SupportedFeatureProfiles, | |
5edd8ba0 | 72 | command: OCPP16RequestCommand | OCPP16IncomingRequestCommand, |
370ae4ee | 73 | ): boolean { |
d8093be1 | 74 | if (!hasFeatureProfile(chargingStation, featureProfile)) { |
370ae4ee JB |
75 | logger.warn( |
76 | `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${ | |
77 | OCPP16StandardParametersKey.SupportedFeatureProfiles | |
5edd8ba0 | 78 | } in configuration`, |
370ae4ee JB |
79 | ); |
80 | return false; | |
81 | } | |
82 | return true; | |
83 | } | |
84 | ||
78085c42 JB |
85 | public static buildMeterValue( |
86 | chargingStation: ChargingStation, | |
87 | connectorId: number, | |
88 | transactionId: number, | |
89 | interval: number, | |
5edd8ba0 | 90 | debug = false, |
78085c42 JB |
91 | ): OCPP16MeterValue { |
92 | const meterValue: OCPP16MeterValue = { | |
c38f0ced | 93 | timestamp: new Date(), |
78085c42 JB |
94 | sampledValue: [], |
95 | }; | |
96 | const connector = chargingStation.getConnectorStatus(connectorId); | |
97 | // SoC measurand | |
ed3d2808 | 98 | const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 99 | chargingStation, |
78085c42 | 100 | connectorId, |
5edd8ba0 | 101 | OCPP16MeterValueMeasurand.STATE_OF_CHARGE, |
78085c42 JB |
102 | ); |
103 | if (socSampledValueTemplate) { | |
860ef183 JB |
104 | const socMaximumValue = 100; |
105 | const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0; | |
5a47f72c | 106 | const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value) |
9bf0ef23 | 107 | ? getRandomFloatFluctuatedRounded( |
78085c42 | 108 | parseInt(socSampledValueTemplate.value), |
5edd8ba0 | 109 | socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 110 | ) |
9bf0ef23 | 111 | : getRandomInteger(socMaximumValue, socMinimumValue); |
78085c42 | 112 | meterValue.sampledValue.push( |
5edd8ba0 | 113 | OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue), |
78085c42 JB |
114 | ); |
115 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
860ef183 | 116 | if ( |
9bf0ef23 JB |
117 | convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue || |
118 | convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue || | |
860ef183 JB |
119 | debug |
120 | ) { | |
78085c42 JB |
121 | logger.error( |
122 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
123 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
124 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 125 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${ |
78085c42 | 126 | meterValue.sampledValue[sampledValuesIndex].value |
9ff486f4 | 127 | }/${socMaximumValue}`, |
78085c42 JB |
128 | ); |
129 | } | |
130 | } | |
131 | // Voltage measurand | |
ed3d2808 | 132 | const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 133 | chargingStation, |
78085c42 | 134 | connectorId, |
5edd8ba0 | 135 | OCPP16MeterValueMeasurand.VOLTAGE, |
78085c42 JB |
136 | ); |
137 | if (voltageSampledValueTemplate) { | |
5a47f72c | 138 | const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value) |
78085c42 | 139 | ? parseInt(voltageSampledValueTemplate.value) |
5398cecf | 140 | : chargingStation.stationInfo.voltageOut!; |
78085c42 JB |
141 | const fluctuationPercent = |
142 | voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 143 | const voltageMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 144 | voltageSampledValueTemplateValue, |
5edd8ba0 | 145 | fluctuationPercent, |
78085c42 JB |
146 | ); |
147 | if ( | |
148 | chargingStation.getNumberOfPhases() !== 3 || | |
5398cecf JB |
149 | (chargingStation.getNumberOfPhases() === 3 && |
150 | chargingStation.stationInfo?.mainVoltageMeterValues) | |
78085c42 JB |
151 | ) { |
152 | meterValue.sampledValue.push( | |
5edd8ba0 | 153 | OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue), |
78085c42 JB |
154 | ); |
155 | } | |
156 | for ( | |
157 | let phase = 1; | |
158 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
159 | phase++ | |
160 | ) { | |
161 | const phaseLineToNeutralValue = `L${phase}-N`; | |
162 | const voltagePhaseLineToNeutralSampledValueTemplate = | |
ed3d2808 | 163 | OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 164 | chargingStation, |
78085c42 JB |
165 | connectorId, |
166 | OCPP16MeterValueMeasurand.VOLTAGE, | |
5edd8ba0 | 167 | phaseLineToNeutralValue as OCPP16MeterValuePhase, |
78085c42 | 168 | ); |
e1d9a0f4 | 169 | let voltagePhaseLineToNeutralMeasurandValue: number | undefined; |
78085c42 | 170 | if (voltagePhaseLineToNeutralSampledValueTemplate) { |
5a47f72c JB |
171 | const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString( |
172 | voltagePhaseLineToNeutralSampledValueTemplate.value, | |
173 | ) | |
174 | ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) | |
175 | : chargingStation.stationInfo.voltageOut!; | |
78085c42 JB |
176 | const fluctuationPhaseToNeutralPercent = |
177 | voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? | |
178 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 179 | voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 180 | voltagePhaseLineToNeutralSampledValueTemplateValue, |
5edd8ba0 | 181 | fluctuationPhaseToNeutralPercent, |
78085c42 JB |
182 | ); |
183 | } | |
184 | meterValue.sampledValue.push( | |
185 | OCPP16ServiceUtils.buildSampledValue( | |
186 | voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate, | |
187 | voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue, | |
72092cfc | 188 | undefined, |
5edd8ba0 JB |
189 | phaseLineToNeutralValue as OCPP16MeterValuePhase, |
190 | ), | |
78085c42 | 191 | ); |
5398cecf | 192 | if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) { |
78085c42 JB |
193 | const phaseLineToLineValue = `L${phase}-L${ |
194 | (phase + 1) % chargingStation.getNumberOfPhases() !== 0 | |
195 | ? (phase + 1) % chargingStation.getNumberOfPhases() | |
196 | : chargingStation.getNumberOfPhases() | |
197 | }`; | |
4c149643 JB |
198 | const voltagePhaseLineToLineValueRounded = roundTo( |
199 | Math.sqrt(chargingStation.getNumberOfPhases()) * | |
200 | chargingStation.stationInfo.voltageOut!, | |
201 | 2, | |
202 | ); | |
78085c42 | 203 | const voltagePhaseLineToLineSampledValueTemplate = |
ed3d2808 | 204 | OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 205 | chargingStation, |
78085c42 JB |
206 | connectorId, |
207 | OCPP16MeterValueMeasurand.VOLTAGE, | |
5edd8ba0 | 208 | phaseLineToLineValue as OCPP16MeterValuePhase, |
78085c42 | 209 | ); |
e1d9a0f4 | 210 | let voltagePhaseLineToLineMeasurandValue: number | undefined; |
78085c42 | 211 | if (voltagePhaseLineToLineSampledValueTemplate) { |
5a47f72c JB |
212 | const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString( |
213 | voltagePhaseLineToLineSampledValueTemplate.value, | |
214 | ) | |
215 | ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) | |
4c149643 | 216 | : voltagePhaseLineToLineValueRounded; |
78085c42 JB |
217 | const fluctuationPhaseLineToLinePercent = |
218 | voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? | |
219 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 220 | voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 221 | voltagePhaseLineToLineSampledValueTemplateValue, |
5edd8ba0 | 222 | fluctuationPhaseLineToLinePercent, |
78085c42 JB |
223 | ); |
224 | } | |
9bf0ef23 | 225 | const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( |
4c149643 | 226 | voltagePhaseLineToLineValueRounded, |
5edd8ba0 | 227 | fluctuationPercent, |
78085c42 JB |
228 | ); |
229 | meterValue.sampledValue.push( | |
230 | OCPP16ServiceUtils.buildSampledValue( | |
231 | voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate, | |
232 | voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue, | |
72092cfc | 233 | undefined, |
5edd8ba0 JB |
234 | phaseLineToLineValue as OCPP16MeterValuePhase, |
235 | ), | |
78085c42 JB |
236 | ); |
237 | } | |
238 | } | |
239 | } | |
240 | // Power.Active.Import measurand | |
ed3d2808 | 241 | const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 242 | chargingStation, |
78085c42 | 243 | connectorId, |
5edd8ba0 | 244 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, |
78085c42 | 245 | ); |
abe9e9dd | 246 | let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; |
78085c42 JB |
247 | if (chargingStation.getNumberOfPhases() === 3) { |
248 | powerPerPhaseSampledValueTemplates = { | |
ed3d2808 | 249 | L1: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 250 | chargingStation, |
78085c42 JB |
251 | connectorId, |
252 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 253 | OCPP16MeterValuePhase.L1_N, |
78085c42 | 254 | ), |
ed3d2808 | 255 | L2: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 256 | chargingStation, |
78085c42 JB |
257 | connectorId, |
258 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 259 | OCPP16MeterValuePhase.L2_N, |
78085c42 | 260 | ), |
ed3d2808 | 261 | L3: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 262 | chargingStation, |
78085c42 JB |
263 | connectorId, |
264 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 265 | OCPP16MeterValuePhase.L3_N, |
78085c42 JB |
266 | ), |
267 | }; | |
268 | } | |
269 | if (powerSampledValueTemplate) { | |
270 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
271 | chargingStation, | |
e1d9a0f4 | 272 | powerSampledValueTemplate.measurand!, |
78085c42 | 273 | ); |
fc040c43 | 274 | const errMsg = `MeterValues measurand ${ |
78085c42 JB |
275 | powerSampledValueTemplate.measurand ?? |
276 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5398cecf | 277 | }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ |
2484ac1e | 278 | chargingStation.templateFile |
78085c42 JB |
279 | }, cannot calculate ${ |
280 | powerSampledValueTemplate.measurand ?? | |
281 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
282 | } measurand value`; | |
e1d9a0f4 | 283 | const powerMeasurandValues: MeasurandValues = {} as MeasurandValues; |
78085c42 | 284 | const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; |
1b6498ba JB |
285 | const connectorMaximumAvailablePower = |
286 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
287 | const connectorMaximumPower = Math.round(connectorMaximumAvailablePower); | |
ad8537a7 | 288 | const connectorMaximumPowerPerPhase = Math.round( |
5edd8ba0 | 289 | connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(), |
78085c42 | 290 | ); |
d71ce3fa | 291 | const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue ?? 0); |
860ef183 | 292 | const connectorMinimumPowerPerPhase = Math.round( |
5edd8ba0 | 293 | connectorMinimumPower / chargingStation.getNumberOfPhases(), |
860ef183 | 294 | ); |
5398cecf | 295 | switch (chargingStation.stationInfo?.currentOutType) { |
78085c42 JB |
296 | case CurrentType.AC: |
297 | if (chargingStation.getNumberOfPhases() === 3) { | |
969c488d JB |
298 | const defaultFluctuatedPowerPerPhase = isNotEmptyString(powerSampledValueTemplate.value) |
299 | ? getRandomFloatFluctuatedRounded( | |
300 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( | |
301 | powerSampledValueTemplate.value, | |
302 | connectorMaximumPower / unitDivider, | |
303 | connectorMinimumPower / unitDivider, | |
304 | { | |
305 | limitationEnabled: | |
306 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
307 | fallbackValue: connectorMinimumPower / unitDivider, | |
308 | }, | |
309 | ) / chargingStation.getNumberOfPhases(), | |
310 | powerSampledValueTemplate.fluctuationPercent ?? | |
311 | Constants.DEFAULT_FLUCTUATION_PERCENT, | |
312 | ) | |
313 | : undefined; | |
314 | const phase1FluctuatedValue = isNotEmptyString( | |
315 | powerPerPhaseSampledValueTemplates.L1?.value, | |
316 | ) | |
317 | ? getRandomFloatFluctuatedRounded( | |
318 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( | |
319 | powerPerPhaseSampledValueTemplates.L1?.value, | |
320 | connectorMaximumPowerPerPhase / unitDivider, | |
321 | connectorMinimumPowerPerPhase / unitDivider, | |
322 | { | |
323 | limitationEnabled: | |
324 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
325 | fallbackValue: connectorMinimumPowerPerPhase / unitDivider, | |
326 | }, | |
327 | ), | |
328 | powerPerPhaseSampledValueTemplates.L1?.fluctuationPercent ?? | |
329 | Constants.DEFAULT_FLUCTUATION_PERCENT, | |
330 | ) | |
331 | : undefined; | |
332 | const phase2FluctuatedValue = isNotEmptyString( | |
333 | powerPerPhaseSampledValueTemplates.L2?.value, | |
334 | ) | |
335 | ? getRandomFloatFluctuatedRounded( | |
336 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( | |
337 | powerPerPhaseSampledValueTemplates.L2?.value, | |
338 | connectorMaximumPowerPerPhase / unitDivider, | |
339 | connectorMinimumPowerPerPhase / unitDivider, | |
340 | { | |
341 | limitationEnabled: | |
342 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
343 | fallbackValue: connectorMinimumPowerPerPhase / unitDivider, | |
344 | }, | |
345 | ), | |
346 | powerPerPhaseSampledValueTemplates.L2?.fluctuationPercent ?? | |
347 | Constants.DEFAULT_FLUCTUATION_PERCENT, | |
348 | ) | |
349 | : undefined; | |
350 | const phase3FluctuatedValue = isNotEmptyString( | |
351 | powerPerPhaseSampledValueTemplates.L3?.value, | |
352 | ) | |
353 | ? getRandomFloatFluctuatedRounded( | |
354 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( | |
355 | powerPerPhaseSampledValueTemplates.L3?.value, | |
356 | connectorMaximumPowerPerPhase / unitDivider, | |
357 | connectorMinimumPowerPerPhase / unitDivider, | |
358 | { | |
359 | limitationEnabled: | |
360 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
361 | fallbackValue: connectorMinimumPowerPerPhase / unitDivider, | |
362 | }, | |
363 | ), | |
364 | powerPerPhaseSampledValueTemplates.L3?.fluctuationPercent ?? | |
365 | Constants.DEFAULT_FLUCTUATION_PERCENT, | |
366 | ) | |
367 | : undefined; | |
78085c42 | 368 | powerMeasurandValues.L1 = |
969c488d JB |
369 | phase1FluctuatedValue ?? |
370 | defaultFluctuatedPowerPerPhase ?? | |
9bf0ef23 | 371 | getRandomFloatRounded( |
860ef183 | 372 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 373 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 374 | ); |
78085c42 | 375 | powerMeasurandValues.L2 = |
969c488d JB |
376 | phase2FluctuatedValue ?? |
377 | defaultFluctuatedPowerPerPhase ?? | |
9bf0ef23 | 378 | getRandomFloatRounded( |
860ef183 | 379 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 380 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 381 | ); |
78085c42 | 382 | powerMeasurandValues.L3 = |
969c488d JB |
383 | phase3FluctuatedValue ?? |
384 | defaultFluctuatedPowerPerPhase ?? | |
9bf0ef23 | 385 | getRandomFloatRounded( |
860ef183 | 386 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 387 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 388 | ); |
78085c42 | 389 | } else { |
856e8f67 | 390 | powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value) |
9bf0ef23 | 391 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
392 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
393 | powerSampledValueTemplate.value, | |
394 | connectorMaximumPower / unitDivider, | |
d71ce3fa | 395 | connectorMinimumPower / unitDivider, |
5398cecf JB |
396 | { |
397 | limitationEnabled: | |
398 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 399 | fallbackValue: connectorMinimumPower / unitDivider, |
5398cecf | 400 | }, |
34464008 | 401 | ), |
78085c42 | 402 | powerSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 403 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 404 | ) |
9bf0ef23 | 405 | : getRandomFloatRounded( |
860ef183 | 406 | connectorMaximumPower / unitDivider, |
5edd8ba0 | 407 | connectorMinimumPower / unitDivider, |
860ef183 | 408 | ); |
78085c42 JB |
409 | powerMeasurandValues.L2 = 0; |
410 | powerMeasurandValues.L3 = 0; | |
411 | } | |
9bf0ef23 | 412 | powerMeasurandValues.allPhases = roundTo( |
78085c42 | 413 | powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, |
5edd8ba0 | 414 | 2, |
78085c42 JB |
415 | ); |
416 | break; | |
417 | case CurrentType.DC: | |
5a47f72c | 418 | powerMeasurandValues.allPhases = isNotEmptyString(powerSampledValueTemplate.value) |
9bf0ef23 | 419 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
420 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
421 | powerSampledValueTemplate.value, | |
422 | connectorMaximumPower / unitDivider, | |
d71ce3fa | 423 | connectorMinimumPower / unitDivider, |
5398cecf JB |
424 | { |
425 | limitationEnabled: | |
426 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 427 | fallbackValue: connectorMinimumPower / unitDivider, |
5398cecf | 428 | }, |
34464008 | 429 | ), |
78085c42 | 430 | powerSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 431 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 432 | ) |
9bf0ef23 | 433 | : getRandomFloatRounded( |
860ef183 | 434 | connectorMaximumPower / unitDivider, |
5edd8ba0 | 435 | connectorMinimumPower / unitDivider, |
860ef183 | 436 | ); |
78085c42 JB |
437 | break; |
438 | default: | |
fc040c43 | 439 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
78085c42 JB |
440 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
441 | } | |
442 | meterValue.sampledValue.push( | |
443 | OCPP16ServiceUtils.buildSampledValue( | |
444 | powerSampledValueTemplate, | |
5edd8ba0 JB |
445 | powerMeasurandValues.allPhases, |
446 | ), | |
78085c42 JB |
447 | ); |
448 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
9bf0ef23 JB |
449 | const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2); |
450 | const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2); | |
78085c42 | 451 | if ( |
9bf0ef23 | 452 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 453 | connectorMaximumPowerRounded || |
9bf0ef23 | 454 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < |
860ef183 | 455 | connectorMinimumPowerRounded || |
78085c42 JB |
456 | debug |
457 | ) { | |
458 | logger.error( | |
459 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
460 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
461 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 462 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${ |
78085c42 | 463 | meterValue.sampledValue[sampledValuesIndex].value |
5edd8ba0 | 464 | }/${connectorMaximumPowerRounded}`, |
78085c42 JB |
465 | ); |
466 | } | |
467 | for ( | |
468 | let phase = 1; | |
469 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
470 | phase++ | |
471 | ) { | |
472 | const phaseValue = `L${phase}-N`; | |
473 | meterValue.sampledValue.push( | |
474 | OCPP16ServiceUtils.buildSampledValue( | |
a37fc6dc JB |
475 | powerPerPhaseSampledValueTemplates[ |
476 | `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates | |
969c488d | 477 | ] ?? powerSampledValueTemplate, |
a37fc6dc | 478 | powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates], |
72092cfc | 479 | undefined, |
5edd8ba0 JB |
480 | phaseValue as OCPP16MeterValuePhase, |
481 | ), | |
78085c42 JB |
482 | ); |
483 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
9bf0ef23 | 484 | const connectorMaximumPowerPerPhaseRounded = roundTo( |
ad8537a7 | 485 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 486 | 2, |
ad8537a7 | 487 | ); |
9bf0ef23 | 488 | const connectorMinimumPowerPerPhaseRounded = roundTo( |
860ef183 | 489 | connectorMinimumPowerPerPhase / unitDivider, |
5edd8ba0 | 490 | 2, |
860ef183 | 491 | ); |
78085c42 | 492 | if ( |
9bf0ef23 | 493 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > |
ad8537a7 | 494 | connectorMaximumPowerPerPhaseRounded || |
9bf0ef23 | 495 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < |
860ef183 | 496 | connectorMinimumPowerPerPhaseRounded || |
78085c42 JB |
497 | debug |
498 | ) { | |
499 | logger.error( | |
500 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
501 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
502 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
503 | }: phase ${ | |
504 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
5edd8ba0 | 505 | }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${ |
78085c42 | 506 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value |
5edd8ba0 | 507 | }/${connectorMaximumPowerPerPhaseRounded}`, |
78085c42 JB |
508 | ); |
509 | } | |
510 | } | |
511 | } | |
512 | // Current.Import measurand | |
ed3d2808 | 513 | const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 514 | chargingStation, |
78085c42 | 515 | connectorId, |
5edd8ba0 | 516 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, |
78085c42 | 517 | ); |
abe9e9dd | 518 | let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; |
78085c42 JB |
519 | if (chargingStation.getNumberOfPhases() === 3) { |
520 | currentPerPhaseSampledValueTemplates = { | |
ed3d2808 | 521 | L1: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 522 | chargingStation, |
78085c42 JB |
523 | connectorId, |
524 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 525 | OCPP16MeterValuePhase.L1, |
78085c42 | 526 | ), |
ed3d2808 | 527 | L2: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 528 | chargingStation, |
78085c42 JB |
529 | connectorId, |
530 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 531 | OCPP16MeterValuePhase.L2, |
78085c42 | 532 | ), |
ed3d2808 | 533 | L3: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 534 | chargingStation, |
78085c42 JB |
535 | connectorId, |
536 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 537 | OCPP16MeterValuePhase.L3, |
78085c42 JB |
538 | ), |
539 | }; | |
540 | } | |
541 | if (currentSampledValueTemplate) { | |
542 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
543 | chargingStation, | |
e1d9a0f4 | 544 | currentSampledValueTemplate.measurand!, |
78085c42 | 545 | ); |
fc040c43 | 546 | const errMsg = `MeterValues measurand ${ |
78085c42 JB |
547 | currentSampledValueTemplate.measurand ?? |
548 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5398cecf | 549 | }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ |
2484ac1e | 550 | chargingStation.templateFile |
78085c42 JB |
551 | }, cannot calculate ${ |
552 | currentSampledValueTemplate.measurand ?? | |
553 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
554 | } measurand value`; | |
abe9e9dd | 555 | const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; |
1b6498ba JB |
556 | const connectorMaximumAvailablePower = |
557 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
860ef183 | 558 | const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0; |
ad8537a7 | 559 | let connectorMaximumAmperage: number; |
5398cecf | 560 | switch (chargingStation.stationInfo?.currentOutType) { |
78085c42 | 561 | case CurrentType.AC: |
ad8537a7 | 562 | connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( |
78085c42 | 563 | chargingStation.getNumberOfPhases(), |
1b6498ba | 564 | connectorMaximumAvailablePower, |
5398cecf | 565 | chargingStation.stationInfo.voltageOut!, |
78085c42 JB |
566 | ); |
567 | if (chargingStation.getNumberOfPhases() === 3) { | |
969c488d JB |
568 | const defaultFluctuatedAmperagePerPhase = isNotEmptyString( |
569 | currentSampledValueTemplate.value, | |
570 | ) | |
571 | ? getRandomFloatFluctuatedRounded( | |
572 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( | |
573 | currentSampledValueTemplate.value, | |
574 | connectorMaximumAmperage, | |
575 | connectorMinimumAmperage, | |
576 | { | |
577 | limitationEnabled: | |
578 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
579 | fallbackValue: connectorMinimumAmperage, | |
580 | }, | |
581 | ), | |
582 | currentSampledValueTemplate.fluctuationPercent ?? | |
583 | Constants.DEFAULT_FLUCTUATION_PERCENT, | |
584 | ) | |
585 | : undefined; | |
586 | const phase1FluctuatedValue = isNotEmptyString( | |
587 | currentPerPhaseSampledValueTemplates.L1?.value, | |
588 | ) | |
589 | ? getRandomFloatFluctuatedRounded( | |
590 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( | |
591 | currentPerPhaseSampledValueTemplates.L1?.value, | |
592 | connectorMaximumAmperage, | |
593 | connectorMinimumAmperage, | |
594 | { | |
595 | limitationEnabled: | |
596 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
597 | fallbackValue: connectorMinimumAmperage, | |
598 | }, | |
599 | ), | |
600 | currentPerPhaseSampledValueTemplates.L1?.fluctuationPercent ?? | |
601 | Constants.DEFAULT_FLUCTUATION_PERCENT, | |
602 | ) | |
603 | : undefined; | |
604 | const phase2FluctuatedValue = isNotEmptyString( | |
605 | currentPerPhaseSampledValueTemplates.L2?.value, | |
606 | ) | |
607 | ? getRandomFloatFluctuatedRounded( | |
608 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( | |
609 | currentPerPhaseSampledValueTemplates.L2?.value, | |
610 | connectorMaximumAmperage, | |
611 | connectorMinimumAmperage, | |
612 | { | |
613 | limitationEnabled: | |
614 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
615 | fallbackValue: connectorMinimumAmperage, | |
616 | }, | |
617 | ), | |
618 | currentPerPhaseSampledValueTemplates.L2?.fluctuationPercent ?? | |
619 | Constants.DEFAULT_FLUCTUATION_PERCENT, | |
620 | ) | |
621 | : undefined; | |
622 | const phase3FluctuatedValue = isNotEmptyString( | |
623 | currentPerPhaseSampledValueTemplates.L3?.value, | |
624 | ) | |
625 | ? getRandomFloatFluctuatedRounded( | |
626 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( | |
627 | currentPerPhaseSampledValueTemplates.L3?.value, | |
628 | connectorMaximumAmperage, | |
629 | connectorMinimumAmperage, | |
630 | { | |
631 | limitationEnabled: | |
632 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
633 | fallbackValue: connectorMinimumAmperage, | |
634 | }, | |
635 | ), | |
636 | currentPerPhaseSampledValueTemplates.L3?.fluctuationPercent ?? | |
637 | Constants.DEFAULT_FLUCTUATION_PERCENT, | |
638 | ) | |
639 | : undefined; | |
78085c42 | 640 | currentMeasurandValues.L1 = |
969c488d JB |
641 | phase1FluctuatedValue ?? |
642 | defaultFluctuatedAmperagePerPhase ?? | |
9bf0ef23 | 643 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 644 | currentMeasurandValues.L2 = |
969c488d JB |
645 | phase2FluctuatedValue ?? |
646 | defaultFluctuatedAmperagePerPhase ?? | |
9bf0ef23 | 647 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 648 | currentMeasurandValues.L3 = |
969c488d JB |
649 | phase3FluctuatedValue ?? |
650 | defaultFluctuatedAmperagePerPhase ?? | |
9bf0ef23 | 651 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 652 | } else { |
5a47f72c | 653 | currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value) |
9bf0ef23 | 654 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
655 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
656 | currentSampledValueTemplate.value, | |
657 | connectorMaximumAmperage, | |
d71ce3fa | 658 | connectorMinimumAmperage, |
5398cecf JB |
659 | { |
660 | limitationEnabled: | |
661 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 662 | fallbackValue: connectorMinimumAmperage, |
5398cecf | 663 | }, |
7bc31f9c | 664 | ), |
78085c42 | 665 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 666 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 667 | ) |
9bf0ef23 | 668 | : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
669 | currentMeasurandValues.L2 = 0; |
670 | currentMeasurandValues.L3 = 0; | |
671 | } | |
9bf0ef23 | 672 | currentMeasurandValues.allPhases = roundTo( |
78085c42 JB |
673 | (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / |
674 | chargingStation.getNumberOfPhases(), | |
5edd8ba0 | 675 | 2, |
78085c42 JB |
676 | ); |
677 | break; | |
678 | case CurrentType.DC: | |
ad8537a7 | 679 | connectorMaximumAmperage = DCElectricUtils.amperage( |
1b6498ba | 680 | connectorMaximumAvailablePower, |
5398cecf | 681 | chargingStation.stationInfo.voltageOut!, |
78085c42 | 682 | ); |
5a47f72c | 683 | currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value) |
9bf0ef23 | 684 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
685 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
686 | currentSampledValueTemplate.value, | |
687 | connectorMaximumAmperage, | |
d71ce3fa | 688 | connectorMinimumAmperage, |
5398cecf JB |
689 | { |
690 | limitationEnabled: | |
691 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 692 | fallbackValue: connectorMinimumAmperage, |
5398cecf | 693 | }, |
7bc31f9c | 694 | ), |
78085c42 | 695 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 696 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 697 | ) |
9bf0ef23 | 698 | : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
699 | break; |
700 | default: | |
fc040c43 | 701 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
78085c42 JB |
702 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
703 | } | |
704 | meterValue.sampledValue.push( | |
705 | OCPP16ServiceUtils.buildSampledValue( | |
706 | currentSampledValueTemplate, | |
5edd8ba0 JB |
707 | currentMeasurandValues.allPhases, |
708 | ), | |
78085c42 JB |
709 | ); |
710 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
711 | if ( | |
9bf0ef23 | 712 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 713 | connectorMaximumAmperage || |
9bf0ef23 | 714 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < |
860ef183 | 715 | connectorMinimumAmperage || |
78085c42 JB |
716 | debug |
717 | ) { | |
718 | logger.error( | |
719 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
720 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
721 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 722 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ |
78085c42 | 723 | meterValue.sampledValue[sampledValuesIndex].value |
5edd8ba0 | 724 | }/${connectorMaximumAmperage}`, |
78085c42 JB |
725 | ); |
726 | } | |
727 | for ( | |
728 | let phase = 1; | |
729 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
730 | phase++ | |
731 | ) { | |
732 | const phaseValue = `L${phase}`; | |
733 | meterValue.sampledValue.push( | |
734 | OCPP16ServiceUtils.buildSampledValue( | |
a37fc6dc JB |
735 | currentPerPhaseSampledValueTemplates[ |
736 | phaseValue as keyof MeasurandPerPhaseSampledValueTemplates | |
969c488d | 737 | ] ?? currentSampledValueTemplate, |
a37fc6dc | 738 | currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates], |
72092cfc | 739 | undefined, |
5edd8ba0 JB |
740 | phaseValue as OCPP16MeterValuePhase, |
741 | ), | |
78085c42 JB |
742 | ); |
743 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
744 | if ( | |
9bf0ef23 | 745 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > |
ad8537a7 | 746 | connectorMaximumAmperage || |
9bf0ef23 | 747 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < |
860ef183 | 748 | connectorMinimumAmperage || |
78085c42 JB |
749 | debug |
750 | ) { | |
751 | logger.error( | |
752 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
753 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
754 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
755 | }: phase ${ | |
756 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
5edd8ba0 | 757 | }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ |
78085c42 | 758 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value |
5edd8ba0 | 759 | }/${connectorMaximumAmperage}`, |
78085c42 JB |
760 | ); |
761 | } | |
762 | } | |
763 | } | |
764 | // Energy.Active.Import.Register measurand (default) | |
ed3d2808 | 765 | const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 766 | chargingStation, |
5edd8ba0 | 767 | connectorId, |
492cf6ab | 768 | ); |
78085c42 JB |
769 | if (energySampledValueTemplate) { |
770 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
771 | chargingStation, | |
e1d9a0f4 | 772 | energySampledValueTemplate.measurand!, |
78085c42 JB |
773 | ); |
774 | const unitDivider = | |
775 | energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
1b6498ba JB |
776 | const connectorMaximumAvailablePower = |
777 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
9bf0ef23 | 778 | const connectorMaximumEnergyRounded = roundTo( |
1b6498ba | 779 | (connectorMaximumAvailablePower * interval) / (3600 * 1000), |
5edd8ba0 | 780 | 2, |
78085c42 | 781 | ); |
d71ce3fa JB |
782 | const connectorMinimumEnergyRounded = roundTo( |
783 | energySampledValueTemplate.minimumValue ?? 0, | |
784 | 2, | |
785 | ); | |
77684af8 | 786 | const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value) |
c6dcc331 | 787 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
788 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
789 | energySampledValueTemplate.value, | |
790 | connectorMaximumEnergyRounded, | |
d71ce3fa | 791 | connectorMinimumEnergyRounded, |
7bc31f9c | 792 | { |
5398cecf | 793 | limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues, |
d71ce3fa | 794 | fallbackValue: connectorMinimumEnergyRounded, |
d624c9af | 795 | unitMultiplier: unitDivider, |
5edd8ba0 | 796 | }, |
34464008 | 797 | ), |
5edd8ba0 | 798 | energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 799 | ) |
d71ce3fa | 800 | : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded); |
78085c42 | 801 | // Persist previous value on connector |
e1d9a0f4 JB |
802 | if (connector) { |
803 | if ( | |
804 | isNullOrUndefined(connector.energyActiveImportRegisterValue) === false && | |
805 | connector.energyActiveImportRegisterValue! >= 0 && | |
806 | isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false && | |
807 | connector.transactionEnergyActiveImportRegisterValue! >= 0 | |
808 | ) { | |
809 | connector.energyActiveImportRegisterValue! += energyValueRounded; | |
810 | connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded; | |
811 | } else { | |
812 | connector.energyActiveImportRegisterValue = 0; | |
813 | connector.transactionEnergyActiveImportRegisterValue = 0; | |
814 | } | |
78085c42 JB |
815 | } |
816 | meterValue.sampledValue.push( | |
817 | OCPP16ServiceUtils.buildSampledValue( | |
818 | energySampledValueTemplate, | |
9bf0ef23 | 819 | roundTo( |
78085c42 JB |
820 | chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / |
821 | unitDivider, | |
5edd8ba0 JB |
822 | 2, |
823 | ), | |
824 | ), | |
78085c42 JB |
825 | ); |
826 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
d71ce3fa JB |
827 | if ( |
828 | energyValueRounded > connectorMaximumEnergyRounded || | |
829 | energyValueRounded < connectorMinimumEnergyRounded || | |
830 | debug | |
831 | ) { | |
78085c42 JB |
832 | logger.error( |
833 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
834 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
835 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
d71ce3fa | 836 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`, |
78085c42 JB |
837 | ); |
838 | } | |
839 | } | |
840 | return meterValue; | |
841 | } | |
842 | ||
e7aeea18 JB |
843 | public static buildTransactionBeginMeterValue( |
844 | chargingStation: ChargingStation, | |
845 | connectorId: number, | |
5edd8ba0 | 846 | meterStart: number, |
e7aeea18 | 847 | ): OCPP16MeterValue { |
fd0c36fa | 848 | const meterValue: OCPP16MeterValue = { |
c38f0ced | 849 | timestamp: new Date(), |
fd0c36fa JB |
850 | sampledValue: [], |
851 | }; | |
9ccca265 | 852 | // Energy.Active.Import.Register measurand (default) |
ed3d2808 | 853 | const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 854 | chargingStation, |
5edd8ba0 | 855 | connectorId, |
492cf6ab | 856 | ); |
9ccca265 | 857 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; |
e7aeea18 JB |
858 | meterValue.sampledValue.push( |
859 | OCPP16ServiceUtils.buildSampledValue( | |
e1d9a0f4 | 860 | sampledValueTemplate!, |
9bf0ef23 | 861 | roundTo((meterStart ?? 0) / unitDivider, 4), |
5edd8ba0 JB |
862 | MeterValueContext.TRANSACTION_BEGIN, |
863 | ), | |
e7aeea18 | 864 | ); |
fd0c36fa JB |
865 | return meterValue; |
866 | } | |
867 | ||
e7aeea18 JB |
868 | public static buildTransactionEndMeterValue( |
869 | chargingStation: ChargingStation, | |
870 | connectorId: number, | |
5edd8ba0 | 871 | meterStop: number, |
e7aeea18 | 872 | ): OCPP16MeterValue { |
fd0c36fa | 873 | const meterValue: OCPP16MeterValue = { |
c38f0ced | 874 | timestamp: new Date(), |
fd0c36fa JB |
875 | sampledValue: [], |
876 | }; | |
9ccca265 | 877 | // Energy.Active.Import.Register measurand (default) |
ed3d2808 | 878 | const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 879 | chargingStation, |
5edd8ba0 | 880 | connectorId, |
492cf6ab | 881 | ); |
9ccca265 | 882 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; |
e7aeea18 JB |
883 | meterValue.sampledValue.push( |
884 | OCPP16ServiceUtils.buildSampledValue( | |
e1d9a0f4 | 885 | sampledValueTemplate!, |
9bf0ef23 | 886 | roundTo((meterStop ?? 0) / unitDivider, 4), |
5edd8ba0 JB |
887 | MeterValueContext.TRANSACTION_END, |
888 | ), | |
e7aeea18 | 889 | ); |
fd0c36fa JB |
890 | return meterValue; |
891 | } | |
892 | ||
e7aeea18 JB |
893 | public static buildTransactionDataMeterValues( |
894 | transactionBeginMeterValue: OCPP16MeterValue, | |
5edd8ba0 | 895 | transactionEndMeterValue: OCPP16MeterValue, |
e7aeea18 | 896 | ): OCPP16MeterValue[] { |
fd0c36fa JB |
897 | const meterValues: OCPP16MeterValue[] = []; |
898 | meterValues.push(transactionBeginMeterValue); | |
899 | meterValues.push(transactionEndMeterValue); | |
900 | return meterValues; | |
901 | } | |
7bc31f9c | 902 | |
d19b10a8 JB |
903 | public static remoteStopTransaction = async ( |
904 | chargingStation: ChargingStation, | |
905 | connectorId: number, | |
906 | ): Promise<GenericResponse> => { | |
907 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( | |
908 | chargingStation, | |
909 | connectorId, | |
910 | OCPP16ChargePointStatus.Finishing, | |
911 | ); | |
912 | const stopResponse = await chargingStation.stopTransactionOnConnector( | |
913 | connectorId, | |
914 | OCPP16StopTransactionReason.REMOTE, | |
915 | ); | |
916 | if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { | |
917 | return OCPP16Constants.OCPP_RESPONSE_ACCEPTED; | |
918 | } | |
919 | return OCPP16Constants.OCPP_RESPONSE_REJECTED; | |
920 | }; | |
921 | ||
366f75f6 JB |
922 | public static changeAvailability = async ( |
923 | chargingStation: ChargingStation, | |
225e32b0 | 924 | connectorIds: number[], |
366f75f6 JB |
925 | chargePointStatus: OCPP16ChargePointStatus, |
926 | availabilityType: OCPP16AvailabilityType, | |
927 | ): Promise<OCPP16ChangeAvailabilityResponse> => { | |
225e32b0 JB |
928 | const responses: OCPP16ChangeAvailabilityResponse[] = []; |
929 | for (const connectorId of connectorIds) { | |
930 | let response: OCPP16ChangeAvailabilityResponse = | |
931 | OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED; | |
932 | const connectorStatus = chargingStation.getConnectorStatus(connectorId)!; | |
933 | if (connectorStatus?.transactionStarted === true) { | |
934 | response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED; | |
935 | } | |
936 | connectorStatus.availability = availabilityType; | |
937 | if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) { | |
938 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( | |
939 | chargingStation, | |
940 | connectorId, | |
941 | chargePointStatus, | |
942 | ); | |
943 | } | |
944 | responses.push(response); | |
366f75f6 | 945 | } |
3b0ed034 | 946 | if (responses.includes(OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED)) { |
225e32b0 | 947 | return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED; |
366f75f6 | 948 | } |
225e32b0 | 949 | return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED; |
366f75f6 JB |
950 | }; |
951 | ||
ed3d2808 JB |
952 | public static setChargingProfile( |
953 | chargingStation: ChargingStation, | |
954 | connectorId: number, | |
5edd8ba0 | 955 | cp: OCPP16ChargingProfile, |
ed3d2808 | 956 | ): void { |
9bf0ef23 | 957 | if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) { |
ed3d2808 | 958 | logger.error( |
5edd8ba0 | 959 | `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`, |
ed3d2808 | 960 | ); |
e1d9a0f4 | 961 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []; |
ed3d2808 | 962 | } |
72092cfc JB |
963 | if ( |
964 | Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false | |
965 | ) { | |
ed3d2808 | 966 | logger.error( |
bbb55ee4 | 967 | `${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`, |
ed3d2808 | 968 | ); |
e1d9a0f4 | 969 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []; |
ed3d2808 JB |
970 | } |
971 | let cpReplaced = false; | |
9bf0ef23 | 972 | if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) { |
ed3d2808 JB |
973 | chargingStation |
974 | .getConnectorStatus(connectorId) | |
72092cfc | 975 | ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { |
ed3d2808 JB |
976 | if ( |
977 | chargingProfile.chargingProfileId === cp.chargingProfileId || | |
978 | (chargingProfile.stackLevel === cp.stackLevel && | |
979 | chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose) | |
980 | ) { | |
e1d9a0f4 | 981 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp; |
ed3d2808 JB |
982 | cpReplaced = true; |
983 | } | |
984 | }); | |
985 | } | |
72092cfc | 986 | !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp); |
ed3d2808 JB |
987 | } |
988 | ||
73d87be1 JB |
989 | public static clearChargingProfiles = ( |
990 | chargingStation: ChargingStation, | |
991 | commandPayload: ClearChargingProfileRequest, | |
992 | chargingProfiles: OCPP16ChargingProfile[] | undefined, | |
993 | ): boolean => { | |
0d1f33ba | 994 | const { id, chargingProfilePurpose, stackLevel } = commandPayload; |
73d87be1 JB |
995 | let clearedCP = false; |
996 | if (isNotEmptyArray(chargingProfiles)) { | |
997 | chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { | |
998 | let clearCurrentCP = false; | |
0d1f33ba | 999 | if (chargingProfile.chargingProfileId === id) { |
73d87be1 JB |
1000 | clearCurrentCP = true; |
1001 | } | |
0d1f33ba | 1002 | if (!chargingProfilePurpose && chargingProfile.stackLevel === stackLevel) { |
73d87be1 JB |
1003 | clearCurrentCP = true; |
1004 | } | |
0d1f33ba | 1005 | if (!stackLevel && chargingProfile.chargingProfilePurpose === chargingProfilePurpose) { |
73d87be1 JB |
1006 | clearCurrentCP = true; |
1007 | } | |
1008 | if ( | |
0d1f33ba JB |
1009 | chargingProfile.stackLevel === stackLevel && |
1010 | chargingProfile.chargingProfilePurpose === chargingProfilePurpose | |
73d87be1 JB |
1011 | ) { |
1012 | clearCurrentCP = true; | |
1013 | } | |
1014 | if (clearCurrentCP) { | |
1015 | chargingProfiles.splice(index, 1); | |
1016 | logger.debug( | |
1017 | `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`, | |
1018 | chargingProfile, | |
1019 | ); | |
1020 | clearedCP = true; | |
1021 | } | |
1022 | }); | |
1023 | } | |
1024 | return clearedCP; | |
1025 | }; | |
1026 | ||
ef9e3b33 | 1027 | public static composeChargingSchedules = ( |
4abf6441 JB |
1028 | chargingScheduleHigher: OCPP16ChargingSchedule | undefined, |
1029 | chargingScheduleLower: OCPP16ChargingSchedule | undefined, | |
d632062f | 1030 | compositeInterval: Interval, |
ef9e3b33 | 1031 | ): OCPP16ChargingSchedule | undefined => { |
4abf6441 | 1032 | if (!chargingScheduleHigher && !chargingScheduleLower) { |
ef9e3b33 JB |
1033 | return undefined; |
1034 | } | |
4abf6441 | 1035 | if (chargingScheduleHigher && !chargingScheduleLower) { |
d632062f | 1036 | return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval); |
ef9e3b33 | 1037 | } |
4abf6441 | 1038 | if (!chargingScheduleHigher && chargingScheduleLower) { |
d632062f | 1039 | return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval); |
ef9e3b33 | 1040 | } |
4abf6441 | 1041 | const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined = |
d632062f | 1042 | OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval); |
4abf6441 | 1043 | const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined = |
d632062f | 1044 | OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval); |
4abf6441 JB |
1045 | const compositeChargingScheduleHigherInterval: Interval = { |
1046 | start: compositeChargingScheduleHigher!.startSchedule!, | |
ef9e3b33 | 1047 | end: addSeconds( |
4abf6441 JB |
1048 | compositeChargingScheduleHigher!.startSchedule!, |
1049 | compositeChargingScheduleHigher!.duration!, | |
ef9e3b33 JB |
1050 | ), |
1051 | }; | |
4abf6441 JB |
1052 | const compositeChargingScheduleLowerInterval: Interval = { |
1053 | start: compositeChargingScheduleLower!.startSchedule!, | |
ef9e3b33 | 1054 | end: addSeconds( |
4abf6441 JB |
1055 | compositeChargingScheduleLower!.startSchedule!, |
1056 | compositeChargingScheduleLower!.duration!, | |
ef9e3b33 JB |
1057 | ), |
1058 | }; | |
4abf6441 JB |
1059 | const higherFirst = isBefore( |
1060 | compositeChargingScheduleHigherInterval.start, | |
1061 | compositeChargingScheduleLowerInterval.start, | |
1062 | ); | |
ef9e3b33 JB |
1063 | if ( |
1064 | !areIntervalsOverlapping( | |
4abf6441 JB |
1065 | compositeChargingScheduleHigherInterval, |
1066 | compositeChargingScheduleLowerInterval, | |
ef9e3b33 JB |
1067 | ) |
1068 | ) { | |
1069 | return { | |
4abf6441 JB |
1070 | ...compositeChargingScheduleLower, |
1071 | ...compositeChargingScheduleHigher!, | |
1072 | startSchedule: higherFirst | |
1073 | ? (compositeChargingScheduleHigherInterval.start as Date) | |
1074 | : (compositeChargingScheduleLowerInterval.start as Date), | |
1075 | duration: higherFirst | |
1076 | ? differenceInSeconds( | |
1077 | compositeChargingScheduleLowerInterval.end, | |
1078 | compositeChargingScheduleHigherInterval.start, | |
1079 | ) | |
1080 | : differenceInSeconds( | |
1081 | compositeChargingScheduleHigherInterval.end, | |
1082 | compositeChargingScheduleLowerInterval.start, | |
1083 | ), | |
1084 | chargingSchedulePeriod: [ | |
1085 | ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { | |
1086 | return { | |
1087 | ...schedulePeriod, | |
1088 | startPeriod: higherFirst | |
1089 | ? 0 | |
1090 | : schedulePeriod.startPeriod + | |
1091 | differenceInSeconds( | |
1092 | compositeChargingScheduleHigherInterval.start, | |
1093 | compositeChargingScheduleLowerInterval.start, | |
1094 | ), | |
1095 | }; | |
1096 | }), | |
1097 | ...compositeChargingScheduleLower!.chargingSchedulePeriod.map((schedulePeriod) => { | |
1098 | return { | |
1099 | ...schedulePeriod, | |
1100 | startPeriod: higherFirst | |
1101 | ? schedulePeriod.startPeriod + | |
1102 | differenceInSeconds( | |
1103 | compositeChargingScheduleLowerInterval.start, | |
1104 | compositeChargingScheduleHigherInterval.start, | |
1105 | ) | |
1106 | : 0, | |
1107 | }; | |
1108 | }), | |
1109 | ].sort((a, b) => a.startPeriod - b.startPeriod), | |
ef9e3b33 JB |
1110 | }; |
1111 | } | |
4abf6441 JB |
1112 | return { |
1113 | ...compositeChargingScheduleLower, | |
1114 | ...compositeChargingScheduleHigher!, | |
1115 | startSchedule: higherFirst | |
1116 | ? (compositeChargingScheduleHigherInterval.start as Date) | |
1117 | : (compositeChargingScheduleLowerInterval.start as Date), | |
1118 | duration: higherFirst | |
1119 | ? differenceInSeconds( | |
1120 | compositeChargingScheduleLowerInterval.end, | |
1121 | compositeChargingScheduleHigherInterval.start, | |
1122 | ) | |
1123 | : differenceInSeconds( | |
1124 | compositeChargingScheduleHigherInterval.end, | |
1125 | compositeChargingScheduleLowerInterval.start, | |
1126 | ), | |
1127 | chargingSchedulePeriod: [ | |
1128 | ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { | |
1129 | return { | |
1130 | ...schedulePeriod, | |
1131 | startPeriod: higherFirst | |
1132 | ? 0 | |
1133 | : schedulePeriod.startPeriod + | |
1134 | differenceInSeconds( | |
1135 | compositeChargingScheduleHigherInterval.start, | |
1136 | compositeChargingScheduleLowerInterval.start, | |
1137 | ), | |
1138 | }; | |
1139 | }), | |
1140 | ...compositeChargingScheduleLower!.chargingSchedulePeriod | |
c4ab56ba | 1141 | .filter((schedulePeriod, index) => { |
4abf6441 JB |
1142 | if ( |
1143 | higherFirst && | |
1144 | isWithinInterval( | |
1145 | addSeconds( | |
1146 | compositeChargingScheduleLowerInterval.start, | |
1147 | schedulePeriod.startPeriod, | |
1148 | ), | |
1149 | { | |
1150 | start: compositeChargingScheduleLowerInterval.start, | |
1151 | end: compositeChargingScheduleHigherInterval.end, | |
1152 | }, | |
1153 | ) | |
1154 | ) { | |
1155 | return false; | |
1156 | } | |
c4ab56ba JB |
1157 | if ( |
1158 | higherFirst && | |
1159 | index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 && | |
1160 | !isWithinInterval( | |
1161 | addSeconds( | |
1162 | compositeChargingScheduleLowerInterval.start, | |
1163 | schedulePeriod.startPeriod, | |
1164 | ), | |
1165 | { | |
1166 | start: compositeChargingScheduleLowerInterval.start, | |
1167 | end: compositeChargingScheduleHigherInterval.end, | |
1168 | }, | |
1169 | ) && | |
1170 | isWithinInterval( | |
1171 | addSeconds( | |
1172 | compositeChargingScheduleLowerInterval.start, | |
1173 | compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod, | |
1174 | ), | |
1175 | { | |
1176 | start: compositeChargingScheduleLowerInterval.start, | |
1177 | end: compositeChargingScheduleHigherInterval.end, | |
1178 | }, | |
1179 | ) | |
1180 | ) { | |
c4ab56ba JB |
1181 | return false; |
1182 | } | |
4abf6441 JB |
1183 | if ( |
1184 | !higherFirst && | |
1185 | isWithinInterval( | |
1186 | addSeconds( | |
1187 | compositeChargingScheduleLowerInterval.start, | |
1188 | schedulePeriod.startPeriod, | |
1189 | ), | |
1190 | { | |
1191 | start: compositeChargingScheduleHigherInterval.start, | |
1192 | end: compositeChargingScheduleLowerInterval.end, | |
1193 | }, | |
1194 | ) | |
1195 | ) { | |
1196 | return false; | |
1197 | } | |
1198 | return true; | |
1199 | }) | |
0e14e1d4 JB |
1200 | .map((schedulePeriod, index) => { |
1201 | if (index === 0 && schedulePeriod.startPeriod !== 0) { | |
1202 | schedulePeriod.startPeriod = 0; | |
1203 | } | |
4abf6441 JB |
1204 | return { |
1205 | ...schedulePeriod, | |
1206 | startPeriod: higherFirst | |
1207 | ? schedulePeriod.startPeriod + | |
1208 | differenceInSeconds( | |
1209 | compositeChargingScheduleLowerInterval.start, | |
1210 | compositeChargingScheduleHigherInterval.start, | |
1211 | ) | |
1212 | : 0, | |
1213 | }; | |
1214 | }), | |
1215 | ].sort((a, b) => a.startPeriod - b.startPeriod), | |
1216 | }; | |
ef9e3b33 JB |
1217 | }; |
1218 | ||
90aceaf6 JB |
1219 | public static hasReservation = ( |
1220 | chargingStation: ChargingStation, | |
1221 | connectorId: number, | |
1222 | idTag: string, | |
1223 | ): boolean => { | |
1224 | const connectorReservation = chargingStation.getReservationBy('connectorId', connectorId); | |
1225 | const chargingStationReservation = chargingStation.getReservationBy('connectorId', 0); | |
1226 | if ( | |
1227 | (chargingStation.getConnectorStatus(connectorId)?.status === | |
1228 | OCPP16ChargePointStatus.Reserved && | |
1229 | connectorReservation && | |
56563a3c | 1230 | !hasReservationExpired(connectorReservation) && |
90aceaf6 | 1231 | // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing |
56563a3c | 1232 | connectorReservation?.idTag === idTag) || |
90aceaf6 JB |
1233 | (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved && |
1234 | chargingStationReservation && | |
56563a3c JB |
1235 | !hasReservationExpired(chargingStationReservation) && |
1236 | chargingStationReservation?.idTag === idTag) | |
90aceaf6 | 1237 | ) { |
88499f52 JB |
1238 | logger.debug( |
1239 | `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`, | |
1240 | connectorReservation ?? chargingStationReservation, | |
1241 | ); | |
56563a3c | 1242 | return true; |
90aceaf6 | 1243 | } |
56563a3c | 1244 | return false; |
90aceaf6 JB |
1245 | }; |
1246 | ||
1b271a54 JB |
1247 | public static parseJsonSchemaFile<T extends JsonType>( |
1248 | relativePath: string, | |
1249 | moduleName?: string, | |
5edd8ba0 | 1250 | methodName?: string, |
1b271a54 | 1251 | ): JSONSchemaType<T> { |
7164966d | 1252 | return super.parseJsonSchemaFile<T>( |
51022aa0 | 1253 | relativePath, |
1b271a54 JB |
1254 | OCPPVersion.VERSION_16, |
1255 | moduleName, | |
5edd8ba0 | 1256 | methodName, |
7164966d | 1257 | ); |
130783a7 JB |
1258 | } |
1259 | ||
ef9e3b33 JB |
1260 | private static composeChargingSchedule = ( |
1261 | chargingSchedule: OCPP16ChargingSchedule, | |
d632062f | 1262 | compositeInterval: Interval, |
ef9e3b33 JB |
1263 | ): OCPP16ChargingSchedule | undefined => { |
1264 | const chargingScheduleInterval: Interval = { | |
1265 | start: chargingSchedule.startSchedule!, | |
1266 | end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), | |
1267 | }; | |
d632062f | 1268 | if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) { |
ef9e3b33 | 1269 | chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod); |
d632062f | 1270 | if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) { |
ef9e3b33 JB |
1271 | return { |
1272 | ...chargingSchedule, | |
d632062f JB |
1273 | startSchedule: compositeInterval.start as Date, |
1274 | duration: differenceInSeconds( | |
1275 | chargingScheduleInterval.end, | |
1276 | compositeInterval.start as Date, | |
1277 | ), | |
0e14e1d4 JB |
1278 | chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod |
1279 | .filter((schedulePeriod, index) => { | |
ef9e3b33 JB |
1280 | if ( |
1281 | isWithinInterval( | |
1282 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!, | |
d632062f | 1283 | compositeInterval, |
ef9e3b33 JB |
1284 | ) |
1285 | ) { | |
1286 | return true; | |
1287 | } | |
1288 | if ( | |
1289 | index < chargingSchedule.chargingSchedulePeriod.length - 1 && | |
1290 | !isWithinInterval( | |
1291 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod), | |
d632062f | 1292 | compositeInterval, |
ef9e3b33 JB |
1293 | ) && |
1294 | isWithinInterval( | |
1295 | addSeconds( | |
1296 | chargingScheduleInterval.start, | |
1297 | chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod, | |
1298 | ), | |
d632062f | 1299 | compositeInterval, |
ef9e3b33 JB |
1300 | ) |
1301 | ) { | |
ef9e3b33 JB |
1302 | return true; |
1303 | } | |
1304 | return false; | |
0e14e1d4 JB |
1305 | }) |
1306 | .map((schedulePeriod, index) => { | |
1307 | if (index === 0 && schedulePeriod.startPeriod !== 0) { | |
1308 | schedulePeriod.startPeriod = 0; | |
1309 | } | |
1310 | return schedulePeriod; | |
1311 | }), | |
ef9e3b33 JB |
1312 | }; |
1313 | } | |
d632062f | 1314 | if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) { |
ef9e3b33 JB |
1315 | return { |
1316 | ...chargingSchedule, | |
d632062f JB |
1317 | duration: differenceInSeconds( |
1318 | compositeInterval.end as Date, | |
1319 | chargingScheduleInterval.start, | |
1320 | ), | |
ef9e3b33 JB |
1321 | chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter((schedulePeriod) => |
1322 | isWithinInterval( | |
1323 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!, | |
d632062f | 1324 | compositeInterval, |
ef9e3b33 JB |
1325 | ), |
1326 | ), | |
1327 | }; | |
1328 | } | |
1329 | return chargingSchedule; | |
1330 | } | |
1331 | }; | |
1332 | ||
7bc31f9c JB |
1333 | private static buildSampledValue( |
1334 | sampledValueTemplate: SampledValueTemplate, | |
1335 | value: number, | |
1336 | context?: MeterValueContext, | |
5edd8ba0 | 1337 | phase?: OCPP16MeterValuePhase, |
7bc31f9c | 1338 | ): OCPP16SampledValue { |
4ed03b6e | 1339 | const sampledValueContext = context ?? sampledValueTemplate?.context; |
7bc31f9c JB |
1340 | const sampledValueLocation = |
1341 | sampledValueTemplate?.location ?? | |
e1d9a0f4 | 1342 | OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!); |
4ed03b6e | 1343 | const sampledValuePhase = phase ?? sampledValueTemplate?.phase; |
7bc31f9c | 1344 | return { |
9bf0ef23 | 1345 | ...(!isNullOrUndefined(sampledValueTemplate.unit) && { |
7bc31f9c JB |
1346 | unit: sampledValueTemplate.unit, |
1347 | }), | |
9bf0ef23 JB |
1348 | ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), |
1349 | ...(!isNullOrUndefined(sampledValueTemplate.measurand) && { | |
7bc31f9c JB |
1350 | measurand: sampledValueTemplate.measurand, |
1351 | }), | |
9bf0ef23 | 1352 | ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), |
1b2cddac | 1353 | ...(!isNullOrUndefined(value) && { value: value.toString() }), |
9bf0ef23 | 1354 | ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), |
e1d9a0f4 | 1355 | } as OCPP16SampledValue; |
7bc31f9c JB |
1356 | } |
1357 | ||
1358 | private static checkMeasurandPowerDivider( | |
1359 | chargingStation: ChargingStation, | |
5edd8ba0 | 1360 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c | 1361 | ): void { |
9bf0ef23 | 1362 | if (isUndefined(chargingStation.powerDivider)) { |
fc040c43 | 1363 | const errMsg = `MeterValues measurand ${ |
7bc31f9c JB |
1364 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER |
1365 | }: powerDivider is undefined`; | |
fc040c43 | 1366 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
7bc31f9c | 1367 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
fa7bccf4 | 1368 | } else if (chargingStation?.powerDivider <= 0) { |
fc040c43 | 1369 | const errMsg = `MeterValues measurand ${ |
7bc31f9c | 1370 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER |
fa7bccf4 | 1371 | }: powerDivider have zero or below value ${chargingStation.powerDivider}`; |
fc040c43 | 1372 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
7bc31f9c JB |
1373 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
1374 | } | |
1375 | } | |
1376 | ||
1377 | private static getMeasurandDefaultLocation( | |
5edd8ba0 | 1378 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c JB |
1379 | ): MeterValueLocation | undefined { |
1380 | switch (measurandType) { | |
1381 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
1382 | return MeterValueLocation.EV; | |
1383 | } | |
1384 | } | |
1385 | ||
3b0ed034 JB |
1386 | // private static getMeasurandDefaultUnit( |
1387 | // measurandType: OCPP16MeterValueMeasurand, | |
1388 | // ): MeterValueUnit | undefined { | |
1389 | // switch (measurandType) { | |
1390 | // case OCPP16MeterValueMeasurand.CURRENT_EXPORT: | |
1391 | // case OCPP16MeterValueMeasurand.CURRENT_IMPORT: | |
1392 | // case OCPP16MeterValueMeasurand.CURRENT_OFFERED: | |
1393 | // return MeterValueUnit.AMP; | |
1394 | // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: | |
1395 | // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: | |
1396 | // return MeterValueUnit.WATT_HOUR; | |
1397 | // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: | |
1398 | // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: | |
1399 | // case OCPP16MeterValueMeasurand.POWER_OFFERED: | |
1400 | // return MeterValueUnit.WATT; | |
1401 | // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
1402 | // return MeterValueUnit.PERCENT; | |
1403 | // case OCPP16MeterValueMeasurand.VOLTAGE: | |
1404 | // return MeterValueUnit.VOLT; | |
1405 | // } | |
1406 | // } | |
6ed92bc1 | 1407 | } |