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
[`L${phase}`] as SampledValueTemplate
) ??
415 powerSampledValueTemplate
,
416 powerMeasurandValues
[`L${phase}`] as number,
418 phaseValue
as OCPP16MeterValuePhase
,
421 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
422 const connectorMaximumPowerPerPhaseRounded
= roundTo(
423 connectorMaximumPowerPerPhase
/ unitDivider
,
426 const connectorMinimumPowerPerPhaseRounded
= roundTo(
427 connectorMinimumPowerPerPhase
/ unitDivider
,
431 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
432 connectorMaximumPowerPerPhaseRounded
||
433 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
434 connectorMinimumPowerPerPhaseRounded
||
438 `${chargingStation.logPrefix()} MeterValues measurand ${
439 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
440 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
442 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
443 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
444 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
445 }/${connectorMaximumPowerPerPhaseRounded}`,
450 // Current.Import measurand
451 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
454 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
456 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
457 if (chargingStation
.getNumberOfPhases() === 3) {
458 currentPerPhaseSampledValueTemplates
= {
459 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
462 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
463 OCPP16MeterValuePhase
.L1
,
465 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
468 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
469 OCPP16MeterValuePhase
.L2
,
471 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
474 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
475 OCPP16MeterValuePhase
.L3
,
479 if (currentSampledValueTemplate
) {
480 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
482 currentSampledValueTemplate
.measurand
!,
484 const errMsg
= `MeterValues measurand ${
485 currentSampledValueTemplate.measurand ??
486 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
487 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
488 chargingStation.templateFile
489 }, cannot calculate ${
490 currentSampledValueTemplate.measurand ??
491 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
493 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
494 const connectorMaximumAvailablePower
=
495 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
496 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
497 let connectorMaximumAmperage
: number;
498 switch (chargingStation
.getCurrentOutType()) {
500 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
501 chargingStation
.getNumberOfPhases(),
502 connectorMaximumAvailablePower
,
503 chargingStation
.getVoltageOut(),
505 if (chargingStation
.getNumberOfPhases() === 3) {
506 const defaultFluctuatedAmperagePerPhase
=
507 currentSampledValueTemplate
.value
&&
508 getRandomFloatFluctuatedRounded(
509 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
510 currentSampledValueTemplate
.value
,
511 connectorMaximumAmperage
,
512 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
514 currentSampledValueTemplate
.fluctuationPercent
??
515 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
517 const phase1FluctuatedValue
=
518 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
519 getRandomFloatFluctuatedRounded(
520 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
521 currentPerPhaseSampledValueTemplates
.L1
.value
,
522 connectorMaximumAmperage
,
523 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
525 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
526 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
528 const phase2FluctuatedValue
=
529 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
530 getRandomFloatFluctuatedRounded(
531 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
532 currentPerPhaseSampledValueTemplates
.L2
.value
,
533 connectorMaximumAmperage
,
534 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
536 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
537 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
539 const phase3FluctuatedValue
=
540 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
541 getRandomFloatFluctuatedRounded(
542 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
543 currentPerPhaseSampledValueTemplates
.L3
.value
,
544 connectorMaximumAmperage
,
545 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
547 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
548 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
550 currentMeasurandValues
.L1
=
551 (phase1FluctuatedValue
as number) ??
552 (defaultFluctuatedAmperagePerPhase
as number) ??
553 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
554 currentMeasurandValues
.L2
=
555 (phase2FluctuatedValue
as number) ??
556 (defaultFluctuatedAmperagePerPhase
as number) ??
557 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
558 currentMeasurandValues
.L3
=
559 (phase3FluctuatedValue
as number) ??
560 (defaultFluctuatedAmperagePerPhase
as number) ??
561 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
563 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
564 ? getRandomFloatFluctuatedRounded(
565 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
566 currentSampledValueTemplate
.value
,
567 connectorMaximumAmperage
,
568 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
570 currentSampledValueTemplate
.fluctuationPercent
??
571 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
573 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
574 currentMeasurandValues
.L2
= 0;
575 currentMeasurandValues
.L3
= 0;
577 currentMeasurandValues
.allPhases
= roundTo(
578 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
579 chargingStation
.getNumberOfPhases(),
584 connectorMaximumAmperage
= DCElectricUtils
.amperage(
585 connectorMaximumAvailablePower
,
586 chargingStation
.getVoltageOut(),
588 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
589 ? getRandomFloatFluctuatedRounded(
590 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
591 currentSampledValueTemplate
.value
,
592 connectorMaximumAmperage
,
593 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
595 currentSampledValueTemplate
.fluctuationPercent
??
596 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
598 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
601 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
602 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
604 meterValue
.sampledValue
.push(
605 OCPP16ServiceUtils
.buildSampledValue(
606 currentSampledValueTemplate
,
607 currentMeasurandValues
.allPhases
,
610 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
612 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
613 connectorMaximumAmperage
||
614 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
615 connectorMinimumAmperage
||
619 `${chargingStation.logPrefix()} MeterValues measurand ${
620 meterValue.sampledValue[sampledValuesIndex].measurand ??
621 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
622 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
623 meterValue.sampledValue[sampledValuesIndex].value
624 }/${connectorMaximumAmperage}`,
629 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
632 const phaseValue
= `L${phase}`;
633 meterValue
.sampledValue
.push(
634 OCPP16ServiceUtils
.buildSampledValue(
635 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
636 currentSampledValueTemplate
,
637 currentMeasurandValues
[phaseValue
] as number,
639 phaseValue
as OCPP16MeterValuePhase
,
642 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
644 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
645 connectorMaximumAmperage
||
646 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
647 connectorMinimumAmperage
||
651 `${chargingStation.logPrefix()} MeterValues measurand ${
652 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
653 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
655 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
656 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
657 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
658 }/${connectorMaximumAmperage}`,
663 // Energy.Active.Import.Register measurand (default)
664 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
668 if (energySampledValueTemplate
) {
669 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
671 energySampledValueTemplate
.measurand
!,
674 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
675 const connectorMaximumAvailablePower
=
676 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
677 const connectorMaximumEnergyRounded
= roundTo(
678 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
681 const energyValueRounded
= energySampledValueTemplate
.value
682 ? // Cumulate the fluctuated value around the static one
683 getRandomFloatFluctuatedRounded(
684 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
685 energySampledValueTemplate
.value
,
686 connectorMaximumEnergyRounded
,
688 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
689 unitMultiplier
: unitDivider
,
692 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
694 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
695 // Persist previous value on connector
698 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
699 connector
.energyActiveImportRegisterValue
! >= 0 &&
700 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
701 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
703 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
704 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
706 connector
.energyActiveImportRegisterValue
= 0;
707 connector
.transactionEnergyActiveImportRegisterValue
= 0;
710 meterValue
.sampledValue
.push(
711 OCPP16ServiceUtils
.buildSampledValue(
712 energySampledValueTemplate
,
714 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
720 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
721 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
723 `${chargingStation.logPrefix()} MeterValues measurand ${
724 meterValue.sampledValue[sampledValuesIndex].measurand ??
725 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
726 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${roundTo(
727 interval / (3600 * 1000),
736 public static buildTransactionBeginMeterValue(
737 chargingStation
: ChargingStation
,
740 ): OCPP16MeterValue
{
741 const meterValue
: OCPP16MeterValue
= {
742 timestamp
: new Date(),
745 // Energy.Active.Import.Register measurand (default)
746 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
750 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
751 meterValue
.sampledValue
.push(
752 OCPP16ServiceUtils
.buildSampledValue(
753 sampledValueTemplate
!,
754 roundTo((meterStart
?? 0) / unitDivider
, 4),
755 MeterValueContext
.TRANSACTION_BEGIN
,
761 public static buildTransactionEndMeterValue(
762 chargingStation
: ChargingStation
,
765 ): OCPP16MeterValue
{
766 const meterValue
: OCPP16MeterValue
= {
767 timestamp
: new Date(),
770 // Energy.Active.Import.Register measurand (default)
771 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
775 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
776 meterValue
.sampledValue
.push(
777 OCPP16ServiceUtils
.buildSampledValue(
778 sampledValueTemplate
!,
779 roundTo((meterStop
?? 0) / unitDivider
, 4),
780 MeterValueContext
.TRANSACTION_END
,
786 public static buildTransactionDataMeterValues(
787 transactionBeginMeterValue
: OCPP16MeterValue
,
788 transactionEndMeterValue
: OCPP16MeterValue
,
789 ): OCPP16MeterValue
[] {
790 const meterValues
: OCPP16MeterValue
[] = [];
791 meterValues
.push(transactionBeginMeterValue
);
792 meterValues
.push(transactionEndMeterValue
);
796 public static setChargingProfile(
797 chargingStation
: ChargingStation
,
799 cp
: OCPP16ChargingProfile
,
801 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
803 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
805 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
808 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
811 `${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`,
813 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
815 let cpReplaced
= false;
816 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
818 .getConnectorStatus(connectorId
)
819 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
821 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
822 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
823 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
825 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
830 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
833 public static parseJsonSchemaFile
<T
extends JsonType
>(
834 relativePath
: string,
837 ): JSONSchemaType
<T
> {
838 return super.parseJsonSchemaFile
<T
>(
840 OCPPVersion
.VERSION_16
,
846 public static async isIdTagAuthorized(
847 chargingStation
: ChargingStation
,
850 ): Promise
<boolean> {
851 let authorized
= false;
852 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
853 if (OCPP16ServiceUtils
.isIdTagLocalAuthorized(chargingStation
, idTag
)) {
854 connectorStatus
.localAuthorizeIdTag
= idTag
;
855 connectorStatus
.idTagLocalAuthorized
= true;
857 } else if (chargingStation
.getMustAuthorizeAtRemoteStart() === true) {
858 connectorStatus
.authorizeIdTag
= idTag
;
859 authorized
= await OCPP16ServiceUtils
.isIdTagRemoteAuthorized(chargingStation
, idTag
);
862 `${chargingStation.logPrefix()} The charging station configuration expects authorize at
863 remote start transaction but local authorization or authorize isn't enabled`,
869 private static buildSampledValue(
870 sampledValueTemplate
: SampledValueTemplate
,
872 context
?: MeterValueContext
,
873 phase
?: OCPP16MeterValuePhase
,
874 ): OCPP16SampledValue
{
875 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
876 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
877 const sampledValueLocation
=
878 sampledValueTemplate
?.location
??
879 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
880 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
882 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
883 unit
: sampledValueTemplate
.unit
,
885 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
886 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
887 measurand
: sampledValueTemplate
.measurand
,
889 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
890 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
891 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
892 } as OCPP16SampledValue
;
895 private static checkMeasurandPowerDivider(
896 chargingStation
: ChargingStation
,
897 measurandType
: OCPP16MeterValueMeasurand
,
899 if (isUndefined(chargingStation
.powerDivider
)) {
900 const errMsg
= `MeterValues measurand ${
901 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
902 }: powerDivider is undefined`;
903 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
904 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
905 } else if (chargingStation
?.powerDivider
<= 0) {
906 const errMsg
= `MeterValues measurand ${
907 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
908 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
909 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
910 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
914 private static getMeasurandDefaultLocation(
915 measurandType
: OCPP16MeterValueMeasurand
,
916 ): MeterValueLocation
| undefined {
917 switch (measurandType
) {
918 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
919 return MeterValueLocation
.EV
;
923 private static getMeasurandDefaultUnit(
924 measurandType
: OCPP16MeterValueMeasurand
,
925 ): MeterValueUnit
| undefined {
926 switch (measurandType
) {
927 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
928 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
929 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
930 return MeterValueUnit
.AMP
;
931 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
932 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
933 return MeterValueUnit
.WATT_HOUR
;
934 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
935 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
936 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
937 return MeterValueUnit
.WATT
;
938 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
939 return MeterValueUnit
.PERCENT
;
940 case OCPP16MeterValueMeasurand
.VOLTAGE
:
941 return MeterValueUnit
.VOLT
;
945 private static isIdTagLocalAuthorized(chargingStation
: ChargingStation
, idTag
: string): boolean {
947 chargingStation
.getLocalAuthListEnabled() === true &&
948 chargingStation
.hasIdTags() === true &&
950 chargingStation
.idTagsCache
951 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
952 ?.find((tag
) => tag
=== idTag
),
957 private static async isIdTagRemoteAuthorized(
958 chargingStation
: ChargingStation
,
960 ): Promise
<boolean> {
961 const authorizeResponse
: OCPP16AuthorizeResponse
=
962 await chargingStation
.ocppRequestService
.requestHandler
<
963 OCPP16AuthorizeRequest
,
964 OCPP16AuthorizeResponse
965 >(chargingStation
, OCPP16RequestCommand
.AUTHORIZE
, {
968 return authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
;