1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
5 import type { ChargingStation
} from
'../../../charging-station';
6 import { OCPPError
} from
'../../../exception';
11 type MeasurandPerPhaseSampledValueTemplates
,
16 type OCPP16ChargingProfile
,
17 type OCPP16IncomingRequestCommand
,
18 type OCPP16MeterValue
,
19 OCPP16MeterValueMeasurand
,
20 OCPP16MeterValuePhase
,
22 type OCPP16SampledValue
,
23 OCPP16StandardParametersKey
,
24 type OCPP16SupportedFeatureProfiles
,
26 type SampledValueTemplate
,
28 } from
'../../../types';
29 import { ACElectricUtils
, Constants
, DCElectricUtils
, Utils
, logger
} from
'../../../utils';
30 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
32 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
33 public static checkFeatureProfile(
34 chargingStation
: ChargingStation
,
35 featureProfile
: OCPP16SupportedFeatureProfiles
,
36 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
38 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
40 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
41 OCPP16StandardParametersKey.SupportedFeatureProfiles
49 public static buildMeterValue(
50 chargingStation
: ChargingStation
,
52 transactionId
: number,
56 const meterValue
: OCPP16MeterValue
= {
57 timestamp
: new Date(),
60 const connector
= chargingStation
.getConnectorStatus(connectorId
);
62 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
65 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
67 if (socSampledValueTemplate
) {
68 const socMaximumValue
= 100;
69 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
70 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
71 ? Utils
.getRandomFloatFluctuatedRounded(
72 parseInt(socSampledValueTemplate
.value
),
73 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
75 : Utils
.getRandomInteger(socMaximumValue
, socMinimumValue
);
76 meterValue
.sampledValue
.push(
77 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
79 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
81 Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
82 Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
86 `${chargingStation.logPrefix()} MeterValues measurand ${
87 meterValue.sampledValue[sampledValuesIndex].measurand ??
88 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
89 }: connector id ${connectorId}, transaction id ${
90 connector?.transactionId
91 }, value: ${socMinimumValue}/${
92 meterValue.sampledValue[sampledValuesIndex].value
93 }/${socMaximumValue}}`
98 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
101 OCPP16MeterValueMeasurand
.VOLTAGE
103 if (voltageSampledValueTemplate
) {
104 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
105 ? parseInt(voltageSampledValueTemplate
.value
)
106 : chargingStation
.getVoltageOut();
107 const fluctuationPercent
=
108 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
109 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
110 voltageSampledValueTemplateValue
,
114 chargingStation
.getNumberOfPhases() !== 3 ||
115 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
117 meterValue
.sampledValue
.push(
118 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
123 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
126 const phaseLineToNeutralValue
= `L${phase}-N`;
127 const voltagePhaseLineToNeutralSampledValueTemplate
=
128 OCPP16ServiceUtils
.getSampledValueTemplate(
131 OCPP16MeterValueMeasurand
.VOLTAGE
,
132 phaseLineToNeutralValue
as OCPP16MeterValuePhase
134 let voltagePhaseLineToNeutralMeasurandValue
: number;
135 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
136 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
137 voltagePhaseLineToNeutralSampledValueTemplate
.value
138 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
139 : chargingStation
.getVoltageOut();
140 const fluctuationPhaseToNeutralPercent
=
141 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
142 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
143 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
144 voltagePhaseLineToNeutralSampledValueTemplateValue
,
145 fluctuationPhaseToNeutralPercent
148 meterValue
.sampledValue
.push(
149 OCPP16ServiceUtils
.buildSampledValue(
150 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
151 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
153 phaseLineToNeutralValue
as OCPP16MeterValuePhase
156 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
157 const phaseLineToLineValue
= `L${phase}-L${
158 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
159 ? (phase + 1) % chargingStation.getNumberOfPhases()
160 : chargingStation.getNumberOfPhases()
162 const voltagePhaseLineToLineSampledValueTemplate
=
163 OCPP16ServiceUtils
.getSampledValueTemplate(
166 OCPP16MeterValueMeasurand
.VOLTAGE
,
167 phaseLineToLineValue
as OCPP16MeterValuePhase
169 let voltagePhaseLineToLineMeasurandValue
: number;
170 if (voltagePhaseLineToLineSampledValueTemplate
) {
171 const voltagePhaseLineToLineSampledValueTemplateValue
=
172 voltagePhaseLineToLineSampledValueTemplate
.value
173 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
174 : Voltage
.VOLTAGE_400
;
175 const fluctuationPhaseLineToLinePercent
=
176 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
177 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
178 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
179 voltagePhaseLineToLineSampledValueTemplateValue
,
180 fluctuationPhaseLineToLinePercent
183 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
187 meterValue
.sampledValue
.push(
188 OCPP16ServiceUtils
.buildSampledValue(
189 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
190 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
192 phaseLineToLineValue
as OCPP16MeterValuePhase
198 // Power.Active.Import measurand
199 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
202 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
204 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
205 if (chargingStation
.getNumberOfPhases() === 3) {
206 powerPerPhaseSampledValueTemplates
= {
207 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
210 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
211 OCPP16MeterValuePhase
.L1_N
213 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
216 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
217 OCPP16MeterValuePhase
.L2_N
219 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
222 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
223 OCPP16MeterValuePhase
.L3_N
227 if (powerSampledValueTemplate
) {
228 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
230 powerSampledValueTemplate
.measurand
232 const errMsg
= `MeterValues measurand ${
233 powerSampledValueTemplate.measurand ??
234 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
235 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
236 chargingStation.templateFile
237 }, cannot calculate ${
238 powerSampledValueTemplate.measurand ??
239 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
241 const powerMeasurandValues
= {} as MeasurandValues
;
242 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
243 const connectorMaximumAvailablePower
=
244 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
245 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
246 const connectorMaximumPowerPerPhase
= Math.round(
247 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
249 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
) ?? 0;
250 const connectorMinimumPowerPerPhase
= Math.round(
251 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
253 switch (chargingStation
.getCurrentOutType()) {
255 if (chargingStation
.getNumberOfPhases() === 3) {
256 const defaultFluctuatedPowerPerPhase
=
257 powerSampledValueTemplate
.value
&&
258 Utils
.getRandomFloatFluctuatedRounded(
259 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
260 powerSampledValueTemplate
.value
,
261 connectorMaximumPower
/ unitDivider
,
262 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
263 ) / chargingStation
.getNumberOfPhases(),
264 powerSampledValueTemplate
.fluctuationPercent
??
265 Constants
.DEFAULT_FLUCTUATION_PERCENT
267 const phase1FluctuatedValue
=
268 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
269 Utils
.getRandomFloatFluctuatedRounded(
270 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
271 powerPerPhaseSampledValueTemplates
.L1
.value
,
272 connectorMaximumPowerPerPhase
/ unitDivider
,
273 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
275 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
276 Constants
.DEFAULT_FLUCTUATION_PERCENT
278 const phase2FluctuatedValue
=
279 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
280 Utils
.getRandomFloatFluctuatedRounded(
281 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
282 powerPerPhaseSampledValueTemplates
.L2
.value
,
283 connectorMaximumPowerPerPhase
/ unitDivider
,
284 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
286 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
287 Constants
.DEFAULT_FLUCTUATION_PERCENT
289 const phase3FluctuatedValue
=
290 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
291 Utils
.getRandomFloatFluctuatedRounded(
292 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
293 powerPerPhaseSampledValueTemplates
.L3
.value
,
294 connectorMaximumPowerPerPhase
/ unitDivider
,
295 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
297 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
298 Constants
.DEFAULT_FLUCTUATION_PERCENT
300 powerMeasurandValues
.L1
=
301 phase1FluctuatedValue
??
302 defaultFluctuatedPowerPerPhase
??
303 Utils
.getRandomFloatRounded(
304 connectorMaximumPowerPerPhase
/ unitDivider
,
305 connectorMinimumPowerPerPhase
/ unitDivider
307 powerMeasurandValues
.L2
=
308 phase2FluctuatedValue
??
309 defaultFluctuatedPowerPerPhase
??
310 Utils
.getRandomFloatRounded(
311 connectorMaximumPowerPerPhase
/ unitDivider
,
312 connectorMinimumPowerPerPhase
/ unitDivider
314 powerMeasurandValues
.L3
=
315 phase3FluctuatedValue
??
316 defaultFluctuatedPowerPerPhase
??
317 Utils
.getRandomFloatRounded(
318 connectorMaximumPowerPerPhase
/ unitDivider
,
319 connectorMinimumPowerPerPhase
/ unitDivider
322 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
323 ? Utils
.getRandomFloatFluctuatedRounded(
324 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
325 powerSampledValueTemplate
.value
,
326 connectorMaximumPower
/ unitDivider
,
327 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
329 powerSampledValueTemplate
.fluctuationPercent
??
330 Constants
.DEFAULT_FLUCTUATION_PERCENT
332 : Utils
.getRandomFloatRounded(
333 connectorMaximumPower
/ unitDivider
,
334 connectorMinimumPower
/ unitDivider
336 powerMeasurandValues
.L2
= 0;
337 powerMeasurandValues
.L3
= 0;
339 powerMeasurandValues
.allPhases
= Utils
.roundTo(
340 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
345 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
346 ? Utils
.getRandomFloatFluctuatedRounded(
347 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
348 powerSampledValueTemplate
.value
,
349 connectorMaximumPower
/ unitDivider
,
350 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
352 powerSampledValueTemplate
.fluctuationPercent
??
353 Constants
.DEFAULT_FLUCTUATION_PERCENT
355 : Utils
.getRandomFloatRounded(
356 connectorMaximumPower
/ unitDivider
,
357 connectorMinimumPower
/ unitDivider
361 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
362 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
364 meterValue
.sampledValue
.push(
365 OCPP16ServiceUtils
.buildSampledValue(
366 powerSampledValueTemplate
,
367 powerMeasurandValues
.allPhases
370 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
371 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
372 const connectorMinimumPowerRounded
= Utils
.roundTo(connectorMinimumPower
/ unitDivider
, 2);
374 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
375 connectorMaximumPowerRounded
||
376 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
377 connectorMinimumPowerRounded
||
381 `${chargingStation.logPrefix()} MeterValues measurand ${
382 meterValue.sampledValue[sampledValuesIndex].measurand ??
383 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
384 }: connector id ${connectorId}, transaction id ${
385 connector?.transactionId
386 }, value: ${connectorMinimumPowerRounded}/${
387 meterValue.sampledValue[sampledValuesIndex].value
388 }/${connectorMaximumPowerRounded}`
393 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
396 const phaseValue
= `L${phase}-N`;
397 meterValue
.sampledValue
.push(
398 OCPP16ServiceUtils
.buildSampledValue(
399 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
400 powerSampledValueTemplate
,
401 powerMeasurandValues
[`L${phase}`] as number,
403 phaseValue
as OCPP16MeterValuePhase
406 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
407 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
408 connectorMaximumPowerPerPhase
/ unitDivider
,
411 const connectorMinimumPowerPerPhaseRounded
= Utils
.roundTo(
412 connectorMinimumPowerPerPhase
/ unitDivider
,
416 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
417 connectorMaximumPowerPerPhaseRounded
||
418 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
419 connectorMinimumPowerPerPhaseRounded
||
423 `${chargingStation.logPrefix()} MeterValues measurand ${
424 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
425 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
427 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
428 }, connector id ${connectorId}, transaction id ${
429 connector?.transactionId
430 }, value: ${connectorMinimumPowerPerPhaseRounded}/${
431 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
432 }/${connectorMaximumPowerPerPhaseRounded}`
437 // Current.Import measurand
438 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
441 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
443 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
444 if (chargingStation
.getNumberOfPhases() === 3) {
445 currentPerPhaseSampledValueTemplates
= {
446 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
449 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
450 OCPP16MeterValuePhase
.L1
452 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
455 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
456 OCPP16MeterValuePhase
.L2
458 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
461 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
462 OCPP16MeterValuePhase
.L3
466 if (currentSampledValueTemplate
) {
467 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
469 currentSampledValueTemplate
.measurand
471 const errMsg
= `MeterValues measurand ${
472 currentSampledValueTemplate.measurand ??
473 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
474 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
475 chargingStation.templateFile
476 }, cannot calculate ${
477 currentSampledValueTemplate.measurand ??
478 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
480 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
481 const connectorMaximumAvailablePower
=
482 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
483 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
484 let connectorMaximumAmperage
: number;
485 switch (chargingStation
.getCurrentOutType()) {
487 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
488 chargingStation
.getNumberOfPhases(),
489 connectorMaximumAvailablePower
,
490 chargingStation
.getVoltageOut()
492 if (chargingStation
.getNumberOfPhases() === 3) {
493 const defaultFluctuatedAmperagePerPhase
=
494 currentSampledValueTemplate
.value
&&
495 Utils
.getRandomFloatFluctuatedRounded(
496 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
497 currentSampledValueTemplate
.value
,
498 connectorMaximumAmperage
,
499 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
501 currentSampledValueTemplate
.fluctuationPercent
??
502 Constants
.DEFAULT_FLUCTUATION_PERCENT
504 const phase1FluctuatedValue
=
505 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
506 Utils
.getRandomFloatFluctuatedRounded(
507 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
508 currentPerPhaseSampledValueTemplates
.L1
.value
,
509 connectorMaximumAmperage
,
510 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
512 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
513 Constants
.DEFAULT_FLUCTUATION_PERCENT
515 const phase2FluctuatedValue
=
516 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
517 Utils
.getRandomFloatFluctuatedRounded(
518 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
519 currentPerPhaseSampledValueTemplates
.L2
.value
,
520 connectorMaximumAmperage
,
521 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
523 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
524 Constants
.DEFAULT_FLUCTUATION_PERCENT
526 const phase3FluctuatedValue
=
527 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
528 Utils
.getRandomFloatFluctuatedRounded(
529 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
530 currentPerPhaseSampledValueTemplates
.L3
.value
,
531 connectorMaximumAmperage
,
532 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
534 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
535 Constants
.DEFAULT_FLUCTUATION_PERCENT
537 currentMeasurandValues
.L1
=
538 phase1FluctuatedValue
??
539 defaultFluctuatedAmperagePerPhase
??
540 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
541 currentMeasurandValues
.L2
=
542 phase2FluctuatedValue
??
543 defaultFluctuatedAmperagePerPhase
??
544 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
545 currentMeasurandValues
.L3
=
546 phase3FluctuatedValue
??
547 defaultFluctuatedAmperagePerPhase
??
548 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
550 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
551 ? Utils
.getRandomFloatFluctuatedRounded(
552 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
553 currentSampledValueTemplate
.value
,
554 connectorMaximumAmperage
,
555 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
557 currentSampledValueTemplate
.fluctuationPercent
??
558 Constants
.DEFAULT_FLUCTUATION_PERCENT
560 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
561 currentMeasurandValues
.L2
= 0;
562 currentMeasurandValues
.L3
= 0;
564 currentMeasurandValues
.allPhases
= Utils
.roundTo(
565 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
566 chargingStation
.getNumberOfPhases(),
571 connectorMaximumAmperage
= DCElectricUtils
.amperage(
572 connectorMaximumAvailablePower
,
573 chargingStation
.getVoltageOut()
575 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
576 ? Utils
.getRandomFloatFluctuatedRounded(
577 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
578 currentSampledValueTemplate
.value
,
579 connectorMaximumAmperage
,
580 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
582 currentSampledValueTemplate
.fluctuationPercent
??
583 Constants
.DEFAULT_FLUCTUATION_PERCENT
585 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
588 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
589 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
591 meterValue
.sampledValue
.push(
592 OCPP16ServiceUtils
.buildSampledValue(
593 currentSampledValueTemplate
,
594 currentMeasurandValues
.allPhases
597 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
599 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
600 connectorMaximumAmperage
||
601 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
602 connectorMinimumAmperage
||
606 `${chargingStation.logPrefix()} MeterValues measurand ${
607 meterValue.sampledValue[sampledValuesIndex].measurand ??
608 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
609 }: connector id ${connectorId}, transaction id ${
610 connector?.transactionId
611 }, value: ${connectorMinimumAmperage}/${
612 meterValue.sampledValue[sampledValuesIndex].value
613 }/${connectorMaximumAmperage}`
618 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
621 const phaseValue
= `L${phase}`;
622 meterValue
.sampledValue
.push(
623 OCPP16ServiceUtils
.buildSampledValue(
624 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
625 currentSampledValueTemplate
,
626 currentMeasurandValues
[phaseValue
] as number,
628 phaseValue
as OCPP16MeterValuePhase
631 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
633 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
634 connectorMaximumAmperage
||
635 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
636 connectorMinimumAmperage
||
640 `${chargingStation.logPrefix()} MeterValues measurand ${
641 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
642 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
644 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
645 }, connector id ${connectorId}, transaction id ${
646 connector?.transactionId
647 }, value: ${connectorMinimumAmperage}/${
648 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
649 }/${connectorMaximumAmperage}`
654 // Energy.Active.Import.Register measurand (default)
655 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
659 if (energySampledValueTemplate
) {
660 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
662 energySampledValueTemplate
.measurand
665 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
666 const connectorMaximumAvailablePower
=
667 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
668 const connectorMaximumEnergyRounded
= Utils
.roundTo(
669 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
672 const energyValueRounded
= energySampledValueTemplate
.value
673 ? // Cumulate the fluctuated value around the static one
674 Utils
.getRandomFloatFluctuatedRounded(
675 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
676 energySampledValueTemplate
.value
,
677 connectorMaximumEnergyRounded
,
679 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
680 unitMultiplier
: unitDivider
,
683 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
685 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
686 // Persist previous value on connector
689 Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
690 connector
.energyActiveImportRegisterValue
>= 0 &&
691 Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
692 connector
.transactionEnergyActiveImportRegisterValue
>= 0
694 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
695 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
697 connector
.energyActiveImportRegisterValue
= 0;
698 connector
.transactionEnergyActiveImportRegisterValue
= 0;
700 meterValue
.sampledValue
.push(
701 OCPP16ServiceUtils
.buildSampledValue(
702 energySampledValueTemplate
,
704 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
710 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
711 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
713 `${chargingStation.logPrefix()} MeterValues measurand ${
714 meterValue.sampledValue[sampledValuesIndex].measurand ??
715 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
716 }: connector id ${connectorId}, transaction id ${
717 connector?.transactionId
718 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
719 interval / (3600 * 1000),
728 public static buildTransactionBeginMeterValue(
729 chargingStation
: ChargingStation
,
732 ): OCPP16MeterValue
{
733 const meterValue
: OCPP16MeterValue
= {
734 timestamp
: new Date(),
737 // Energy.Active.Import.Register measurand (default)
738 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
742 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
743 meterValue
.sampledValue
.push(
744 OCPP16ServiceUtils
.buildSampledValue(
745 sampledValueTemplate
,
746 Utils
.roundTo((meterStart
?? 0) / unitDivider
, 4),
747 MeterValueContext
.TRANSACTION_BEGIN
753 public static buildTransactionEndMeterValue(
754 chargingStation
: ChargingStation
,
757 ): OCPP16MeterValue
{
758 const meterValue
: OCPP16MeterValue
= {
759 timestamp
: new Date(),
762 // Energy.Active.Import.Register measurand (default)
763 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
767 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
768 meterValue
.sampledValue
.push(
769 OCPP16ServiceUtils
.buildSampledValue(
770 sampledValueTemplate
,
771 Utils
.roundTo((meterStop
?? 0) / unitDivider
, 4),
772 MeterValueContext
.TRANSACTION_END
778 public static buildTransactionDataMeterValues(
779 transactionBeginMeterValue
: OCPP16MeterValue
,
780 transactionEndMeterValue
: OCPP16MeterValue
781 ): OCPP16MeterValue
[] {
782 const meterValues
: OCPP16MeterValue
[] = [];
783 meterValues
.push(transactionBeginMeterValue
);
784 meterValues
.push(transactionEndMeterValue
);
788 public static setChargingProfile(
789 chargingStation
: ChargingStation
,
791 cp
: OCPP16ChargingProfile
794 Utils
.isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)
797 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
799 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
802 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
805 `${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`
807 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
809 let cpReplaced
= false;
810 if (Utils
.isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
812 .getConnectorStatus(connectorId
)
813 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
815 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
816 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
817 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
819 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
824 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
827 public static parseJsonSchemaFile
<T
extends JsonType
>(
828 relativePath
: string,
831 ): JSONSchemaType
<T
> {
832 return super.parseJsonSchemaFile
<T
>(
834 OCPPVersion
.VERSION_16
,
840 private static buildSampledValue(
841 sampledValueTemplate
: SampledValueTemplate
,
843 context
?: MeterValueContext
,
844 phase
?: OCPP16MeterValuePhase
845 ): OCPP16SampledValue
{
846 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
847 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
848 const sampledValueLocation
=
849 sampledValueTemplate
?.location
??
850 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
851 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
853 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
854 unit
: sampledValueTemplate
.unit
,
856 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
857 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
858 measurand
: sampledValueTemplate
.measurand
,
860 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
861 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
862 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
866 private static checkMeasurandPowerDivider(
867 chargingStation
: ChargingStation
,
868 measurandType
: OCPP16MeterValueMeasurand
870 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
871 const errMsg
= `MeterValues measurand ${
872 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
873 }: powerDivider is undefined`;
874 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
875 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
876 } else if (chargingStation
?.powerDivider
<= 0) {
877 const errMsg
= `MeterValues measurand ${
878 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
879 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
880 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
881 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
885 private static getMeasurandDefaultLocation(
886 measurandType
: OCPP16MeterValueMeasurand
887 ): MeterValueLocation
| undefined {
888 switch (measurandType
) {
889 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
890 return MeterValueLocation
.EV
;
894 private static getMeasurandDefaultUnit(
895 measurandType
: OCPP16MeterValueMeasurand
896 ): MeterValueUnit
| undefined {
897 switch (measurandType
) {
898 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
899 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
900 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
901 return MeterValueUnit
.AMP
;
902 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
903 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
904 return MeterValueUnit
.WATT_HOUR
;
905 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
906 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
907 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
908 return MeterValueUnit
.WATT
;
909 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
910 return MeterValueUnit
.PERCENT
;
911 case OCPP16MeterValueMeasurand
.VOLTAGE
:
912 return MeterValueUnit
.VOLT
;