1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
3 import { ACElectricUtils
, DCElectricUtils
} from
'../../../utils/ElectricUtils';
4 import { CurrentType
, Voltage
} from
'../../../types/ChargingStationTemplate';
5 import MeasurandPerPhaseSampledValueTemplates
, {
7 } from
'../../../types/MeasurandPerPhaseSampledValueTemplates';
13 OCPP16MeterValueMeasurand
,
14 OCPP16MeterValuePhase
,
16 } from
'../../../types/ocpp/1.6/MeterValues';
18 OCPP16IncomingRequestCommand
,
20 } from
'../../../types/ocpp/1.6/Requests';
22 OCPP16StandardParametersKey
,
23 OCPP16SupportedFeatureProfiles
,
24 } from
'../../../types/ocpp/1.6/Configuration';
26 import type ChargingStation from
'../../ChargingStation';
27 import Constants from
'../../../utils/Constants';
28 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
29 import MeasurandValues from
'../../../types/MeasurandValues';
30 import OCPPError from
'../../../exception/OCPPError';
31 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
32 import Utils from
'../../../utils/Utils';
33 import logger from
'../../../utils/Logger';
35 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
36 public static checkFeatureProfile(
37 chargingStation
: ChargingStation
,
38 featureProfile
: OCPP16SupportedFeatureProfiles
,
39 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
41 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
43 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
44 OCPP16StandardParametersKey.SupportedFeatureProfiles
52 public static buildMeterValue(
53 chargingStation
: ChargingStation
,
55 transactionId
: number,
59 const meterValue
: OCPP16MeterValue
= {
60 timestamp
: new Date().toISOString(),
63 const connector
= chargingStation
.getConnectorStatus(connectorId
);
65 const socSampledValueTemplate
= chargingStation
.getSampledValueTemplate(
67 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
69 if (socSampledValueTemplate
) {
70 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
71 ? Utils
.getRandomFloatFluctuatedRounded(
72 parseInt(socSampledValueTemplate
.value
),
73 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
75 : Utils
.getRandomInteger(100);
76 meterValue
.sampledValue
.push(
77 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
79 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
80 if (Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > 100 || debug
) {
82 `${chargingStation.logPrefix()} MeterValues measurand ${
83 meterValue.sampledValue[sampledValuesIndex].measurand ??
84 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
85 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
86 meterValue.sampledValue[sampledValuesIndex].value
92 const voltageSampledValueTemplate
= chargingStation
.getSampledValueTemplate(
94 OCPP16MeterValueMeasurand
.VOLTAGE
96 if (voltageSampledValueTemplate
) {
97 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
98 ? parseInt(voltageSampledValueTemplate
.value
)
99 : chargingStation
.getVoltageOut();
100 const fluctuationPercent
=
101 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
102 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
103 voltageSampledValueTemplateValue
,
107 chargingStation
.getNumberOfPhases() !== 3 ||
108 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
110 meterValue
.sampledValue
.push(
111 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
116 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
119 const phaseLineToNeutralValue
= `L${phase}-N`;
120 const voltagePhaseLineToNeutralSampledValueTemplate
=
121 chargingStation
.getSampledValueTemplate(
123 OCPP16MeterValueMeasurand
.VOLTAGE
,
124 phaseLineToNeutralValue
as OCPP16MeterValuePhase
126 let voltagePhaseLineToNeutralMeasurandValue
: number;
127 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
128 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
129 voltagePhaseLineToNeutralSampledValueTemplate
.value
130 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
131 : chargingStation
.getVoltageOut();
132 const fluctuationPhaseToNeutralPercent
=
133 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
134 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
135 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
136 voltagePhaseLineToNeutralSampledValueTemplateValue
,
137 fluctuationPhaseToNeutralPercent
140 meterValue
.sampledValue
.push(
141 OCPP16ServiceUtils
.buildSampledValue(
142 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
143 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
145 phaseLineToNeutralValue
as OCPP16MeterValuePhase
148 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
149 const phaseLineToLineValue
= `L${phase}-L${
150 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
151 ? (phase + 1) % chargingStation.getNumberOfPhases()
152 : chargingStation.getNumberOfPhases()
154 const voltagePhaseLineToLineSampledValueTemplate
=
155 chargingStation
.getSampledValueTemplate(
157 OCPP16MeterValueMeasurand
.VOLTAGE
,
158 phaseLineToLineValue
as OCPP16MeterValuePhase
160 let voltagePhaseLineToLineMeasurandValue
: number;
161 if (voltagePhaseLineToLineSampledValueTemplate
) {
162 const voltagePhaseLineToLineSampledValueTemplateValue
=
163 voltagePhaseLineToLineSampledValueTemplate
.value
164 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
165 : Voltage
.VOLTAGE_400
;
166 const fluctuationPhaseLineToLinePercent
=
167 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
168 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
169 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
170 voltagePhaseLineToLineSampledValueTemplateValue
,
171 fluctuationPhaseLineToLinePercent
174 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
178 meterValue
.sampledValue
.push(
179 OCPP16ServiceUtils
.buildSampledValue(
180 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
181 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
183 phaseLineToLineValue
as OCPP16MeterValuePhase
189 // Power.Active.Import measurand
190 const powerSampledValueTemplate
= chargingStation
.getSampledValueTemplate(
192 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
194 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
195 if (chargingStation
.getNumberOfPhases() === 3) {
196 powerPerPhaseSampledValueTemplates
= {
197 L1
: chargingStation
.getSampledValueTemplate(
199 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
200 OCPP16MeterValuePhase
.L1_N
202 L2
: chargingStation
.getSampledValueTemplate(
204 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
205 OCPP16MeterValuePhase
.L2_N
207 L3
: chargingStation
.getSampledValueTemplate(
209 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
210 OCPP16MeterValuePhase
.L3_N
214 if (powerSampledValueTemplate
) {
215 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
217 powerSampledValueTemplate
.measurand
219 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
220 powerSampledValueTemplate.measurand ??
221 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
222 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
223 chargingStation.templateFile
224 }, cannot calculate ${
225 powerSampledValueTemplate.measurand ??
226 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
228 const powerMeasurandValues
= {} as MeasurandValues
;
229 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
230 const connectorMaximumAvailablePower
=
231 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
232 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
233 const connectorMaximumPowerPerPhase
= Math.round(
234 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
236 switch (chargingStation
.getCurrentOutType()) {
238 if (chargingStation
.getNumberOfPhases() === 3) {
239 const defaultFluctuatedPowerPerPhase
=
240 powerSampledValueTemplate
.value
&&
241 Utils
.getRandomFloatFluctuatedRounded(
242 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
243 powerSampledValueTemplate
.value
,
244 connectorMaximumPower
/ unitDivider
,
245 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
246 ) / chargingStation
.getNumberOfPhases(),
247 powerSampledValueTemplate
.fluctuationPercent
??
248 Constants
.DEFAULT_FLUCTUATION_PERCENT
250 const phase1FluctuatedValue
=
251 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
252 Utils
.getRandomFloatFluctuatedRounded(
253 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
254 powerPerPhaseSampledValueTemplates
.L1
.value
,
255 connectorMaximumPowerPerPhase
/ unitDivider
,
256 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
258 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
259 Constants
.DEFAULT_FLUCTUATION_PERCENT
261 const phase2FluctuatedValue
=
262 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
263 Utils
.getRandomFloatFluctuatedRounded(
264 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
265 powerPerPhaseSampledValueTemplates
.L2
.value
,
266 connectorMaximumPowerPerPhase
/ unitDivider
,
267 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
269 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
270 Constants
.DEFAULT_FLUCTUATION_PERCENT
272 const phase3FluctuatedValue
=
273 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
274 Utils
.getRandomFloatFluctuatedRounded(
275 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
276 powerPerPhaseSampledValueTemplates
.L3
.value
,
277 connectorMaximumPowerPerPhase
/ unitDivider
,
278 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
280 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
281 Constants
.DEFAULT_FLUCTUATION_PERCENT
283 powerMeasurandValues
.L1
=
284 phase1FluctuatedValue
??
285 defaultFluctuatedPowerPerPhase
??
286 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
287 powerMeasurandValues
.L2
=
288 phase2FluctuatedValue
??
289 defaultFluctuatedPowerPerPhase
??
290 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
291 powerMeasurandValues
.L3
=
292 phase3FluctuatedValue
??
293 defaultFluctuatedPowerPerPhase
??
294 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
296 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
297 ? Utils
.getRandomFloatFluctuatedRounded(
298 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
299 powerSampledValueTemplate
.value
,
300 connectorMaximumPower
/ unitDivider
,
301 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
303 powerSampledValueTemplate
.fluctuationPercent
??
304 Constants
.DEFAULT_FLUCTUATION_PERCENT
306 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
307 powerMeasurandValues
.L2
= 0;
308 powerMeasurandValues
.L3
= 0;
310 powerMeasurandValues
.allPhases
= Utils
.roundTo(
311 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
316 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
317 ? Utils
.getRandomFloatFluctuatedRounded(
318 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
319 powerSampledValueTemplate
.value
,
320 connectorMaximumPower
/ unitDivider
,
321 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
323 powerSampledValueTemplate
.fluctuationPercent
??
324 Constants
.DEFAULT_FLUCTUATION_PERCENT
326 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
329 logger
.error(errMsg
);
330 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
332 meterValue
.sampledValue
.push(
333 OCPP16ServiceUtils
.buildSampledValue(
334 powerSampledValueTemplate
,
335 powerMeasurandValues
.allPhases
338 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
339 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
341 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
342 connectorMaximumPowerRounded
||
346 `${chargingStation.logPrefix()} MeterValues measurand ${
347 meterValue.sampledValue[sampledValuesIndex].measurand ??
348 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
349 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
350 meterValue.sampledValue[sampledValuesIndex].value
351 }/${connectorMaximumPowerRounded}`
356 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
359 const phaseValue
= `L${phase}-N`;
360 meterValue
.sampledValue
.push(
361 OCPP16ServiceUtils
.buildSampledValue(
362 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
363 powerSampledValueTemplate
,
364 powerMeasurandValues
[`L${phase}`] as number,
366 phaseValue
as OCPP16MeterValuePhase
369 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
370 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
371 connectorMaximumPowerPerPhase
/ unitDivider
,
375 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
376 connectorMaximumPowerPerPhaseRounded
||
380 `${chargingStation.logPrefix()} MeterValues measurand ${
381 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
382 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
384 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
385 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
386 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
387 }/${connectorMaximumPowerPerPhaseRounded}`
392 // Current.Import measurand
393 const currentSampledValueTemplate
= chargingStation
.getSampledValueTemplate(
395 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
397 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
398 if (chargingStation
.getNumberOfPhases() === 3) {
399 currentPerPhaseSampledValueTemplates
= {
400 L1
: chargingStation
.getSampledValueTemplate(
402 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
403 OCPP16MeterValuePhase
.L1
405 L2
: chargingStation
.getSampledValueTemplate(
407 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
408 OCPP16MeterValuePhase
.L2
410 L3
: chargingStation
.getSampledValueTemplate(
412 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
413 OCPP16MeterValuePhase
.L3
417 if (currentSampledValueTemplate
) {
418 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
420 currentSampledValueTemplate
.measurand
422 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
423 currentSampledValueTemplate.measurand ??
424 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
425 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
426 chargingStation.templateFile
427 }, cannot calculate ${
428 currentSampledValueTemplate.measurand ??
429 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
431 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
432 const connectorMaximumAvailablePower
=
433 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
434 let connectorMaximumAmperage
: number;
435 switch (chargingStation
.getCurrentOutType()) {
437 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
438 chargingStation
.getNumberOfPhases(),
439 connectorMaximumAvailablePower
,
440 chargingStation
.getVoltageOut()
442 if (chargingStation
.getNumberOfPhases() === 3) {
443 const defaultFluctuatedAmperagePerPhase
=
444 currentSampledValueTemplate
.value
&&
445 Utils
.getRandomFloatFluctuatedRounded(
446 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
447 currentSampledValueTemplate
.value
,
448 connectorMaximumAmperage
,
449 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
451 currentSampledValueTemplate
.fluctuationPercent
??
452 Constants
.DEFAULT_FLUCTUATION_PERCENT
454 const phase1FluctuatedValue
=
455 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
456 Utils
.getRandomFloatFluctuatedRounded(
457 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
458 currentPerPhaseSampledValueTemplates
.L1
.value
,
459 connectorMaximumAmperage
,
460 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
462 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
463 Constants
.DEFAULT_FLUCTUATION_PERCENT
465 const phase2FluctuatedValue
=
466 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
467 Utils
.getRandomFloatFluctuatedRounded(
468 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
469 currentPerPhaseSampledValueTemplates
.L2
.value
,
470 connectorMaximumAmperage
,
471 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
473 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
474 Constants
.DEFAULT_FLUCTUATION_PERCENT
476 const phase3FluctuatedValue
=
477 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
478 Utils
.getRandomFloatFluctuatedRounded(
479 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
480 currentPerPhaseSampledValueTemplates
.L3
.value
,
481 connectorMaximumAmperage
,
482 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
484 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
485 Constants
.DEFAULT_FLUCTUATION_PERCENT
487 currentMeasurandValues
.L1
=
488 phase1FluctuatedValue
??
489 defaultFluctuatedAmperagePerPhase
??
490 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
491 currentMeasurandValues
.L2
=
492 phase2FluctuatedValue
??
493 defaultFluctuatedAmperagePerPhase
??
494 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
495 currentMeasurandValues
.L3
=
496 phase3FluctuatedValue
??
497 defaultFluctuatedAmperagePerPhase
??
498 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
500 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
501 ? Utils
.getRandomFloatFluctuatedRounded(
502 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
503 currentSampledValueTemplate
.value
,
504 connectorMaximumAmperage
,
505 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
507 currentSampledValueTemplate
.fluctuationPercent
??
508 Constants
.DEFAULT_FLUCTUATION_PERCENT
510 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
511 currentMeasurandValues
.L2
= 0;
512 currentMeasurandValues
.L3
= 0;
514 currentMeasurandValues
.allPhases
= Utils
.roundTo(
515 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
516 chargingStation
.getNumberOfPhases(),
521 connectorMaximumAmperage
= DCElectricUtils
.amperage(
522 connectorMaximumAvailablePower
,
523 chargingStation
.getVoltageOut()
525 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
526 ? Utils
.getRandomFloatFluctuatedRounded(
527 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
528 currentSampledValueTemplate
.value
,
529 connectorMaximumAmperage
,
530 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
532 currentSampledValueTemplate
.fluctuationPercent
??
533 Constants
.DEFAULT_FLUCTUATION_PERCENT
535 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
538 logger
.error(errMsg
);
539 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
541 meterValue
.sampledValue
.push(
542 OCPP16ServiceUtils
.buildSampledValue(
543 currentSampledValueTemplate
,
544 currentMeasurandValues
.allPhases
547 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
549 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
550 connectorMaximumAmperage
||
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 }/${connectorMaximumAmperage}`
564 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
567 const phaseValue
= `L${phase}`;
568 meterValue
.sampledValue
.push(
569 OCPP16ServiceUtils
.buildSampledValue(
570 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
571 currentSampledValueTemplate
,
572 currentMeasurandValues
[phaseValue
] as number,
574 phaseValue
as OCPP16MeterValuePhase
577 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
579 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
580 connectorMaximumAmperage
||
584 `${chargingStation.logPrefix()} MeterValues measurand ${
585 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
586 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
588 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
589 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
590 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
591 }/${connectorMaximumAmperage}`
596 // Energy.Active.Import.Register measurand (default)
597 const energySampledValueTemplate
= chargingStation
.getSampledValueTemplate(connectorId
);
598 if (energySampledValueTemplate
) {
599 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
601 energySampledValueTemplate
.measurand
604 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
605 const connectorMaximumAvailablePower
=
606 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
607 const connectorMaximumEnergyRounded
= Utils
.roundTo(
608 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
611 const energyValueRounded
= energySampledValueTemplate
.value
612 ? // Cumulate the fluctuated value around the static one
613 Utils
.getRandomFloatFluctuatedRounded(
614 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
615 energySampledValueTemplate
.value
,
616 connectorMaximumEnergyRounded
,
618 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
619 unitMultiplier
: unitDivider
,
622 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
624 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
625 // Persist previous value on connector
628 !Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) &&
629 connector
.energyActiveImportRegisterValue
>= 0 &&
630 !Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) &&
631 connector
.transactionEnergyActiveImportRegisterValue
>= 0
633 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
634 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
636 connector
.energyActiveImportRegisterValue
= 0;
637 connector
.transactionEnergyActiveImportRegisterValue
= 0;
639 meterValue
.sampledValue
.push(
640 OCPP16ServiceUtils
.buildSampledValue(
641 energySampledValueTemplate
,
643 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
649 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
650 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
652 `${chargingStation.logPrefix()} MeterValues measurand ${
653 meterValue.sampledValue[sampledValuesIndex].measurand ??
654 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
655 }: connectorId ${connectorId}, transaction ${
656 connector.transactionId
657 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
658 interval / (3600 * 1000),
667 public static buildTransactionBeginMeterValue(
668 chargingStation
: ChargingStation
,
671 ): OCPP16MeterValue
{
672 const meterValue
: OCPP16MeterValue
= {
673 timestamp
: new Date().toISOString(),
676 // Energy.Active.Import.Register measurand (default)
677 const sampledValueTemplate
= chargingStation
.getSampledValueTemplate(connectorId
);
678 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
679 meterValue
.sampledValue
.push(
680 OCPP16ServiceUtils
.buildSampledValue(
681 sampledValueTemplate
,
682 Utils
.roundTo(meterStart
/ unitDivider
, 4),
683 MeterValueContext
.TRANSACTION_BEGIN
689 public static buildTransactionEndMeterValue(
690 chargingStation
: ChargingStation
,
693 ): OCPP16MeterValue
{
694 const meterValue
: OCPP16MeterValue
= {
695 timestamp
: new Date().toISOString(),
698 // Energy.Active.Import.Register measurand (default)
699 const sampledValueTemplate
= chargingStation
.getSampledValueTemplate(connectorId
);
700 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
701 meterValue
.sampledValue
.push(
702 OCPP16ServiceUtils
.buildSampledValue(
703 sampledValueTemplate
,
704 Utils
.roundTo(meterStop
/ unitDivider
, 4),
705 MeterValueContext
.TRANSACTION_END
711 public static buildTransactionDataMeterValues(
712 transactionBeginMeterValue
: OCPP16MeterValue
,
713 transactionEndMeterValue
: OCPP16MeterValue
714 ): OCPP16MeterValue
[] {
715 const meterValues
: OCPP16MeterValue
[] = [];
716 meterValues
.push(transactionBeginMeterValue
);
717 meterValues
.push(transactionEndMeterValue
);
721 private static buildSampledValue(
722 sampledValueTemplate
: SampledValueTemplate
,
724 context
?: MeterValueContext
,
725 phase
?: OCPP16MeterValuePhase
726 ): OCPP16SampledValue
{
727 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
728 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
729 const sampledValueLocation
=
730 sampledValueTemplate
?.location
??
731 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
732 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
734 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
735 unit
: sampledValueTemplate
.unit
,
737 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
738 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
739 measurand
: sampledValueTemplate
.measurand
,
741 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
742 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
743 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
747 private static checkMeasurandPowerDivider(
748 chargingStation
: ChargingStation
,
749 measurandType
: OCPP16MeterValueMeasurand
751 if (Utils
.isUndefined(chargingStation
.stationInfo
.powerDivider
)) {
752 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
753 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
754 }: powerDivider is undefined`;
755 logger
.error(errMsg
);
756 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
757 } else if (chargingStation
.stationInfo
?.powerDivider
<= 0) {
758 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
759 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
760 }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
761 logger
.error(errMsg
);
762 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
766 private static getMeasurandDefaultLocation(
767 measurandType
: OCPP16MeterValueMeasurand
768 ): MeterValueLocation
| undefined {
769 switch (measurandType
) {
770 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
771 return MeterValueLocation
.EV
;
775 private static getMeasurandDefaultUnit(
776 measurandType
: OCPP16MeterValueMeasurand
777 ): MeterValueUnit
| undefined {
778 switch (measurandType
) {
779 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
780 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
781 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
782 return MeterValueUnit
.AMP
;
783 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
784 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
785 return MeterValueUnit
.WATT_HOUR
;
786 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
787 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
788 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
789 return MeterValueUnit
.WATT
;
790 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
791 return MeterValueUnit
.PERCENT
;
792 case OCPP16MeterValueMeasurand
.VOLTAGE
:
793 return MeterValueUnit
.VOLT
;