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