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';
13 type MeasurandPerPhaseSampledValueTemplates
,
18 type OCPP16ChargingProfile
,
19 type OCPP16IncomingRequestCommand
,
20 type OCPP16MeterValue
,
21 OCPP16MeterValueMeasurand
,
22 OCPP16MeterValuePhase
,
24 type OCPP16SampledValue
,
25 OCPP16StandardParametersKey
,
26 type OCPP16SupportedFeatureProfiles
,
28 type SampledValueTemplate
,
30 } from
'../../../types';
31 import { Constants
} from
'../../../utils/Constants';
32 import { ACElectricUtils
, DCElectricUtils
} from
'../../../utils/ElectricUtils';
33 import { logger
} from
'../../../utils/Logger';
34 import { Utils
} from
'../../../utils/Utils';
35 import type { ChargingStation
} from
'../../ChargingStation';
36 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
38 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
39 public static checkFeatureProfile(
40 chargingStation
: ChargingStation
,
41 featureProfile
: OCPP16SupportedFeatureProfiles
,
42 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
44 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
46 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
47 OCPP16StandardParametersKey.SupportedFeatureProfiles
55 public static buildMeterValue(
56 chargingStation
: ChargingStation
,
58 transactionId
: number,
62 const meterValue
: OCPP16MeterValue
= {
63 timestamp
: new Date(),
66 const connector
= chargingStation
.getConnectorStatus(connectorId
);
68 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
71 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
73 if (socSampledValueTemplate
) {
74 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
75 ? Utils
.getRandomFloatFluctuatedRounded(
76 parseInt(socSampledValueTemplate
.value
),
77 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
79 : Utils
.getRandomInteger(100);
80 meterValue
.sampledValue
.push(
81 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
83 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
84 if (Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > 100 || debug
) {
86 `${chargingStation.logPrefix()} MeterValues measurand ${
87 meterValue.sampledValue[sampledValuesIndex].measurand ??
88 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
89 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
90 meterValue.sampledValue[sampledValuesIndex].value
96 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
99 OCPP16MeterValueMeasurand
.VOLTAGE
101 if (voltageSampledValueTemplate
) {
102 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
103 ? parseInt(voltageSampledValueTemplate
.value
)
104 : chargingStation
.getVoltageOut();
105 const fluctuationPercent
=
106 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
107 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
108 voltageSampledValueTemplateValue
,
112 chargingStation
.getNumberOfPhases() !== 3 ||
113 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
115 meterValue
.sampledValue
.push(
116 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
121 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
124 const phaseLineToNeutralValue
= `L${phase}-N`;
125 const voltagePhaseLineToNeutralSampledValueTemplate
=
126 OCPP16ServiceUtils
.getSampledValueTemplate(
129 OCPP16MeterValueMeasurand
.VOLTAGE
,
130 phaseLineToNeutralValue
as OCPP16MeterValuePhase
132 let voltagePhaseLineToNeutralMeasurandValue
: number;
133 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
134 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
135 voltagePhaseLineToNeutralSampledValueTemplate
.value
136 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
137 : chargingStation
.getVoltageOut();
138 const fluctuationPhaseToNeutralPercent
=
139 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
140 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
141 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
142 voltagePhaseLineToNeutralSampledValueTemplateValue
,
143 fluctuationPhaseToNeutralPercent
146 meterValue
.sampledValue
.push(
147 OCPP16ServiceUtils
.buildSampledValue(
148 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
149 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
151 phaseLineToNeutralValue
as OCPP16MeterValuePhase
154 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
155 const phaseLineToLineValue
= `L${phase}-L${
156 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
157 ? (phase + 1) % chargingStation.getNumberOfPhases()
158 : chargingStation.getNumberOfPhases()
160 const voltagePhaseLineToLineSampledValueTemplate
=
161 OCPP16ServiceUtils
.getSampledValueTemplate(
164 OCPP16MeterValueMeasurand
.VOLTAGE
,
165 phaseLineToLineValue
as OCPP16MeterValuePhase
167 let voltagePhaseLineToLineMeasurandValue
: number;
168 if (voltagePhaseLineToLineSampledValueTemplate
) {
169 const voltagePhaseLineToLineSampledValueTemplateValue
=
170 voltagePhaseLineToLineSampledValueTemplate
.value
171 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
172 : Voltage
.VOLTAGE_400
;
173 const fluctuationPhaseLineToLinePercent
=
174 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
175 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
176 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
177 voltagePhaseLineToLineSampledValueTemplateValue
,
178 fluctuationPhaseLineToLinePercent
181 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
185 meterValue
.sampledValue
.push(
186 OCPP16ServiceUtils
.buildSampledValue(
187 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
188 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
190 phaseLineToLineValue
as OCPP16MeterValuePhase
196 // Power.Active.Import measurand
197 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
200 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
202 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
203 if (chargingStation
.getNumberOfPhases() === 3) {
204 powerPerPhaseSampledValueTemplates
= {
205 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
208 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
209 OCPP16MeterValuePhase
.L1_N
211 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
214 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
215 OCPP16MeterValuePhase
.L2_N
217 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
220 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
221 OCPP16MeterValuePhase
.L3_N
225 if (powerSampledValueTemplate
) {
226 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
228 powerSampledValueTemplate
.measurand
230 const errMsg
= `MeterValues measurand ${
231 powerSampledValueTemplate.measurand ??
232 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
233 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
234 chargingStation.templateFile
235 }, cannot calculate ${
236 powerSampledValueTemplate.measurand ??
237 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
239 const powerMeasurandValues
= {} as MeasurandValues
;
240 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
241 const connectorMaximumAvailablePower
=
242 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
243 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
244 const connectorMaximumPowerPerPhase
= Math.round(
245 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
247 switch (chargingStation
.getCurrentOutType()) {
249 if (chargingStation
.getNumberOfPhases() === 3) {
250 const defaultFluctuatedPowerPerPhase
=
251 powerSampledValueTemplate
.value
&&
252 Utils
.getRandomFloatFluctuatedRounded(
253 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
254 powerSampledValueTemplate
.value
,
255 connectorMaximumPower
/ unitDivider
,
256 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
257 ) / chargingStation
.getNumberOfPhases(),
258 powerSampledValueTemplate
.fluctuationPercent
??
259 Constants
.DEFAULT_FLUCTUATION_PERCENT
261 const phase1FluctuatedValue
=
262 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
263 Utils
.getRandomFloatFluctuatedRounded(
264 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
265 powerPerPhaseSampledValueTemplates
.L1
.value
,
266 connectorMaximumPowerPerPhase
/ unitDivider
,
267 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
269 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
270 Constants
.DEFAULT_FLUCTUATION_PERCENT
272 const phase2FluctuatedValue
=
273 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
274 Utils
.getRandomFloatFluctuatedRounded(
275 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
276 powerPerPhaseSampledValueTemplates
.L2
.value
,
277 connectorMaximumPowerPerPhase
/ unitDivider
,
278 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
280 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
281 Constants
.DEFAULT_FLUCTUATION_PERCENT
283 const phase3FluctuatedValue
=
284 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
285 Utils
.getRandomFloatFluctuatedRounded(
286 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
287 powerPerPhaseSampledValueTemplates
.L3
.value
,
288 connectorMaximumPowerPerPhase
/ unitDivider
,
289 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
291 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
292 Constants
.DEFAULT_FLUCTUATION_PERCENT
294 powerMeasurandValues
.L1
=
295 phase1FluctuatedValue
??
296 defaultFluctuatedPowerPerPhase
??
297 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
298 powerMeasurandValues
.L2
=
299 phase2FluctuatedValue
??
300 defaultFluctuatedPowerPerPhase
??
301 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
302 powerMeasurandValues
.L3
=
303 phase3FluctuatedValue
??
304 defaultFluctuatedPowerPerPhase
??
305 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
307 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
308 ? Utils
.getRandomFloatFluctuatedRounded(
309 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
310 powerSampledValueTemplate
.value
,
311 connectorMaximumPower
/ unitDivider
,
312 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
314 powerSampledValueTemplate
.fluctuationPercent
??
315 Constants
.DEFAULT_FLUCTUATION_PERCENT
317 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
318 powerMeasurandValues
.L2
= 0;
319 powerMeasurandValues
.L3
= 0;
321 powerMeasurandValues
.allPhases
= Utils
.roundTo(
322 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
327 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
328 ? Utils
.getRandomFloatFluctuatedRounded(
329 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
330 powerSampledValueTemplate
.value
,
331 connectorMaximumPower
/ unitDivider
,
332 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
334 powerSampledValueTemplate
.fluctuationPercent
??
335 Constants
.DEFAULT_FLUCTUATION_PERCENT
337 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
340 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
341 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
343 meterValue
.sampledValue
.push(
344 OCPP16ServiceUtils
.buildSampledValue(
345 powerSampledValueTemplate
,
346 powerMeasurandValues
.allPhases
349 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
350 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
352 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
353 connectorMaximumPowerRounded
||
357 `${chargingStation.logPrefix()} MeterValues measurand ${
358 meterValue.sampledValue[sampledValuesIndex].measurand ??
359 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
360 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
361 meterValue.sampledValue[sampledValuesIndex].value
362 }/${connectorMaximumPowerRounded}`
367 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
370 const phaseValue
= `L${phase}-N`;
371 meterValue
.sampledValue
.push(
372 OCPP16ServiceUtils
.buildSampledValue(
373 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
374 powerSampledValueTemplate
,
375 powerMeasurandValues
[`L${phase}`] as number,
377 phaseValue
as OCPP16MeterValuePhase
380 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
381 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
382 connectorMaximumPowerPerPhase
/ unitDivider
,
386 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
387 connectorMaximumPowerPerPhaseRounded
||
391 `${chargingStation.logPrefix()} MeterValues measurand ${
392 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
393 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
395 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
396 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
397 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
398 }/${connectorMaximumPowerPerPhaseRounded}`
403 // Current.Import measurand
404 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
407 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
409 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
410 if (chargingStation
.getNumberOfPhases() === 3) {
411 currentPerPhaseSampledValueTemplates
= {
412 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
415 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
416 OCPP16MeterValuePhase
.L1
418 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
421 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
422 OCPP16MeterValuePhase
.L2
424 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
427 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
428 OCPP16MeterValuePhase
.L3
432 if (currentSampledValueTemplate
) {
433 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
435 currentSampledValueTemplate
.measurand
437 const errMsg
= `MeterValues measurand ${
438 currentSampledValueTemplate.measurand ??
439 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
440 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
441 chargingStation.templateFile
442 }, cannot calculate ${
443 currentSampledValueTemplate.measurand ??
444 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
446 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
447 const connectorMaximumAvailablePower
=
448 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
449 let connectorMaximumAmperage
: number;
450 switch (chargingStation
.getCurrentOutType()) {
452 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
453 chargingStation
.getNumberOfPhases(),
454 connectorMaximumAvailablePower
,
455 chargingStation
.getVoltageOut()
457 if (chargingStation
.getNumberOfPhases() === 3) {
458 const defaultFluctuatedAmperagePerPhase
=
459 currentSampledValueTemplate
.value
&&
460 Utils
.getRandomFloatFluctuatedRounded(
461 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
462 currentSampledValueTemplate
.value
,
463 connectorMaximumAmperage
,
464 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
466 currentSampledValueTemplate
.fluctuationPercent
??
467 Constants
.DEFAULT_FLUCTUATION_PERCENT
469 const phase1FluctuatedValue
=
470 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
471 Utils
.getRandomFloatFluctuatedRounded(
472 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
473 currentPerPhaseSampledValueTemplates
.L1
.value
,
474 connectorMaximumAmperage
,
475 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
477 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
478 Constants
.DEFAULT_FLUCTUATION_PERCENT
480 const phase2FluctuatedValue
=
481 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
482 Utils
.getRandomFloatFluctuatedRounded(
483 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
484 currentPerPhaseSampledValueTemplates
.L2
.value
,
485 connectorMaximumAmperage
,
486 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
488 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
489 Constants
.DEFAULT_FLUCTUATION_PERCENT
491 const phase3FluctuatedValue
=
492 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
493 Utils
.getRandomFloatFluctuatedRounded(
494 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
495 currentPerPhaseSampledValueTemplates
.L3
.value
,
496 connectorMaximumAmperage
,
497 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
499 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
500 Constants
.DEFAULT_FLUCTUATION_PERCENT
502 currentMeasurandValues
.L1
=
503 phase1FluctuatedValue
??
504 defaultFluctuatedAmperagePerPhase
??
505 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
506 currentMeasurandValues
.L2
=
507 phase2FluctuatedValue
??
508 defaultFluctuatedAmperagePerPhase
??
509 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
510 currentMeasurandValues
.L3
=
511 phase3FluctuatedValue
??
512 defaultFluctuatedAmperagePerPhase
??
513 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
515 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
516 ? Utils
.getRandomFloatFluctuatedRounded(
517 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
518 currentSampledValueTemplate
.value
,
519 connectorMaximumAmperage
,
520 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
522 currentSampledValueTemplate
.fluctuationPercent
??
523 Constants
.DEFAULT_FLUCTUATION_PERCENT
525 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
526 currentMeasurandValues
.L2
= 0;
527 currentMeasurandValues
.L3
= 0;
529 currentMeasurandValues
.allPhases
= Utils
.roundTo(
530 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
531 chargingStation
.getNumberOfPhases(),
536 connectorMaximumAmperage
= DCElectricUtils
.amperage(
537 connectorMaximumAvailablePower
,
538 chargingStation
.getVoltageOut()
540 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
541 ? Utils
.getRandomFloatFluctuatedRounded(
542 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
543 currentSampledValueTemplate
.value
,
544 connectorMaximumAmperage
,
545 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
547 currentSampledValueTemplate
.fluctuationPercent
??
548 Constants
.DEFAULT_FLUCTUATION_PERCENT
550 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
553 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
554 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
556 meterValue
.sampledValue
.push(
557 OCPP16ServiceUtils
.buildSampledValue(
558 currentSampledValueTemplate
,
559 currentMeasurandValues
.allPhases
562 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
564 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
565 connectorMaximumAmperage
||
569 `${chargingStation.logPrefix()} MeterValues measurand ${
570 meterValue.sampledValue[sampledValuesIndex].measurand ??
571 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
572 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
573 meterValue.sampledValue[sampledValuesIndex].value
574 }/${connectorMaximumAmperage}`
579 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
582 const phaseValue
= `L${phase}`;
583 meterValue
.sampledValue
.push(
584 OCPP16ServiceUtils
.buildSampledValue(
585 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
586 currentSampledValueTemplate
,
587 currentMeasurandValues
[phaseValue
] as number,
589 phaseValue
as OCPP16MeterValuePhase
592 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
594 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
595 connectorMaximumAmperage
||
599 `${chargingStation.logPrefix()} MeterValues measurand ${
600 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
601 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
603 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
604 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
605 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
606 }/${connectorMaximumAmperage}`
611 // Energy.Active.Import.Register measurand (default)
612 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
616 if (energySampledValueTemplate
) {
617 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
619 energySampledValueTemplate
.measurand
622 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
623 const connectorMaximumAvailablePower
=
624 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
625 const connectorMaximumEnergyRounded
= Utils
.roundTo(
626 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
629 const energyValueRounded
= energySampledValueTemplate
.value
630 ? // Cumulate the fluctuated value around the static one
631 Utils
.getRandomFloatFluctuatedRounded(
632 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
633 energySampledValueTemplate
.value
,
634 connectorMaximumEnergyRounded
,
636 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
637 unitMultiplier
: unitDivider
,
640 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
642 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
643 // Persist previous value on connector
646 Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
647 connector
.energyActiveImportRegisterValue
>= 0 &&
648 Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
649 connector
.transactionEnergyActiveImportRegisterValue
>= 0
651 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
652 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
654 connector
.energyActiveImportRegisterValue
= 0;
655 connector
.transactionEnergyActiveImportRegisterValue
= 0;
657 meterValue
.sampledValue
.push(
658 OCPP16ServiceUtils
.buildSampledValue(
659 energySampledValueTemplate
,
661 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
667 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
668 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
670 `${chargingStation.logPrefix()} MeterValues measurand ${
671 meterValue.sampledValue[sampledValuesIndex].measurand ??
672 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
673 }: connectorId ${connectorId}, transaction ${
674 connector?.transactionId
675 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
676 interval / (3600 * 1000),
685 public static buildTransactionBeginMeterValue(
686 chargingStation
: ChargingStation
,
689 ): OCPP16MeterValue
{
690 const meterValue
: OCPP16MeterValue
= {
691 timestamp
: new Date(),
694 // Energy.Active.Import.Register measurand (default)
695 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
699 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
700 meterValue
.sampledValue
.push(
701 OCPP16ServiceUtils
.buildSampledValue(
702 sampledValueTemplate
,
703 Utils
.roundTo((meterStart
?? 0) / unitDivider
, 4),
704 MeterValueContext
.TRANSACTION_BEGIN
710 public static buildTransactionEndMeterValue(
711 chargingStation
: ChargingStation
,
714 ): OCPP16MeterValue
{
715 const meterValue
: OCPP16MeterValue
= {
716 timestamp
: new Date(),
719 // Energy.Active.Import.Register measurand (default)
720 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
724 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
725 meterValue
.sampledValue
.push(
726 OCPP16ServiceUtils
.buildSampledValue(
727 sampledValueTemplate
,
728 Utils
.roundTo((meterStop
?? 0) / unitDivider
, 4),
729 MeterValueContext
.TRANSACTION_END
735 public static buildTransactionDataMeterValues(
736 transactionBeginMeterValue
: OCPP16MeterValue
,
737 transactionEndMeterValue
: OCPP16MeterValue
738 ): OCPP16MeterValue
[] {
739 const meterValues
: OCPP16MeterValue
[] = [];
740 meterValues
.push(transactionBeginMeterValue
);
741 meterValues
.push(transactionEndMeterValue
);
745 public static setChargingProfile(
746 chargingStation
: ChargingStation
,
748 cp
: OCPP16ChargingProfile
751 Utils
.isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)
754 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
756 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
759 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
762 `${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`
764 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
766 let cpReplaced
= false;
767 if (Utils
.isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
769 .getConnectorStatus(connectorId
)
770 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
772 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
773 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
774 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
776 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
781 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
784 public static parseJsonSchemaFile
<T
extends JsonType
>(
785 relativePath
: string,
788 ): JSONSchemaType
<T
> {
789 return super.parseJsonSchemaFile
<T
>(
790 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), relativePath
),
791 OCPPVersion
.VERSION_16
,
797 private static buildSampledValue(
798 sampledValueTemplate
: SampledValueTemplate
,
800 context
?: MeterValueContext
,
801 phase
?: OCPP16MeterValuePhase
802 ): OCPP16SampledValue
{
803 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
804 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
805 const sampledValueLocation
=
806 sampledValueTemplate
?.location
??
807 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
808 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
810 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
811 unit
: sampledValueTemplate
.unit
,
813 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
814 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
815 measurand
: sampledValueTemplate
.measurand
,
817 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
818 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
819 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
823 private static checkMeasurandPowerDivider(
824 chargingStation
: ChargingStation
,
825 measurandType
: OCPP16MeterValueMeasurand
827 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
828 const errMsg
= `MeterValues measurand ${
829 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
830 }: powerDivider is undefined`;
831 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
832 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
833 } else if (chargingStation
?.powerDivider
<= 0) {
834 const errMsg
= `MeterValues measurand ${
835 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
836 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
837 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
838 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
842 private static getMeasurandDefaultLocation(
843 measurandType
: OCPP16MeterValueMeasurand
844 ): MeterValueLocation
| undefined {
845 switch (measurandType
) {
846 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
847 return MeterValueLocation
.EV
;
851 private static getMeasurandDefaultUnit(
852 measurandType
: OCPP16MeterValueMeasurand
853 ): MeterValueUnit
| undefined {
854 switch (measurandType
) {
855 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
856 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
857 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
858 return MeterValueUnit
.AMP
;
859 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
860 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
861 return MeterValueUnit
.WATT_HOUR
;
862 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
863 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
864 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
865 return MeterValueUnit
.WATT
;
866 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
867 return MeterValueUnit
.PERCENT
;
868 case OCPP16MeterValueMeasurand
.VOLTAGE
:
869 return MeterValueUnit
.VOLT
;