Commit | Line | Data |
---|---|---|
c8eeb62b JB |
1 | // Partial Copyright Jerome Benoit. 2021. All Rights Reserved. |
2 | ||
78085c42 JB |
3 | import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils'; |
4 | import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate'; | |
5 | import MeasurandPerPhaseSampledValueTemplates, { | |
6 | SampledValueTemplate, | |
7 | } from '../../../types/MeasurandPerPhaseSampledValueTemplates'; | |
e7aeea18 JB |
8 | import { |
9 | MeterValueContext, | |
10 | MeterValueLocation, | |
11 | MeterValueUnit, | |
12 | OCPP16MeterValue, | |
13 | OCPP16MeterValueMeasurand, | |
14 | OCPP16MeterValuePhase, | |
15 | OCPP16SampledValue, | |
16 | } from '../../../types/ocpp/1.6/MeterValues'; | |
6ed92bc1 | 17 | |
73b9adec | 18 | import type ChargingStation from '../../ChargingStation'; |
78085c42 | 19 | import Constants from '../../../utils/Constants'; |
14763b46 | 20 | import { ErrorType } from '../../../types/ocpp/ErrorType'; |
78085c42 JB |
21 | import MeasurandValues from '../../../types/MeasurandValues'; |
22 | import { OCPP16RequestCommand } from '../../../types/ocpp/1.6/Requests'; | |
e58068fd | 23 | import OCPPError from '../../../exception/OCPPError'; |
6ed92bc1 | 24 | import Utils from '../../../utils/Utils'; |
9f2e3130 | 25 | import logger from '../../../utils/Logger'; |
6ed92bc1 JB |
26 | |
27 | export class OCPP16ServiceUtils { | |
e7aeea18 JB |
28 | public static checkMeasurandPowerDivider( |
29 | chargingStation: ChargingStation, | |
30 | measurandType: OCPP16MeterValueMeasurand | |
31 | ): void { | |
6ed92bc1 | 32 | if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) { |
e7aeea18 JB |
33 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ |
34 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
35 | }: powerDivider is undefined`; | |
9f2e3130 | 36 | logger.error(errMsg); |
3d12fce4 | 37 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
fd0c36fa | 38 | } else if (chargingStation.stationInfo?.powerDivider <= 0) { |
e7aeea18 JB |
39 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ |
40 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
41 | }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`; | |
9f2e3130 | 42 | logger.error(errMsg); |
3d12fce4 | 43 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
6ed92bc1 JB |
44 | } |
45 | } | |
46 | ||
e7aeea18 JB |
47 | public static buildSampledValue( |
48 | sampledValueTemplate: SampledValueTemplate, | |
49 | value: number, | |
50 | context?: MeterValueContext, | |
51 | phase?: OCPP16MeterValuePhase | |
52 | ): OCPP16SampledValue { | |
53 | const sampledValueValue = value ?? sampledValueTemplate?.value ?? null; | |
54 | const sampledValueContext = context ?? sampledValueTemplate?.context ?? null; | |
55 | const sampledValueLocation = | |
56 | sampledValueTemplate?.location ?? | |
57 | OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null); | |
58 | const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null; | |
6ed92bc1 | 59 | return { |
e7aeea18 JB |
60 | ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && { |
61 | unit: sampledValueTemplate.unit, | |
62 | }), | |
63 | ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), | |
64 | ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && { | |
65 | measurand: sampledValueTemplate.measurand, | |
66 | }), | |
67 | ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), | |
68 | ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }), | |
69 | ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), | |
6ed92bc1 JB |
70 | }; |
71 | } | |
72 | ||
e7aeea18 JB |
73 | public static getMeasurandDefaultUnit( |
74 | measurandType: OCPP16MeterValueMeasurand | |
75 | ): MeterValueUnit | undefined { | |
6ed92bc1 JB |
76 | switch (measurandType) { |
77 | case OCPP16MeterValueMeasurand.CURRENT_EXPORT: | |
78 | case OCPP16MeterValueMeasurand.CURRENT_IMPORT: | |
79 | case OCPP16MeterValueMeasurand.CURRENT_OFFERED: | |
80 | return MeterValueUnit.AMP; | |
81 | case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: | |
82 | case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: | |
83 | return MeterValueUnit.WATT_HOUR; | |
84 | case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: | |
85 | case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: | |
86 | case OCPP16MeterValueMeasurand.POWER_OFFERED: | |
87 | return MeterValueUnit.WATT; | |
88 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
89 | return MeterValueUnit.PERCENT; | |
90 | case OCPP16MeterValueMeasurand.VOLTAGE: | |
91 | return MeterValueUnit.VOLT; | |
92 | } | |
93 | } | |
94 | ||
e7aeea18 JB |
95 | public static getMeasurandDefaultLocation( |
96 | measurandType: OCPP16MeterValueMeasurand | |
97 | ): MeterValueLocation | undefined { | |
6ed92bc1 JB |
98 | switch (measurandType) { |
99 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
100 | return MeterValueLocation.EV; | |
101 | } | |
102 | } | |
fd0c36fa | 103 | |
78085c42 JB |
104 | public static buildMeterValue( |
105 | chargingStation: ChargingStation, | |
106 | connectorId: number, | |
107 | transactionId: number, | |
108 | interval: number, | |
109 | debug = false | |
110 | ): OCPP16MeterValue { | |
111 | const meterValue: OCPP16MeterValue = { | |
112 | timestamp: new Date().toISOString(), | |
113 | sampledValue: [], | |
114 | }; | |
115 | const connector = chargingStation.getConnectorStatus(connectorId); | |
116 | // SoC measurand | |
117 | const socSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
118 | connectorId, | |
119 | OCPP16MeterValueMeasurand.STATE_OF_CHARGE | |
120 | ); | |
121 | if (socSampledValueTemplate) { | |
122 | const socSampledValueTemplateValue = socSampledValueTemplate.value | |
123 | ? Utils.getRandomFloatFluctuatedRounded( | |
124 | parseInt(socSampledValueTemplate.value), | |
125 | socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT | |
126 | ) | |
127 | : Utils.getRandomInteger(100); | |
128 | meterValue.sampledValue.push( | |
129 | OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue) | |
130 | ); | |
131 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
132 | if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) { | |
133 | logger.error( | |
134 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
135 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
136 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
137 | }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
138 | meterValue.sampledValue[sampledValuesIndex].value | |
139 | }/100` | |
140 | ); | |
141 | } | |
142 | } | |
143 | // Voltage measurand | |
144 | const voltageSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
145 | connectorId, | |
146 | OCPP16MeterValueMeasurand.VOLTAGE | |
147 | ); | |
148 | if (voltageSampledValueTemplate) { | |
149 | const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value | |
150 | ? parseInt(voltageSampledValueTemplate.value) | |
151 | : chargingStation.getVoltageOut(); | |
152 | const fluctuationPercent = | |
153 | voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; | |
154 | const voltageMeasurandValue = Utils.getRandomFloatFluctuatedRounded( | |
155 | voltageSampledValueTemplateValue, | |
156 | fluctuationPercent | |
157 | ); | |
158 | if ( | |
159 | chargingStation.getNumberOfPhases() !== 3 || | |
160 | (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues()) | |
161 | ) { | |
162 | meterValue.sampledValue.push( | |
163 | OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue) | |
164 | ); | |
165 | } | |
166 | for ( | |
167 | let phase = 1; | |
168 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
169 | phase++ | |
170 | ) { | |
171 | const phaseLineToNeutralValue = `L${phase}-N`; | |
172 | const voltagePhaseLineToNeutralSampledValueTemplate = | |
173 | chargingStation.getSampledValueTemplate( | |
174 | connectorId, | |
175 | OCPP16MeterValueMeasurand.VOLTAGE, | |
176 | phaseLineToNeutralValue as OCPP16MeterValuePhase | |
177 | ); | |
178 | let voltagePhaseLineToNeutralMeasurandValue: number; | |
179 | if (voltagePhaseLineToNeutralSampledValueTemplate) { | |
180 | const voltagePhaseLineToNeutralSampledValueTemplateValue = | |
181 | voltagePhaseLineToNeutralSampledValueTemplate.value | |
182 | ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) | |
183 | : chargingStation.getVoltageOut(); | |
184 | const fluctuationPhaseToNeutralPercent = | |
185 | voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? | |
186 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
187 | voltagePhaseLineToNeutralMeasurandValue = Utils.getRandomFloatFluctuatedRounded( | |
188 | voltagePhaseLineToNeutralSampledValueTemplateValue, | |
189 | fluctuationPhaseToNeutralPercent | |
190 | ); | |
191 | } | |
192 | meterValue.sampledValue.push( | |
193 | OCPP16ServiceUtils.buildSampledValue( | |
194 | voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate, | |
195 | voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue, | |
196 | null, | |
197 | phaseLineToNeutralValue as OCPP16MeterValuePhase | |
198 | ) | |
199 | ); | |
200 | if (chargingStation.getPhaseLineToLineVoltageMeterValues()) { | |
201 | const phaseLineToLineValue = `L${phase}-L${ | |
202 | (phase + 1) % chargingStation.getNumberOfPhases() !== 0 | |
203 | ? (phase + 1) % chargingStation.getNumberOfPhases() | |
204 | : chargingStation.getNumberOfPhases() | |
205 | }`; | |
206 | const voltagePhaseLineToLineSampledValueTemplate = | |
207 | chargingStation.getSampledValueTemplate( | |
208 | connectorId, | |
209 | OCPP16MeterValueMeasurand.VOLTAGE, | |
210 | phaseLineToLineValue as OCPP16MeterValuePhase | |
211 | ); | |
212 | let voltagePhaseLineToLineMeasurandValue: number; | |
213 | if (voltagePhaseLineToLineSampledValueTemplate) { | |
214 | const voltagePhaseLineToLineSampledValueTemplateValue = | |
215 | voltagePhaseLineToLineSampledValueTemplate.value | |
216 | ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) | |
217 | : Voltage.VOLTAGE_400; | |
218 | const fluctuationPhaseLineToLinePercent = | |
219 | voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? | |
220 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
221 | voltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded( | |
222 | voltagePhaseLineToLineSampledValueTemplateValue, | |
223 | fluctuationPhaseLineToLinePercent | |
224 | ); | |
225 | } | |
226 | const defaultVoltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded( | |
227 | Voltage.VOLTAGE_400, | |
228 | fluctuationPercent | |
229 | ); | |
230 | meterValue.sampledValue.push( | |
231 | OCPP16ServiceUtils.buildSampledValue( | |
232 | voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate, | |
233 | voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue, | |
234 | null, | |
235 | phaseLineToLineValue as OCPP16MeterValuePhase | |
236 | ) | |
237 | ); | |
238 | } | |
239 | } | |
240 | } | |
241 | // Power.Active.Import measurand | |
242 | const powerSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
243 | connectorId, | |
244 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT | |
245 | ); | |
246 | let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; | |
247 | if (chargingStation.getNumberOfPhases() === 3) { | |
248 | powerPerPhaseSampledValueTemplates = { | |
249 | L1: chargingStation.getSampledValueTemplate( | |
250 | connectorId, | |
251 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
252 | OCPP16MeterValuePhase.L1_N | |
253 | ), | |
254 | L2: chargingStation.getSampledValueTemplate( | |
255 | connectorId, | |
256 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
257 | OCPP16MeterValuePhase.L2_N | |
258 | ), | |
259 | L3: chargingStation.getSampledValueTemplate( | |
260 | connectorId, | |
261 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
262 | OCPP16MeterValuePhase.L3_N | |
263 | ), | |
264 | }; | |
265 | } | |
266 | if (powerSampledValueTemplate) { | |
267 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
268 | chargingStation, | |
269 | powerSampledValueTemplate.measurand | |
270 | ); | |
271 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ | |
272 | powerSampledValueTemplate.measurand ?? | |
273 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
274 | }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ | |
2484ac1e | 275 | chargingStation.templateFile |
78085c42 JB |
276 | }, cannot calculate ${ |
277 | powerSampledValueTemplate.measurand ?? | |
278 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
279 | } measurand value`; | |
280 | const powerMeasurandValues = {} as MeasurandValues; | |
281 | const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; | |
ad8537a7 JB |
282 | const connectorMaximumPower = Math.round( |
283 | chargingStation.getConnectorMaximumAvailablePower(connectorId) | |
78085c42 | 284 | ); |
ad8537a7 JB |
285 | const connectorMaximumPowerPerPhase = Math.round( |
286 | chargingStation.getConnectorMaximumAvailablePower(connectorId) / | |
78085c42 JB |
287 | chargingStation.getNumberOfPhases() |
288 | ); | |
289 | switch (chargingStation.getCurrentOutType()) { | |
290 | case CurrentType.AC: | |
291 | if (chargingStation.getNumberOfPhases() === 3) { | |
292 | const defaultFluctuatedPowerPerPhase = | |
293 | powerSampledValueTemplate.value && | |
294 | Utils.getRandomFloatFluctuatedRounded( | |
295 | parseInt(powerSampledValueTemplate.value) / chargingStation.getNumberOfPhases(), | |
296 | powerSampledValueTemplate.fluctuationPercent ?? | |
297 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
298 | ); | |
299 | const phase1FluctuatedValue = | |
300 | powerPerPhaseSampledValueTemplates?.L1?.value && | |
301 | Utils.getRandomFloatFluctuatedRounded( | |
302 | parseInt(powerPerPhaseSampledValueTemplates.L1.value), | |
303 | powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? | |
304 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
305 | ); | |
306 | const phase2FluctuatedValue = | |
307 | powerPerPhaseSampledValueTemplates?.L2?.value && | |
308 | Utils.getRandomFloatFluctuatedRounded( | |
309 | parseInt(powerPerPhaseSampledValueTemplates.L2.value), | |
310 | powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? | |
311 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
312 | ); | |
313 | const phase3FluctuatedValue = | |
314 | powerPerPhaseSampledValueTemplates?.L3?.value && | |
315 | Utils.getRandomFloatFluctuatedRounded( | |
316 | parseInt(powerPerPhaseSampledValueTemplates.L3.value), | |
317 | powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? | |
318 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
319 | ); | |
320 | powerMeasurandValues.L1 = | |
321 | phase1FluctuatedValue ?? | |
322 | defaultFluctuatedPowerPerPhase ?? | |
ad8537a7 | 323 | Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); |
78085c42 JB |
324 | powerMeasurandValues.L2 = |
325 | phase2FluctuatedValue ?? | |
326 | defaultFluctuatedPowerPerPhase ?? | |
ad8537a7 | 327 | Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); |
78085c42 JB |
328 | powerMeasurandValues.L3 = |
329 | phase3FluctuatedValue ?? | |
330 | defaultFluctuatedPowerPerPhase ?? | |
ad8537a7 | 331 | Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); |
78085c42 JB |
332 | } else { |
333 | powerMeasurandValues.L1 = powerSampledValueTemplate.value | |
334 | ? Utils.getRandomFloatFluctuatedRounded( | |
335 | parseInt(powerSampledValueTemplate.value), | |
336 | powerSampledValueTemplate.fluctuationPercent ?? | |
337 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
338 | ) | |
ad8537a7 | 339 | : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider); |
78085c42 JB |
340 | powerMeasurandValues.L2 = 0; |
341 | powerMeasurandValues.L3 = 0; | |
342 | } | |
343 | powerMeasurandValues.allPhases = Utils.roundTo( | |
344 | powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, | |
345 | 2 | |
346 | ); | |
347 | break; | |
348 | case CurrentType.DC: | |
349 | powerMeasurandValues.allPhases = powerSampledValueTemplate.value | |
350 | ? Utils.getRandomFloatFluctuatedRounded( | |
351 | parseInt(powerSampledValueTemplate.value), | |
352 | powerSampledValueTemplate.fluctuationPercent ?? | |
353 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
354 | ) | |
ad8537a7 | 355 | : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider); |
78085c42 JB |
356 | break; |
357 | default: | |
358 | logger.error(errMsg); | |
359 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); | |
360 | } | |
361 | meterValue.sampledValue.push( | |
362 | OCPP16ServiceUtils.buildSampledValue( | |
363 | powerSampledValueTemplate, | |
364 | powerMeasurandValues.allPhases | |
365 | ) | |
366 | ); | |
367 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
ad8537a7 | 368 | const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2); |
78085c42 | 369 | if ( |
71a77ac2 | 370 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 371 | connectorMaximumPowerRounded || |
78085c42 JB |
372 | debug |
373 | ) { | |
374 | logger.error( | |
375 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
376 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
377 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
378 | }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
379 | meterValue.sampledValue[sampledValuesIndex].value | |
ad8537a7 | 380 | }/${connectorMaximumPowerRounded}` |
78085c42 JB |
381 | ); |
382 | } | |
383 | for ( | |
384 | let phase = 1; | |
385 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
386 | phase++ | |
387 | ) { | |
388 | const phaseValue = `L${phase}-N`; | |
389 | meterValue.sampledValue.push( | |
390 | OCPP16ServiceUtils.buildSampledValue( | |
391 | (powerPerPhaseSampledValueTemplates[`L${phase}`] as SampledValueTemplate) ?? | |
392 | powerSampledValueTemplate, | |
393 | powerMeasurandValues[`L${phase}`] as number, | |
394 | null, | |
395 | phaseValue as OCPP16MeterValuePhase | |
396 | ) | |
397 | ); | |
398 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
ad8537a7 JB |
399 | const connectorMaximumPowerPerPhaseRounded = Utils.roundTo( |
400 | connectorMaximumPowerPerPhase / unitDivider, | |
401 | 2 | |
402 | ); | |
78085c42 JB |
403 | if ( |
404 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > | |
ad8537a7 | 405 | connectorMaximumPowerPerPhaseRounded || |
78085c42 JB |
406 | debug |
407 | ) { | |
408 | logger.error( | |
409 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
410 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
411 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
412 | }: phase ${ | |
413 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
414 | }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
415 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value | |
ad8537a7 | 416 | }/${connectorMaximumPowerPerPhaseRounded}` |
78085c42 JB |
417 | ); |
418 | } | |
419 | } | |
420 | } | |
421 | // Current.Import measurand | |
422 | const currentSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
423 | connectorId, | |
424 | OCPP16MeterValueMeasurand.CURRENT_IMPORT | |
425 | ); | |
426 | let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; | |
427 | if (chargingStation.getNumberOfPhases() === 3) { | |
428 | currentPerPhaseSampledValueTemplates = { | |
429 | L1: chargingStation.getSampledValueTemplate( | |
430 | connectorId, | |
431 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
432 | OCPP16MeterValuePhase.L1 | |
433 | ), | |
434 | L2: chargingStation.getSampledValueTemplate( | |
435 | connectorId, | |
436 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
437 | OCPP16MeterValuePhase.L2 | |
438 | ), | |
439 | L3: chargingStation.getSampledValueTemplate( | |
440 | connectorId, | |
441 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
442 | OCPP16MeterValuePhase.L3 | |
443 | ), | |
444 | }; | |
445 | } | |
446 | if (currentSampledValueTemplate) { | |
447 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
448 | chargingStation, | |
449 | currentSampledValueTemplate.measurand | |
450 | ); | |
451 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ | |
452 | currentSampledValueTemplate.measurand ?? | |
453 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
454 | }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ | |
2484ac1e | 455 | chargingStation.templateFile |
78085c42 JB |
456 | }, cannot calculate ${ |
457 | currentSampledValueTemplate.measurand ?? | |
458 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
459 | } measurand value`; | |
460 | const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; | |
ad8537a7 | 461 | let connectorMaximumAmperage: number; |
78085c42 JB |
462 | switch (chargingStation.getCurrentOutType()) { |
463 | case CurrentType.AC: | |
ad8537a7 | 464 | connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( |
78085c42 | 465 | chargingStation.getNumberOfPhases(), |
ad8537a7 | 466 | chargingStation.getConnectorMaximumAvailablePower(connectorId), |
78085c42 JB |
467 | chargingStation.getVoltageOut() |
468 | ); | |
469 | if (chargingStation.getNumberOfPhases() === 3) { | |
470 | const defaultFluctuatedAmperagePerPhase = | |
471 | currentSampledValueTemplate.value && | |
472 | Utils.getRandomFloatFluctuatedRounded( | |
473 | parseInt(currentSampledValueTemplate.value), | |
474 | currentSampledValueTemplate.fluctuationPercent ?? | |
475 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
476 | ); | |
477 | const phase1FluctuatedValue = | |
478 | currentPerPhaseSampledValueTemplates?.L1?.value && | |
479 | Utils.getRandomFloatFluctuatedRounded( | |
480 | parseInt(currentPerPhaseSampledValueTemplates.L1.value), | |
481 | currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? | |
482 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
483 | ); | |
484 | const phase2FluctuatedValue = | |
485 | currentPerPhaseSampledValueTemplates?.L2?.value && | |
486 | Utils.getRandomFloatFluctuatedRounded( | |
487 | parseInt(currentPerPhaseSampledValueTemplates.L2.value), | |
488 | currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? | |
489 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
490 | ); | |
491 | const phase3FluctuatedValue = | |
492 | currentPerPhaseSampledValueTemplates?.L3?.value && | |
493 | Utils.getRandomFloatFluctuatedRounded( | |
494 | parseInt(currentPerPhaseSampledValueTemplates.L3.value), | |
495 | currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? | |
496 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
497 | ); | |
498 | currentMeasurandValues.L1 = | |
499 | phase1FluctuatedValue ?? | |
500 | defaultFluctuatedAmperagePerPhase ?? | |
ad8537a7 | 501 | Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
502 | currentMeasurandValues.L2 = |
503 | phase2FluctuatedValue ?? | |
504 | defaultFluctuatedAmperagePerPhase ?? | |
ad8537a7 | 505 | Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
506 | currentMeasurandValues.L3 = |
507 | phase3FluctuatedValue ?? | |
508 | defaultFluctuatedAmperagePerPhase ?? | |
ad8537a7 | 509 | Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
510 | } else { |
511 | currentMeasurandValues.L1 = currentSampledValueTemplate.value | |
512 | ? Utils.getRandomFloatFluctuatedRounded( | |
513 | parseInt(currentSampledValueTemplate.value), | |
514 | currentSampledValueTemplate.fluctuationPercent ?? | |
515 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
516 | ) | |
ad8537a7 | 517 | : Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
518 | currentMeasurandValues.L2 = 0; |
519 | currentMeasurandValues.L3 = 0; | |
520 | } | |
521 | currentMeasurandValues.allPhases = Utils.roundTo( | |
522 | (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / | |
523 | chargingStation.getNumberOfPhases(), | |
524 | 2 | |
525 | ); | |
526 | break; | |
527 | case CurrentType.DC: | |
ad8537a7 JB |
528 | connectorMaximumAmperage = DCElectricUtils.amperage( |
529 | chargingStation.getConnectorMaximumAvailablePower(connectorId), | |
78085c42 JB |
530 | chargingStation.getVoltageOut() |
531 | ); | |
532 | currentMeasurandValues.allPhases = currentSampledValueTemplate.value | |
533 | ? Utils.getRandomFloatFluctuatedRounded( | |
534 | parseInt(currentSampledValueTemplate.value), | |
535 | currentSampledValueTemplate.fluctuationPercent ?? | |
536 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
537 | ) | |
ad8537a7 | 538 | : Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
539 | break; |
540 | default: | |
541 | logger.error(errMsg); | |
542 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); | |
543 | } | |
544 | meterValue.sampledValue.push( | |
545 | OCPP16ServiceUtils.buildSampledValue( | |
546 | currentSampledValueTemplate, | |
547 | currentMeasurandValues.allPhases | |
548 | ) | |
549 | ); | |
550 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
551 | if ( | |
ad8537a7 JB |
552 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
553 | connectorMaximumAmperage || | |
78085c42 JB |
554 | debug |
555 | ) { | |
556 | logger.error( | |
557 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
558 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
559 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
560 | }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
561 | meterValue.sampledValue[sampledValuesIndex].value | |
ad8537a7 | 562 | }/${connectorMaximumAmperage}` |
78085c42 JB |
563 | ); |
564 | } | |
565 | for ( | |
566 | let phase = 1; | |
567 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
568 | phase++ | |
569 | ) { | |
570 | const phaseValue = `L${phase}`; | |
571 | meterValue.sampledValue.push( | |
572 | OCPP16ServiceUtils.buildSampledValue( | |
573 | (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ?? | |
574 | currentSampledValueTemplate, | |
575 | currentMeasurandValues[phaseValue] as number, | |
576 | null, | |
577 | phaseValue as OCPP16MeterValuePhase | |
578 | ) | |
579 | ); | |
580 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
581 | if ( | |
582 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > | |
ad8537a7 | 583 | connectorMaximumAmperage || |
78085c42 JB |
584 | debug |
585 | ) { | |
586 | logger.error( | |
587 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
588 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
589 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
590 | }: phase ${ | |
591 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
592 | }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
593 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value | |
ad8537a7 | 594 | }/${connectorMaximumAmperage}` |
78085c42 JB |
595 | ); |
596 | } | |
597 | } | |
598 | } | |
599 | // Energy.Active.Import.Register measurand (default) | |
600 | const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
601 | if (energySampledValueTemplate) { | |
602 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
603 | chargingStation, | |
604 | energySampledValueTemplate.measurand | |
605 | ); | |
606 | const unitDivider = | |
607 | energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
ad8537a7 JB |
608 | const connectorMaximumEnergyRounded = Utils.roundTo( |
609 | (chargingStation.getConnectorMaximumAvailablePower(connectorId) * interval) / (3600 * 1000), | |
78085c42 JB |
610 | 2 |
611 | ); | |
612 | const energyValueRounded = energySampledValueTemplate.value | |
613 | ? // Cumulate the fluctuated value around the static one | |
614 | Utils.getRandomFloatFluctuatedRounded( | |
615 | parseInt(energySampledValueTemplate.value), | |
616 | energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT | |
617 | ) | |
ad8537a7 | 618 | : Utils.getRandomFloatRounded(connectorMaximumEnergyRounded); |
78085c42 JB |
619 | // Persist previous value on connector |
620 | if ( | |
621 | connector && | |
622 | !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) && | |
623 | connector.energyActiveImportRegisterValue >= 0 && | |
624 | !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) && | |
625 | connector.transactionEnergyActiveImportRegisterValue >= 0 | |
626 | ) { | |
627 | connector.energyActiveImportRegisterValue += energyValueRounded; | |
628 | connector.transactionEnergyActiveImportRegisterValue += energyValueRounded; | |
629 | } else { | |
630 | connector.energyActiveImportRegisterValue = 0; | |
631 | connector.transactionEnergyActiveImportRegisterValue = 0; | |
632 | } | |
633 | meterValue.sampledValue.push( | |
634 | OCPP16ServiceUtils.buildSampledValue( | |
635 | energySampledValueTemplate, | |
636 | Utils.roundTo( | |
637 | chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / | |
638 | unitDivider, | |
639 | 2 | |
640 | ) | |
641 | ) | |
642 | ); | |
643 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
ad8537a7 | 644 | if (energyValueRounded > connectorMaximumEnergyRounded || debug) { |
78085c42 JB |
645 | logger.error( |
646 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
647 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
648 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
649 | }: connectorId ${connectorId}, transaction ${ | |
650 | connector.transactionId | |
ad8537a7 | 651 | }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo( |
78085c42 JB |
652 | interval / (3600 * 1000), |
653 | 4 | |
654 | )}h` | |
655 | ); | |
656 | } | |
657 | } | |
658 | return meterValue; | |
659 | } | |
660 | ||
e7aeea18 JB |
661 | public static buildTransactionBeginMeterValue( |
662 | chargingStation: ChargingStation, | |
663 | connectorId: number, | |
78085c42 | 664 | meterStart: number |
e7aeea18 | 665 | ): OCPP16MeterValue { |
fd0c36fa JB |
666 | const meterValue: OCPP16MeterValue = { |
667 | timestamp: new Date().toISOString(), | |
668 | sampledValue: [], | |
669 | }; | |
9ccca265 JB |
670 | // Energy.Active.Import.Register measurand (default) |
671 | const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
672 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
e7aeea18 JB |
673 | meterValue.sampledValue.push( |
674 | OCPP16ServiceUtils.buildSampledValue( | |
675 | sampledValueTemplate, | |
78085c42 | 676 | Utils.roundTo(meterStart / unitDivider, 4), |
e7aeea18 JB |
677 | MeterValueContext.TRANSACTION_BEGIN |
678 | ) | |
679 | ); | |
fd0c36fa JB |
680 | return meterValue; |
681 | } | |
682 | ||
e7aeea18 JB |
683 | public static buildTransactionEndMeterValue( |
684 | chargingStation: ChargingStation, | |
685 | connectorId: number, | |
78085c42 | 686 | meterStop: number |
e7aeea18 | 687 | ): OCPP16MeterValue { |
fd0c36fa JB |
688 | const meterValue: OCPP16MeterValue = { |
689 | timestamp: new Date().toISOString(), | |
690 | sampledValue: [], | |
691 | }; | |
9ccca265 JB |
692 | // Energy.Active.Import.Register measurand (default) |
693 | const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
694 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
e7aeea18 JB |
695 | meterValue.sampledValue.push( |
696 | OCPP16ServiceUtils.buildSampledValue( | |
697 | sampledValueTemplate, | |
78085c42 | 698 | Utils.roundTo(meterStop / unitDivider, 4), |
e7aeea18 JB |
699 | MeterValueContext.TRANSACTION_END |
700 | ) | |
701 | ); | |
fd0c36fa JB |
702 | return meterValue; |
703 | } | |
704 | ||
e7aeea18 JB |
705 | public static buildTransactionDataMeterValues( |
706 | transactionBeginMeterValue: OCPP16MeterValue, | |
707 | transactionEndMeterValue: OCPP16MeterValue | |
708 | ): OCPP16MeterValue[] { | |
fd0c36fa JB |
709 | const meterValues: OCPP16MeterValue[] = []; |
710 | meterValues.push(transactionBeginMeterValue); | |
711 | meterValues.push(transactionEndMeterValue); | |
712 | return meterValues; | |
713 | } | |
6ed92bc1 | 714 | } |