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 ${ | |
275 | chargingStation.stationTemplateFile | |
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; | |
282 | const maxPower = Math.round( | |
283 | chargingStation.stationInfo.maxPower / chargingStation.stationInfo.powerDivider | |
284 | ); | |
285 | const maxPowerPerPhase = Math.round( | |
286 | chargingStation.stationInfo.maxPower / | |
287 | chargingStation.stationInfo.powerDivider / | |
288 | chargingStation.getNumberOfPhases() | |
289 | ); | |
290 | switch (chargingStation.getCurrentOutType()) { | |
291 | case CurrentType.AC: | |
292 | if (chargingStation.getNumberOfPhases() === 3) { | |
293 | const defaultFluctuatedPowerPerPhase = | |
294 | powerSampledValueTemplate.value && | |
295 | Utils.getRandomFloatFluctuatedRounded( | |
296 | parseInt(powerSampledValueTemplate.value) / chargingStation.getNumberOfPhases(), | |
297 | powerSampledValueTemplate.fluctuationPercent ?? | |
298 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
299 | ); | |
300 | const phase1FluctuatedValue = | |
301 | powerPerPhaseSampledValueTemplates?.L1?.value && | |
302 | Utils.getRandomFloatFluctuatedRounded( | |
303 | parseInt(powerPerPhaseSampledValueTemplates.L1.value), | |
304 | powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? | |
305 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
306 | ); | |
307 | const phase2FluctuatedValue = | |
308 | powerPerPhaseSampledValueTemplates?.L2?.value && | |
309 | Utils.getRandomFloatFluctuatedRounded( | |
310 | parseInt(powerPerPhaseSampledValueTemplates.L2.value), | |
311 | powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? | |
312 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
313 | ); | |
314 | const phase3FluctuatedValue = | |
315 | powerPerPhaseSampledValueTemplates?.L3?.value && | |
316 | Utils.getRandomFloatFluctuatedRounded( | |
317 | parseInt(powerPerPhaseSampledValueTemplates.L3.value), | |
318 | powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? | |
319 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
320 | ); | |
321 | powerMeasurandValues.L1 = | |
322 | phase1FluctuatedValue ?? | |
323 | defaultFluctuatedPowerPerPhase ?? | |
324 | Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); | |
325 | powerMeasurandValues.L2 = | |
326 | phase2FluctuatedValue ?? | |
327 | defaultFluctuatedPowerPerPhase ?? | |
328 | Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); | |
329 | powerMeasurandValues.L3 = | |
330 | phase3FluctuatedValue ?? | |
331 | defaultFluctuatedPowerPerPhase ?? | |
332 | Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider); | |
333 | } else { | |
334 | powerMeasurandValues.L1 = powerSampledValueTemplate.value | |
335 | ? Utils.getRandomFloatFluctuatedRounded( | |
336 | parseInt(powerSampledValueTemplate.value), | |
337 | powerSampledValueTemplate.fluctuationPercent ?? | |
338 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
339 | ) | |
340 | : Utils.getRandomFloatRounded(maxPower / unitDivider); | |
341 | powerMeasurandValues.L2 = 0; | |
342 | powerMeasurandValues.L3 = 0; | |
343 | } | |
344 | powerMeasurandValues.allPhases = Utils.roundTo( | |
345 | powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, | |
346 | 2 | |
347 | ); | |
348 | break; | |
349 | case CurrentType.DC: | |
350 | powerMeasurandValues.allPhases = powerSampledValueTemplate.value | |
351 | ? Utils.getRandomFloatFluctuatedRounded( | |
352 | parseInt(powerSampledValueTemplate.value), | |
353 | powerSampledValueTemplate.fluctuationPercent ?? | |
354 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
355 | ) | |
356 | : Utils.getRandomFloatRounded(maxPower / unitDivider); | |
357 | break; | |
358 | default: | |
359 | logger.error(errMsg); | |
360 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); | |
361 | } | |
362 | meterValue.sampledValue.push( | |
363 | OCPP16ServiceUtils.buildSampledValue( | |
364 | powerSampledValueTemplate, | |
365 | powerMeasurandValues.allPhases | |
366 | ) | |
367 | ); | |
368 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
369 | const maxPowerRounded = Utils.roundTo(maxPower / unitDivider, 2); | |
370 | if ( | |
371 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxPowerRounded || | |
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 | |
380 | }/${maxPowerRounded}` | |
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; | |
399 | const maxPowerPerPhaseRounded = Utils.roundTo(maxPowerPerPhase / unitDivider, 2); | |
400 | if ( | |
401 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > | |
402 | maxPowerPerPhaseRounded || | |
403 | debug | |
404 | ) { | |
405 | logger.error( | |
406 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
407 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
408 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
409 | }: phase ${ | |
410 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
411 | }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
412 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value | |
413 | }/${maxPowerPerPhaseRounded}` | |
414 | ); | |
415 | } | |
416 | } | |
417 | } | |
418 | // Current.Import measurand | |
419 | const currentSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
420 | connectorId, | |
421 | OCPP16MeterValueMeasurand.CURRENT_IMPORT | |
422 | ); | |
423 | let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; | |
424 | if (chargingStation.getNumberOfPhases() === 3) { | |
425 | currentPerPhaseSampledValueTemplates = { | |
426 | L1: chargingStation.getSampledValueTemplate( | |
427 | connectorId, | |
428 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
429 | OCPP16MeterValuePhase.L1 | |
430 | ), | |
431 | L2: chargingStation.getSampledValueTemplate( | |
432 | connectorId, | |
433 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
434 | OCPP16MeterValuePhase.L2 | |
435 | ), | |
436 | L3: chargingStation.getSampledValueTemplate( | |
437 | connectorId, | |
438 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
439 | OCPP16MeterValuePhase.L3 | |
440 | ), | |
441 | }; | |
442 | } | |
443 | if (currentSampledValueTemplate) { | |
444 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
445 | chargingStation, | |
446 | currentSampledValueTemplate.measurand | |
447 | ); | |
448 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ | |
449 | currentSampledValueTemplate.measurand ?? | |
450 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
451 | }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ | |
452 | chargingStation.stationTemplateFile | |
453 | }, cannot calculate ${ | |
454 | currentSampledValueTemplate.measurand ?? | |
455 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
456 | } measurand value`; | |
457 | const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; | |
458 | let maxAmperage: number; | |
459 | switch (chargingStation.getCurrentOutType()) { | |
460 | case CurrentType.AC: | |
461 | maxAmperage = ACElectricUtils.amperagePerPhaseFromPower( | |
462 | chargingStation.getNumberOfPhases(), | |
463 | chargingStation.stationInfo.maxPower / chargingStation.stationInfo.powerDivider, | |
464 | chargingStation.getVoltageOut() | |
465 | ); | |
466 | if (chargingStation.getNumberOfPhases() === 3) { | |
467 | const defaultFluctuatedAmperagePerPhase = | |
468 | currentSampledValueTemplate.value && | |
469 | Utils.getRandomFloatFluctuatedRounded( | |
470 | parseInt(currentSampledValueTemplate.value), | |
471 | currentSampledValueTemplate.fluctuationPercent ?? | |
472 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
473 | ); | |
474 | const phase1FluctuatedValue = | |
475 | currentPerPhaseSampledValueTemplates?.L1?.value && | |
476 | Utils.getRandomFloatFluctuatedRounded( | |
477 | parseInt(currentPerPhaseSampledValueTemplates.L1.value), | |
478 | currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? | |
479 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
480 | ); | |
481 | const phase2FluctuatedValue = | |
482 | currentPerPhaseSampledValueTemplates?.L2?.value && | |
483 | Utils.getRandomFloatFluctuatedRounded( | |
484 | parseInt(currentPerPhaseSampledValueTemplates.L2.value), | |
485 | currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? | |
486 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
487 | ); | |
488 | const phase3FluctuatedValue = | |
489 | currentPerPhaseSampledValueTemplates?.L3?.value && | |
490 | Utils.getRandomFloatFluctuatedRounded( | |
491 | parseInt(currentPerPhaseSampledValueTemplates.L3.value), | |
492 | currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? | |
493 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
494 | ); | |
495 | currentMeasurandValues.L1 = | |
496 | phase1FluctuatedValue ?? | |
497 | defaultFluctuatedAmperagePerPhase ?? | |
498 | Utils.getRandomFloatRounded(maxAmperage); | |
499 | currentMeasurandValues.L2 = | |
500 | phase2FluctuatedValue ?? | |
501 | defaultFluctuatedAmperagePerPhase ?? | |
502 | Utils.getRandomFloatRounded(maxAmperage); | |
503 | currentMeasurandValues.L3 = | |
504 | phase3FluctuatedValue ?? | |
505 | defaultFluctuatedAmperagePerPhase ?? | |
506 | Utils.getRandomFloatRounded(maxAmperage); | |
507 | } else { | |
508 | currentMeasurandValues.L1 = currentSampledValueTemplate.value | |
509 | ? Utils.getRandomFloatFluctuatedRounded( | |
510 | parseInt(currentSampledValueTemplate.value), | |
511 | currentSampledValueTemplate.fluctuationPercent ?? | |
512 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
513 | ) | |
514 | : Utils.getRandomFloatRounded(maxAmperage); | |
515 | currentMeasurandValues.L2 = 0; | |
516 | currentMeasurandValues.L3 = 0; | |
517 | } | |
518 | currentMeasurandValues.allPhases = Utils.roundTo( | |
519 | (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / | |
520 | chargingStation.getNumberOfPhases(), | |
521 | 2 | |
522 | ); | |
523 | break; | |
524 | case CurrentType.DC: | |
525 | maxAmperage = DCElectricUtils.amperage( | |
526 | chargingStation.stationInfo.maxPower / chargingStation.stationInfo.powerDivider, | |
527 | chargingStation.getVoltageOut() | |
528 | ); | |
529 | currentMeasurandValues.allPhases = currentSampledValueTemplate.value | |
530 | ? Utils.getRandomFloatFluctuatedRounded( | |
531 | parseInt(currentSampledValueTemplate.value), | |
532 | currentSampledValueTemplate.fluctuationPercent ?? | |
533 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
534 | ) | |
535 | : Utils.getRandomFloatRounded(maxAmperage); | |
536 | break; | |
537 | default: | |
538 | logger.error(errMsg); | |
539 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); | |
540 | } | |
541 | meterValue.sampledValue.push( | |
542 | OCPP16ServiceUtils.buildSampledValue( | |
543 | currentSampledValueTemplate, | |
544 | currentMeasurandValues.allPhases | |
545 | ) | |
546 | ); | |
547 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
548 | if ( | |
549 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxAmperage || | |
550 | debug | |
551 | ) { | |
552 | logger.error( | |
553 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
554 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
555 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
556 | }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
557 | meterValue.sampledValue[sampledValuesIndex].value | |
558 | }/${maxAmperage}` | |
559 | ); | |
560 | } | |
561 | for ( | |
562 | let phase = 1; | |
563 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
564 | phase++ | |
565 | ) { | |
566 | const phaseValue = `L${phase}`; | |
567 | meterValue.sampledValue.push( | |
568 | OCPP16ServiceUtils.buildSampledValue( | |
569 | (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ?? | |
570 | currentSampledValueTemplate, | |
571 | currentMeasurandValues[phaseValue] as number, | |
572 | null, | |
573 | phaseValue as OCPP16MeterValuePhase | |
574 | ) | |
575 | ); | |
576 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
577 | if ( | |
578 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > | |
579 | maxAmperage || | |
580 | debug | |
581 | ) { | |
582 | logger.error( | |
583 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
584 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
585 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
586 | }: phase ${ | |
587 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
588 | }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
589 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value | |
590 | }/${maxAmperage}` | |
591 | ); | |
592 | } | |
593 | } | |
594 | } | |
595 | // Energy.Active.Import.Register measurand (default) | |
596 | const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
597 | if (energySampledValueTemplate) { | |
598 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
599 | chargingStation, | |
600 | energySampledValueTemplate.measurand | |
601 | ); | |
602 | const unitDivider = | |
603 | energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
604 | const maxEnergyRounded = Utils.roundTo( | |
605 | ((chargingStation.stationInfo.maxPower / chargingStation.stationInfo.powerDivider) * | |
606 | interval) / | |
607 | (3600 * 1000), | |
608 | 2 | |
609 | ); | |
610 | const energyValueRounded = energySampledValueTemplate.value | |
611 | ? // Cumulate the fluctuated value around the static one | |
612 | Utils.getRandomFloatFluctuatedRounded( | |
613 | parseInt(energySampledValueTemplate.value), | |
614 | energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT | |
615 | ) | |
616 | : Utils.getRandomFloatRounded(maxEnergyRounded); | |
617 | // Persist previous value on connector | |
618 | if ( | |
619 | connector && | |
620 | !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) && | |
621 | connector.energyActiveImportRegisterValue >= 0 && | |
622 | !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) && | |
623 | connector.transactionEnergyActiveImportRegisterValue >= 0 | |
624 | ) { | |
625 | connector.energyActiveImportRegisterValue += energyValueRounded; | |
626 | connector.transactionEnergyActiveImportRegisterValue += energyValueRounded; | |
627 | } else { | |
628 | connector.energyActiveImportRegisterValue = 0; | |
629 | connector.transactionEnergyActiveImportRegisterValue = 0; | |
630 | } | |
631 | meterValue.sampledValue.push( | |
632 | OCPP16ServiceUtils.buildSampledValue( | |
633 | energySampledValueTemplate, | |
634 | Utils.roundTo( | |
635 | chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / | |
636 | unitDivider, | |
637 | 2 | |
638 | ) | |
639 | ) | |
640 | ); | |
641 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
642 | if (energyValueRounded > maxEnergyRounded || debug) { | |
643 | logger.error( | |
644 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
645 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
646 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
647 | }: connectorId ${connectorId}, transaction ${ | |
648 | connector.transactionId | |
649 | }, value: ${energyValueRounded}/${maxEnergyRounded}, duration: ${Utils.roundTo( | |
650 | interval / (3600 * 1000), | |
651 | 4 | |
652 | )}h` | |
653 | ); | |
654 | } | |
655 | } | |
656 | return meterValue; | |
657 | } | |
658 | ||
e7aeea18 JB |
659 | public static buildTransactionBeginMeterValue( |
660 | chargingStation: ChargingStation, | |
661 | connectorId: number, | |
78085c42 | 662 | meterStart: number |
e7aeea18 | 663 | ): OCPP16MeterValue { |
fd0c36fa JB |
664 | const meterValue: OCPP16MeterValue = { |
665 | timestamp: new Date().toISOString(), | |
666 | sampledValue: [], | |
667 | }; | |
9ccca265 JB |
668 | // Energy.Active.Import.Register measurand (default) |
669 | const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
670 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
e7aeea18 JB |
671 | meterValue.sampledValue.push( |
672 | OCPP16ServiceUtils.buildSampledValue( | |
673 | sampledValueTemplate, | |
78085c42 | 674 | Utils.roundTo(meterStart / unitDivider, 4), |
e7aeea18 JB |
675 | MeterValueContext.TRANSACTION_BEGIN |
676 | ) | |
677 | ); | |
fd0c36fa JB |
678 | return meterValue; |
679 | } | |
680 | ||
e7aeea18 JB |
681 | public static buildTransactionEndMeterValue( |
682 | chargingStation: ChargingStation, | |
683 | connectorId: number, | |
78085c42 | 684 | meterStop: number |
e7aeea18 | 685 | ): OCPP16MeterValue { |
fd0c36fa JB |
686 | const meterValue: OCPP16MeterValue = { |
687 | timestamp: new Date().toISOString(), | |
688 | sampledValue: [], | |
689 | }; | |
9ccca265 JB |
690 | // Energy.Active.Import.Register measurand (default) |
691 | const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
692 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
e7aeea18 JB |
693 | meterValue.sampledValue.push( |
694 | OCPP16ServiceUtils.buildSampledValue( | |
695 | sampledValueTemplate, | |
78085c42 | 696 | Utils.roundTo(meterStop / unitDivider, 4), |
e7aeea18 JB |
697 | MeterValueContext.TRANSACTION_END |
698 | ) | |
699 | ); | |
fd0c36fa JB |
700 | return meterValue; |
701 | } | |
702 | ||
e7aeea18 JB |
703 | public static buildTransactionDataMeterValues( |
704 | transactionBeginMeterValue: OCPP16MeterValue, | |
705 | transactionEndMeterValue: OCPP16MeterValue | |
706 | ): OCPP16MeterValue[] { | |
fd0c36fa JB |
707 | const meterValues: OCPP16MeterValue[] = []; |
708 | meterValues.push(transactionBeginMeterValue); | |
709 | meterValues.push(transactionEndMeterValue); | |
710 | return meterValues; | |
711 | } | |
6ed92bc1 | 712 | } |