1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
5 import { type ChargingStation
, getIdTagsFile
} from
'../../../charging-station';
6 import { OCPPError
} from
'../../../exception';
12 type MeasurandPerPhaseSampledValueTemplates
,
17 OCPP16AuthorizationStatus
,
18 type OCPP16AuthorizeRequest
,
19 type OCPP16AuthorizeResponse
,
20 type OCPP16ChargingProfile
,
21 type OCPP16IncomingRequestCommand
,
22 type OCPP16MeterValue
,
23 OCPP16MeterValueMeasurand
,
24 OCPP16MeterValuePhase
,
26 type OCPP16SampledValue
,
27 OCPP16StandardParametersKey
,
28 type OCPP16SupportedFeatureProfiles
,
30 type SampledValueTemplate
,
32 } from
'../../../types';
39 getRandomFloatFluctuatedRounded
,
40 getRandomFloatRounded
,
48 } from
'../../../utils';
49 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
51 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
52 public static checkFeatureProfile(
53 chargingStation
: ChargingStation
,
54 featureProfile
: OCPP16SupportedFeatureProfiles
,
55 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
57 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
59 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
60 OCPP16StandardParametersKey.SupportedFeatureProfiles
68 public static buildMeterValue(
69 chargingStation
: ChargingStation
,
71 transactionId
: number,
75 const meterValue
: OCPP16MeterValue
= {
76 timestamp
: new Date(),
79 const connector
= chargingStation
.getConnectorStatus(connectorId
);
81 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
84 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
86 if (socSampledValueTemplate
) {
87 const socMaximumValue
= 100;
88 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
89 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
90 ? getRandomFloatFluctuatedRounded(
91 parseInt(socSampledValueTemplate
.value
),
92 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
94 : getRandomInteger(socMaximumValue
, socMinimumValue
);
95 meterValue
.sampledValue
.push(
96 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
98 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
100 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
101 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
105 `${chargingStation.logPrefix()} MeterValues measurand ${
106 meterValue.sampledValue[sampledValuesIndex].measurand ??
107 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
108 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
109 meterValue.sampledValue[sampledValuesIndex].value
110 }/${socMaximumValue}}`,
115 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
118 OCPP16MeterValueMeasurand
.VOLTAGE
,
120 if (voltageSampledValueTemplate
) {
121 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
122 ? parseInt(voltageSampledValueTemplate
.value
)
123 : chargingStation
.getVoltageOut();
124 const fluctuationPercent
=
125 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
126 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
127 voltageSampledValueTemplateValue
,
131 chargingStation
.getNumberOfPhases() !== 3 ||
132 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
134 meterValue
.sampledValue
.push(
135 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
140 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
143 const phaseLineToNeutralValue
= `L${phase}-N`;
144 const voltagePhaseLineToNeutralSampledValueTemplate
=
145 OCPP16ServiceUtils
.getSampledValueTemplate(
148 OCPP16MeterValueMeasurand
.VOLTAGE
,
149 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
151 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
152 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
153 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
154 voltagePhaseLineToNeutralSampledValueTemplate
.value
155 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
156 : chargingStation
.getVoltageOut();
157 const fluctuationPhaseToNeutralPercent
=
158 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
159 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
160 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
161 voltagePhaseLineToNeutralSampledValueTemplateValue
,
162 fluctuationPhaseToNeutralPercent
,
165 meterValue
.sampledValue
.push(
166 OCPP16ServiceUtils
.buildSampledValue(
167 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
168 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
170 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
173 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
174 const phaseLineToLineValue
= `L${phase}-L${
175 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
176 ? (phase + 1) % chargingStation.getNumberOfPhases()
177 : chargingStation.getNumberOfPhases()
179 const voltagePhaseLineToLineSampledValueTemplate
=
180 OCPP16ServiceUtils
.getSampledValueTemplate(
183 OCPP16MeterValueMeasurand
.VOLTAGE
,
184 phaseLineToLineValue
as OCPP16MeterValuePhase
,
186 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
187 if (voltagePhaseLineToLineSampledValueTemplate
) {
188 const voltagePhaseLineToLineSampledValueTemplateValue
=
189 voltagePhaseLineToLineSampledValueTemplate
.value
190 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
191 : Voltage
.VOLTAGE_400
;
192 const fluctuationPhaseLineToLinePercent
=
193 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
194 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
195 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
196 voltagePhaseLineToLineSampledValueTemplateValue
,
197 fluctuationPhaseLineToLinePercent
,
200 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
204 meterValue
.sampledValue
.push(
205 OCPP16ServiceUtils
.buildSampledValue(
206 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
207 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
209 phaseLineToLineValue
as OCPP16MeterValuePhase
,
215 // Power.Active.Import measurand
216 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
219 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
221 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
222 if (chargingStation
.getNumberOfPhases() === 3) {
223 powerPerPhaseSampledValueTemplates
= {
224 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
227 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
228 OCPP16MeterValuePhase
.L1_N
,
230 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
233 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
234 OCPP16MeterValuePhase
.L2_N
,
236 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
239 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
240 OCPP16MeterValuePhase
.L3_N
,
244 if (powerSampledValueTemplate
) {
245 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
247 powerSampledValueTemplate
.measurand
!,
249 const errMsg
= `MeterValues measurand ${
250 powerSampledValueTemplate.measurand ??
251 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
252 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
253 chargingStation.templateFile
254 }, cannot calculate ${
255 powerSampledValueTemplate.measurand ??
256 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
258 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
259 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
260 const connectorMaximumAvailablePower
=
261 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
262 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
263 const connectorMaximumPowerPerPhase
= Math.round(
264 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
266 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
!) ?? 0;
267 const connectorMinimumPowerPerPhase
= Math.round(
268 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
270 switch (chargingStation
.getCurrentOutType()) {
272 if (chargingStation
.getNumberOfPhases() === 3) {
273 const defaultFluctuatedPowerPerPhase
=
274 powerSampledValueTemplate
.value
&&
275 getRandomFloatFluctuatedRounded(
276 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
277 powerSampledValueTemplate
.value
,
278 connectorMaximumPower
/ unitDivider
,
279 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
280 ) / chargingStation
.getNumberOfPhases(),
281 powerSampledValueTemplate
.fluctuationPercent
??
282 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
284 const phase1FluctuatedValue
=
285 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
286 getRandomFloatFluctuatedRounded(
287 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
288 powerPerPhaseSampledValueTemplates
.L1
.value
,
289 connectorMaximumPowerPerPhase
/ unitDivider
,
290 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
292 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
293 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
295 const phase2FluctuatedValue
=
296 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
297 getRandomFloatFluctuatedRounded(
298 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
299 powerPerPhaseSampledValueTemplates
.L2
.value
,
300 connectorMaximumPowerPerPhase
/ unitDivider
,
301 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
303 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
304 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
306 const phase3FluctuatedValue
=
307 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
308 getRandomFloatFluctuatedRounded(
309 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
310 powerPerPhaseSampledValueTemplates
.L3
.value
,
311 connectorMaximumPowerPerPhase
/ unitDivider
,
312 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
314 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
315 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
317 powerMeasurandValues
.L1
=
318 (phase1FluctuatedValue
as number) ??
319 (defaultFluctuatedPowerPerPhase
as number) ??
320 getRandomFloatRounded(
321 connectorMaximumPowerPerPhase
/ unitDivider
,
322 connectorMinimumPowerPerPhase
/ unitDivider
,
324 powerMeasurandValues
.L2
=
325 (phase2FluctuatedValue
as number) ??
326 (defaultFluctuatedPowerPerPhase
as number) ??
327 getRandomFloatRounded(
328 connectorMaximumPowerPerPhase
/ unitDivider
,
329 connectorMinimumPowerPerPhase
/ unitDivider
,
331 powerMeasurandValues
.L3
=
332 (phase3FluctuatedValue
as number) ??
333 (defaultFluctuatedPowerPerPhase
as number) ??
334 getRandomFloatRounded(
335 connectorMaximumPowerPerPhase
/ unitDivider
,
336 connectorMinimumPowerPerPhase
/ unitDivider
,
339 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
340 ? getRandomFloatFluctuatedRounded(
341 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
342 powerSampledValueTemplate
.value
,
343 connectorMaximumPower
/ unitDivider
,
344 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
346 powerSampledValueTemplate
.fluctuationPercent
??
347 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
349 : getRandomFloatRounded(
350 connectorMaximumPower
/ unitDivider
,
351 connectorMinimumPower
/ unitDivider
,
353 powerMeasurandValues
.L2
= 0;
354 powerMeasurandValues
.L3
= 0;
356 powerMeasurandValues
.allPhases
= roundTo(
357 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
362 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
363 ? getRandomFloatFluctuatedRounded(
364 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
365 powerSampledValueTemplate
.value
,
366 connectorMaximumPower
/ unitDivider
,
367 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
369 powerSampledValueTemplate
.fluctuationPercent
??
370 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
372 : getRandomFloatRounded(
373 connectorMaximumPower
/ unitDivider
,
374 connectorMinimumPower
/ unitDivider
,
378 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
379 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
381 meterValue
.sampledValue
.push(
382 OCPP16ServiceUtils
.buildSampledValue(
383 powerSampledValueTemplate
,
384 powerMeasurandValues
.allPhases
,
387 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
388 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
389 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
391 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
392 connectorMaximumPowerRounded
||
393 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
394 connectorMinimumPowerRounded
||
398 `${chargingStation.logPrefix()} MeterValues measurand ${
399 meterValue.sampledValue[sampledValuesIndex].measurand ??
400 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
401 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
402 meterValue.sampledValue[sampledValuesIndex].value
403 }/${connectorMaximumPowerRounded}`,
408 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
411 const phaseValue
= `L${phase}-N`;
412 meterValue
.sampledValue
.push(
413 OCPP16ServiceUtils
.buildSampledValue(
414 powerPerPhaseSampledValueTemplates
[
415 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
416 ]! ?? powerSampledValueTemplate
,
417 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
419 phaseValue
as OCPP16MeterValuePhase
,
422 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
423 const connectorMaximumPowerPerPhaseRounded
= roundTo(
424 connectorMaximumPowerPerPhase
/ unitDivider
,
427 const connectorMinimumPowerPerPhaseRounded
= roundTo(
428 connectorMinimumPowerPerPhase
/ unitDivider
,
432 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
433 connectorMaximumPowerPerPhaseRounded
||
434 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
435 connectorMinimumPowerPerPhaseRounded
||
439 `${chargingStation.logPrefix()} MeterValues measurand ${
440 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
441 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
443 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
444 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
445 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
446 }/${connectorMaximumPowerPerPhaseRounded}`,
451 // Current.Import measurand
452 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
455 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
457 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
458 if (chargingStation
.getNumberOfPhases() === 3) {
459 currentPerPhaseSampledValueTemplates
= {
460 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
463 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
464 OCPP16MeterValuePhase
.L1
,
466 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
469 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
470 OCPP16MeterValuePhase
.L2
,
472 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
475 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
476 OCPP16MeterValuePhase
.L3
,
480 if (currentSampledValueTemplate
) {
481 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
483 currentSampledValueTemplate
.measurand
!,
485 const errMsg
= `MeterValues measurand ${
486 currentSampledValueTemplate.measurand ??
487 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
488 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
489 chargingStation.templateFile
490 }, cannot calculate ${
491 currentSampledValueTemplate.measurand ??
492 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
494 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
495 const connectorMaximumAvailablePower
=
496 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
497 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
498 let connectorMaximumAmperage
: number;
499 switch (chargingStation
.getCurrentOutType()) {
501 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
502 chargingStation
.getNumberOfPhases(),
503 connectorMaximumAvailablePower
,
504 chargingStation
.getVoltageOut(),
506 if (chargingStation
.getNumberOfPhases() === 3) {
507 const defaultFluctuatedAmperagePerPhase
=
508 currentSampledValueTemplate
.value
&&
509 getRandomFloatFluctuatedRounded(
510 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
511 currentSampledValueTemplate
.value
,
512 connectorMaximumAmperage
,
513 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
515 currentSampledValueTemplate
.fluctuationPercent
??
516 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
518 const phase1FluctuatedValue
=
519 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
520 getRandomFloatFluctuatedRounded(
521 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
522 currentPerPhaseSampledValueTemplates
.L1
.value
,
523 connectorMaximumAmperage
,
524 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
526 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
527 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
529 const phase2FluctuatedValue
=
530 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
531 getRandomFloatFluctuatedRounded(
532 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
533 currentPerPhaseSampledValueTemplates
.L2
.value
,
534 connectorMaximumAmperage
,
535 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
537 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
538 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
540 const phase3FluctuatedValue
=
541 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
542 getRandomFloatFluctuatedRounded(
543 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
544 currentPerPhaseSampledValueTemplates
.L3
.value
,
545 connectorMaximumAmperage
,
546 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
548 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
549 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
551 currentMeasurandValues
.L1
=
552 (phase1FluctuatedValue
as number) ??
553 (defaultFluctuatedAmperagePerPhase
as number) ??
554 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
555 currentMeasurandValues
.L2
=
556 (phase2FluctuatedValue
as number) ??
557 (defaultFluctuatedAmperagePerPhase
as number) ??
558 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
559 currentMeasurandValues
.L3
=
560 (phase3FluctuatedValue
as number) ??
561 (defaultFluctuatedAmperagePerPhase
as number) ??
562 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
564 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
565 ? getRandomFloatFluctuatedRounded(
566 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
567 currentSampledValueTemplate
.value
,
568 connectorMaximumAmperage
,
569 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
571 currentSampledValueTemplate
.fluctuationPercent
??
572 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
574 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
575 currentMeasurandValues
.L2
= 0;
576 currentMeasurandValues
.L3
= 0;
578 currentMeasurandValues
.allPhases
= roundTo(
579 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
580 chargingStation
.getNumberOfPhases(),
585 connectorMaximumAmperage
= DCElectricUtils
.amperage(
586 connectorMaximumAvailablePower
,
587 chargingStation
.getVoltageOut(),
589 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
590 ? getRandomFloatFluctuatedRounded(
591 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
592 currentSampledValueTemplate
.value
,
593 connectorMaximumAmperage
,
594 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
596 currentSampledValueTemplate
.fluctuationPercent
??
597 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
599 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
602 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
603 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
605 meterValue
.sampledValue
.push(
606 OCPP16ServiceUtils
.buildSampledValue(
607 currentSampledValueTemplate
,
608 currentMeasurandValues
.allPhases
,
611 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
613 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
614 connectorMaximumAmperage
||
615 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
616 connectorMinimumAmperage
||
620 `${chargingStation.logPrefix()} MeterValues measurand ${
621 meterValue.sampledValue[sampledValuesIndex].measurand ??
622 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
623 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
624 meterValue.sampledValue[sampledValuesIndex].value
625 }/${connectorMaximumAmperage}`,
630 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
633 const phaseValue
= `L${phase}`;
634 meterValue
.sampledValue
.push(
635 OCPP16ServiceUtils
.buildSampledValue(
636 currentPerPhaseSampledValueTemplates
[
637 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
638 ]! ?? currentSampledValueTemplate
,
639 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
641 phaseValue
as OCPP16MeterValuePhase
,
644 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
646 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
647 connectorMaximumAmperage
||
648 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
649 connectorMinimumAmperage
||
653 `${chargingStation.logPrefix()} MeterValues measurand ${
654 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
655 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
657 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
658 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
659 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
660 }/${connectorMaximumAmperage}`,
665 // Energy.Active.Import.Register measurand (default)
666 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
670 if (energySampledValueTemplate
) {
671 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
673 energySampledValueTemplate
.measurand
!,
676 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
677 const connectorMaximumAvailablePower
=
678 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
679 const connectorMaximumEnergyRounded
= roundTo(
680 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
683 const energyValueRounded
= energySampledValueTemplate
.value
684 ? // Cumulate the fluctuated value around the static one
685 getRandomFloatFluctuatedRounded(
686 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
687 energySampledValueTemplate
.value
,
688 connectorMaximumEnergyRounded
,
690 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
691 unitMultiplier
: unitDivider
,
694 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
696 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
697 // Persist previous value on connector
700 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
701 connector
.energyActiveImportRegisterValue
! >= 0 &&
702 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
703 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
705 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
706 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
708 connector
.energyActiveImportRegisterValue
= 0;
709 connector
.transactionEnergyActiveImportRegisterValue
= 0;
712 meterValue
.sampledValue
.push(
713 OCPP16ServiceUtils
.buildSampledValue(
714 energySampledValueTemplate
,
716 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
722 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
723 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
725 `${chargingStation.logPrefix()} MeterValues measurand ${
726 meterValue.sampledValue[sampledValuesIndex].measurand ??
727 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
728 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${roundTo(
729 interval / (3600 * 1000),
738 public static buildTransactionBeginMeterValue(
739 chargingStation
: ChargingStation
,
742 ): OCPP16MeterValue
{
743 const meterValue
: OCPP16MeterValue
= {
744 timestamp
: new Date(),
747 // Energy.Active.Import.Register measurand (default)
748 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
752 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
753 meterValue
.sampledValue
.push(
754 OCPP16ServiceUtils
.buildSampledValue(
755 sampledValueTemplate
!,
756 roundTo((meterStart
?? 0) / unitDivider
, 4),
757 MeterValueContext
.TRANSACTION_BEGIN
,
763 public static buildTransactionEndMeterValue(
764 chargingStation
: ChargingStation
,
767 ): OCPP16MeterValue
{
768 const meterValue
: OCPP16MeterValue
= {
769 timestamp
: new Date(),
772 // Energy.Active.Import.Register measurand (default)
773 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
777 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
778 meterValue
.sampledValue
.push(
779 OCPP16ServiceUtils
.buildSampledValue(
780 sampledValueTemplate
!,
781 roundTo((meterStop
?? 0) / unitDivider
, 4),
782 MeterValueContext
.TRANSACTION_END
,
788 public static buildTransactionDataMeterValues(
789 transactionBeginMeterValue
: OCPP16MeterValue
,
790 transactionEndMeterValue
: OCPP16MeterValue
,
791 ): OCPP16MeterValue
[] {
792 const meterValues
: OCPP16MeterValue
[] = [];
793 meterValues
.push(transactionBeginMeterValue
);
794 meterValues
.push(transactionEndMeterValue
);
798 public static setChargingProfile(
799 chargingStation
: ChargingStation
,
801 cp
: OCPP16ChargingProfile
,
803 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
805 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
807 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
810 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
813 `${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`,
815 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
817 let cpReplaced
= false;
818 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
820 .getConnectorStatus(connectorId
)
821 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
823 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
824 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
825 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
827 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
832 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
835 public static parseJsonSchemaFile
<T
extends JsonType
>(
836 relativePath
: string,
839 ): JSONSchemaType
<T
> {
840 return super.parseJsonSchemaFile
<T
>(
842 OCPPVersion
.VERSION_16
,
848 public static async isIdTagAuthorized(
849 chargingStation
: ChargingStation
,
852 ): Promise
<boolean> {
853 let authorized
= false;
854 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
855 if (OCPP16ServiceUtils
.isIdTagLocalAuthorized(chargingStation
, idTag
)) {
856 connectorStatus
.localAuthorizeIdTag
= idTag
;
857 connectorStatus
.idTagLocalAuthorized
= true;
859 } else if (chargingStation
.getMustAuthorizeAtRemoteStart() === true) {
860 connectorStatus
.authorizeIdTag
= idTag
;
861 authorized
= await OCPP16ServiceUtils
.isIdTagRemoteAuthorized(chargingStation
, idTag
);
864 `${chargingStation.logPrefix()} The charging station configuration expects authorize at
865 remote start transaction but local authorization or authorize isn't enabled`,
871 private static buildSampledValue(
872 sampledValueTemplate
: SampledValueTemplate
,
874 context
?: MeterValueContext
,
875 phase
?: OCPP16MeterValuePhase
,
876 ): OCPP16SampledValue
{
877 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
878 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
879 const sampledValueLocation
=
880 sampledValueTemplate
?.location
??
881 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
882 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
884 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
885 unit
: sampledValueTemplate
.unit
,
887 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
888 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
889 measurand
: sampledValueTemplate
.measurand
,
891 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
892 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
893 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
894 } as OCPP16SampledValue
;
897 private static checkMeasurandPowerDivider(
898 chargingStation
: ChargingStation
,
899 measurandType
: OCPP16MeterValueMeasurand
,
901 if (isUndefined(chargingStation
.powerDivider
)) {
902 const errMsg
= `MeterValues measurand ${
903 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
904 }: powerDivider is undefined`;
905 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
906 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
907 } else if (chargingStation
?.powerDivider
<= 0) {
908 const errMsg
= `MeterValues measurand ${
909 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
910 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
911 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
912 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
916 private static getMeasurandDefaultLocation(
917 measurandType
: OCPP16MeterValueMeasurand
,
918 ): MeterValueLocation
| undefined {
919 switch (measurandType
) {
920 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
921 return MeterValueLocation
.EV
;
925 private static getMeasurandDefaultUnit(
926 measurandType
: OCPP16MeterValueMeasurand
,
927 ): MeterValueUnit
| undefined {
928 switch (measurandType
) {
929 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
930 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
931 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
932 return MeterValueUnit
.AMP
;
933 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
934 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
935 return MeterValueUnit
.WATT_HOUR
;
936 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
937 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
938 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
939 return MeterValueUnit
.WATT
;
940 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
941 return MeterValueUnit
.PERCENT
;
942 case OCPP16MeterValueMeasurand
.VOLTAGE
:
943 return MeterValueUnit
.VOLT
;
947 private static isIdTagLocalAuthorized(chargingStation
: ChargingStation
, idTag
: string): boolean {
949 chargingStation
.getLocalAuthListEnabled() === true &&
950 chargingStation
.hasIdTags() === true &&
952 chargingStation
.idTagsCache
953 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
954 ?.find((tag
) => tag
=== idTag
),
959 private static async isIdTagRemoteAuthorized(
960 chargingStation
: ChargingStation
,
962 ): Promise
<boolean> {
963 const authorizeResponse
: OCPP16AuthorizeResponse
=
964 await chargingStation
.ocppRequestService
.requestHandler
<
965 OCPP16AuthorizeRequest
,
966 OCPP16AuthorizeResponse
967 >(chargingStation
, OCPP16RequestCommand
.AUTHORIZE
, {
970 return authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
;