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 { ChargingStationUtils
} from
'../../ChargingStationUtils';
28 import Constants from
'../../../utils/Constants';
29 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
30 import MeasurandValues from
'../../../types/MeasurandValues';
31 import OCPPError from
'../../../exception/OCPPError';
32 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
33 import Utils from
'../../../utils/Utils';
34 import logger from
'../../../utils/Logger';
36 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
37 public static checkFeatureProfile(
38 chargingStation
: ChargingStation
,
39 featureProfile
: OCPP16SupportedFeatureProfiles
,
40 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
42 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
44 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
45 OCPP16StandardParametersKey.SupportedFeatureProfiles
53 public static buildMeterValue(
54 chargingStation
: ChargingStation
,
56 transactionId
: number,
60 const meterValue
: OCPP16MeterValue
= {
61 timestamp
: new Date().toISOString(),
64 const connector
= chargingStation
.getConnectorStatus(connectorId
);
66 const socSampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
69 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
71 if (socSampledValueTemplate
) {
72 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
73 ? Utils
.getRandomFloatFluctuatedRounded(
74 parseInt(socSampledValueTemplate
.value
),
75 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
77 : Utils
.getRandomInteger(100);
78 meterValue
.sampledValue
.push(
79 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
81 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
82 if (Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > 100 || debug
) {
84 `${chargingStation.logPrefix()} MeterValues measurand ${
85 meterValue.sampledValue[sampledValuesIndex].measurand ??
86 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
87 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
88 meterValue.sampledValue[sampledValuesIndex].value
94 const voltageSampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
97 OCPP16MeterValueMeasurand
.VOLTAGE
99 if (voltageSampledValueTemplate
) {
100 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
101 ? parseInt(voltageSampledValueTemplate
.value
)
102 : chargingStation
.getVoltageOut();
103 const fluctuationPercent
=
104 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
105 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
106 voltageSampledValueTemplateValue
,
110 chargingStation
.getNumberOfPhases() !== 3 ||
111 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
113 meterValue
.sampledValue
.push(
114 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
119 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
122 const phaseLineToNeutralValue
= `L${phase}-N`;
123 const voltagePhaseLineToNeutralSampledValueTemplate
=
124 ChargingStationUtils
.getSampledValueTemplate(
127 OCPP16MeterValueMeasurand
.VOLTAGE
,
128 phaseLineToNeutralValue
as OCPP16MeterValuePhase
130 let voltagePhaseLineToNeutralMeasurandValue
: number;
131 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
132 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
133 voltagePhaseLineToNeutralSampledValueTemplate
.value
134 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
135 : chargingStation
.getVoltageOut();
136 const fluctuationPhaseToNeutralPercent
=
137 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
138 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
139 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
140 voltagePhaseLineToNeutralSampledValueTemplateValue
,
141 fluctuationPhaseToNeutralPercent
144 meterValue
.sampledValue
.push(
145 OCPP16ServiceUtils
.buildSampledValue(
146 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
147 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
149 phaseLineToNeutralValue
as OCPP16MeterValuePhase
152 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
153 const phaseLineToLineValue
= `L${phase}-L${
154 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
155 ? (phase + 1) % chargingStation.getNumberOfPhases()
156 : chargingStation.getNumberOfPhases()
158 const voltagePhaseLineToLineSampledValueTemplate
=
159 ChargingStationUtils
.getSampledValueTemplate(
162 OCPP16MeterValueMeasurand
.VOLTAGE
,
163 phaseLineToLineValue
as OCPP16MeterValuePhase
165 let voltagePhaseLineToLineMeasurandValue
: number;
166 if (voltagePhaseLineToLineSampledValueTemplate
) {
167 const voltagePhaseLineToLineSampledValueTemplateValue
=
168 voltagePhaseLineToLineSampledValueTemplate
.value
169 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
170 : Voltage
.VOLTAGE_400
;
171 const fluctuationPhaseLineToLinePercent
=
172 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
173 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
174 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
175 voltagePhaseLineToLineSampledValueTemplateValue
,
176 fluctuationPhaseLineToLinePercent
179 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
183 meterValue
.sampledValue
.push(
184 OCPP16ServiceUtils
.buildSampledValue(
185 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
186 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
188 phaseLineToLineValue
as OCPP16MeterValuePhase
194 // Power.Active.Import measurand
195 const powerSampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
198 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
200 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
201 if (chargingStation
.getNumberOfPhases() === 3) {
202 powerPerPhaseSampledValueTemplates
= {
203 L1
: ChargingStationUtils
.getSampledValueTemplate(
206 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
207 OCPP16MeterValuePhase
.L1_N
209 L2
: ChargingStationUtils
.getSampledValueTemplate(
212 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
213 OCPP16MeterValuePhase
.L2_N
215 L3
: ChargingStationUtils
.getSampledValueTemplate(
218 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
219 OCPP16MeterValuePhase
.L3_N
223 if (powerSampledValueTemplate
) {
224 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
226 powerSampledValueTemplate
.measurand
228 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
229 powerSampledValueTemplate.measurand ??
230 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
231 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
232 chargingStation.templateFile
233 }, cannot calculate ${
234 powerSampledValueTemplate.measurand ??
235 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
237 const powerMeasurandValues
= {} as MeasurandValues
;
238 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
239 const connectorMaximumAvailablePower
=
240 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
241 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
242 const connectorMaximumPowerPerPhase
= Math.round(
243 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
245 switch (chargingStation
.getCurrentOutType()) {
247 if (chargingStation
.getNumberOfPhases() === 3) {
248 const defaultFluctuatedPowerPerPhase
=
249 powerSampledValueTemplate
.value
&&
250 Utils
.getRandomFloatFluctuatedRounded(
251 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
252 powerSampledValueTemplate
.value
,
253 connectorMaximumPower
/ unitDivider
,
254 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
255 ) / chargingStation
.getNumberOfPhases(),
256 powerSampledValueTemplate
.fluctuationPercent
??
257 Constants
.DEFAULT_FLUCTUATION_PERCENT
259 const phase1FluctuatedValue
=
260 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
261 Utils
.getRandomFloatFluctuatedRounded(
262 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
263 powerPerPhaseSampledValueTemplates
.L1
.value
,
264 connectorMaximumPowerPerPhase
/ unitDivider
,
265 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
267 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
268 Constants
.DEFAULT_FLUCTUATION_PERCENT
270 const phase2FluctuatedValue
=
271 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
272 Utils
.getRandomFloatFluctuatedRounded(
273 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
274 powerPerPhaseSampledValueTemplates
.L2
.value
,
275 connectorMaximumPowerPerPhase
/ unitDivider
,
276 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
278 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
279 Constants
.DEFAULT_FLUCTUATION_PERCENT
281 const phase3FluctuatedValue
=
282 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
283 Utils
.getRandomFloatFluctuatedRounded(
284 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
285 powerPerPhaseSampledValueTemplates
.L3
.value
,
286 connectorMaximumPowerPerPhase
/ unitDivider
,
287 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
289 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
290 Constants
.DEFAULT_FLUCTUATION_PERCENT
292 powerMeasurandValues
.L1
=
293 phase1FluctuatedValue
??
294 defaultFluctuatedPowerPerPhase
??
295 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
296 powerMeasurandValues
.L2
=
297 phase2FluctuatedValue
??
298 defaultFluctuatedPowerPerPhase
??
299 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
300 powerMeasurandValues
.L3
=
301 phase3FluctuatedValue
??
302 defaultFluctuatedPowerPerPhase
??
303 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
305 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
306 ? Utils
.getRandomFloatFluctuatedRounded(
307 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
308 powerSampledValueTemplate
.value
,
309 connectorMaximumPower
/ unitDivider
,
310 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
312 powerSampledValueTemplate
.fluctuationPercent
??
313 Constants
.DEFAULT_FLUCTUATION_PERCENT
315 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
316 powerMeasurandValues
.L2
= 0;
317 powerMeasurandValues
.L3
= 0;
319 powerMeasurandValues
.allPhases
= Utils
.roundTo(
320 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
325 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
326 ? Utils
.getRandomFloatFluctuatedRounded(
327 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
328 powerSampledValueTemplate
.value
,
329 connectorMaximumPower
/ unitDivider
,
330 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
332 powerSampledValueTemplate
.fluctuationPercent
??
333 Constants
.DEFAULT_FLUCTUATION_PERCENT
335 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
338 logger
.error(errMsg
);
339 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
341 meterValue
.sampledValue
.push(
342 OCPP16ServiceUtils
.buildSampledValue(
343 powerSampledValueTemplate
,
344 powerMeasurandValues
.allPhases
347 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
348 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
350 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
351 connectorMaximumPowerRounded
||
355 `${chargingStation.logPrefix()} MeterValues measurand ${
356 meterValue.sampledValue[sampledValuesIndex].measurand ??
357 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
358 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
359 meterValue.sampledValue[sampledValuesIndex].value
360 }/${connectorMaximumPowerRounded}`
365 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
368 const phaseValue
= `L${phase}-N`;
369 meterValue
.sampledValue
.push(
370 OCPP16ServiceUtils
.buildSampledValue(
371 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
372 powerSampledValueTemplate
,
373 powerMeasurandValues
[`L${phase}`] as number,
375 phaseValue
as OCPP16MeterValuePhase
378 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
379 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
380 connectorMaximumPowerPerPhase
/ unitDivider
,
384 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
385 connectorMaximumPowerPerPhaseRounded
||
389 `${chargingStation.logPrefix()} MeterValues measurand ${
390 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
391 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
393 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
394 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
395 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
396 }/${connectorMaximumPowerPerPhaseRounded}`
401 // Current.Import measurand
402 const currentSampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
405 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
407 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
408 if (chargingStation
.getNumberOfPhases() === 3) {
409 currentPerPhaseSampledValueTemplates
= {
410 L1
: ChargingStationUtils
.getSampledValueTemplate(
413 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
414 OCPP16MeterValuePhase
.L1
416 L2
: ChargingStationUtils
.getSampledValueTemplate(
419 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
420 OCPP16MeterValuePhase
.L2
422 L3
: ChargingStationUtils
.getSampledValueTemplate(
425 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
426 OCPP16MeterValuePhase
.L3
430 if (currentSampledValueTemplate
) {
431 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
433 currentSampledValueTemplate
.measurand
435 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
436 currentSampledValueTemplate.measurand ??
437 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
438 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
439 chargingStation.templateFile
440 }, cannot calculate ${
441 currentSampledValueTemplate.measurand ??
442 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
444 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
445 const connectorMaximumAvailablePower
=
446 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
447 let connectorMaximumAmperage
: number;
448 switch (chargingStation
.getCurrentOutType()) {
450 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
451 chargingStation
.getNumberOfPhases(),
452 connectorMaximumAvailablePower
,
453 chargingStation
.getVoltageOut()
455 if (chargingStation
.getNumberOfPhases() === 3) {
456 const defaultFluctuatedAmperagePerPhase
=
457 currentSampledValueTemplate
.value
&&
458 Utils
.getRandomFloatFluctuatedRounded(
459 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
460 currentSampledValueTemplate
.value
,
461 connectorMaximumAmperage
,
462 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
464 currentSampledValueTemplate
.fluctuationPercent
??
465 Constants
.DEFAULT_FLUCTUATION_PERCENT
467 const phase1FluctuatedValue
=
468 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
469 Utils
.getRandomFloatFluctuatedRounded(
470 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
471 currentPerPhaseSampledValueTemplates
.L1
.value
,
472 connectorMaximumAmperage
,
473 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
475 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
476 Constants
.DEFAULT_FLUCTUATION_PERCENT
478 const phase2FluctuatedValue
=
479 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
480 Utils
.getRandomFloatFluctuatedRounded(
481 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
482 currentPerPhaseSampledValueTemplates
.L2
.value
,
483 connectorMaximumAmperage
,
484 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
486 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
487 Constants
.DEFAULT_FLUCTUATION_PERCENT
489 const phase3FluctuatedValue
=
490 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
491 Utils
.getRandomFloatFluctuatedRounded(
492 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
493 currentPerPhaseSampledValueTemplates
.L3
.value
,
494 connectorMaximumAmperage
,
495 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
497 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
498 Constants
.DEFAULT_FLUCTUATION_PERCENT
500 currentMeasurandValues
.L1
=
501 phase1FluctuatedValue
??
502 defaultFluctuatedAmperagePerPhase
??
503 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
504 currentMeasurandValues
.L2
=
505 phase2FluctuatedValue
??
506 defaultFluctuatedAmperagePerPhase
??
507 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
508 currentMeasurandValues
.L3
=
509 phase3FluctuatedValue
??
510 defaultFluctuatedAmperagePerPhase
??
511 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
513 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
514 ? Utils
.getRandomFloatFluctuatedRounded(
515 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
516 currentSampledValueTemplate
.value
,
517 connectorMaximumAmperage
,
518 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
520 currentSampledValueTemplate
.fluctuationPercent
??
521 Constants
.DEFAULT_FLUCTUATION_PERCENT
523 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
524 currentMeasurandValues
.L2
= 0;
525 currentMeasurandValues
.L3
= 0;
527 currentMeasurandValues
.allPhases
= Utils
.roundTo(
528 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
529 chargingStation
.getNumberOfPhases(),
534 connectorMaximumAmperage
= DCElectricUtils
.amperage(
535 connectorMaximumAvailablePower
,
536 chargingStation
.getVoltageOut()
538 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
539 ? Utils
.getRandomFloatFluctuatedRounded(
540 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
541 currentSampledValueTemplate
.value
,
542 connectorMaximumAmperage
,
543 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
545 currentSampledValueTemplate
.fluctuationPercent
??
546 Constants
.DEFAULT_FLUCTUATION_PERCENT
548 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
551 logger
.error(errMsg
);
552 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
554 meterValue
.sampledValue
.push(
555 OCPP16ServiceUtils
.buildSampledValue(
556 currentSampledValueTemplate
,
557 currentMeasurandValues
.allPhases
560 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
562 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
563 connectorMaximumAmperage
||
567 `${chargingStation.logPrefix()} MeterValues measurand ${
568 meterValue.sampledValue[sampledValuesIndex].measurand ??
569 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
570 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
571 meterValue.sampledValue[sampledValuesIndex].value
572 }/${connectorMaximumAmperage}`
577 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
580 const phaseValue
= `L${phase}`;
581 meterValue
.sampledValue
.push(
582 OCPP16ServiceUtils
.buildSampledValue(
583 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
584 currentSampledValueTemplate
,
585 currentMeasurandValues
[phaseValue
] as number,
587 phaseValue
as OCPP16MeterValuePhase
590 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
592 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
593 connectorMaximumAmperage
||
597 `${chargingStation.logPrefix()} MeterValues measurand ${
598 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
599 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
601 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
602 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
603 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
604 }/${connectorMaximumAmperage}`
609 // Energy.Active.Import.Register measurand (default)
610 const energySampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
614 if (energySampledValueTemplate
) {
615 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
617 energySampledValueTemplate
.measurand
620 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
621 const connectorMaximumAvailablePower
=
622 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
623 const connectorMaximumEnergyRounded
= Utils
.roundTo(
624 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
627 const energyValueRounded
= energySampledValueTemplate
.value
628 ? // Cumulate the fluctuated value around the static one
629 Utils
.getRandomFloatFluctuatedRounded(
630 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
631 energySampledValueTemplate
.value
,
632 connectorMaximumEnergyRounded
,
634 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
635 unitMultiplier
: unitDivider
,
638 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
640 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
641 // Persist previous value on connector
644 !Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) &&
645 connector
.energyActiveImportRegisterValue
>= 0 &&
646 !Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) &&
647 connector
.transactionEnergyActiveImportRegisterValue
>= 0
649 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
650 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
652 connector
.energyActiveImportRegisterValue
= 0;
653 connector
.transactionEnergyActiveImportRegisterValue
= 0;
655 meterValue
.sampledValue
.push(
656 OCPP16ServiceUtils
.buildSampledValue(
657 energySampledValueTemplate
,
659 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
665 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
666 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
668 `${chargingStation.logPrefix()} MeterValues measurand ${
669 meterValue.sampledValue[sampledValuesIndex].measurand ??
670 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
671 }: connectorId ${connectorId}, transaction ${
672 connector.transactionId
673 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
674 interval / (3600 * 1000),
683 public static buildTransactionBeginMeterValue(
684 chargingStation
: ChargingStation
,
687 ): OCPP16MeterValue
{
688 const meterValue
: OCPP16MeterValue
= {
689 timestamp
: new Date().toISOString(),
692 // Energy.Active.Import.Register measurand (default)
693 const sampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
697 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
698 meterValue
.sampledValue
.push(
699 OCPP16ServiceUtils
.buildSampledValue(
700 sampledValueTemplate
,
701 Utils
.roundTo(meterStart
/ unitDivider
, 4),
702 MeterValueContext
.TRANSACTION_BEGIN
708 public static buildTransactionEndMeterValue(
709 chargingStation
: ChargingStation
,
712 ): OCPP16MeterValue
{
713 const meterValue
: OCPP16MeterValue
= {
714 timestamp
: new Date().toISOString(),
717 // Energy.Active.Import.Register measurand (default)
718 const sampledValueTemplate
= ChargingStationUtils
.getSampledValueTemplate(
722 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
723 meterValue
.sampledValue
.push(
724 OCPP16ServiceUtils
.buildSampledValue(
725 sampledValueTemplate
,
726 Utils
.roundTo(meterStop
/ unitDivider
, 4),
727 MeterValueContext
.TRANSACTION_END
733 public static buildTransactionDataMeterValues(
734 transactionBeginMeterValue
: OCPP16MeterValue
,
735 transactionEndMeterValue
: OCPP16MeterValue
736 ): OCPP16MeterValue
[] {
737 const meterValues
: OCPP16MeterValue
[] = [];
738 meterValues
.push(transactionBeginMeterValue
);
739 meterValues
.push(transactionEndMeterValue
);
743 private static buildSampledValue(
744 sampledValueTemplate
: SampledValueTemplate
,
746 context
?: MeterValueContext
,
747 phase
?: OCPP16MeterValuePhase
748 ): OCPP16SampledValue
{
749 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
750 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
751 const sampledValueLocation
=
752 sampledValueTemplate
?.location
??
753 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
754 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
756 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
757 unit
: sampledValueTemplate
.unit
,
759 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
760 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
761 measurand
: sampledValueTemplate
.measurand
,
763 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
764 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
765 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
769 private static checkMeasurandPowerDivider(
770 chargingStation
: ChargingStation
,
771 measurandType
: OCPP16MeterValueMeasurand
773 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
774 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
775 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
776 }: powerDivider is undefined`;
777 logger
.error(errMsg
);
778 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
779 } else if (chargingStation
?.powerDivider
<= 0) {
780 const errMsg
= `${chargingStation.logPrefix()} MeterValues measurand ${
781 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
782 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
783 logger
.error(errMsg
);
784 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
788 private static getMeasurandDefaultLocation(
789 measurandType
: OCPP16MeterValueMeasurand
790 ): MeterValueLocation
| undefined {
791 switch (measurandType
) {
792 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
793 return MeterValueLocation
.EV
;
797 private static getMeasurandDefaultUnit(
798 measurandType
: OCPP16MeterValueMeasurand
799 ): MeterValueUnit
| undefined {
800 switch (measurandType
) {
801 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
802 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
803 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
804 return MeterValueUnit
.AMP
;
805 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
806 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
807 return MeterValueUnit
.WATT_HOUR
;
808 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
809 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
810 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
811 return MeterValueUnit
.WATT
;
812 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
813 return MeterValueUnit
.PERCENT
;
814 case OCPP16MeterValueMeasurand
.VOLTAGE
:
815 return MeterValueUnit
.VOLT
;