Commit | Line | Data |
---|---|---|
edd13439 | 1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
c8eeb62b | 2 | |
130783a7 JB |
3 | import type { JSONSchemaType } from 'ajv'; |
4 | ||
ae725be3 | 5 | import { type ChargingStation, hasFeatureProfile } from '../../../charging-station'; |
268a74bb | 6 | import { OCPPError } from '../../../exception'; |
e7aeea18 | 7 | import { |
73d87be1 | 8 | type ClearChargingProfileRequest, |
268a74bb JB |
9 | CurrentType, |
10 | ErrorType, | |
11 | type JsonType, | |
12 | type MeasurandPerPhaseSampledValueTemplates, | |
13 | type MeasurandValues, | |
e7aeea18 JB |
14 | MeterValueContext, |
15 | MeterValueLocation, | |
16 | MeterValueUnit, | |
268a74bb JB |
17 | type OCPP16ChargingProfile, |
18 | type OCPP16IncomingRequestCommand, | |
27782dbc | 19 | type OCPP16MeterValue, |
e7aeea18 JB |
20 | OCPP16MeterValueMeasurand, |
21 | OCPP16MeterValuePhase, | |
370ae4ee | 22 | OCPP16RequestCommand, |
268a74bb JB |
23 | type OCPP16SampledValue, |
24 | OCPP16StandardParametersKey, | |
25 | type OCPP16SupportedFeatureProfiles, | |
26 | OCPPVersion, | |
27 | type SampledValueTemplate, | |
28 | Voltage, | |
29 | } from '../../../types'; | |
9bf0ef23 JB |
30 | import { |
31 | ACElectricUtils, | |
32 | Constants, | |
33 | DCElectricUtils, | |
34 | convertToFloat, | |
35 | convertToInt, | |
36 | getRandomFloatFluctuatedRounded, | |
37 | getRandomFloatRounded, | |
38 | getRandomInteger, | |
39 | isNotEmptyArray, | |
9bf0ef23 JB |
40 | isNullOrUndefined, |
41 | isUndefined, | |
42 | logger, | |
43 | roundTo, | |
44 | } from '../../../utils'; | |
4c3c0d59 | 45 | import { OCPPServiceUtils } from '../OCPPServiceUtils'; |
6ed92bc1 | 46 | |
7bc31f9c | 47 | export class OCPP16ServiceUtils extends OCPPServiceUtils { |
370ae4ee JB |
48 | public static checkFeatureProfile( |
49 | chargingStation: ChargingStation, | |
50 | featureProfile: OCPP16SupportedFeatureProfiles, | |
5edd8ba0 | 51 | command: OCPP16RequestCommand | OCPP16IncomingRequestCommand, |
370ae4ee | 52 | ): boolean { |
d8093be1 | 53 | if (!hasFeatureProfile(chargingStation, featureProfile)) { |
370ae4ee JB |
54 | logger.warn( |
55 | `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${ | |
56 | OCPP16StandardParametersKey.SupportedFeatureProfiles | |
5edd8ba0 | 57 | } in configuration`, |
370ae4ee JB |
58 | ); |
59 | return false; | |
60 | } | |
61 | return true; | |
62 | } | |
63 | ||
78085c42 JB |
64 | public static buildMeterValue( |
65 | chargingStation: ChargingStation, | |
66 | connectorId: number, | |
67 | transactionId: number, | |
68 | interval: number, | |
5edd8ba0 | 69 | debug = false, |
78085c42 JB |
70 | ): OCPP16MeterValue { |
71 | const meterValue: OCPP16MeterValue = { | |
c38f0ced | 72 | timestamp: new Date(), |
78085c42 JB |
73 | sampledValue: [], |
74 | }; | |
75 | const connector = chargingStation.getConnectorStatus(connectorId); | |
76 | // SoC measurand | |
ed3d2808 | 77 | const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 78 | chargingStation, |
78085c42 | 79 | connectorId, |
5edd8ba0 | 80 | OCPP16MeterValueMeasurand.STATE_OF_CHARGE, |
78085c42 JB |
81 | ); |
82 | if (socSampledValueTemplate) { | |
860ef183 JB |
83 | const socMaximumValue = 100; |
84 | const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0; | |
78085c42 | 85 | const socSampledValueTemplateValue = socSampledValueTemplate.value |
9bf0ef23 | 86 | ? getRandomFloatFluctuatedRounded( |
78085c42 | 87 | parseInt(socSampledValueTemplate.value), |
5edd8ba0 | 88 | socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 89 | ) |
9bf0ef23 | 90 | : getRandomInteger(socMaximumValue, socMinimumValue); |
78085c42 | 91 | meterValue.sampledValue.push( |
5edd8ba0 | 92 | OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue), |
78085c42 JB |
93 | ); |
94 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
860ef183 | 95 | if ( |
9bf0ef23 JB |
96 | convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue || |
97 | convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue || | |
860ef183 JB |
98 | debug |
99 | ) { | |
78085c42 JB |
100 | logger.error( |
101 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
102 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
103 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 104 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${ |
78085c42 | 105 | meterValue.sampledValue[sampledValuesIndex].value |
5edd8ba0 | 106 | }/${socMaximumValue}}`, |
78085c42 JB |
107 | ); |
108 | } | |
109 | } | |
110 | // Voltage measurand | |
ed3d2808 | 111 | const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 112 | chargingStation, |
78085c42 | 113 | connectorId, |
5edd8ba0 | 114 | OCPP16MeterValueMeasurand.VOLTAGE, |
78085c42 JB |
115 | ); |
116 | if (voltageSampledValueTemplate) { | |
117 | const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value | |
118 | ? parseInt(voltageSampledValueTemplate.value) | |
119 | : chargingStation.getVoltageOut(); | |
120 | const fluctuationPercent = | |
121 | voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 122 | const voltageMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 123 | voltageSampledValueTemplateValue, |
5edd8ba0 | 124 | fluctuationPercent, |
78085c42 JB |
125 | ); |
126 | if ( | |
127 | chargingStation.getNumberOfPhases() !== 3 || | |
128 | (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues()) | |
129 | ) { | |
130 | meterValue.sampledValue.push( | |
5edd8ba0 | 131 | OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue), |
78085c42 JB |
132 | ); |
133 | } | |
134 | for ( | |
135 | let phase = 1; | |
136 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
137 | phase++ | |
138 | ) { | |
139 | const phaseLineToNeutralValue = `L${phase}-N`; | |
140 | const voltagePhaseLineToNeutralSampledValueTemplate = | |
ed3d2808 | 141 | OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 142 | chargingStation, |
78085c42 JB |
143 | connectorId, |
144 | OCPP16MeterValueMeasurand.VOLTAGE, | |
5edd8ba0 | 145 | phaseLineToNeutralValue as OCPP16MeterValuePhase, |
78085c42 | 146 | ); |
e1d9a0f4 | 147 | let voltagePhaseLineToNeutralMeasurandValue: number | undefined; |
78085c42 JB |
148 | if (voltagePhaseLineToNeutralSampledValueTemplate) { |
149 | const voltagePhaseLineToNeutralSampledValueTemplateValue = | |
150 | voltagePhaseLineToNeutralSampledValueTemplate.value | |
151 | ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) | |
152 | : chargingStation.getVoltageOut(); | |
153 | const fluctuationPhaseToNeutralPercent = | |
154 | voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? | |
155 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 156 | voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 157 | voltagePhaseLineToNeutralSampledValueTemplateValue, |
5edd8ba0 | 158 | fluctuationPhaseToNeutralPercent, |
78085c42 JB |
159 | ); |
160 | } | |
161 | meterValue.sampledValue.push( | |
162 | OCPP16ServiceUtils.buildSampledValue( | |
163 | voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate, | |
164 | voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue, | |
72092cfc | 165 | undefined, |
5edd8ba0 JB |
166 | phaseLineToNeutralValue as OCPP16MeterValuePhase, |
167 | ), | |
78085c42 JB |
168 | ); |
169 | if (chargingStation.getPhaseLineToLineVoltageMeterValues()) { | |
170 | const phaseLineToLineValue = `L${phase}-L${ | |
171 | (phase + 1) % chargingStation.getNumberOfPhases() !== 0 | |
172 | ? (phase + 1) % chargingStation.getNumberOfPhases() | |
173 | : chargingStation.getNumberOfPhases() | |
174 | }`; | |
175 | const voltagePhaseLineToLineSampledValueTemplate = | |
ed3d2808 | 176 | OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 177 | chargingStation, |
78085c42 JB |
178 | connectorId, |
179 | OCPP16MeterValueMeasurand.VOLTAGE, | |
5edd8ba0 | 180 | phaseLineToLineValue as OCPP16MeterValuePhase, |
78085c42 | 181 | ); |
e1d9a0f4 | 182 | let voltagePhaseLineToLineMeasurandValue: number | undefined; |
78085c42 JB |
183 | if (voltagePhaseLineToLineSampledValueTemplate) { |
184 | const voltagePhaseLineToLineSampledValueTemplateValue = | |
185 | voltagePhaseLineToLineSampledValueTemplate.value | |
186 | ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) | |
187 | : Voltage.VOLTAGE_400; | |
188 | const fluctuationPhaseLineToLinePercent = | |
189 | voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? | |
190 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 191 | voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 192 | voltagePhaseLineToLineSampledValueTemplateValue, |
5edd8ba0 | 193 | fluctuationPhaseLineToLinePercent, |
78085c42 JB |
194 | ); |
195 | } | |
9bf0ef23 | 196 | const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 197 | Voltage.VOLTAGE_400, |
5edd8ba0 | 198 | fluctuationPercent, |
78085c42 JB |
199 | ); |
200 | meterValue.sampledValue.push( | |
201 | OCPP16ServiceUtils.buildSampledValue( | |
202 | voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate, | |
203 | voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue, | |
72092cfc | 204 | undefined, |
5edd8ba0 JB |
205 | phaseLineToLineValue as OCPP16MeterValuePhase, |
206 | ), | |
78085c42 JB |
207 | ); |
208 | } | |
209 | } | |
210 | } | |
211 | // Power.Active.Import measurand | |
ed3d2808 | 212 | const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 213 | chargingStation, |
78085c42 | 214 | connectorId, |
5edd8ba0 | 215 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, |
78085c42 | 216 | ); |
abe9e9dd | 217 | let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; |
78085c42 JB |
218 | if (chargingStation.getNumberOfPhases() === 3) { |
219 | powerPerPhaseSampledValueTemplates = { | |
ed3d2808 | 220 | L1: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 221 | chargingStation, |
78085c42 JB |
222 | connectorId, |
223 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 224 | OCPP16MeterValuePhase.L1_N, |
78085c42 | 225 | ), |
ed3d2808 | 226 | L2: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 227 | chargingStation, |
78085c42 JB |
228 | connectorId, |
229 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 230 | OCPP16MeterValuePhase.L2_N, |
78085c42 | 231 | ), |
ed3d2808 | 232 | L3: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 233 | chargingStation, |
78085c42 JB |
234 | connectorId, |
235 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 236 | OCPP16MeterValuePhase.L3_N, |
78085c42 JB |
237 | ), |
238 | }; | |
239 | } | |
240 | if (powerSampledValueTemplate) { | |
241 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
242 | chargingStation, | |
e1d9a0f4 | 243 | powerSampledValueTemplate.measurand!, |
78085c42 | 244 | ); |
fc040c43 | 245 | const errMsg = `MeterValues measurand ${ |
78085c42 JB |
246 | powerSampledValueTemplate.measurand ?? |
247 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
248 | }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ | |
2484ac1e | 249 | chargingStation.templateFile |
78085c42 JB |
250 | }, cannot calculate ${ |
251 | powerSampledValueTemplate.measurand ?? | |
252 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
253 | } measurand value`; | |
e1d9a0f4 | 254 | const powerMeasurandValues: MeasurandValues = {} as MeasurandValues; |
78085c42 | 255 | const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; |
1b6498ba JB |
256 | const connectorMaximumAvailablePower = |
257 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
258 | const connectorMaximumPower = Math.round(connectorMaximumAvailablePower); | |
ad8537a7 | 259 | const connectorMaximumPowerPerPhase = Math.round( |
5edd8ba0 | 260 | connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(), |
78085c42 | 261 | ); |
e1d9a0f4 | 262 | const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue!) ?? 0; |
860ef183 | 263 | const connectorMinimumPowerPerPhase = Math.round( |
5edd8ba0 | 264 | connectorMinimumPower / chargingStation.getNumberOfPhases(), |
860ef183 | 265 | ); |
78085c42 JB |
266 | switch (chargingStation.getCurrentOutType()) { |
267 | case CurrentType.AC: | |
268 | if (chargingStation.getNumberOfPhases() === 3) { | |
269 | const defaultFluctuatedPowerPerPhase = | |
270 | powerSampledValueTemplate.value && | |
9bf0ef23 | 271 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
272 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
273 | powerSampledValueTemplate.value, | |
274 | connectorMaximumPower / unitDivider, | |
5edd8ba0 | 275 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 276 | ) / chargingStation.getNumberOfPhases(), |
78085c42 | 277 | powerSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 278 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
279 | ); |
280 | const phase1FluctuatedValue = | |
e1d9a0f4 | 281 | powerPerPhaseSampledValueTemplates.L1?.value && |
9bf0ef23 | 282 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
283 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
284 | powerPerPhaseSampledValueTemplates.L1.value, | |
285 | connectorMaximumPowerPerPhase / unitDivider, | |
5edd8ba0 | 286 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 287 | ), |
78085c42 | 288 | powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? |
5edd8ba0 | 289 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
290 | ); |
291 | const phase2FluctuatedValue = | |
e1d9a0f4 | 292 | powerPerPhaseSampledValueTemplates.L2?.value && |
9bf0ef23 | 293 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
294 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
295 | powerPerPhaseSampledValueTemplates.L2.value, | |
296 | connectorMaximumPowerPerPhase / unitDivider, | |
5edd8ba0 | 297 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 298 | ), |
78085c42 | 299 | powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? |
5edd8ba0 | 300 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
301 | ); |
302 | const phase3FluctuatedValue = | |
e1d9a0f4 | 303 | powerPerPhaseSampledValueTemplates.L3?.value && |
9bf0ef23 | 304 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
305 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
306 | powerPerPhaseSampledValueTemplates.L3.value, | |
307 | connectorMaximumPowerPerPhase / unitDivider, | |
5edd8ba0 | 308 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 309 | ), |
78085c42 | 310 | powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? |
5edd8ba0 | 311 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
312 | ); |
313 | powerMeasurandValues.L1 = | |
e1d9a0f4 JB |
314 | (phase1FluctuatedValue as number) ?? |
315 | (defaultFluctuatedPowerPerPhase as number) ?? | |
9bf0ef23 | 316 | getRandomFloatRounded( |
860ef183 | 317 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 318 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 319 | ); |
78085c42 | 320 | powerMeasurandValues.L2 = |
e1d9a0f4 JB |
321 | (phase2FluctuatedValue as number) ?? |
322 | (defaultFluctuatedPowerPerPhase as number) ?? | |
9bf0ef23 | 323 | getRandomFloatRounded( |
860ef183 | 324 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 325 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 326 | ); |
78085c42 | 327 | powerMeasurandValues.L3 = |
e1d9a0f4 JB |
328 | (phase3FluctuatedValue as number) ?? |
329 | (defaultFluctuatedPowerPerPhase as number) ?? | |
9bf0ef23 | 330 | getRandomFloatRounded( |
860ef183 | 331 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 332 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 333 | ); |
78085c42 JB |
334 | } else { |
335 | powerMeasurandValues.L1 = powerSampledValueTemplate.value | |
9bf0ef23 | 336 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
337 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
338 | powerSampledValueTemplate.value, | |
339 | connectorMaximumPower / unitDivider, | |
5edd8ba0 | 340 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 341 | ), |
78085c42 | 342 | powerSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 343 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 344 | ) |
9bf0ef23 | 345 | : getRandomFloatRounded( |
860ef183 | 346 | connectorMaximumPower / unitDivider, |
5edd8ba0 | 347 | connectorMinimumPower / unitDivider, |
860ef183 | 348 | ); |
78085c42 JB |
349 | powerMeasurandValues.L2 = 0; |
350 | powerMeasurandValues.L3 = 0; | |
351 | } | |
9bf0ef23 | 352 | powerMeasurandValues.allPhases = roundTo( |
78085c42 | 353 | powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, |
5edd8ba0 | 354 | 2, |
78085c42 JB |
355 | ); |
356 | break; | |
357 | case CurrentType.DC: | |
358 | powerMeasurandValues.allPhases = powerSampledValueTemplate.value | |
9bf0ef23 | 359 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
360 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
361 | powerSampledValueTemplate.value, | |
362 | connectorMaximumPower / unitDivider, | |
5edd8ba0 | 363 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 364 | ), |
78085c42 | 365 | powerSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 366 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 367 | ) |
9bf0ef23 | 368 | : getRandomFloatRounded( |
860ef183 | 369 | connectorMaximumPower / unitDivider, |
5edd8ba0 | 370 | connectorMinimumPower / unitDivider, |
860ef183 | 371 | ); |
78085c42 JB |
372 | break; |
373 | default: | |
fc040c43 | 374 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
78085c42 JB |
375 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
376 | } | |
377 | meterValue.sampledValue.push( | |
378 | OCPP16ServiceUtils.buildSampledValue( | |
379 | powerSampledValueTemplate, | |
5edd8ba0 JB |
380 | powerMeasurandValues.allPhases, |
381 | ), | |
78085c42 JB |
382 | ); |
383 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
9bf0ef23 JB |
384 | const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2); |
385 | const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2); | |
78085c42 | 386 | if ( |
9bf0ef23 | 387 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 388 | connectorMaximumPowerRounded || |
9bf0ef23 | 389 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < |
860ef183 | 390 | connectorMinimumPowerRounded || |
78085c42 JB |
391 | debug |
392 | ) { | |
393 | logger.error( | |
394 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
395 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
396 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 397 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${ |
78085c42 | 398 | meterValue.sampledValue[sampledValuesIndex].value |
5edd8ba0 | 399 | }/${connectorMaximumPowerRounded}`, |
78085c42 JB |
400 | ); |
401 | } | |
402 | for ( | |
403 | let phase = 1; | |
404 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
405 | phase++ | |
406 | ) { | |
407 | const phaseValue = `L${phase}-N`; | |
408 | meterValue.sampledValue.push( | |
409 | OCPP16ServiceUtils.buildSampledValue( | |
a37fc6dc JB |
410 | powerPerPhaseSampledValueTemplates[ |
411 | `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates | |
412 | ]! ?? powerSampledValueTemplate, | |
413 | powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates], | |
72092cfc | 414 | undefined, |
5edd8ba0 JB |
415 | phaseValue as OCPP16MeterValuePhase, |
416 | ), | |
78085c42 JB |
417 | ); |
418 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
9bf0ef23 | 419 | const connectorMaximumPowerPerPhaseRounded = roundTo( |
ad8537a7 | 420 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 421 | 2, |
ad8537a7 | 422 | ); |
9bf0ef23 | 423 | const connectorMinimumPowerPerPhaseRounded = roundTo( |
860ef183 | 424 | connectorMinimumPowerPerPhase / unitDivider, |
5edd8ba0 | 425 | 2, |
860ef183 | 426 | ); |
78085c42 | 427 | if ( |
9bf0ef23 | 428 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > |
ad8537a7 | 429 | connectorMaximumPowerPerPhaseRounded || |
9bf0ef23 | 430 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < |
860ef183 | 431 | connectorMinimumPowerPerPhaseRounded || |
78085c42 JB |
432 | debug |
433 | ) { | |
434 | logger.error( | |
435 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
436 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
437 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
438 | }: phase ${ | |
439 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
5edd8ba0 | 440 | }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${ |
78085c42 | 441 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value |
5edd8ba0 | 442 | }/${connectorMaximumPowerPerPhaseRounded}`, |
78085c42 JB |
443 | ); |
444 | } | |
445 | } | |
446 | } | |
447 | // Current.Import measurand | |
ed3d2808 | 448 | const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 449 | chargingStation, |
78085c42 | 450 | connectorId, |
5edd8ba0 | 451 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, |
78085c42 | 452 | ); |
abe9e9dd | 453 | let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; |
78085c42 JB |
454 | if (chargingStation.getNumberOfPhases() === 3) { |
455 | currentPerPhaseSampledValueTemplates = { | |
ed3d2808 | 456 | L1: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 457 | chargingStation, |
78085c42 JB |
458 | connectorId, |
459 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 460 | OCPP16MeterValuePhase.L1, |
78085c42 | 461 | ), |
ed3d2808 | 462 | L2: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 463 | chargingStation, |
78085c42 JB |
464 | connectorId, |
465 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 466 | OCPP16MeterValuePhase.L2, |
78085c42 | 467 | ), |
ed3d2808 | 468 | L3: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 469 | chargingStation, |
78085c42 JB |
470 | connectorId, |
471 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 472 | OCPP16MeterValuePhase.L3, |
78085c42 JB |
473 | ), |
474 | }; | |
475 | } | |
476 | if (currentSampledValueTemplate) { | |
477 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
478 | chargingStation, | |
e1d9a0f4 | 479 | currentSampledValueTemplate.measurand!, |
78085c42 | 480 | ); |
fc040c43 | 481 | const errMsg = `MeterValues measurand ${ |
78085c42 JB |
482 | currentSampledValueTemplate.measurand ?? |
483 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
484 | }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ | |
2484ac1e | 485 | chargingStation.templateFile |
78085c42 JB |
486 | }, cannot calculate ${ |
487 | currentSampledValueTemplate.measurand ?? | |
488 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
489 | } measurand value`; | |
abe9e9dd | 490 | const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; |
1b6498ba JB |
491 | const connectorMaximumAvailablePower = |
492 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
860ef183 | 493 | const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0; |
ad8537a7 | 494 | let connectorMaximumAmperage: number; |
78085c42 JB |
495 | switch (chargingStation.getCurrentOutType()) { |
496 | case CurrentType.AC: | |
ad8537a7 | 497 | connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( |
78085c42 | 498 | chargingStation.getNumberOfPhases(), |
1b6498ba | 499 | connectorMaximumAvailablePower, |
5edd8ba0 | 500 | chargingStation.getVoltageOut(), |
78085c42 JB |
501 | ); |
502 | if (chargingStation.getNumberOfPhases() === 3) { | |
503 | const defaultFluctuatedAmperagePerPhase = | |
504 | currentSampledValueTemplate.value && | |
9bf0ef23 | 505 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
506 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
507 | currentSampledValueTemplate.value, | |
508 | connectorMaximumAmperage, | |
5edd8ba0 | 509 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
7bc31f9c | 510 | ), |
78085c42 | 511 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 512 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
513 | ); |
514 | const phase1FluctuatedValue = | |
e1d9a0f4 | 515 | currentPerPhaseSampledValueTemplates.L1?.value && |
9bf0ef23 | 516 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
517 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
518 | currentPerPhaseSampledValueTemplates.L1.value, | |
519 | connectorMaximumAmperage, | |
5edd8ba0 | 520 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 521 | ), |
78085c42 | 522 | currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? |
5edd8ba0 | 523 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
524 | ); |
525 | const phase2FluctuatedValue = | |
e1d9a0f4 | 526 | currentPerPhaseSampledValueTemplates.L2?.value && |
9bf0ef23 | 527 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
528 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
529 | currentPerPhaseSampledValueTemplates.L2.value, | |
530 | connectorMaximumAmperage, | |
5edd8ba0 | 531 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 532 | ), |
78085c42 | 533 | currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? |
5edd8ba0 | 534 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
535 | ); |
536 | const phase3FluctuatedValue = | |
e1d9a0f4 | 537 | currentPerPhaseSampledValueTemplates.L3?.value && |
9bf0ef23 | 538 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
539 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
540 | currentPerPhaseSampledValueTemplates.L3.value, | |
541 | connectorMaximumAmperage, | |
5edd8ba0 | 542 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 543 | ), |
78085c42 | 544 | currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? |
5edd8ba0 | 545 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
546 | ); |
547 | currentMeasurandValues.L1 = | |
e1d9a0f4 JB |
548 | (phase1FluctuatedValue as number) ?? |
549 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 550 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 551 | currentMeasurandValues.L2 = |
e1d9a0f4 JB |
552 | (phase2FluctuatedValue as number) ?? |
553 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 554 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 555 | currentMeasurandValues.L3 = |
e1d9a0f4 JB |
556 | (phase3FluctuatedValue as number) ?? |
557 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 558 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
559 | } else { |
560 | currentMeasurandValues.L1 = currentSampledValueTemplate.value | |
9bf0ef23 | 561 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
562 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
563 | currentSampledValueTemplate.value, | |
564 | connectorMaximumAmperage, | |
5edd8ba0 | 565 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
7bc31f9c | 566 | ), |
78085c42 | 567 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 568 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 569 | ) |
9bf0ef23 | 570 | : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
571 | currentMeasurandValues.L2 = 0; |
572 | currentMeasurandValues.L3 = 0; | |
573 | } | |
9bf0ef23 | 574 | currentMeasurandValues.allPhases = roundTo( |
78085c42 JB |
575 | (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / |
576 | chargingStation.getNumberOfPhases(), | |
5edd8ba0 | 577 | 2, |
78085c42 JB |
578 | ); |
579 | break; | |
580 | case CurrentType.DC: | |
ad8537a7 | 581 | connectorMaximumAmperage = DCElectricUtils.amperage( |
1b6498ba | 582 | connectorMaximumAvailablePower, |
5edd8ba0 | 583 | chargingStation.getVoltageOut(), |
78085c42 JB |
584 | ); |
585 | currentMeasurandValues.allPhases = currentSampledValueTemplate.value | |
9bf0ef23 | 586 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
587 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
588 | currentSampledValueTemplate.value, | |
589 | connectorMaximumAmperage, | |
5edd8ba0 | 590 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
7bc31f9c | 591 | ), |
78085c42 | 592 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 593 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 594 | ) |
9bf0ef23 | 595 | : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
596 | break; |
597 | default: | |
fc040c43 | 598 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
78085c42 JB |
599 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
600 | } | |
601 | meterValue.sampledValue.push( | |
602 | OCPP16ServiceUtils.buildSampledValue( | |
603 | currentSampledValueTemplate, | |
5edd8ba0 JB |
604 | currentMeasurandValues.allPhases, |
605 | ), | |
78085c42 JB |
606 | ); |
607 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
608 | if ( | |
9bf0ef23 | 609 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 610 | connectorMaximumAmperage || |
9bf0ef23 | 611 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < |
860ef183 | 612 | connectorMinimumAmperage || |
78085c42 JB |
613 | debug |
614 | ) { | |
615 | logger.error( | |
616 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
617 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
618 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 619 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ |
78085c42 | 620 | meterValue.sampledValue[sampledValuesIndex].value |
5edd8ba0 | 621 | }/${connectorMaximumAmperage}`, |
78085c42 JB |
622 | ); |
623 | } | |
624 | for ( | |
625 | let phase = 1; | |
626 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
627 | phase++ | |
628 | ) { | |
629 | const phaseValue = `L${phase}`; | |
630 | meterValue.sampledValue.push( | |
631 | OCPP16ServiceUtils.buildSampledValue( | |
a37fc6dc JB |
632 | currentPerPhaseSampledValueTemplates[ |
633 | phaseValue as keyof MeasurandPerPhaseSampledValueTemplates | |
634 | ]! ?? currentSampledValueTemplate, | |
635 | currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates], | |
72092cfc | 636 | undefined, |
5edd8ba0 JB |
637 | phaseValue as OCPP16MeterValuePhase, |
638 | ), | |
78085c42 JB |
639 | ); |
640 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
641 | if ( | |
9bf0ef23 | 642 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > |
ad8537a7 | 643 | connectorMaximumAmperage || |
9bf0ef23 | 644 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < |
860ef183 | 645 | connectorMinimumAmperage || |
78085c42 JB |
646 | debug |
647 | ) { | |
648 | logger.error( | |
649 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
650 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
651 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
652 | }: phase ${ | |
653 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
5edd8ba0 | 654 | }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ |
78085c42 | 655 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value |
5edd8ba0 | 656 | }/${connectorMaximumAmperage}`, |
78085c42 JB |
657 | ); |
658 | } | |
659 | } | |
660 | } | |
661 | // Energy.Active.Import.Register measurand (default) | |
ed3d2808 | 662 | const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 663 | chargingStation, |
5edd8ba0 | 664 | connectorId, |
492cf6ab | 665 | ); |
78085c42 JB |
666 | if (energySampledValueTemplate) { |
667 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
668 | chargingStation, | |
e1d9a0f4 | 669 | energySampledValueTemplate.measurand!, |
78085c42 JB |
670 | ); |
671 | const unitDivider = | |
672 | energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
1b6498ba JB |
673 | const connectorMaximumAvailablePower = |
674 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
9bf0ef23 | 675 | const connectorMaximumEnergyRounded = roundTo( |
1b6498ba | 676 | (connectorMaximumAvailablePower * interval) / (3600 * 1000), |
5edd8ba0 | 677 | 2, |
78085c42 JB |
678 | ); |
679 | const energyValueRounded = energySampledValueTemplate.value | |
680 | ? // Cumulate the fluctuated value around the static one | |
9bf0ef23 | 681 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
682 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
683 | energySampledValueTemplate.value, | |
684 | connectorMaximumEnergyRounded, | |
685 | { | |
686 | limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(), | |
687 | unitMultiplier: unitDivider, | |
5edd8ba0 | 688 | }, |
34464008 | 689 | ), |
5edd8ba0 | 690 | energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 691 | ) |
9bf0ef23 | 692 | : getRandomFloatRounded(connectorMaximumEnergyRounded); |
78085c42 | 693 | // Persist previous value on connector |
e1d9a0f4 JB |
694 | if (connector) { |
695 | if ( | |
696 | isNullOrUndefined(connector.energyActiveImportRegisterValue) === false && | |
697 | connector.energyActiveImportRegisterValue! >= 0 && | |
698 | isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false && | |
699 | connector.transactionEnergyActiveImportRegisterValue! >= 0 | |
700 | ) { | |
701 | connector.energyActiveImportRegisterValue! += energyValueRounded; | |
702 | connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded; | |
703 | } else { | |
704 | connector.energyActiveImportRegisterValue = 0; | |
705 | connector.transactionEnergyActiveImportRegisterValue = 0; | |
706 | } | |
78085c42 JB |
707 | } |
708 | meterValue.sampledValue.push( | |
709 | OCPP16ServiceUtils.buildSampledValue( | |
710 | energySampledValueTemplate, | |
9bf0ef23 | 711 | roundTo( |
78085c42 JB |
712 | chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / |
713 | unitDivider, | |
5edd8ba0 JB |
714 | 2, |
715 | ), | |
716 | ), | |
78085c42 JB |
717 | ); |
718 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
ad8537a7 | 719 | if (energyValueRounded > connectorMaximumEnergyRounded || debug) { |
78085c42 JB |
720 | logger.error( |
721 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
722 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
723 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
04c32a95 | 724 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`, |
78085c42 JB |
725 | ); |
726 | } | |
727 | } | |
728 | return meterValue; | |
729 | } | |
730 | ||
e7aeea18 JB |
731 | public static buildTransactionBeginMeterValue( |
732 | chargingStation: ChargingStation, | |
733 | connectorId: number, | |
5edd8ba0 | 734 | meterStart: number, |
e7aeea18 | 735 | ): OCPP16MeterValue { |
fd0c36fa | 736 | const meterValue: OCPP16MeterValue = { |
c38f0ced | 737 | timestamp: new Date(), |
fd0c36fa JB |
738 | sampledValue: [], |
739 | }; | |
9ccca265 | 740 | // Energy.Active.Import.Register measurand (default) |
ed3d2808 | 741 | const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 742 | chargingStation, |
5edd8ba0 | 743 | connectorId, |
492cf6ab | 744 | ); |
9ccca265 | 745 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; |
e7aeea18 JB |
746 | meterValue.sampledValue.push( |
747 | OCPP16ServiceUtils.buildSampledValue( | |
e1d9a0f4 | 748 | sampledValueTemplate!, |
9bf0ef23 | 749 | roundTo((meterStart ?? 0) / unitDivider, 4), |
5edd8ba0 JB |
750 | MeterValueContext.TRANSACTION_BEGIN, |
751 | ), | |
e7aeea18 | 752 | ); |
fd0c36fa JB |
753 | return meterValue; |
754 | } | |
755 | ||
e7aeea18 JB |
756 | public static buildTransactionEndMeterValue( |
757 | chargingStation: ChargingStation, | |
758 | connectorId: number, | |
5edd8ba0 | 759 | meterStop: number, |
e7aeea18 | 760 | ): OCPP16MeterValue { |
fd0c36fa | 761 | const meterValue: OCPP16MeterValue = { |
c38f0ced | 762 | timestamp: new Date(), |
fd0c36fa JB |
763 | sampledValue: [], |
764 | }; | |
9ccca265 | 765 | // Energy.Active.Import.Register measurand (default) |
ed3d2808 | 766 | const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 767 | chargingStation, |
5edd8ba0 | 768 | connectorId, |
492cf6ab | 769 | ); |
9ccca265 | 770 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; |
e7aeea18 JB |
771 | meterValue.sampledValue.push( |
772 | OCPP16ServiceUtils.buildSampledValue( | |
e1d9a0f4 | 773 | sampledValueTemplate!, |
9bf0ef23 | 774 | roundTo((meterStop ?? 0) / unitDivider, 4), |
5edd8ba0 JB |
775 | MeterValueContext.TRANSACTION_END, |
776 | ), | |
e7aeea18 | 777 | ); |
fd0c36fa JB |
778 | return meterValue; |
779 | } | |
780 | ||
e7aeea18 JB |
781 | public static buildTransactionDataMeterValues( |
782 | transactionBeginMeterValue: OCPP16MeterValue, | |
5edd8ba0 | 783 | transactionEndMeterValue: OCPP16MeterValue, |
e7aeea18 | 784 | ): OCPP16MeterValue[] { |
fd0c36fa JB |
785 | const meterValues: OCPP16MeterValue[] = []; |
786 | meterValues.push(transactionBeginMeterValue); | |
787 | meterValues.push(transactionEndMeterValue); | |
788 | return meterValues; | |
789 | } | |
7bc31f9c | 790 | |
ed3d2808 JB |
791 | public static setChargingProfile( |
792 | chargingStation: ChargingStation, | |
793 | connectorId: number, | |
5edd8ba0 | 794 | cp: OCPP16ChargingProfile, |
ed3d2808 | 795 | ): void { |
9bf0ef23 | 796 | if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) { |
ed3d2808 | 797 | logger.error( |
5edd8ba0 | 798 | `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`, |
ed3d2808 | 799 | ); |
e1d9a0f4 | 800 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []; |
ed3d2808 | 801 | } |
72092cfc JB |
802 | if ( |
803 | Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false | |
804 | ) { | |
ed3d2808 | 805 | logger.error( |
5edd8ba0 | 806 | `${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 initialization`, |
ed3d2808 | 807 | ); |
e1d9a0f4 | 808 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []; |
ed3d2808 JB |
809 | } |
810 | let cpReplaced = false; | |
9bf0ef23 | 811 | if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) { |
ed3d2808 JB |
812 | chargingStation |
813 | .getConnectorStatus(connectorId) | |
72092cfc | 814 | ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { |
ed3d2808 JB |
815 | if ( |
816 | chargingProfile.chargingProfileId === cp.chargingProfileId || | |
817 | (chargingProfile.stackLevel === cp.stackLevel && | |
818 | chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose) | |
819 | ) { | |
e1d9a0f4 | 820 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp; |
ed3d2808 JB |
821 | cpReplaced = true; |
822 | } | |
823 | }); | |
824 | } | |
72092cfc | 825 | !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp); |
ed3d2808 JB |
826 | } |
827 | ||
73d87be1 JB |
828 | public static clearChargingProfiles = ( |
829 | chargingStation: ChargingStation, | |
830 | commandPayload: ClearChargingProfileRequest, | |
831 | chargingProfiles: OCPP16ChargingProfile[] | undefined, | |
832 | ): boolean => { | |
833 | let clearedCP = false; | |
834 | if (isNotEmptyArray(chargingProfiles)) { | |
835 | chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { | |
836 | let clearCurrentCP = false; | |
837 | if (chargingProfile.chargingProfileId === commandPayload.id) { | |
838 | clearCurrentCP = true; | |
839 | } | |
840 | if ( | |
841 | !commandPayload.chargingProfilePurpose && | |
842 | chargingProfile.stackLevel === commandPayload.stackLevel | |
843 | ) { | |
844 | clearCurrentCP = true; | |
845 | } | |
846 | if ( | |
847 | !chargingProfile.stackLevel && | |
848 | chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose | |
849 | ) { | |
850 | clearCurrentCP = true; | |
851 | } | |
852 | if ( | |
853 | chargingProfile.stackLevel === commandPayload.stackLevel && | |
854 | chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose | |
855 | ) { | |
856 | clearCurrentCP = true; | |
857 | } | |
858 | if (clearCurrentCP) { | |
859 | chargingProfiles.splice(index, 1); | |
860 | logger.debug( | |
861 | `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`, | |
862 | chargingProfile, | |
863 | ); | |
864 | clearedCP = true; | |
865 | } | |
866 | }); | |
867 | } | |
868 | return clearedCP; | |
869 | }; | |
870 | ||
1b271a54 JB |
871 | public static parseJsonSchemaFile<T extends JsonType>( |
872 | relativePath: string, | |
873 | moduleName?: string, | |
5edd8ba0 | 874 | methodName?: string, |
1b271a54 | 875 | ): JSONSchemaType<T> { |
7164966d | 876 | return super.parseJsonSchemaFile<T>( |
51022aa0 | 877 | relativePath, |
1b271a54 JB |
878 | OCPPVersion.VERSION_16, |
879 | moduleName, | |
5edd8ba0 | 880 | methodName, |
7164966d | 881 | ); |
130783a7 JB |
882 | } |
883 | ||
7bc31f9c JB |
884 | private static buildSampledValue( |
885 | sampledValueTemplate: SampledValueTemplate, | |
886 | value: number, | |
887 | context?: MeterValueContext, | |
5edd8ba0 | 888 | phase?: OCPP16MeterValuePhase, |
7bc31f9c JB |
889 | ): OCPP16SampledValue { |
890 | const sampledValueValue = value ?? sampledValueTemplate?.value ?? null; | |
891 | const sampledValueContext = context ?? sampledValueTemplate?.context ?? null; | |
892 | const sampledValueLocation = | |
893 | sampledValueTemplate?.location ?? | |
e1d9a0f4 | 894 | OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!); |
7bc31f9c JB |
895 | const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null; |
896 | return { | |
9bf0ef23 | 897 | ...(!isNullOrUndefined(sampledValueTemplate.unit) && { |
7bc31f9c JB |
898 | unit: sampledValueTemplate.unit, |
899 | }), | |
9bf0ef23 JB |
900 | ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), |
901 | ...(!isNullOrUndefined(sampledValueTemplate.measurand) && { | |
7bc31f9c JB |
902 | measurand: sampledValueTemplate.measurand, |
903 | }), | |
9bf0ef23 JB |
904 | ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), |
905 | ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }), | |
906 | ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), | |
e1d9a0f4 | 907 | } as OCPP16SampledValue; |
7bc31f9c JB |
908 | } |
909 | ||
910 | private static checkMeasurandPowerDivider( | |
911 | chargingStation: ChargingStation, | |
5edd8ba0 | 912 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c | 913 | ): void { |
9bf0ef23 | 914 | if (isUndefined(chargingStation.powerDivider)) { |
fc040c43 | 915 | const errMsg = `MeterValues measurand ${ |
7bc31f9c JB |
916 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER |
917 | }: powerDivider is undefined`; | |
fc040c43 | 918 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
7bc31f9c | 919 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
fa7bccf4 | 920 | } else if (chargingStation?.powerDivider <= 0) { |
fc040c43 | 921 | const errMsg = `MeterValues measurand ${ |
7bc31f9c | 922 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER |
fa7bccf4 | 923 | }: powerDivider have zero or below value ${chargingStation.powerDivider}`; |
fc040c43 | 924 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
7bc31f9c JB |
925 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
926 | } | |
927 | } | |
928 | ||
929 | private static getMeasurandDefaultLocation( | |
5edd8ba0 | 930 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c JB |
931 | ): MeterValueLocation | undefined { |
932 | switch (measurandType) { | |
933 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
934 | return MeterValueLocation.EV; | |
935 | } | |
936 | } | |
937 | ||
938 | private static getMeasurandDefaultUnit( | |
5edd8ba0 | 939 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c JB |
940 | ): MeterValueUnit | undefined { |
941 | switch (measurandType) { | |
942 | case OCPP16MeterValueMeasurand.CURRENT_EXPORT: | |
943 | case OCPP16MeterValueMeasurand.CURRENT_IMPORT: | |
944 | case OCPP16MeterValueMeasurand.CURRENT_OFFERED: | |
945 | return MeterValueUnit.AMP; | |
946 | case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: | |
947 | case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: | |
948 | return MeterValueUnit.WATT_HOUR; | |
949 | case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: | |
950 | case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: | |
951 | case OCPP16MeterValueMeasurand.POWER_OFFERED: | |
952 | return MeterValueUnit.WATT; | |
953 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
954 | return MeterValueUnit.PERCENT; | |
955 | case OCPP16MeterValueMeasurand.VOLTAGE: | |
956 | return MeterValueUnit.VOLT; | |
957 | } | |
958 | } | |
6ed92bc1 | 959 | } |