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