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