1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import path from
'node:path';
4 import { fileURLToPath
} from
'node:url';
6 import type { JSONSchemaType
} from
'ajv';
8 import type { ChargingStation
} from
'../../../charging-station';
9 import { OCPPError
} from
'../../../exception';
14 type MeasurandPerPhaseSampledValueTemplates
,
19 type OCPP16ChargingProfile
,
20 type OCPP16IncomingRequestCommand
,
21 type OCPP16MeterValue
,
22 OCPP16MeterValueMeasurand
,
23 OCPP16MeterValuePhase
,
25 type OCPP16SampledValue
,
26 OCPP16StandardParametersKey
,
27 type OCPP16SupportedFeatureProfiles
,
29 type SampledValueTemplate
,
31 } from
'../../../types';
32 import { ACElectricUtils
, Constants
, DCElectricUtils
, Utils
, logger
} from
'../../../utils';
33 import { OCPPServiceUtils
} from
'../internal';
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(),
63 const connector
= chargingStation
.getConnectorStatus(connectorId
);
65 const socSampledValueTemplate
= OCPP16ServiceUtils
.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
= OCPP16ServiceUtils
.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 OCPP16ServiceUtils
.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 OCPP16ServiceUtils
.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
= OCPP16ServiceUtils
.getSampledValueTemplate(
197 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
199 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
=
200 Constants
.EMPTY_OBJECT
;
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
= Constants
.EMPTY_OBJECT
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 Constants
.EMPTY_OBJECT
;
409 if (chargingStation
.getNumberOfPhases() === 3) {
410 currentPerPhaseSampledValueTemplates
= {
411 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
414 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
415 OCPP16MeterValuePhase
.L1
417 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
420 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
421 OCPP16MeterValuePhase
.L2
423 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
426 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
427 OCPP16MeterValuePhase
.L3
431 if (currentSampledValueTemplate
) {
432 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
434 currentSampledValueTemplate
.measurand
436 const errMsg
= `MeterValues measurand ${
437 currentSampledValueTemplate.measurand ??
438 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
439 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
440 chargingStation.templateFile
441 }, cannot calculate ${
442 currentSampledValueTemplate.measurand ??
443 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
445 const currentMeasurandValues
: MeasurandValues
= Constants
.EMPTY_OBJECT
as MeasurandValues
;
446 const connectorMaximumAvailablePower
=
447 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
448 let connectorMaximumAmperage
: number;
449 switch (chargingStation
.getCurrentOutType()) {
451 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
452 chargingStation
.getNumberOfPhases(),
453 connectorMaximumAvailablePower
,
454 chargingStation
.getVoltageOut()
456 if (chargingStation
.getNumberOfPhases() === 3) {
457 const defaultFluctuatedAmperagePerPhase
=
458 currentSampledValueTemplate
.value
&&
459 Utils
.getRandomFloatFluctuatedRounded(
460 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
461 currentSampledValueTemplate
.value
,
462 connectorMaximumAmperage
,
463 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
465 currentSampledValueTemplate
.fluctuationPercent
??
466 Constants
.DEFAULT_FLUCTUATION_PERCENT
468 const phase1FluctuatedValue
=
469 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
470 Utils
.getRandomFloatFluctuatedRounded(
471 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
472 currentPerPhaseSampledValueTemplates
.L1
.value
,
473 connectorMaximumAmperage
,
474 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
476 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
477 Constants
.DEFAULT_FLUCTUATION_PERCENT
479 const phase2FluctuatedValue
=
480 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
481 Utils
.getRandomFloatFluctuatedRounded(
482 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
483 currentPerPhaseSampledValueTemplates
.L2
.value
,
484 connectorMaximumAmperage
,
485 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
487 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
488 Constants
.DEFAULT_FLUCTUATION_PERCENT
490 const phase3FluctuatedValue
=
491 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
492 Utils
.getRandomFloatFluctuatedRounded(
493 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
494 currentPerPhaseSampledValueTemplates
.L3
.value
,
495 connectorMaximumAmperage
,
496 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
498 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
499 Constants
.DEFAULT_FLUCTUATION_PERCENT
501 currentMeasurandValues
.L1
=
502 phase1FluctuatedValue
??
503 defaultFluctuatedAmperagePerPhase
??
504 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
505 currentMeasurandValues
.L2
=
506 phase2FluctuatedValue
??
507 defaultFluctuatedAmperagePerPhase
??
508 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
509 currentMeasurandValues
.L3
=
510 phase3FluctuatedValue
??
511 defaultFluctuatedAmperagePerPhase
??
512 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
514 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
515 ? Utils
.getRandomFloatFluctuatedRounded(
516 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
517 currentSampledValueTemplate
.value
,
518 connectorMaximumAmperage
,
519 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
521 currentSampledValueTemplate
.fluctuationPercent
??
522 Constants
.DEFAULT_FLUCTUATION_PERCENT
524 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
525 currentMeasurandValues
.L2
= 0;
526 currentMeasurandValues
.L3
= 0;
528 currentMeasurandValues
.allPhases
= Utils
.roundTo(
529 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
530 chargingStation
.getNumberOfPhases(),
535 connectorMaximumAmperage
= DCElectricUtils
.amperage(
536 connectorMaximumAvailablePower
,
537 chargingStation
.getVoltageOut()
539 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
540 ? Utils
.getRandomFloatFluctuatedRounded(
541 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
542 currentSampledValueTemplate
.value
,
543 connectorMaximumAmperage
,
544 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
546 currentSampledValueTemplate
.fluctuationPercent
??
547 Constants
.DEFAULT_FLUCTUATION_PERCENT
549 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
552 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
553 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
555 meterValue
.sampledValue
.push(
556 OCPP16ServiceUtils
.buildSampledValue(
557 currentSampledValueTemplate
,
558 currentMeasurandValues
.allPhases
561 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
563 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
564 connectorMaximumAmperage
||
568 `${chargingStation.logPrefix()} MeterValues measurand ${
569 meterValue.sampledValue[sampledValuesIndex].measurand ??
570 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
571 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
572 meterValue.sampledValue[sampledValuesIndex].value
573 }/${connectorMaximumAmperage}`
578 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
581 const phaseValue
= `L${phase}`;
582 meterValue
.sampledValue
.push(
583 OCPP16ServiceUtils
.buildSampledValue(
584 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
585 currentSampledValueTemplate
,
586 currentMeasurandValues
[phaseValue
] as number,
588 phaseValue
as OCPP16MeterValuePhase
591 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
593 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
594 connectorMaximumAmperage
||
598 `${chargingStation.logPrefix()} MeterValues measurand ${
599 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
600 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
602 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
603 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
604 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
605 }/${connectorMaximumAmperage}`
610 // Energy.Active.Import.Register measurand (default)
611 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
615 if (energySampledValueTemplate
) {
616 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
618 energySampledValueTemplate
.measurand
621 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
622 const connectorMaximumAvailablePower
=
623 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
624 const connectorMaximumEnergyRounded
= Utils
.roundTo(
625 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
628 const energyValueRounded
= energySampledValueTemplate
.value
629 ? // Cumulate the fluctuated value around the static one
630 Utils
.getRandomFloatFluctuatedRounded(
631 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
632 energySampledValueTemplate
.value
,
633 connectorMaximumEnergyRounded
,
635 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
636 unitMultiplier
: unitDivider
,
639 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
641 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
642 // Persist previous value on connector
645 Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
646 connector
.energyActiveImportRegisterValue
>= 0 &&
647 Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
648 connector
.transactionEnergyActiveImportRegisterValue
>= 0
650 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
651 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
653 connector
.energyActiveImportRegisterValue
= 0;
654 connector
.transactionEnergyActiveImportRegisterValue
= 0;
656 meterValue
.sampledValue
.push(
657 OCPP16ServiceUtils
.buildSampledValue(
658 energySampledValueTemplate
,
660 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
666 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
667 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
669 `${chargingStation.logPrefix()} MeterValues measurand ${
670 meterValue.sampledValue[sampledValuesIndex].measurand ??
671 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
672 }: connectorId ${connectorId}, transaction ${
673 connector?.transactionId
674 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
675 interval / (3600 * 1000),
684 public static buildTransactionBeginMeterValue(
685 chargingStation
: ChargingStation
,
688 ): OCPP16MeterValue
{
689 const meterValue
: OCPP16MeterValue
= {
690 timestamp
: new Date(),
693 // Energy.Active.Import.Register measurand (default)
694 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
698 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
699 meterValue
.sampledValue
.push(
700 OCPP16ServiceUtils
.buildSampledValue(
701 sampledValueTemplate
,
702 Utils
.roundTo((meterStart
?? 0) / unitDivider
, 4),
703 MeterValueContext
.TRANSACTION_BEGIN
709 public static buildTransactionEndMeterValue(
710 chargingStation
: ChargingStation
,
713 ): OCPP16MeterValue
{
714 const meterValue
: OCPP16MeterValue
= {
715 timestamp
: new Date(),
718 // Energy.Active.Import.Register measurand (default)
719 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
723 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
724 meterValue
.sampledValue
.push(
725 OCPP16ServiceUtils
.buildSampledValue(
726 sampledValueTemplate
,
727 Utils
.roundTo((meterStop
?? 0) / unitDivider
, 4),
728 MeterValueContext
.TRANSACTION_END
734 public static buildTransactionDataMeterValues(
735 transactionBeginMeterValue
: OCPP16MeterValue
,
736 transactionEndMeterValue
: OCPP16MeterValue
737 ): OCPP16MeterValue
[] {
738 const meterValues
: OCPP16MeterValue
[] = [];
739 meterValues
.push(transactionBeginMeterValue
);
740 meterValues
.push(transactionEndMeterValue
);
744 public static setChargingProfile(
745 chargingStation
: ChargingStation
,
747 cp
: OCPP16ChargingProfile
750 Utils
.isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)
753 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
755 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
758 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
761 `${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`
763 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
765 let cpReplaced
= false;
766 if (Utils
.isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
768 .getConnectorStatus(connectorId
)
769 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
771 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
772 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
773 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
775 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
780 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
783 public static parseJsonSchemaFile
<T
extends JsonType
>(
784 relativePath
: string,
787 ): JSONSchemaType
<T
> {
788 return super.parseJsonSchemaFile
<T
>(
789 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), relativePath
),
790 OCPPVersion
.VERSION_16
,
796 private static buildSampledValue(
797 sampledValueTemplate
: SampledValueTemplate
,
799 context
?: MeterValueContext
,
800 phase
?: OCPP16MeterValuePhase
801 ): OCPP16SampledValue
{
802 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
803 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
804 const sampledValueLocation
=
805 sampledValueTemplate
?.location
??
806 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
807 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
809 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
810 unit
: sampledValueTemplate
.unit
,
812 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
813 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
814 measurand
: sampledValueTemplate
.measurand
,
816 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
817 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
818 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
822 private static checkMeasurandPowerDivider(
823 chargingStation
: ChargingStation
,
824 measurandType
: OCPP16MeterValueMeasurand
826 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
827 const errMsg
= `MeterValues measurand ${
828 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
829 }: powerDivider is undefined`;
830 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
831 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
832 } else if (chargingStation
?.powerDivider
<= 0) {
833 const errMsg
= `MeterValues measurand ${
834 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
835 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
836 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
837 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
841 private static getMeasurandDefaultLocation(
842 measurandType
: OCPP16MeterValueMeasurand
843 ): MeterValueLocation
| undefined {
844 switch (measurandType
) {
845 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
846 return MeterValueLocation
.EV
;
850 private static getMeasurandDefaultUnit(
851 measurandType
: OCPP16MeterValueMeasurand
852 ): MeterValueUnit
| undefined {
853 switch (measurandType
) {
854 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
855 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
856 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
857 return MeterValueUnit
.AMP
;
858 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
859 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
860 return MeterValueUnit
.WATT_HOUR
;
861 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
862 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
863 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
864 return MeterValueUnit
.WATT
;
865 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
866 return MeterValueUnit
.PERCENT
;
867 case OCPP16MeterValueMeasurand
.VOLTAGE
:
868 return MeterValueUnit
.VOLT
;