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 OCPPError from
'../../../exception/OCPPError';
9 import { CurrentType
, Voltage
} from
'../../../types/ChargingStationTemplate';
10 import type { JsonType
} from
'../../../types/JsonType';
12 MeasurandPerPhaseSampledValueTemplates
,
14 } from
'../../../types/MeasurandPerPhaseSampledValueTemplates';
15 import type { MeasurandValues
} from
'../../../types/MeasurandValues';
16 import type { OCPP16ChargingProfile
} from
'../../../types/ocpp/1.6/ChargingProfile';
18 OCPP16StandardParametersKey
,
19 OCPP16SupportedFeatureProfiles
,
20 } from
'../../../types/ocpp/1.6/Configuration';
25 type OCPP16MeterValue
,
26 OCPP16MeterValueMeasurand
,
27 OCPP16MeterValuePhase
,
28 type OCPP16SampledValue
,
29 } from
'../../../types/ocpp/1.6/MeterValues';
31 type OCPP16IncomingRequestCommand
,
33 } from
'../../../types/ocpp/1.6/Requests';
34 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
35 import { OCPPVersion
} from
'../../../types/ocpp/OCPPVersion';
36 import Constants from
'../../../utils/Constants';
37 import { ACElectricUtils
, DCElectricUtils
} from
'../../../utils/ElectricUtils';
38 import logger from
'../../../utils/Logger';
39 import Utils from
'../../../utils/Utils';
40 import type ChargingStation from
'../../ChargingStation';
41 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
43 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
44 public static checkFeatureProfile(
45 chargingStation
: ChargingStation
,
46 featureProfile
: OCPP16SupportedFeatureProfiles
,
47 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
49 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
51 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
52 OCPP16StandardParametersKey.SupportedFeatureProfiles
60 public static buildMeterValue(
61 chargingStation
: ChargingStation
,
63 transactionId
: number,
67 const meterValue
: OCPP16MeterValue
= {
68 timestamp
: new Date(),
71 const connector
= chargingStation
.getConnectorStatus(connectorId
);
73 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
76 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
78 if (socSampledValueTemplate
) {
79 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
80 ? Utils
.getRandomFloatFluctuatedRounded(
81 parseInt(socSampledValueTemplate
.value
),
82 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
84 : Utils
.getRandomInteger(100);
85 meterValue
.sampledValue
.push(
86 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
88 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
89 if (Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > 100 || debug
) {
91 `${chargingStation.logPrefix()} MeterValues measurand ${
92 meterValue.sampledValue[sampledValuesIndex].measurand ??
93 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
94 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
95 meterValue.sampledValue[sampledValuesIndex].value
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 switch (chargingStation
.getCurrentOutType()) {
254 if (chargingStation
.getNumberOfPhases() === 3) {
255 const defaultFluctuatedPowerPerPhase
=
256 powerSampledValueTemplate
.value
&&
257 Utils
.getRandomFloatFluctuatedRounded(
258 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
259 powerSampledValueTemplate
.value
,
260 connectorMaximumPower
/ unitDivider
,
261 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
262 ) / chargingStation
.getNumberOfPhases(),
263 powerSampledValueTemplate
.fluctuationPercent
??
264 Constants
.DEFAULT_FLUCTUATION_PERCENT
266 const phase1FluctuatedValue
=
267 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
268 Utils
.getRandomFloatFluctuatedRounded(
269 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
270 powerPerPhaseSampledValueTemplates
.L1
.value
,
271 connectorMaximumPowerPerPhase
/ unitDivider
,
272 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
274 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
275 Constants
.DEFAULT_FLUCTUATION_PERCENT
277 const phase2FluctuatedValue
=
278 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
279 Utils
.getRandomFloatFluctuatedRounded(
280 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
281 powerPerPhaseSampledValueTemplates
.L2
.value
,
282 connectorMaximumPowerPerPhase
/ unitDivider
,
283 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
285 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
286 Constants
.DEFAULT_FLUCTUATION_PERCENT
288 const phase3FluctuatedValue
=
289 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
290 Utils
.getRandomFloatFluctuatedRounded(
291 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
292 powerPerPhaseSampledValueTemplates
.L3
.value
,
293 connectorMaximumPowerPerPhase
/ unitDivider
,
294 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
296 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
297 Constants
.DEFAULT_FLUCTUATION_PERCENT
299 powerMeasurandValues
.L1
=
300 phase1FluctuatedValue
??
301 defaultFluctuatedPowerPerPhase
??
302 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
303 powerMeasurandValues
.L2
=
304 phase2FluctuatedValue
??
305 defaultFluctuatedPowerPerPhase
??
306 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
307 powerMeasurandValues
.L3
=
308 phase3FluctuatedValue
??
309 defaultFluctuatedPowerPerPhase
??
310 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
312 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
313 ? Utils
.getRandomFloatFluctuatedRounded(
314 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
315 powerSampledValueTemplate
.value
,
316 connectorMaximumPower
/ unitDivider
,
317 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
319 powerSampledValueTemplate
.fluctuationPercent
??
320 Constants
.DEFAULT_FLUCTUATION_PERCENT
322 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
323 powerMeasurandValues
.L2
= 0;
324 powerMeasurandValues
.L3
= 0;
326 powerMeasurandValues
.allPhases
= Utils
.roundTo(
327 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
332 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
333 ? Utils
.getRandomFloatFluctuatedRounded(
334 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
335 powerSampledValueTemplate
.value
,
336 connectorMaximumPower
/ unitDivider
,
337 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
339 powerSampledValueTemplate
.fluctuationPercent
??
340 Constants
.DEFAULT_FLUCTUATION_PERCENT
342 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
345 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
346 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
348 meterValue
.sampledValue
.push(
349 OCPP16ServiceUtils
.buildSampledValue(
350 powerSampledValueTemplate
,
351 powerMeasurandValues
.allPhases
354 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
355 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
357 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
358 connectorMaximumPowerRounded
||
362 `${chargingStation.logPrefix()} MeterValues measurand ${
363 meterValue.sampledValue[sampledValuesIndex].measurand ??
364 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
365 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
366 meterValue.sampledValue[sampledValuesIndex].value
367 }/${connectorMaximumPowerRounded}`
372 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
375 const phaseValue
= `L${phase}-N`;
376 meterValue
.sampledValue
.push(
377 OCPP16ServiceUtils
.buildSampledValue(
378 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
379 powerSampledValueTemplate
,
380 powerMeasurandValues
[`L${phase}`] as number,
382 phaseValue
as OCPP16MeterValuePhase
385 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
386 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
387 connectorMaximumPowerPerPhase
/ unitDivider
,
391 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
392 connectorMaximumPowerPerPhaseRounded
||
396 `${chargingStation.logPrefix()} MeterValues measurand ${
397 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
398 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
400 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
401 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
402 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
403 }/${connectorMaximumPowerPerPhaseRounded}`
408 // Current.Import measurand
409 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
412 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
414 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
415 if (chargingStation
.getNumberOfPhases() === 3) {
416 currentPerPhaseSampledValueTemplates
= {
417 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
420 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
421 OCPP16MeterValuePhase
.L1
423 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
426 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
427 OCPP16MeterValuePhase
.L2
429 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
432 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
433 OCPP16MeterValuePhase
.L3
437 if (currentSampledValueTemplate
) {
438 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
440 currentSampledValueTemplate
.measurand
442 const errMsg
= `MeterValues measurand ${
443 currentSampledValueTemplate.measurand ??
444 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
445 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
446 chargingStation.templateFile
447 }, cannot calculate ${
448 currentSampledValueTemplate.measurand ??
449 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
451 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
452 const connectorMaximumAvailablePower
=
453 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
454 let connectorMaximumAmperage
: number;
455 switch (chargingStation
.getCurrentOutType()) {
457 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
458 chargingStation
.getNumberOfPhases(),
459 connectorMaximumAvailablePower
,
460 chargingStation
.getVoltageOut()
462 if (chargingStation
.getNumberOfPhases() === 3) {
463 const defaultFluctuatedAmperagePerPhase
=
464 currentSampledValueTemplate
.value
&&
465 Utils
.getRandomFloatFluctuatedRounded(
466 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
467 currentSampledValueTemplate
.value
,
468 connectorMaximumAmperage
,
469 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
471 currentSampledValueTemplate
.fluctuationPercent
??
472 Constants
.DEFAULT_FLUCTUATION_PERCENT
474 const phase1FluctuatedValue
=
475 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
476 Utils
.getRandomFloatFluctuatedRounded(
477 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
478 currentPerPhaseSampledValueTemplates
.L1
.value
,
479 connectorMaximumAmperage
,
480 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
482 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
483 Constants
.DEFAULT_FLUCTUATION_PERCENT
485 const phase2FluctuatedValue
=
486 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
487 Utils
.getRandomFloatFluctuatedRounded(
488 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
489 currentPerPhaseSampledValueTemplates
.L2
.value
,
490 connectorMaximumAmperage
,
491 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
493 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
494 Constants
.DEFAULT_FLUCTUATION_PERCENT
496 const phase3FluctuatedValue
=
497 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
498 Utils
.getRandomFloatFluctuatedRounded(
499 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
500 currentPerPhaseSampledValueTemplates
.L3
.value
,
501 connectorMaximumAmperage
,
502 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
504 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
505 Constants
.DEFAULT_FLUCTUATION_PERCENT
507 currentMeasurandValues
.L1
=
508 phase1FluctuatedValue
??
509 defaultFluctuatedAmperagePerPhase
??
510 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
511 currentMeasurandValues
.L2
=
512 phase2FluctuatedValue
??
513 defaultFluctuatedAmperagePerPhase
??
514 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
515 currentMeasurandValues
.L3
=
516 phase3FluctuatedValue
??
517 defaultFluctuatedAmperagePerPhase
??
518 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
520 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
521 ? Utils
.getRandomFloatFluctuatedRounded(
522 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
523 currentSampledValueTemplate
.value
,
524 connectorMaximumAmperage
,
525 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
527 currentSampledValueTemplate
.fluctuationPercent
??
528 Constants
.DEFAULT_FLUCTUATION_PERCENT
530 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
531 currentMeasurandValues
.L2
= 0;
532 currentMeasurandValues
.L3
= 0;
534 currentMeasurandValues
.allPhases
= Utils
.roundTo(
535 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
536 chargingStation
.getNumberOfPhases(),
541 connectorMaximumAmperage
= DCElectricUtils
.amperage(
542 connectorMaximumAvailablePower
,
543 chargingStation
.getVoltageOut()
545 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
546 ? Utils
.getRandomFloatFluctuatedRounded(
547 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
548 currentSampledValueTemplate
.value
,
549 connectorMaximumAmperage
,
550 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
552 currentSampledValueTemplate
.fluctuationPercent
??
553 Constants
.DEFAULT_FLUCTUATION_PERCENT
555 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
558 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
559 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
561 meterValue
.sampledValue
.push(
562 OCPP16ServiceUtils
.buildSampledValue(
563 currentSampledValueTemplate
,
564 currentMeasurandValues
.allPhases
567 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
569 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
570 connectorMaximumAmperage
||
574 `${chargingStation.logPrefix()} MeterValues measurand ${
575 meterValue.sampledValue[sampledValuesIndex].measurand ??
576 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
577 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
578 meterValue.sampledValue[sampledValuesIndex].value
579 }/${connectorMaximumAmperage}`
584 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
587 const phaseValue
= `L${phase}`;
588 meterValue
.sampledValue
.push(
589 OCPP16ServiceUtils
.buildSampledValue(
590 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
591 currentSampledValueTemplate
,
592 currentMeasurandValues
[phaseValue
] as number,
594 phaseValue
as OCPP16MeterValuePhase
597 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
599 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
600 connectorMaximumAmperage
||
604 `${chargingStation.logPrefix()} MeterValues measurand ${
605 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
606 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
608 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
609 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
610 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
611 }/${connectorMaximumAmperage}`
616 // Energy.Active.Import.Register measurand (default)
617 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
621 if (energySampledValueTemplate
) {
622 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
624 energySampledValueTemplate
.measurand
627 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
628 const connectorMaximumAvailablePower
=
629 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
630 const connectorMaximumEnergyRounded
= Utils
.roundTo(
631 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
634 const energyValueRounded
= energySampledValueTemplate
.value
635 ? // Cumulate the fluctuated value around the static one
636 Utils
.getRandomFloatFluctuatedRounded(
637 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
638 energySampledValueTemplate
.value
,
639 connectorMaximumEnergyRounded
,
641 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
642 unitMultiplier
: unitDivider
,
645 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
647 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
648 // Persist previous value on connector
651 Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
652 connector
.energyActiveImportRegisterValue
>= 0 &&
653 Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
654 connector
.transactionEnergyActiveImportRegisterValue
>= 0
656 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
657 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
659 connector
.energyActiveImportRegisterValue
= 0;
660 connector
.transactionEnergyActiveImportRegisterValue
= 0;
662 meterValue
.sampledValue
.push(
663 OCPP16ServiceUtils
.buildSampledValue(
664 energySampledValueTemplate
,
666 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
672 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
673 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
675 `${chargingStation.logPrefix()} MeterValues measurand ${
676 meterValue.sampledValue[sampledValuesIndex].measurand ??
677 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
678 }: connectorId ${connectorId}, transaction ${
679 connector?.transactionId
680 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
681 interval / (3600 * 1000),
690 public static buildTransactionBeginMeterValue(
691 chargingStation
: ChargingStation
,
694 ): OCPP16MeterValue
{
695 const meterValue
: OCPP16MeterValue
= {
696 timestamp
: new Date(),
699 // Energy.Active.Import.Register measurand (default)
700 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
704 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
705 meterValue
.sampledValue
.push(
706 OCPP16ServiceUtils
.buildSampledValue(
707 sampledValueTemplate
,
708 Utils
.roundTo((meterStart
?? 0) / unitDivider
, 4),
709 MeterValueContext
.TRANSACTION_BEGIN
715 public static buildTransactionEndMeterValue(
716 chargingStation
: ChargingStation
,
719 ): OCPP16MeterValue
{
720 const meterValue
: OCPP16MeterValue
= {
721 timestamp
: new Date(),
724 // Energy.Active.Import.Register measurand (default)
725 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
729 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
730 meterValue
.sampledValue
.push(
731 OCPP16ServiceUtils
.buildSampledValue(
732 sampledValueTemplate
,
733 Utils
.roundTo((meterStop
?? 0) / unitDivider
, 4),
734 MeterValueContext
.TRANSACTION_END
740 public static buildTransactionDataMeterValues(
741 transactionBeginMeterValue
: OCPP16MeterValue
,
742 transactionEndMeterValue
: OCPP16MeterValue
743 ): OCPP16MeterValue
[] {
744 const meterValues
: OCPP16MeterValue
[] = [];
745 meterValues
.push(transactionBeginMeterValue
);
746 meterValues
.push(transactionEndMeterValue
);
750 public static setChargingProfile(
751 chargingStation
: ChargingStation
,
753 cp
: OCPP16ChargingProfile
756 Utils
.isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)
759 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
761 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
764 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
767 `${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`
769 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
771 let cpReplaced
= false;
772 if (!Utils
.isEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
774 .getConnectorStatus(connectorId
)
775 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
777 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
778 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
779 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
781 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
786 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
789 public static parseJsonSchemaFile
<T
extends JsonType
>(relativePath
: string): JSONSchemaType
<T
> {
790 return super.parseJsonSchemaFile
<T
>(
791 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), relativePath
),
792 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
;