1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
3 import OCPPError from
'../../../exception/OCPPError';
4 import { CurrentType
, Voltage
} from
'../../../types/ChargingStationTemplate';
5 import MeasurandPerPhaseSampledValueTemplates
, {
7 } from
'../../../types/MeasurandPerPhaseSampledValueTemplates';
8 import MeasurandValues from
'../../../types/MeasurandValues';
10 OCPP16StandardParametersKey
,
11 OCPP16SupportedFeatureProfiles
,
12 } from
'../../../types/ocpp/1.6/Configuration';
18 OCPP16MeterValueMeasurand
,
19 OCPP16MeterValuePhase
,
21 } from
'../../../types/ocpp/1.6/MeterValues';
23 OCPP16IncomingRequestCommand
,
25 } from
'../../../types/ocpp/1.6/Requests';
26 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
27 import Constants from
'../../../utils/Constants';
28 import { ACElectricUtils
, DCElectricUtils
} from
'../../../utils/ElectricUtils';
29 import logger from
'../../../utils/Logger';
30 import Utils from
'../../../utils/Utils';
31 import type ChargingStation from
'../../ChargingStation';
32 import { ChargingStationUtils
} from
'../../ChargingStationUtils';
33 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
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
= ChargingStationUtils
.getSampledValueTemplate(
68 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
70 if (socSampledValueTemplate
) {
71 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
72 ? Utils
.getRandomFloatFluctuatedRounded(
73 parseInt(socSampledValueTemplate
.value
),
74 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
76 : Utils
.getRandomInteger(100);
77 meterValue
.sampledValue
.push(
78 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
80 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
81 if (Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > 100 || debug
) {
83 `${chargingStation.logPrefix()} MeterValues measurand ${
84 meterValue.sampledValue[sampledValuesIndex].measurand ??
85 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
86 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
87 meterValue.sampledValue[sampledValuesIndex].value
93 const voltageSampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
96 OCPP16MeterValueMeasurand
.VOLTAGE
98 if (voltageSampledValueTemplate
) {
99 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
100 ? parseInt(voltageSampledValueTemplate
.value
)
101 : chargingStation
.getVoltageOut();
102 const fluctuationPercent
=
103 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
104 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
105 voltageSampledValueTemplateValue
,
109 chargingStation
.getNumberOfPhases() !== 3 ||
110 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
112 meterValue
.sampledValue
.push(
113 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
118 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
121 const phaseLineToNeutralValue
= `L${phase}-N`;
122 const voltagePhaseLineToNeutralSampledValueTemplate
=
123 ChargingStationUtils
.getSampledValueTemplate(
126 OCPP16MeterValueMeasurand
.VOLTAGE
,
127 phaseLineToNeutralValue
as OCPP16MeterValuePhase
129 let voltagePhaseLineToNeutralMeasurandValue
: number;
130 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
131 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
132 voltagePhaseLineToNeutralSampledValueTemplate
.value
133 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
134 : chargingStation
.getVoltageOut();
135 const fluctuationPhaseToNeutralPercent
=
136 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
137 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
138 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
139 voltagePhaseLineToNeutralSampledValueTemplateValue
,
140 fluctuationPhaseToNeutralPercent
143 meterValue
.sampledValue
.push(
144 OCPP16ServiceUtils
.buildSampledValue(
145 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
146 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
148 phaseLineToNeutralValue
as OCPP16MeterValuePhase
151 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
152 const phaseLineToLineValue
= `L${phase}-L${
153 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
154 ? (phase + 1) % chargingStation.getNumberOfPhases()
155 : chargingStation.getNumberOfPhases()
157 const voltagePhaseLineToLineSampledValueTemplate
=
158 ChargingStationUtils
.getSampledValueTemplate(
161 OCPP16MeterValueMeasurand
.VOLTAGE
,
162 phaseLineToLineValue
as OCPP16MeterValuePhase
164 let voltagePhaseLineToLineMeasurandValue
: number;
165 if (voltagePhaseLineToLineSampledValueTemplate
) {
166 const voltagePhaseLineToLineSampledValueTemplateValue
=
167 voltagePhaseLineToLineSampledValueTemplate
.value
168 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
169 : Voltage
.VOLTAGE_400
;
170 const fluctuationPhaseLineToLinePercent
=
171 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
172 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
173 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
174 voltagePhaseLineToLineSampledValueTemplateValue
,
175 fluctuationPhaseLineToLinePercent
178 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
182 meterValue
.sampledValue
.push(
183 OCPP16ServiceUtils
.buildSampledValue(
184 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
185 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
187 phaseLineToLineValue
as OCPP16MeterValuePhase
193 // Power.Active.Import measurand
194 const powerSampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
197 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
199 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
200 if (chargingStation
.getNumberOfPhases() === 3) {
201 powerPerPhaseSampledValueTemplates
= {
202 L1
: ChargingStationUtils
.getSampledValueTemplate(
205 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
206 OCPP16MeterValuePhase
.L1_N
208 L2
: ChargingStationUtils
.getSampledValueTemplate(
211 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
212 OCPP16MeterValuePhase
.L2_N
214 L3
: ChargingStationUtils
.getSampledValueTemplate(
217 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
218 OCPP16MeterValuePhase
.L3_N
222 if (powerSampledValueTemplate
) {
223 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
225 powerSampledValueTemplate
.measurand
227 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
228 powerSampledValueTemplate.measurand ??
229 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
230 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
231 chargingStation.templateFile
232 }, cannot calculate ${
233 powerSampledValueTemplate.measurand ??
234 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
236 const powerMeasurandValues
= {} as MeasurandValues
;
237 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
238 const connectorMaximumAvailablePower
=
239 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
240 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
241 const connectorMaximumPowerPerPhase
= Math.round(
242 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
244 switch (chargingStation
.getCurrentOutType()) {
246 if (chargingStation
.getNumberOfPhases() === 3) {
247 const defaultFluctuatedPowerPerPhase
=
248 powerSampledValueTemplate
.value
&&
249 Utils
.getRandomFloatFluctuatedRounded(
250 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
251 powerSampledValueTemplate
.value
,
252 connectorMaximumPower
/ unitDivider
,
253 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
254 ) / chargingStation
.getNumberOfPhases(),
255 powerSampledValueTemplate
.fluctuationPercent
??
256 Constants
.DEFAULT_FLUCTUATION_PERCENT
258 const phase1FluctuatedValue
=
259 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
260 Utils
.getRandomFloatFluctuatedRounded(
261 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
262 powerPerPhaseSampledValueTemplates
.L1
.value
,
263 connectorMaximumPowerPerPhase
/ unitDivider
,
264 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
266 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
267 Constants
.DEFAULT_FLUCTUATION_PERCENT
269 const phase2FluctuatedValue
=
270 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
271 Utils
.getRandomFloatFluctuatedRounded(
272 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
273 powerPerPhaseSampledValueTemplates
.L2
.value
,
274 connectorMaximumPowerPerPhase
/ unitDivider
,
275 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
277 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
278 Constants
.DEFAULT_FLUCTUATION_PERCENT
280 const phase3FluctuatedValue
=
281 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
282 Utils
.getRandomFloatFluctuatedRounded(
283 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
284 powerPerPhaseSampledValueTemplates
.L3
.value
,
285 connectorMaximumPowerPerPhase
/ unitDivider
,
286 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
288 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
289 Constants
.DEFAULT_FLUCTUATION_PERCENT
291 powerMeasurandValues
.L1
=
292 phase1FluctuatedValue
??
293 defaultFluctuatedPowerPerPhase
??
294 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
295 powerMeasurandValues
.L2
=
296 phase2FluctuatedValue
??
297 defaultFluctuatedPowerPerPhase
??
298 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
299 powerMeasurandValues
.L3
=
300 phase3FluctuatedValue
??
301 defaultFluctuatedPowerPerPhase
??
302 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
304 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
305 ? Utils
.getRandomFloatFluctuatedRounded(
306 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
307 powerSampledValueTemplate
.value
,
308 connectorMaximumPower
/ unitDivider
,
309 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
311 powerSampledValueTemplate
.fluctuationPercent
??
312 Constants
.DEFAULT_FLUCTUATION_PERCENT
314 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
315 powerMeasurandValues
.L2
= 0;
316 powerMeasurandValues
.L3
= 0;
318 powerMeasurandValues
.allPhases
= Utils
.roundTo(
319 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
324 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
325 ? Utils
.getRandomFloatFluctuatedRounded(
326 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
327 powerSampledValueTemplate
.value
,
328 connectorMaximumPower
/ unitDivider
,
329 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
331 powerSampledValueTemplate
.fluctuationPercent
??
332 Constants
.DEFAULT_FLUCTUATION_PERCENT
334 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
337 logger
.error(errMsg
);
338 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
340 meterValue
.sampledValue
.push(
341 OCPP16ServiceUtils
.buildSampledValue(
342 powerSampledValueTemplate
,
343 powerMeasurandValues
.allPhases
346 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
347 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
349 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
350 connectorMaximumPowerRounded
||
354 `${chargingStation.logPrefix()} MeterValues measurand ${
355 meterValue.sampledValue[sampledValuesIndex].measurand ??
356 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
357 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
358 meterValue.sampledValue[sampledValuesIndex].value
359 }/${connectorMaximumPowerRounded}`
364 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
367 const phaseValue
= `L${phase}-N`;
368 meterValue
.sampledValue
.push(
369 OCPP16ServiceUtils
.buildSampledValue(
370 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
371 powerSampledValueTemplate
,
372 powerMeasurandValues
[`L${phase}`] as number,
374 phaseValue
as OCPP16MeterValuePhase
377 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
378 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
379 connectorMaximumPowerPerPhase
/ unitDivider
,
383 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
384 connectorMaximumPowerPerPhaseRounded
||
388 `${chargingStation.logPrefix()} MeterValues measurand ${
389 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
390 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
392 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
393 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
394 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
395 }/${connectorMaximumPowerPerPhaseRounded}`
400 // Current.Import measurand
401 const currentSampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
404 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
406 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
407 if (chargingStation
.getNumberOfPhases() === 3) {
408 currentPerPhaseSampledValueTemplates
= {
409 L1
: ChargingStationUtils
.getSampledValueTemplate(
412 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
413 OCPP16MeterValuePhase
.L1
415 L2
: ChargingStationUtils
.getSampledValueTemplate(
418 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
419 OCPP16MeterValuePhase
.L2
421 L3
: ChargingStationUtils
.getSampledValueTemplate(
424 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
425 OCPP16MeterValuePhase
.L3
429 if (currentSampledValueTemplate
) {
430 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
432 currentSampledValueTemplate
.measurand
434 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
435 currentSampledValueTemplate.measurand ??
436 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
437 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
438 chargingStation.templateFile
439 }, cannot calculate ${
440 currentSampledValueTemplate.measurand ??
441 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
443 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
444 const connectorMaximumAvailablePower
=
445 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
446 let connectorMaximumAmperage
: number;
447 switch (chargingStation
.getCurrentOutType()) {
449 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
450 chargingStation
.getNumberOfPhases(),
451 connectorMaximumAvailablePower
,
452 chargingStation
.getVoltageOut()
454 if (chargingStation
.getNumberOfPhases() === 3) {
455 const defaultFluctuatedAmperagePerPhase
=
456 currentSampledValueTemplate
.value
&&
457 Utils
.getRandomFloatFluctuatedRounded(
458 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
459 currentSampledValueTemplate
.value
,
460 connectorMaximumAmperage
,
461 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
463 currentSampledValueTemplate
.fluctuationPercent
??
464 Constants
.DEFAULT_FLUCTUATION_PERCENT
466 const phase1FluctuatedValue
=
467 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
468 Utils
.getRandomFloatFluctuatedRounded(
469 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
470 currentPerPhaseSampledValueTemplates
.L1
.value
,
471 connectorMaximumAmperage
,
472 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
474 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
475 Constants
.DEFAULT_FLUCTUATION_PERCENT
477 const phase2FluctuatedValue
=
478 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
479 Utils
.getRandomFloatFluctuatedRounded(
480 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
481 currentPerPhaseSampledValueTemplates
.L2
.value
,
482 connectorMaximumAmperage
,
483 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
485 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
486 Constants
.DEFAULT_FLUCTUATION_PERCENT
488 const phase3FluctuatedValue
=
489 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
490 Utils
.getRandomFloatFluctuatedRounded(
491 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
492 currentPerPhaseSampledValueTemplates
.L3
.value
,
493 connectorMaximumAmperage
,
494 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
496 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
497 Constants
.DEFAULT_FLUCTUATION_PERCENT
499 currentMeasurandValues
.L1
=
500 phase1FluctuatedValue
??
501 defaultFluctuatedAmperagePerPhase
??
502 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
503 currentMeasurandValues
.L2
=
504 phase2FluctuatedValue
??
505 defaultFluctuatedAmperagePerPhase
??
506 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
507 currentMeasurandValues
.L3
=
508 phase3FluctuatedValue
??
509 defaultFluctuatedAmperagePerPhase
??
510 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
512 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
513 ? Utils
.getRandomFloatFluctuatedRounded(
514 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
515 currentSampledValueTemplate
.value
,
516 connectorMaximumAmperage
,
517 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
519 currentSampledValueTemplate
.fluctuationPercent
??
520 Constants
.DEFAULT_FLUCTUATION_PERCENT
522 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
523 currentMeasurandValues
.L2
= 0;
524 currentMeasurandValues
.L3
= 0;
526 currentMeasurandValues
.allPhases
= Utils
.roundTo(
527 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
528 chargingStation
.getNumberOfPhases(),
533 connectorMaximumAmperage
= DCElectricUtils
.amperage(
534 connectorMaximumAvailablePower
,
535 chargingStation
.getVoltageOut()
537 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
538 ? Utils
.getRandomFloatFluctuatedRounded(
539 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
540 currentSampledValueTemplate
.value
,
541 connectorMaximumAmperage
,
542 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
544 currentSampledValueTemplate
.fluctuationPercent
??
545 Constants
.DEFAULT_FLUCTUATION_PERCENT
547 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
550 logger
.error(errMsg
);
551 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
553 meterValue
.sampledValue
.push(
554 OCPP16ServiceUtils
.buildSampledValue(
555 currentSampledValueTemplate
,
556 currentMeasurandValues
.allPhases
559 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
561 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
562 connectorMaximumAmperage
||
566 `${chargingStation.logPrefix()} MeterValues measurand ${
567 meterValue.sampledValue[sampledValuesIndex].measurand ??
568 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
569 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
570 meterValue.sampledValue[sampledValuesIndex].value
571 }/${connectorMaximumAmperage}`
576 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
579 const phaseValue
= `L${phase}`;
580 meterValue
.sampledValue
.push(
581 OCPP16ServiceUtils
.buildSampledValue(
582 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
583 currentSampledValueTemplate
,
584 currentMeasurandValues
[phaseValue
] as number,
586 phaseValue
as OCPP16MeterValuePhase
589 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
591 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
592 connectorMaximumAmperage
||
596 `${chargingStation.logPrefix()} MeterValues measurand ${
597 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
598 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
600 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
601 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
602 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
603 }/${connectorMaximumAmperage}`
608 // Energy.Active.Import.Register measurand (default)
609 const energySampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
613 if (energySampledValueTemplate
) {
614 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
616 energySampledValueTemplate
.measurand
619 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
620 const connectorMaximumAvailablePower
=
621 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
622 const connectorMaximumEnergyRounded
= Utils
.roundTo(
623 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
626 const energyValueRounded
= energySampledValueTemplate
.value
627 ? // Cumulate the fluctuated value around the static one
628 Utils
.getRandomFloatFluctuatedRounded(
629 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
630 energySampledValueTemplate
.value
,
631 connectorMaximumEnergyRounded
,
633 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
634 unitMultiplier
: unitDivider
,
637 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
639 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
640 // Persist previous value on connector
643 !Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) &&
644 connector
.energyActiveImportRegisterValue
>= 0 &&
645 !Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) &&
646 connector
.transactionEnergyActiveImportRegisterValue
>= 0
648 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
649 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
651 connector
.energyActiveImportRegisterValue
= 0;
652 connector
.transactionEnergyActiveImportRegisterValue
= 0;
654 meterValue
.sampledValue
.push(
655 OCPP16ServiceUtils
.buildSampledValue(
656 energySampledValueTemplate
,
658 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
664 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
665 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
667 `${chargingStation.logPrefix()} MeterValues measurand ${
668 meterValue.sampledValue[sampledValuesIndex].measurand ??
669 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
670 }: connectorId ${connectorId}, transaction ${
671 connector.transactionId
672 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
673 interval / (3600 * 1000),
682 public static buildTransactionBeginMeterValue(
683 chargingStation
: ChargingStation
,
686 ): OCPP16MeterValue
{
687 const meterValue
: OCPP16MeterValue
= {
688 timestamp
: new Date().toISOString(),
691 // Energy.Active.Import.Register measurand (default)
692 const sampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
696 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
697 meterValue
.sampledValue
.push(
698 OCPP16ServiceUtils
.buildSampledValue(
699 sampledValueTemplate
,
700 Utils
.roundTo(meterStart
/ unitDivider
, 4),
701 MeterValueContext
.TRANSACTION_BEGIN
707 public static buildTransactionEndMeterValue(
708 chargingStation
: ChargingStation
,
711 ): OCPP16MeterValue
{
712 const meterValue
: OCPP16MeterValue
= {
713 timestamp
: new Date().toISOString(),
716 // Energy.Active.Import.Register measurand (default)
717 const sampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
721 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
722 meterValue
.sampledValue
.push(
723 OCPP16ServiceUtils
.buildSampledValue(
724 sampledValueTemplate
,
725 Utils
.roundTo(meterStop
/ unitDivider
, 4),
726 MeterValueContext
.TRANSACTION_END
732 public static buildTransactionDataMeterValues(
733 transactionBeginMeterValue
: OCPP16MeterValue
,
734 transactionEndMeterValue
: OCPP16MeterValue
735 ): OCPP16MeterValue
[] {
736 const meterValues
: OCPP16MeterValue
[] = [];
737 meterValues
.push(transactionBeginMeterValue
);
738 meterValues
.push(transactionEndMeterValue
);
742 private static buildSampledValue(
743 sampledValueTemplate
: SampledValueTemplate
,
745 context
?: MeterValueContext
,
746 phase
?: OCPP16MeterValuePhase
747 ): OCPP16SampledValue
{
748 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
749 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
750 const sampledValueLocation
=
751 sampledValueTemplate
?.location
??
752 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
753 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
755 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
756 unit
: sampledValueTemplate
.unit
,
758 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
759 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
760 measurand
: sampledValueTemplate
.measurand
,
762 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
763 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
764 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
768 private static checkMeasurandPowerDivider(
769 chargingStation
: ChargingStation
,
770 measurandType
: OCPP16MeterValueMeasurand
772 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
773 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
774 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
775 }: powerDivider is undefined`;
776 logger
.error(errMsg
);
777 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
778 } else if (chargingStation
?.powerDivider
<= 0) {
779 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
780 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
781 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
782 logger
.error(errMsg
);
783 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
787 private static getMeasurandDefaultLocation(
788 measurandType
: OCPP16MeterValueMeasurand
789 ): MeterValueLocation
| undefined {
790 switch (measurandType
) {
791 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
792 return MeterValueLocation
.EV
;
796 private static getMeasurandDefaultUnit(
797 measurandType
: OCPP16MeterValueMeasurand
798 ): MeterValueUnit
| undefined {
799 switch (measurandType
) {
800 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
801 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
802 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
803 return MeterValueUnit
.AMP
;
804 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
805 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
806 return MeterValueUnit
.WATT_HOUR
;
807 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
808 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
809 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
810 return MeterValueUnit
.WATT
;
811 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
812 return MeterValueUnit
.PERCENT
;
813 case OCPP16MeterValueMeasurand
.VOLTAGE
:
814 return MeterValueUnit
.VOLT
;