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 socMaximumValue
= 100;
72 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
73 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
74 ? Utils
.getRandomFloatFluctuatedRounded(
75 parseInt(socSampledValueTemplate
.value
),
76 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
78 : Utils
.getRandomInteger(socMaximumValue
, socMinimumValue
);
79 meterValue
.sampledValue
.push(
80 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
82 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
84 Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
85 Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
89 `${chargingStation.logPrefix()} MeterValues measurand ${
90 meterValue.sampledValue[sampledValuesIndex].measurand ??
91 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
92 }: connector id ${connectorId}, transaction id ${
93 connector?.transactionId
94 }, value: ${socMinimumValue}/${
95 meterValue.sampledValue[sampledValuesIndex].value
96 }/${socMaximumValue}}`
101 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
104 OCPP16MeterValueMeasurand
.VOLTAGE
106 if (voltageSampledValueTemplate
) {
107 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
108 ? parseInt(voltageSampledValueTemplate
.value
)
109 : chargingStation
.getVoltageOut();
110 const fluctuationPercent
=
111 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
112 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
113 voltageSampledValueTemplateValue
,
117 chargingStation
.getNumberOfPhases() !== 3 ||
118 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
120 meterValue
.sampledValue
.push(
121 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
126 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
129 const phaseLineToNeutralValue
= `L${phase}-N`;
130 const voltagePhaseLineToNeutralSampledValueTemplate
=
131 OCPP16ServiceUtils
.getSampledValueTemplate(
134 OCPP16MeterValueMeasurand
.VOLTAGE
,
135 phaseLineToNeutralValue
as OCPP16MeterValuePhase
137 let voltagePhaseLineToNeutralMeasurandValue
: number;
138 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
139 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
140 voltagePhaseLineToNeutralSampledValueTemplate
.value
141 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
142 : chargingStation
.getVoltageOut();
143 const fluctuationPhaseToNeutralPercent
=
144 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
145 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
146 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
147 voltagePhaseLineToNeutralSampledValueTemplateValue
,
148 fluctuationPhaseToNeutralPercent
151 meterValue
.sampledValue
.push(
152 OCPP16ServiceUtils
.buildSampledValue(
153 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
154 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
156 phaseLineToNeutralValue
as OCPP16MeterValuePhase
159 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
160 const phaseLineToLineValue
= `L${phase}-L${
161 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
162 ? (phase + 1) % chargingStation.getNumberOfPhases()
163 : chargingStation.getNumberOfPhases()
165 const voltagePhaseLineToLineSampledValueTemplate
=
166 OCPP16ServiceUtils
.getSampledValueTemplate(
169 OCPP16MeterValueMeasurand
.VOLTAGE
,
170 phaseLineToLineValue
as OCPP16MeterValuePhase
172 let voltagePhaseLineToLineMeasurandValue
: number;
173 if (voltagePhaseLineToLineSampledValueTemplate
) {
174 const voltagePhaseLineToLineSampledValueTemplateValue
=
175 voltagePhaseLineToLineSampledValueTemplate
.value
176 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
177 : Voltage
.VOLTAGE_400
;
178 const fluctuationPhaseLineToLinePercent
=
179 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
180 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
181 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
182 voltagePhaseLineToLineSampledValueTemplateValue
,
183 fluctuationPhaseLineToLinePercent
186 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
190 meterValue
.sampledValue
.push(
191 OCPP16ServiceUtils
.buildSampledValue(
192 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
193 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
195 phaseLineToLineValue
as OCPP16MeterValuePhase
201 // Power.Active.Import measurand
202 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
205 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
207 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
208 if (chargingStation
.getNumberOfPhases() === 3) {
209 powerPerPhaseSampledValueTemplates
= {
210 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
213 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
214 OCPP16MeterValuePhase
.L1_N
216 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
219 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
220 OCPP16MeterValuePhase
.L2_N
222 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
225 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
226 OCPP16MeterValuePhase
.L3_N
230 if (powerSampledValueTemplate
) {
231 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
233 powerSampledValueTemplate
.measurand
235 const errMsg
= `MeterValues measurand ${
236 powerSampledValueTemplate.measurand ??
237 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
238 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
239 chargingStation.templateFile
240 }, cannot calculate ${
241 powerSampledValueTemplate.measurand ??
242 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
244 const powerMeasurandValues
= {} as MeasurandValues
;
245 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
246 const connectorMaximumAvailablePower
=
247 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
248 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
249 const connectorMaximumPowerPerPhase
= Math.round(
250 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
252 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
) ?? 0;
253 const connectorMinimumPowerPerPhase
= Math.round(
254 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
256 switch (chargingStation
.getCurrentOutType()) {
258 if (chargingStation
.getNumberOfPhases() === 3) {
259 const defaultFluctuatedPowerPerPhase
=
260 powerSampledValueTemplate
.value
&&
261 Utils
.getRandomFloatFluctuatedRounded(
262 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
263 powerSampledValueTemplate
.value
,
264 connectorMaximumPower
/ unitDivider
,
265 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
266 ) / chargingStation
.getNumberOfPhases(),
267 powerSampledValueTemplate
.fluctuationPercent
??
268 Constants
.DEFAULT_FLUCTUATION_PERCENT
270 const phase1FluctuatedValue
=
271 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
272 Utils
.getRandomFloatFluctuatedRounded(
273 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
274 powerPerPhaseSampledValueTemplates
.L1
.value
,
275 connectorMaximumPowerPerPhase
/ unitDivider
,
276 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
278 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
279 Constants
.DEFAULT_FLUCTUATION_PERCENT
281 const phase2FluctuatedValue
=
282 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
283 Utils
.getRandomFloatFluctuatedRounded(
284 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
285 powerPerPhaseSampledValueTemplates
.L2
.value
,
286 connectorMaximumPowerPerPhase
/ unitDivider
,
287 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
289 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
290 Constants
.DEFAULT_FLUCTUATION_PERCENT
292 const phase3FluctuatedValue
=
293 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
294 Utils
.getRandomFloatFluctuatedRounded(
295 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
296 powerPerPhaseSampledValueTemplates
.L3
.value
,
297 connectorMaximumPowerPerPhase
/ unitDivider
,
298 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
300 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
301 Constants
.DEFAULT_FLUCTUATION_PERCENT
303 powerMeasurandValues
.L1
=
304 phase1FluctuatedValue
??
305 defaultFluctuatedPowerPerPhase
??
306 Utils
.getRandomFloatRounded(
307 connectorMaximumPowerPerPhase
/ unitDivider
,
308 connectorMinimumPowerPerPhase
/ unitDivider
310 powerMeasurandValues
.L2
=
311 phase2FluctuatedValue
??
312 defaultFluctuatedPowerPerPhase
??
313 Utils
.getRandomFloatRounded(
314 connectorMaximumPowerPerPhase
/ unitDivider
,
315 connectorMinimumPowerPerPhase
/ unitDivider
317 powerMeasurandValues
.L3
=
318 phase3FluctuatedValue
??
319 defaultFluctuatedPowerPerPhase
??
320 Utils
.getRandomFloatRounded(
321 connectorMaximumPowerPerPhase
/ unitDivider
,
322 connectorMinimumPowerPerPhase
/ unitDivider
325 powerMeasurandValues
.L1
= 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(
336 connectorMaximumPower
/ unitDivider
,
337 connectorMinimumPower
/ unitDivider
339 powerMeasurandValues
.L2
= 0;
340 powerMeasurandValues
.L3
= 0;
342 powerMeasurandValues
.allPhases
= Utils
.roundTo(
343 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
348 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
349 ? Utils
.getRandomFloatFluctuatedRounded(
350 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
351 powerSampledValueTemplate
.value
,
352 connectorMaximumPower
/ unitDivider
,
353 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
355 powerSampledValueTemplate
.fluctuationPercent
??
356 Constants
.DEFAULT_FLUCTUATION_PERCENT
358 : Utils
.getRandomFloatRounded(
359 connectorMaximumPower
/ unitDivider
,
360 connectorMinimumPower
/ unitDivider
364 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
365 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
367 meterValue
.sampledValue
.push(
368 OCPP16ServiceUtils
.buildSampledValue(
369 powerSampledValueTemplate
,
370 powerMeasurandValues
.allPhases
373 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
374 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
375 const connectorMinimumPowerRounded
= Utils
.roundTo(connectorMinimumPower
/ unitDivider
, 2);
377 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
378 connectorMaximumPowerRounded
||
379 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
380 connectorMinimumPowerRounded
||
384 `${chargingStation.logPrefix()} MeterValues measurand ${
385 meterValue.sampledValue[sampledValuesIndex].measurand ??
386 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
387 }: connector id ${connectorId}, transaction id ${
388 connector?.transactionId
389 }, value: ${connectorMinimumPowerRounded}/${
390 meterValue.sampledValue[sampledValuesIndex].value
391 }/${connectorMaximumPowerRounded}`
396 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
399 const phaseValue
= `L${phase}-N`;
400 meterValue
.sampledValue
.push(
401 OCPP16ServiceUtils
.buildSampledValue(
402 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
403 powerSampledValueTemplate
,
404 powerMeasurandValues
[`L${phase}`] as number,
406 phaseValue
as OCPP16MeterValuePhase
409 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
410 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
411 connectorMaximumPowerPerPhase
/ unitDivider
,
414 const connectorMinimumPowerPerPhaseRounded
= Utils
.roundTo(
415 connectorMinimumPowerPerPhase
/ unitDivider
,
419 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
420 connectorMaximumPowerPerPhaseRounded
||
421 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
422 connectorMinimumPowerPerPhaseRounded
||
426 `${chargingStation.logPrefix()} MeterValues measurand ${
427 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
428 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
430 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
431 }, connector id ${connectorId}, transaction id ${
432 connector?.transactionId
433 }, value: ${connectorMinimumPowerPerPhaseRounded}/${
434 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
435 }/${connectorMaximumPowerPerPhaseRounded}`
440 // Current.Import measurand
441 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
444 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
446 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
447 if (chargingStation
.getNumberOfPhases() === 3) {
448 currentPerPhaseSampledValueTemplates
= {
449 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
452 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
453 OCPP16MeterValuePhase
.L1
455 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
458 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
459 OCPP16MeterValuePhase
.L2
461 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
464 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
465 OCPP16MeterValuePhase
.L3
469 if (currentSampledValueTemplate
) {
470 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
472 currentSampledValueTemplate
.measurand
474 const errMsg
= `MeterValues measurand ${
475 currentSampledValueTemplate.measurand ??
476 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
477 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
478 chargingStation.templateFile
479 }, cannot calculate ${
480 currentSampledValueTemplate.measurand ??
481 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
483 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
484 const connectorMaximumAvailablePower
=
485 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
486 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
487 let connectorMaximumAmperage
: number;
488 switch (chargingStation
.getCurrentOutType()) {
490 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
491 chargingStation
.getNumberOfPhases(),
492 connectorMaximumAvailablePower
,
493 chargingStation
.getVoltageOut()
495 if (chargingStation
.getNumberOfPhases() === 3) {
496 const defaultFluctuatedAmperagePerPhase
=
497 currentSampledValueTemplate
.value
&&
498 Utils
.getRandomFloatFluctuatedRounded(
499 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
500 currentSampledValueTemplate
.value
,
501 connectorMaximumAmperage
,
502 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
504 currentSampledValueTemplate
.fluctuationPercent
??
505 Constants
.DEFAULT_FLUCTUATION_PERCENT
507 const phase1FluctuatedValue
=
508 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
509 Utils
.getRandomFloatFluctuatedRounded(
510 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
511 currentPerPhaseSampledValueTemplates
.L1
.value
,
512 connectorMaximumAmperage
,
513 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
515 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
516 Constants
.DEFAULT_FLUCTUATION_PERCENT
518 const phase2FluctuatedValue
=
519 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
520 Utils
.getRandomFloatFluctuatedRounded(
521 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
522 currentPerPhaseSampledValueTemplates
.L2
.value
,
523 connectorMaximumAmperage
,
524 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
526 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
527 Constants
.DEFAULT_FLUCTUATION_PERCENT
529 const phase3FluctuatedValue
=
530 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
531 Utils
.getRandomFloatFluctuatedRounded(
532 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
533 currentPerPhaseSampledValueTemplates
.L3
.value
,
534 connectorMaximumAmperage
,
535 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
537 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
538 Constants
.DEFAULT_FLUCTUATION_PERCENT
540 currentMeasurandValues
.L1
=
541 phase1FluctuatedValue
??
542 defaultFluctuatedAmperagePerPhase
??
543 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
544 currentMeasurandValues
.L2
=
545 phase2FluctuatedValue
??
546 defaultFluctuatedAmperagePerPhase
??
547 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
548 currentMeasurandValues
.L3
=
549 phase3FluctuatedValue
??
550 defaultFluctuatedAmperagePerPhase
??
551 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
553 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
554 ? Utils
.getRandomFloatFluctuatedRounded(
555 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
556 currentSampledValueTemplate
.value
,
557 connectorMaximumAmperage
,
558 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
560 currentSampledValueTemplate
.fluctuationPercent
??
561 Constants
.DEFAULT_FLUCTUATION_PERCENT
563 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
564 currentMeasurandValues
.L2
= 0;
565 currentMeasurandValues
.L3
= 0;
567 currentMeasurandValues
.allPhases
= Utils
.roundTo(
568 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
569 chargingStation
.getNumberOfPhases(),
574 connectorMaximumAmperage
= DCElectricUtils
.amperage(
575 connectorMaximumAvailablePower
,
576 chargingStation
.getVoltageOut()
578 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
579 ? Utils
.getRandomFloatFluctuatedRounded(
580 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
581 currentSampledValueTemplate
.value
,
582 connectorMaximumAmperage
,
583 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
585 currentSampledValueTemplate
.fluctuationPercent
??
586 Constants
.DEFAULT_FLUCTUATION_PERCENT
588 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
591 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
592 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
594 meterValue
.sampledValue
.push(
595 OCPP16ServiceUtils
.buildSampledValue(
596 currentSampledValueTemplate
,
597 currentMeasurandValues
.allPhases
600 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
602 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
603 connectorMaximumAmperage
||
604 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
605 connectorMinimumAmperage
||
609 `${chargingStation.logPrefix()} MeterValues measurand ${
610 meterValue.sampledValue[sampledValuesIndex].measurand ??
611 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
612 }: connector id ${connectorId}, transaction id ${
613 connector?.transactionId
614 }, value: ${connectorMinimumAmperage}/${
615 meterValue.sampledValue[sampledValuesIndex].value
616 }/${connectorMaximumAmperage}`
621 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
624 const phaseValue
= `L${phase}`;
625 meterValue
.sampledValue
.push(
626 OCPP16ServiceUtils
.buildSampledValue(
627 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
628 currentSampledValueTemplate
,
629 currentMeasurandValues
[phaseValue
] as number,
631 phaseValue
as OCPP16MeterValuePhase
634 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
636 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
637 connectorMaximumAmperage
||
638 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
639 connectorMinimumAmperage
||
643 `${chargingStation.logPrefix()} MeterValues measurand ${
644 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
645 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
647 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
648 }, connector id ${connectorId}, transaction id ${
649 connector?.transactionId
650 }, value: ${connectorMinimumAmperage}/${
651 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
652 }/${connectorMaximumAmperage}`
657 // Energy.Active.Import.Register measurand (default)
658 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
662 if (energySampledValueTemplate
) {
663 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
665 energySampledValueTemplate
.measurand
668 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
669 const connectorMaximumAvailablePower
=
670 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
671 const connectorMaximumEnergyRounded
= Utils
.roundTo(
672 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
675 const energyValueRounded
= energySampledValueTemplate
.value
676 ? // Cumulate the fluctuated value around the static one
677 Utils
.getRandomFloatFluctuatedRounded(
678 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
679 energySampledValueTemplate
.value
,
680 connectorMaximumEnergyRounded
,
682 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
683 unitMultiplier
: unitDivider
,
686 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
688 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
689 // Persist previous value on connector
692 Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
693 connector
.energyActiveImportRegisterValue
>= 0 &&
694 Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
695 connector
.transactionEnergyActiveImportRegisterValue
>= 0
697 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
698 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
700 connector
.energyActiveImportRegisterValue
= 0;
701 connector
.transactionEnergyActiveImportRegisterValue
= 0;
703 meterValue
.sampledValue
.push(
704 OCPP16ServiceUtils
.buildSampledValue(
705 energySampledValueTemplate
,
707 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
713 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
714 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
716 `${chargingStation.logPrefix()} MeterValues measurand ${
717 meterValue.sampledValue[sampledValuesIndex].measurand ??
718 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
719 }: connector id ${connectorId}, transaction id ${
720 connector?.transactionId
721 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
722 interval / (3600 * 1000),
731 public static buildTransactionBeginMeterValue(
732 chargingStation
: ChargingStation
,
735 ): OCPP16MeterValue
{
736 const meterValue
: OCPP16MeterValue
= {
737 timestamp
: new Date(),
740 // Energy.Active.Import.Register measurand (default)
741 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
745 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
746 meterValue
.sampledValue
.push(
747 OCPP16ServiceUtils
.buildSampledValue(
748 sampledValueTemplate
,
749 Utils
.roundTo((meterStart
?? 0) / unitDivider
, 4),
750 MeterValueContext
.TRANSACTION_BEGIN
756 public static buildTransactionEndMeterValue(
757 chargingStation
: ChargingStation
,
760 ): OCPP16MeterValue
{
761 const meterValue
: OCPP16MeterValue
= {
762 timestamp
: new Date(),
765 // Energy.Active.Import.Register measurand (default)
766 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
770 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
771 meterValue
.sampledValue
.push(
772 OCPP16ServiceUtils
.buildSampledValue(
773 sampledValueTemplate
,
774 Utils
.roundTo((meterStop
?? 0) / unitDivider
, 4),
775 MeterValueContext
.TRANSACTION_END
781 public static buildTransactionDataMeterValues(
782 transactionBeginMeterValue
: OCPP16MeterValue
,
783 transactionEndMeterValue
: OCPP16MeterValue
784 ): OCPP16MeterValue
[] {
785 const meterValues
: OCPP16MeterValue
[] = [];
786 meterValues
.push(transactionBeginMeterValue
);
787 meterValues
.push(transactionEndMeterValue
);
791 public static setChargingProfile(
792 chargingStation
: ChargingStation
,
794 cp
: OCPP16ChargingProfile
797 Utils
.isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)
800 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
802 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
805 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
808 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
810 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
812 let cpReplaced
= false;
813 if (Utils
.isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
815 .getConnectorStatus(connectorId
)
816 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
818 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
819 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
820 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
822 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
827 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
830 public static parseJsonSchemaFile
<T
extends JsonType
>(
831 relativePath
: string,
834 ): JSONSchemaType
<T
> {
835 return super.parseJsonSchemaFile
<T
>(
836 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), relativePath
),
837 OCPPVersion
.VERSION_16
,
843 private static buildSampledValue(
844 sampledValueTemplate
: SampledValueTemplate
,
846 context
?: MeterValueContext
,
847 phase
?: OCPP16MeterValuePhase
848 ): OCPP16SampledValue
{
849 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
850 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
851 const sampledValueLocation
=
852 sampledValueTemplate
?.location
??
853 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
854 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
856 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
857 unit
: sampledValueTemplate
.unit
,
859 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
860 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
861 measurand
: sampledValueTemplate
.measurand
,
863 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
864 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
865 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
869 private static checkMeasurandPowerDivider(
870 chargingStation
: ChargingStation
,
871 measurandType
: OCPP16MeterValueMeasurand
873 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
874 const errMsg
= `MeterValues measurand ${
875 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
876 }: powerDivider is undefined`;
877 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
878 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
879 } else if (chargingStation
?.powerDivider
<= 0) {
880 const errMsg
= `MeterValues measurand ${
881 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
882 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
883 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
884 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
888 private static getMeasurandDefaultLocation(
889 measurandType
: OCPP16MeterValueMeasurand
890 ): MeterValueLocation
| undefined {
891 switch (measurandType
) {
892 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
893 return MeterValueLocation
.EV
;
897 private static getMeasurandDefaultUnit(
898 measurandType
: OCPP16MeterValueMeasurand
899 ): MeterValueUnit
| undefined {
900 switch (measurandType
) {
901 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
902 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
903 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
904 return MeterValueUnit
.AMP
;
905 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
906 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
907 return MeterValueUnit
.WATT_HOUR
;
908 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
909 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
910 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
911 return MeterValueUnit
.WATT
;
912 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
913 return MeterValueUnit
.PERCENT
;
914 case OCPP16MeterValueMeasurand
.VOLTAGE
:
915 return MeterValueUnit
.VOLT
;