1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
3 import OCPPError from
'../../../exception/OCPPError';
4 import { CurrentType
, Voltage
} from
'../../../types/ChargingStationTemplate';
6 MeasurandPerPhaseSampledValueTemplates
,
8 } from
'../../../types/MeasurandPerPhaseSampledValueTemplates';
9 import type { MeasurandValues
} from
'../../../types/MeasurandValues';
10 import type { OCPP16ChargingProfile
} from
'../../../types/ocpp/1.6/ChargingProfile';
12 OCPP16StandardParametersKey
,
13 OCPP16SupportedFeatureProfiles
,
14 } from
'../../../types/ocpp/1.6/Configuration';
19 type OCPP16MeterValue
,
20 OCPP16MeterValueMeasurand
,
21 OCPP16MeterValuePhase
,
22 type OCPP16SampledValue
,
23 } from
'../../../types/ocpp/1.6/MeterValues';
25 OCPP16IncomingRequestCommand
,
27 } from
'../../../types/ocpp/1.6/Requests';
28 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
29 import Constants from
'../../../utils/Constants';
30 import { ACElectricUtils
, DCElectricUtils
} from
'../../../utils/ElectricUtils';
31 import logger from
'../../../utils/Logger';
32 import Utils from
'../../../utils/Utils';
33 import type ChargingStation from
'../../ChargingStation';
34 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
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
= OCPP16ServiceUtils
.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
= OCPP16ServiceUtils
.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 OCPP16ServiceUtils
.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 OCPP16ServiceUtils
.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
= OCPP16ServiceUtils
.getSampledValueTemplate(
198 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
200 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
201 if (chargingStation
.getNumberOfPhases() === 3) {
202 powerPerPhaseSampledValueTemplates
= {
203 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
206 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
207 OCPP16MeterValuePhase
.L1_N
209 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
212 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
213 OCPP16MeterValuePhase
.L2_N
215 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
218 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
219 OCPP16MeterValuePhase
.L3_N
223 if (powerSampledValueTemplate
) {
224 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
226 powerSampledValueTemplate
.measurand
228 const errMsg
= `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(`${chargingStation.logPrefix()} ${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
= OCPP16ServiceUtils
.getSampledValueTemplate(
405 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
407 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
408 if (chargingStation
.getNumberOfPhases() === 3) {
409 currentPerPhaseSampledValueTemplates
= {
410 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
413 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
414 OCPP16MeterValuePhase
.L1
416 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
419 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
420 OCPP16MeterValuePhase
.L2
422 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
425 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
426 OCPP16MeterValuePhase
.L3
430 if (currentSampledValueTemplate
) {
431 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
433 currentSampledValueTemplate
.measurand
435 const errMsg
= `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(`${chargingStation.logPrefix()} ${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
= OCPP16ServiceUtils
.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
) === false &&
645 connector
.energyActiveImportRegisterValue
>= 0 &&
646 Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
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
= OCPP16ServiceUtils
.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
?? 0) / 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
= OCPP16ServiceUtils
.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
?? 0) / 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 public static setChargingProfile(
744 chargingStation
: ChargingStation
,
746 cp
: OCPP16ChargingProfile
748 if (Utils
.isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
)) {
750 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
752 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
754 if (Array.isArray(chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
) === false) {
756 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
758 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
760 let cpReplaced
= false;
761 if (!Utils
.isEmptyArray(chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
)) {
763 .getConnectorStatus(connectorId
)
764 .chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
766 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
767 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
768 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
770 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
775 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
?.push(cp
);
778 private static buildSampledValue(
779 sampledValueTemplate
: SampledValueTemplate
,
781 context
?: MeterValueContext
,
782 phase
?: OCPP16MeterValuePhase
783 ): OCPP16SampledValue
{
784 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
785 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
786 const sampledValueLocation
=
787 sampledValueTemplate
?.location
??
788 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
789 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
791 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
792 unit
: sampledValueTemplate
.unit
,
794 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
795 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
796 measurand
: sampledValueTemplate
.measurand
,
798 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
799 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
800 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
804 private static checkMeasurandPowerDivider(
805 chargingStation
: ChargingStation
,
806 measurandType
: OCPP16MeterValueMeasurand
808 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
809 const errMsg
= `MeterValues measurand ${
810 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
811 }: powerDivider is undefined`;
812 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
813 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
814 } else if (chargingStation
?.powerDivider
<= 0) {
815 const errMsg
= `MeterValues measurand ${
816 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
817 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
818 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
819 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
823 private static getMeasurandDefaultLocation(
824 measurandType
: OCPP16MeterValueMeasurand
825 ): MeterValueLocation
| undefined {
826 switch (measurandType
) {
827 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
828 return MeterValueLocation
.EV
;
832 private static getMeasurandDefaultUnit(
833 measurandType
: OCPP16MeterValueMeasurand
834 ): MeterValueUnit
| undefined {
835 switch (measurandType
) {
836 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
837 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
838 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
839 return MeterValueUnit
.AMP
;
840 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
841 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
842 return MeterValueUnit
.WATT_HOUR
;
843 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
844 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
845 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
846 return MeterValueUnit
.WATT
;
847 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
848 return MeterValueUnit
.PERCENT
;
849 case OCPP16MeterValueMeasurand
.VOLTAGE
:
850 return MeterValueUnit
.VOLT
;