1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
5 import { type ChargingStation
, hasFeatureProfile
} from
'../../../charging-station';
6 import { OCPPError
} from
'../../../exception';
8 type ClearChargingProfileRequest
,
12 type MeasurandPerPhaseSampledValueTemplates
,
17 type OCPP16ChargingProfile
,
18 type OCPP16IncomingRequestCommand
,
19 type OCPP16MeterValue
,
20 OCPP16MeterValueMeasurand
,
21 OCPP16MeterValuePhase
,
23 type OCPP16SampledValue
,
24 OCPP16StandardParametersKey
,
25 type OCPP16SupportedFeatureProfiles
,
27 type SampledValueTemplate
,
29 } from
'../../../types';
36 getRandomFloatFluctuatedRounded
,
37 getRandomFloatRounded
,
44 } from
'../../../utils';
45 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
47 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
48 public static checkFeatureProfile(
49 chargingStation
: ChargingStation
,
50 featureProfile
: OCPP16SupportedFeatureProfiles
,
51 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
53 if (!hasFeatureProfile(chargingStation
, featureProfile
)) {
55 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
56 OCPP16StandardParametersKey.SupportedFeatureProfiles
64 public static buildMeterValue(
65 chargingStation
: ChargingStation
,
67 transactionId
: number,
71 const meterValue
: OCPP16MeterValue
= {
72 timestamp
: new Date(),
75 const connector
= chargingStation
.getConnectorStatus(connectorId
);
77 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
80 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
82 if (socSampledValueTemplate
) {
83 const socMaximumValue
= 100;
84 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
85 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
86 ? getRandomFloatFluctuatedRounded(
87 parseInt(socSampledValueTemplate
.value
),
88 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
90 : getRandomInteger(socMaximumValue
, socMinimumValue
);
91 meterValue
.sampledValue
.push(
92 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
94 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
96 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
97 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
101 `${chargingStation.logPrefix()} MeterValues measurand ${
102 meterValue.sampledValue[sampledValuesIndex].measurand ??
103 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
104 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
105 meterValue.sampledValue[sampledValuesIndex].value
106 }/${socMaximumValue}}`,
111 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
114 OCPP16MeterValueMeasurand
.VOLTAGE
,
116 if (voltageSampledValueTemplate
) {
117 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
118 ? parseInt(voltageSampledValueTemplate
.value
)
119 : chargingStation
.getVoltageOut();
120 const fluctuationPercent
=
121 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
122 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
123 voltageSampledValueTemplateValue
,
127 chargingStation
.getNumberOfPhases() !== 3 ||
128 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
130 meterValue
.sampledValue
.push(
131 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
136 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
139 const phaseLineToNeutralValue
= `L${phase}-N`;
140 const voltagePhaseLineToNeutralSampledValueTemplate
=
141 OCPP16ServiceUtils
.getSampledValueTemplate(
144 OCPP16MeterValueMeasurand
.VOLTAGE
,
145 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
147 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
148 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
149 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
150 voltagePhaseLineToNeutralSampledValueTemplate
.value
151 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
152 : chargingStation
.getVoltageOut();
153 const fluctuationPhaseToNeutralPercent
=
154 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
155 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
156 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
157 voltagePhaseLineToNeutralSampledValueTemplateValue
,
158 fluctuationPhaseToNeutralPercent
,
161 meterValue
.sampledValue
.push(
162 OCPP16ServiceUtils
.buildSampledValue(
163 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
164 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
166 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
169 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
170 const phaseLineToLineValue
= `L${phase}-L${
171 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
172 ? (phase + 1) % chargingStation.getNumberOfPhases()
173 : chargingStation.getNumberOfPhases()
175 const voltagePhaseLineToLineSampledValueTemplate
=
176 OCPP16ServiceUtils
.getSampledValueTemplate(
179 OCPP16MeterValueMeasurand
.VOLTAGE
,
180 phaseLineToLineValue
as OCPP16MeterValuePhase
,
182 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
183 if (voltagePhaseLineToLineSampledValueTemplate
) {
184 const voltagePhaseLineToLineSampledValueTemplateValue
=
185 voltagePhaseLineToLineSampledValueTemplate
.value
186 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
187 : Voltage
.VOLTAGE_400
;
188 const fluctuationPhaseLineToLinePercent
=
189 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
190 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
191 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
192 voltagePhaseLineToLineSampledValueTemplateValue
,
193 fluctuationPhaseLineToLinePercent
,
196 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
200 meterValue
.sampledValue
.push(
201 OCPP16ServiceUtils
.buildSampledValue(
202 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
203 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
205 phaseLineToLineValue
as OCPP16MeterValuePhase
,
211 // Power.Active.Import measurand
212 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
215 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
217 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
218 if (chargingStation
.getNumberOfPhases() === 3) {
219 powerPerPhaseSampledValueTemplates
= {
220 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
223 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
224 OCPP16MeterValuePhase
.L1_N
,
226 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
229 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
230 OCPP16MeterValuePhase
.L2_N
,
232 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
235 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
236 OCPP16MeterValuePhase
.L3_N
,
240 if (powerSampledValueTemplate
) {
241 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
243 powerSampledValueTemplate
.measurand
!,
245 const errMsg
= `MeterValues measurand ${
246 powerSampledValueTemplate.measurand ??
247 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
248 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
249 chargingStation.templateFile
250 }, cannot calculate ${
251 powerSampledValueTemplate.measurand ??
252 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
254 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
255 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
256 const connectorMaximumAvailablePower
=
257 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
258 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
259 const connectorMaximumPowerPerPhase
= Math.round(
260 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
262 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
!) ?? 0;
263 const connectorMinimumPowerPerPhase
= Math.round(
264 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
266 switch (chargingStation
.getCurrentOutType()) {
268 if (chargingStation
.getNumberOfPhases() === 3) {
269 const defaultFluctuatedPowerPerPhase
=
270 powerSampledValueTemplate
.value
&&
271 getRandomFloatFluctuatedRounded(
272 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
273 powerSampledValueTemplate
.value
,
274 connectorMaximumPower
/ unitDivider
,
275 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
276 ) / chargingStation
.getNumberOfPhases(),
277 powerSampledValueTemplate
.fluctuationPercent
??
278 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
280 const phase1FluctuatedValue
=
281 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
282 getRandomFloatFluctuatedRounded(
283 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
284 powerPerPhaseSampledValueTemplates
.L1
.value
,
285 connectorMaximumPowerPerPhase
/ unitDivider
,
286 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
288 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
289 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
291 const phase2FluctuatedValue
=
292 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
293 getRandomFloatFluctuatedRounded(
294 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
295 powerPerPhaseSampledValueTemplates
.L2
.value
,
296 connectorMaximumPowerPerPhase
/ unitDivider
,
297 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
299 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
300 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
302 const phase3FluctuatedValue
=
303 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
304 getRandomFloatFluctuatedRounded(
305 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
306 powerPerPhaseSampledValueTemplates
.L3
.value
,
307 connectorMaximumPowerPerPhase
/ unitDivider
,
308 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
310 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
311 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
313 powerMeasurandValues
.L1
=
314 (phase1FluctuatedValue
as number) ??
315 (defaultFluctuatedPowerPerPhase
as number) ??
316 getRandomFloatRounded(
317 connectorMaximumPowerPerPhase
/ unitDivider
,
318 connectorMinimumPowerPerPhase
/ unitDivider
,
320 powerMeasurandValues
.L2
=
321 (phase2FluctuatedValue
as number) ??
322 (defaultFluctuatedPowerPerPhase
as number) ??
323 getRandomFloatRounded(
324 connectorMaximumPowerPerPhase
/ unitDivider
,
325 connectorMinimumPowerPerPhase
/ unitDivider
,
327 powerMeasurandValues
.L3
=
328 (phase3FluctuatedValue
as number) ??
329 (defaultFluctuatedPowerPerPhase
as number) ??
330 getRandomFloatRounded(
331 connectorMaximumPowerPerPhase
/ unitDivider
,
332 connectorMinimumPowerPerPhase
/ unitDivider
,
335 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
336 ? getRandomFloatFluctuatedRounded(
337 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
338 powerSampledValueTemplate
.value
,
339 connectorMaximumPower
/ unitDivider
,
340 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
342 powerSampledValueTemplate
.fluctuationPercent
??
343 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
345 : getRandomFloatRounded(
346 connectorMaximumPower
/ unitDivider
,
347 connectorMinimumPower
/ unitDivider
,
349 powerMeasurandValues
.L2
= 0;
350 powerMeasurandValues
.L3
= 0;
352 powerMeasurandValues
.allPhases
= roundTo(
353 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
358 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
359 ? getRandomFloatFluctuatedRounded(
360 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
361 powerSampledValueTemplate
.value
,
362 connectorMaximumPower
/ unitDivider
,
363 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
365 powerSampledValueTemplate
.fluctuationPercent
??
366 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
368 : getRandomFloatRounded(
369 connectorMaximumPower
/ unitDivider
,
370 connectorMinimumPower
/ unitDivider
,
374 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
375 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
377 meterValue
.sampledValue
.push(
378 OCPP16ServiceUtils
.buildSampledValue(
379 powerSampledValueTemplate
,
380 powerMeasurandValues
.allPhases
,
383 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
384 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
385 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
387 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
388 connectorMaximumPowerRounded
||
389 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
390 connectorMinimumPowerRounded
||
394 `${chargingStation.logPrefix()} MeterValues measurand ${
395 meterValue.sampledValue[sampledValuesIndex].measurand ??
396 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
397 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
398 meterValue.sampledValue[sampledValuesIndex].value
399 }/${connectorMaximumPowerRounded}`,
404 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
407 const phaseValue
= `L${phase}-N`;
408 meterValue
.sampledValue
.push(
409 OCPP16ServiceUtils
.buildSampledValue(
410 powerPerPhaseSampledValueTemplates
[
411 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
412 ]! ?? powerSampledValueTemplate
,
413 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
415 phaseValue
as OCPP16MeterValuePhase
,
418 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
419 const connectorMaximumPowerPerPhaseRounded
= roundTo(
420 connectorMaximumPowerPerPhase
/ unitDivider
,
423 const connectorMinimumPowerPerPhaseRounded
= roundTo(
424 connectorMinimumPowerPerPhase
/ unitDivider
,
428 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
429 connectorMaximumPowerPerPhaseRounded
||
430 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
431 connectorMinimumPowerPerPhaseRounded
||
435 `${chargingStation.logPrefix()} MeterValues measurand ${
436 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
437 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
439 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
440 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
441 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
442 }/${connectorMaximumPowerPerPhaseRounded}`,
447 // Current.Import measurand
448 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
451 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
453 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
454 if (chargingStation
.getNumberOfPhases() === 3) {
455 currentPerPhaseSampledValueTemplates
= {
456 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
459 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
460 OCPP16MeterValuePhase
.L1
,
462 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
465 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
466 OCPP16MeterValuePhase
.L2
,
468 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
471 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
472 OCPP16MeterValuePhase
.L3
,
476 if (currentSampledValueTemplate
) {
477 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
479 currentSampledValueTemplate
.measurand
!,
481 const errMsg
= `MeterValues measurand ${
482 currentSampledValueTemplate.measurand ??
483 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
484 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
485 chargingStation.templateFile
486 }, cannot calculate ${
487 currentSampledValueTemplate.measurand ??
488 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
490 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
491 const connectorMaximumAvailablePower
=
492 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
493 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
494 let connectorMaximumAmperage
: number;
495 switch (chargingStation
.getCurrentOutType()) {
497 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
498 chargingStation
.getNumberOfPhases(),
499 connectorMaximumAvailablePower
,
500 chargingStation
.getVoltageOut(),
502 if (chargingStation
.getNumberOfPhases() === 3) {
503 const defaultFluctuatedAmperagePerPhase
=
504 currentSampledValueTemplate
.value
&&
505 getRandomFloatFluctuatedRounded(
506 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
507 currentSampledValueTemplate
.value
,
508 connectorMaximumAmperage
,
509 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
511 currentSampledValueTemplate
.fluctuationPercent
??
512 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
514 const phase1FluctuatedValue
=
515 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
516 getRandomFloatFluctuatedRounded(
517 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
518 currentPerPhaseSampledValueTemplates
.L1
.value
,
519 connectorMaximumAmperage
,
520 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
522 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
523 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
525 const phase2FluctuatedValue
=
526 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
527 getRandomFloatFluctuatedRounded(
528 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
529 currentPerPhaseSampledValueTemplates
.L2
.value
,
530 connectorMaximumAmperage
,
531 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
533 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
534 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
536 const phase3FluctuatedValue
=
537 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
538 getRandomFloatFluctuatedRounded(
539 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
540 currentPerPhaseSampledValueTemplates
.L3
.value
,
541 connectorMaximumAmperage
,
542 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
544 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
545 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
547 currentMeasurandValues
.L1
=
548 (phase1FluctuatedValue
as number) ??
549 (defaultFluctuatedAmperagePerPhase
as number) ??
550 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
551 currentMeasurandValues
.L2
=
552 (phase2FluctuatedValue
as number) ??
553 (defaultFluctuatedAmperagePerPhase
as number) ??
554 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
555 currentMeasurandValues
.L3
=
556 (phase3FluctuatedValue
as number) ??
557 (defaultFluctuatedAmperagePerPhase
as number) ??
558 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
560 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
561 ? getRandomFloatFluctuatedRounded(
562 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
563 currentSampledValueTemplate
.value
,
564 connectorMaximumAmperage
,
565 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
567 currentSampledValueTemplate
.fluctuationPercent
??
568 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
570 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
571 currentMeasurandValues
.L2
= 0;
572 currentMeasurandValues
.L3
= 0;
574 currentMeasurandValues
.allPhases
= roundTo(
575 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
576 chargingStation
.getNumberOfPhases(),
581 connectorMaximumAmperage
= DCElectricUtils
.amperage(
582 connectorMaximumAvailablePower
,
583 chargingStation
.getVoltageOut(),
585 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
586 ? getRandomFloatFluctuatedRounded(
587 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
588 currentSampledValueTemplate
.value
,
589 connectorMaximumAmperage
,
590 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
592 currentSampledValueTemplate
.fluctuationPercent
??
593 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
595 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
598 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
599 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
601 meterValue
.sampledValue
.push(
602 OCPP16ServiceUtils
.buildSampledValue(
603 currentSampledValueTemplate
,
604 currentMeasurandValues
.allPhases
,
607 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
609 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
610 connectorMaximumAmperage
||
611 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
612 connectorMinimumAmperage
||
616 `${chargingStation.logPrefix()} MeterValues measurand ${
617 meterValue.sampledValue[sampledValuesIndex].measurand ??
618 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
619 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
620 meterValue.sampledValue[sampledValuesIndex].value
621 }/${connectorMaximumAmperage}`,
626 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
629 const phaseValue
= `L${phase}`;
630 meterValue
.sampledValue
.push(
631 OCPP16ServiceUtils
.buildSampledValue(
632 currentPerPhaseSampledValueTemplates
[
633 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
634 ]! ?? currentSampledValueTemplate
,
635 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
637 phaseValue
as OCPP16MeterValuePhase
,
640 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
642 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
643 connectorMaximumAmperage
||
644 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
645 connectorMinimumAmperage
||
649 `${chargingStation.logPrefix()} MeterValues measurand ${
650 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
651 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
653 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
654 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
655 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
656 }/${connectorMaximumAmperage}`,
661 // Energy.Active.Import.Register measurand (default)
662 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
666 if (energySampledValueTemplate
) {
667 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
669 energySampledValueTemplate
.measurand
!,
672 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
673 const connectorMaximumAvailablePower
=
674 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
675 const connectorMaximumEnergyRounded
= roundTo(
676 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
679 const energyValueRounded
= energySampledValueTemplate
.value
680 ? // Cumulate the fluctuated value around the static one
681 getRandomFloatFluctuatedRounded(
682 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
683 energySampledValueTemplate
.value
,
684 connectorMaximumEnergyRounded
,
686 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
687 unitMultiplier
: unitDivider
,
690 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
692 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
693 // Persist previous value on connector
696 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
697 connector
.energyActiveImportRegisterValue
! >= 0 &&
698 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
699 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
701 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
702 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
704 connector
.energyActiveImportRegisterValue
= 0;
705 connector
.transactionEnergyActiveImportRegisterValue
= 0;
708 meterValue
.sampledValue
.push(
709 OCPP16ServiceUtils
.buildSampledValue(
710 energySampledValueTemplate
,
712 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
718 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
719 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
721 `${chargingStation.logPrefix()} MeterValues measurand ${
722 meterValue.sampledValue[sampledValuesIndex].measurand ??
723 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
724 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
731 public static buildTransactionBeginMeterValue(
732 chargingStation
: ChargingStation
,
735 ): OCPP16MeterValue
{
736 const meterValue
: OCPP16MeterValue
= {
737 timestamp
: new Date(),
740 // Energy.Active.Import.Register measurand (default)
741 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
745 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
746 meterValue
.sampledValue
.push(
747 OCPP16ServiceUtils
.buildSampledValue(
748 sampledValueTemplate
!,
749 roundTo((meterStart
?? 0) / unitDivider
, 4),
750 MeterValueContext
.TRANSACTION_BEGIN
,
756 public static buildTransactionEndMeterValue(
757 chargingStation
: ChargingStation
,
760 ): OCPP16MeterValue
{
761 const meterValue
: OCPP16MeterValue
= {
762 timestamp
: new Date(),
765 // Energy.Active.Import.Register measurand (default)
766 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
770 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
771 meterValue
.sampledValue
.push(
772 OCPP16ServiceUtils
.buildSampledValue(
773 sampledValueTemplate
!,
774 roundTo((meterStop
?? 0) / unitDivider
, 4),
775 MeterValueContext
.TRANSACTION_END
,
781 public static buildTransactionDataMeterValues(
782 transactionBeginMeterValue
: OCPP16MeterValue
,
783 transactionEndMeterValue
: OCPP16MeterValue
,
784 ): OCPP16MeterValue
[] {
785 const meterValues
: OCPP16MeterValue
[] = [];
786 meterValues
.push(transactionBeginMeterValue
);
787 meterValues
.push(transactionEndMeterValue
);
791 public static setChargingProfile(
792 chargingStation
: ChargingStation
,
794 cp
: OCPP16ChargingProfile
,
796 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
798 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
800 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
803 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
806 `${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`,
808 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
810 let cpReplaced
= false;
811 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
813 .getConnectorStatus(connectorId
)
814 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
816 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
817 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
818 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
820 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
825 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
828 public static clearChargingProfiles
= (
829 chargingStation
: ChargingStation
,
830 commandPayload
: ClearChargingProfileRequest
,
831 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
833 let clearedCP
= false;
834 if (isNotEmptyArray(chargingProfiles
)) {
835 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
836 let clearCurrentCP
= false;
837 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
838 clearCurrentCP
= true;
841 !commandPayload
.chargingProfilePurpose
&&
842 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
844 clearCurrentCP
= true;
847 !chargingProfile
.stackLevel
&&
848 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
850 clearCurrentCP
= true;
853 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
854 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
856 clearCurrentCP
= true;
858 if (clearCurrentCP
) {
859 chargingProfiles
.splice(index
, 1);
861 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
871 public static parseJsonSchemaFile
<T
extends JsonType
>(
872 relativePath
: string,
875 ): JSONSchemaType
<T
> {
876 return super.parseJsonSchemaFile
<T
>(
878 OCPPVersion
.VERSION_16
,
884 private static buildSampledValue(
885 sampledValueTemplate
: SampledValueTemplate
,
887 context
?: MeterValueContext
,
888 phase
?: OCPP16MeterValuePhase
,
889 ): OCPP16SampledValue
{
890 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
891 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
892 const sampledValueLocation
=
893 sampledValueTemplate
?.location
??
894 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
895 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
897 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
898 unit
: sampledValueTemplate
.unit
,
900 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
901 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
902 measurand
: sampledValueTemplate
.measurand
,
904 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
905 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
906 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
907 } as OCPP16SampledValue
;
910 private static checkMeasurandPowerDivider(
911 chargingStation
: ChargingStation
,
912 measurandType
: OCPP16MeterValueMeasurand
,
914 if (isUndefined(chargingStation
.powerDivider
)) {
915 const errMsg
= `MeterValues measurand ${
916 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
917 }: powerDivider is undefined`;
918 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
919 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
920 } else if (chargingStation
?.powerDivider
<= 0) {
921 const errMsg
= `MeterValues measurand ${
922 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
923 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
924 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
925 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
929 private static getMeasurandDefaultLocation(
930 measurandType
: OCPP16MeterValueMeasurand
,
931 ): MeterValueLocation
| undefined {
932 switch (measurandType
) {
933 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
934 return MeterValueLocation
.EV
;
938 private static getMeasurandDefaultUnit(
939 measurandType
: OCPP16MeterValueMeasurand
,
940 ): MeterValueUnit
| undefined {
941 switch (measurandType
) {
942 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
943 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
944 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
945 return MeterValueUnit
.AMP
;
946 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
947 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
948 return MeterValueUnit
.WATT_HOUR
;
949 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
950 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
951 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
952 return MeterValueUnit
.WATT
;
953 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
954 return MeterValueUnit
.PERCENT
;
955 case OCPP16MeterValueMeasurand
.VOLTAGE
:
956 return MeterValueUnit
.VOLT
;