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';
11 type MeasurandPerPhaseSampledValueTemplates
,
16 OCPP16AuthorizationStatus
,
17 type OCPP16AuthorizeRequest
,
18 type OCPP16AuthorizeResponse
,
19 type OCPP16ChargingProfile
,
20 type OCPP16IncomingRequestCommand
,
21 type OCPP16MeterValue
,
22 OCPP16MeterValueMeasurand
,
23 OCPP16MeterValuePhase
,
25 type OCPP16SampledValue
,
26 OCPP16StandardParametersKey
,
27 type OCPP16SupportedFeatureProfiles
,
29 type SampledValueTemplate
,
31 } from
'../../../types';
38 getRandomFloatFluctuatedRounded
,
39 getRandomFloatRounded
,
47 } from
'../../../utils';
48 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
50 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
51 public static checkFeatureProfile(
52 chargingStation
: ChargingStation
,
53 featureProfile
: OCPP16SupportedFeatureProfiles
,
54 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
56 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
58 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
59 OCPP16StandardParametersKey.SupportedFeatureProfiles
67 public static buildMeterValue(
68 chargingStation
: ChargingStation
,
70 transactionId
: number,
74 const meterValue
: OCPP16MeterValue
= {
75 timestamp
: new Date(),
78 const connector
= chargingStation
.getConnectorStatus(connectorId
);
80 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
83 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
85 if (socSampledValueTemplate
) {
86 const socMaximumValue
= 100;
87 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
88 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
89 ? getRandomFloatFluctuatedRounded(
90 parseInt(socSampledValueTemplate
.value
),
91 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
93 : getRandomInteger(socMaximumValue
, socMinimumValue
);
94 meterValue
.sampledValue
.push(
95 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
97 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
99 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
100 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
104 `${chargingStation.logPrefix()} MeterValues measurand ${
105 meterValue.sampledValue[sampledValuesIndex].measurand ??
106 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
107 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
108 meterValue.sampledValue[sampledValuesIndex].value
109 }/${socMaximumValue}}`,
114 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
117 OCPP16MeterValueMeasurand
.VOLTAGE
,
119 if (voltageSampledValueTemplate
) {
120 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
121 ? parseInt(voltageSampledValueTemplate
.value
)
122 : chargingStation
.getVoltageOut();
123 const fluctuationPercent
=
124 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
125 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
126 voltageSampledValueTemplateValue
,
130 chargingStation
.getNumberOfPhases() !== 3 ||
131 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
133 meterValue
.sampledValue
.push(
134 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
139 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
142 const phaseLineToNeutralValue
= `L${phase}-N`;
143 const voltagePhaseLineToNeutralSampledValueTemplate
=
144 OCPP16ServiceUtils
.getSampledValueTemplate(
147 OCPP16MeterValueMeasurand
.VOLTAGE
,
148 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
150 let voltagePhaseLineToNeutralMeasurandValue
: number;
151 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
152 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
153 voltagePhaseLineToNeutralSampledValueTemplate
.value
154 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
155 : chargingStation
.getVoltageOut();
156 const fluctuationPhaseToNeutralPercent
=
157 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
158 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
159 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
160 voltagePhaseLineToNeutralSampledValueTemplateValue
,
161 fluctuationPhaseToNeutralPercent
,
164 meterValue
.sampledValue
.push(
165 OCPP16ServiceUtils
.buildSampledValue(
166 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
167 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
169 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
172 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
173 const phaseLineToLineValue
= `L${phase}-L${
174 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
175 ? (phase + 1) % chargingStation.getNumberOfPhases()
176 : chargingStation.getNumberOfPhases()
178 const voltagePhaseLineToLineSampledValueTemplate
=
179 OCPP16ServiceUtils
.getSampledValueTemplate(
182 OCPP16MeterValueMeasurand
.VOLTAGE
,
183 phaseLineToLineValue
as OCPP16MeterValuePhase
,
185 let voltagePhaseLineToLineMeasurandValue
: number;
186 if (voltagePhaseLineToLineSampledValueTemplate
) {
187 const voltagePhaseLineToLineSampledValueTemplateValue
=
188 voltagePhaseLineToLineSampledValueTemplate
.value
189 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
190 : Voltage
.VOLTAGE_400
;
191 const fluctuationPhaseLineToLinePercent
=
192 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
193 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
194 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
195 voltagePhaseLineToLineSampledValueTemplateValue
,
196 fluctuationPhaseLineToLinePercent
,
199 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
203 meterValue
.sampledValue
.push(
204 OCPP16ServiceUtils
.buildSampledValue(
205 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
206 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
208 phaseLineToLineValue
as OCPP16MeterValuePhase
,
214 // Power.Active.Import measurand
215 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
218 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
220 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
221 if (chargingStation
.getNumberOfPhases() === 3) {
222 powerPerPhaseSampledValueTemplates
= {
223 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
226 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
227 OCPP16MeterValuePhase
.L1_N
,
229 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
232 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
233 OCPP16MeterValuePhase
.L2_N
,
235 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
238 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
239 OCPP16MeterValuePhase
.L3_N
,
243 if (powerSampledValueTemplate
) {
244 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
246 powerSampledValueTemplate
.measurand
,
248 const errMsg
= `MeterValues measurand ${
249 powerSampledValueTemplate.measurand ??
250 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
251 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
252 chargingStation.templateFile
253 }, cannot calculate ${
254 powerSampledValueTemplate.measurand ??
255 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
257 const powerMeasurandValues
= {} as MeasurandValues
;
258 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
259 const connectorMaximumAvailablePower
=
260 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
261 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
262 const connectorMaximumPowerPerPhase
= Math.round(
263 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
265 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
) ?? 0;
266 const connectorMinimumPowerPerPhase
= Math.round(
267 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
269 switch (chargingStation
.getCurrentOutType()) {
271 if (chargingStation
.getNumberOfPhases() === 3) {
272 const defaultFluctuatedPowerPerPhase
=
273 powerSampledValueTemplate
.value
&&
274 getRandomFloatFluctuatedRounded(
275 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
276 powerSampledValueTemplate
.value
,
277 connectorMaximumPower
/ unitDivider
,
278 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
279 ) / chargingStation
.getNumberOfPhases(),
280 powerSampledValueTemplate
.fluctuationPercent
??
281 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
283 const phase1FluctuatedValue
=
284 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
285 getRandomFloatFluctuatedRounded(
286 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
287 powerPerPhaseSampledValueTemplates
.L1
.value
,
288 connectorMaximumPowerPerPhase
/ unitDivider
,
289 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
291 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
292 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
294 const phase2FluctuatedValue
=
295 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
296 getRandomFloatFluctuatedRounded(
297 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
298 powerPerPhaseSampledValueTemplates
.L2
.value
,
299 connectorMaximumPowerPerPhase
/ unitDivider
,
300 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
302 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
303 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
305 const phase3FluctuatedValue
=
306 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
307 getRandomFloatFluctuatedRounded(
308 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
309 powerPerPhaseSampledValueTemplates
.L3
.value
,
310 connectorMaximumPowerPerPhase
/ unitDivider
,
311 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
313 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
314 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
316 powerMeasurandValues
.L1
=
317 phase1FluctuatedValue
??
318 defaultFluctuatedPowerPerPhase
??
319 getRandomFloatRounded(
320 connectorMaximumPowerPerPhase
/ unitDivider
,
321 connectorMinimumPowerPerPhase
/ unitDivider
,
323 powerMeasurandValues
.L2
=
324 phase2FluctuatedValue
??
325 defaultFluctuatedPowerPerPhase
??
326 getRandomFloatRounded(
327 connectorMaximumPowerPerPhase
/ unitDivider
,
328 connectorMinimumPowerPerPhase
/ unitDivider
,
330 powerMeasurandValues
.L3
=
331 phase3FluctuatedValue
??
332 defaultFluctuatedPowerPerPhase
??
333 getRandomFloatRounded(
334 connectorMaximumPowerPerPhase
/ unitDivider
,
335 connectorMinimumPowerPerPhase
/ unitDivider
,
338 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
339 ? getRandomFloatFluctuatedRounded(
340 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
341 powerSampledValueTemplate
.value
,
342 connectorMaximumPower
/ unitDivider
,
343 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
345 powerSampledValueTemplate
.fluctuationPercent
??
346 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
348 : getRandomFloatRounded(
349 connectorMaximumPower
/ unitDivider
,
350 connectorMinimumPower
/ unitDivider
,
352 powerMeasurandValues
.L2
= 0;
353 powerMeasurandValues
.L3
= 0;
355 powerMeasurandValues
.allPhases
= roundTo(
356 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
361 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
362 ? getRandomFloatFluctuatedRounded(
363 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
364 powerSampledValueTemplate
.value
,
365 connectorMaximumPower
/ unitDivider
,
366 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
368 powerSampledValueTemplate
.fluctuationPercent
??
369 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
371 : getRandomFloatRounded(
372 connectorMaximumPower
/ unitDivider
,
373 connectorMinimumPower
/ unitDivider
,
377 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
378 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
380 meterValue
.sampledValue
.push(
381 OCPP16ServiceUtils
.buildSampledValue(
382 powerSampledValueTemplate
,
383 powerMeasurandValues
.allPhases
,
386 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
387 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
388 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
390 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
391 connectorMaximumPowerRounded
||
392 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
393 connectorMinimumPowerRounded
||
397 `${chargingStation.logPrefix()} MeterValues measurand ${
398 meterValue.sampledValue[sampledValuesIndex].measurand ??
399 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
400 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
401 meterValue.sampledValue[sampledValuesIndex].value
402 }/${connectorMaximumPowerRounded}`,
407 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
410 const phaseValue
= `L${phase}-N`;
411 meterValue
.sampledValue
.push(
412 OCPP16ServiceUtils
.buildSampledValue(
413 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
414 powerSampledValueTemplate
,
415 powerMeasurandValues
[`L${phase}`] as number,
417 phaseValue
as OCPP16MeterValuePhase
,
420 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
421 const connectorMaximumPowerPerPhaseRounded
= roundTo(
422 connectorMaximumPowerPerPhase
/ unitDivider
,
425 const connectorMinimumPowerPerPhaseRounded
= roundTo(
426 connectorMinimumPowerPerPhase
/ unitDivider
,
430 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
431 connectorMaximumPowerPerPhaseRounded
||
432 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
433 connectorMinimumPowerPerPhaseRounded
||
437 `${chargingStation.logPrefix()} MeterValues measurand ${
438 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
439 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
441 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
442 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
443 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
444 }/${connectorMaximumPowerPerPhaseRounded}`,
449 // Current.Import measurand
450 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
453 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
455 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
456 if (chargingStation
.getNumberOfPhases() === 3) {
457 currentPerPhaseSampledValueTemplates
= {
458 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
461 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
462 OCPP16MeterValuePhase
.L1
,
464 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
467 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
468 OCPP16MeterValuePhase
.L2
,
470 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
473 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
474 OCPP16MeterValuePhase
.L3
,
478 if (currentSampledValueTemplate
) {
479 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
481 currentSampledValueTemplate
.measurand
,
483 const errMsg
= `MeterValues measurand ${
484 currentSampledValueTemplate.measurand ??
485 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
486 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
487 chargingStation.templateFile
488 }, cannot calculate ${
489 currentSampledValueTemplate.measurand ??
490 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
492 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
493 const connectorMaximumAvailablePower
=
494 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
495 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
496 let connectorMaximumAmperage
: number;
497 switch (chargingStation
.getCurrentOutType()) {
499 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
500 chargingStation
.getNumberOfPhases(),
501 connectorMaximumAvailablePower
,
502 chargingStation
.getVoltageOut(),
504 if (chargingStation
.getNumberOfPhases() === 3) {
505 const defaultFluctuatedAmperagePerPhase
=
506 currentSampledValueTemplate
.value
&&
507 getRandomFloatFluctuatedRounded(
508 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
509 currentSampledValueTemplate
.value
,
510 connectorMaximumAmperage
,
511 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
513 currentSampledValueTemplate
.fluctuationPercent
??
514 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
516 const phase1FluctuatedValue
=
517 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
518 getRandomFloatFluctuatedRounded(
519 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
520 currentPerPhaseSampledValueTemplates
.L1
.value
,
521 connectorMaximumAmperage
,
522 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
524 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
525 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
527 const phase2FluctuatedValue
=
528 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
529 getRandomFloatFluctuatedRounded(
530 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
531 currentPerPhaseSampledValueTemplates
.L2
.value
,
532 connectorMaximumAmperage
,
533 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
535 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
536 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
538 const phase3FluctuatedValue
=
539 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
540 getRandomFloatFluctuatedRounded(
541 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
542 currentPerPhaseSampledValueTemplates
.L3
.value
,
543 connectorMaximumAmperage
,
544 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
546 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
547 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
549 currentMeasurandValues
.L1
=
550 phase1FluctuatedValue
??
551 defaultFluctuatedAmperagePerPhase
??
552 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
553 currentMeasurandValues
.L2
=
554 phase2FluctuatedValue
??
555 defaultFluctuatedAmperagePerPhase
??
556 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
557 currentMeasurandValues
.L3
=
558 phase3FluctuatedValue
??
559 defaultFluctuatedAmperagePerPhase
??
560 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
562 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
563 ? getRandomFloatFluctuatedRounded(
564 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
565 currentSampledValueTemplate
.value
,
566 connectorMaximumAmperage
,
567 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
569 currentSampledValueTemplate
.fluctuationPercent
??
570 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
572 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
573 currentMeasurandValues
.L2
= 0;
574 currentMeasurandValues
.L3
= 0;
576 currentMeasurandValues
.allPhases
= roundTo(
577 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
578 chargingStation
.getNumberOfPhases(),
583 connectorMaximumAmperage
= DCElectricUtils
.amperage(
584 connectorMaximumAvailablePower
,
585 chargingStation
.getVoltageOut(),
587 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
588 ? getRandomFloatFluctuatedRounded(
589 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
590 currentSampledValueTemplate
.value
,
591 connectorMaximumAmperage
,
592 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
594 currentSampledValueTemplate
.fluctuationPercent
??
595 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
597 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
600 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
601 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
603 meterValue
.sampledValue
.push(
604 OCPP16ServiceUtils
.buildSampledValue(
605 currentSampledValueTemplate
,
606 currentMeasurandValues
.allPhases
,
609 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
611 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
612 connectorMaximumAmperage
||
613 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
614 connectorMinimumAmperage
||
618 `${chargingStation.logPrefix()} MeterValues measurand ${
619 meterValue.sampledValue[sampledValuesIndex].measurand ??
620 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
621 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
622 meterValue.sampledValue[sampledValuesIndex].value
623 }/${connectorMaximumAmperage}`,
628 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
631 const phaseValue
= `L${phase}`;
632 meterValue
.sampledValue
.push(
633 OCPP16ServiceUtils
.buildSampledValue(
634 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
635 currentSampledValueTemplate
,
636 currentMeasurandValues
[phaseValue
] as number,
638 phaseValue
as OCPP16MeterValuePhase
,
641 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
643 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
644 connectorMaximumAmperage
||
645 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
646 connectorMinimumAmperage
||
650 `${chargingStation.logPrefix()} MeterValues measurand ${
651 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
652 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
654 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
655 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
656 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
657 }/${connectorMaximumAmperage}`,
662 // Energy.Active.Import.Register measurand (default)
663 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
667 if (energySampledValueTemplate
) {
668 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
670 energySampledValueTemplate
.measurand
,
673 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
674 const connectorMaximumAvailablePower
=
675 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
676 const connectorMaximumEnergyRounded
= roundTo(
677 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
680 const energyValueRounded
= energySampledValueTemplate
.value
681 ? // Cumulate the fluctuated value around the static one
682 getRandomFloatFluctuatedRounded(
683 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
684 energySampledValueTemplate
.value
,
685 connectorMaximumEnergyRounded
,
687 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
688 unitMultiplier
: unitDivider
,
691 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
693 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
694 // Persist previous value on connector
697 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
698 connector
.energyActiveImportRegisterValue
>= 0 &&
699 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
700 connector
.transactionEnergyActiveImportRegisterValue
>= 0
702 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
703 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
705 connector
.energyActiveImportRegisterValue
= 0;
706 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: ${roundTo(
725 interval / (3600 * 1000),
734 public static buildTransactionBeginMeterValue(
735 chargingStation
: ChargingStation
,
738 ): OCPP16MeterValue
{
739 const meterValue
: OCPP16MeterValue
= {
740 timestamp
: new Date(),
743 // Energy.Active.Import.Register measurand (default)
744 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
748 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
749 meterValue
.sampledValue
.push(
750 OCPP16ServiceUtils
.buildSampledValue(
751 sampledValueTemplate
,
752 roundTo((meterStart
?? 0) / unitDivider
, 4),
753 MeterValueContext
.TRANSACTION_BEGIN
,
759 public static buildTransactionEndMeterValue(
760 chargingStation
: ChargingStation
,
763 ): OCPP16MeterValue
{
764 const meterValue
: OCPP16MeterValue
= {
765 timestamp
: new Date(),
768 // Energy.Active.Import.Register measurand (default)
769 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
773 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
774 meterValue
.sampledValue
.push(
775 OCPP16ServiceUtils
.buildSampledValue(
776 sampledValueTemplate
,
777 roundTo((meterStop
?? 0) / unitDivider
, 4),
778 MeterValueContext
.TRANSACTION_END
,
784 public static buildTransactionDataMeterValues(
785 transactionBeginMeterValue
: OCPP16MeterValue
,
786 transactionEndMeterValue
: OCPP16MeterValue
,
787 ): OCPP16MeterValue
[] {
788 const meterValues
: OCPP16MeterValue
[] = [];
789 meterValues
.push(transactionBeginMeterValue
);
790 meterValues
.push(transactionEndMeterValue
);
794 public static setChargingProfile(
795 chargingStation
: ChargingStation
,
797 cp
: OCPP16ChargingProfile
,
799 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
801 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
803 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
806 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
809 `${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`,
811 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
813 let cpReplaced
= false;
814 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
816 .getConnectorStatus(connectorId
)
817 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
819 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
820 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
821 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
823 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
828 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
831 public static parseJsonSchemaFile
<T
extends JsonType
>(
832 relativePath
: string,
835 ): JSONSchemaType
<T
> {
836 return super.parseJsonSchemaFile
<T
>(
838 OCPPVersion
.VERSION_16
,
844 public static async isIdTagAuthorized(
845 chargingStation
: ChargingStation
,
848 ): Promise
<boolean> {
849 let authorized
= false;
850 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
);
851 if (OCPP16ServiceUtils
.isIdTagLocalAuthorized(chargingStation
, idTag
)) {
852 connectorStatus
.localAuthorizeIdTag
= idTag
;
853 connectorStatus
.idTagLocalAuthorized
= true;
855 } else if (chargingStation
.getMustAuthorizeAtRemoteStart() === true) {
856 connectorStatus
.authorizeIdTag
= idTag
;
857 authorized
= await OCPP16ServiceUtils
.isIdTagRemoteAuthorized(chargingStation
, idTag
);
860 `${chargingStation.logPrefix()} The charging station configuration expects authorize at
861 remote start transaction but local authorization or authorize isn't enabled`,
867 private static buildSampledValue(
868 sampledValueTemplate
: SampledValueTemplate
,
870 context
?: MeterValueContext
,
871 phase
?: OCPP16MeterValuePhase
,
872 ): OCPP16SampledValue
{
873 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
874 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
875 const sampledValueLocation
=
876 sampledValueTemplate
?.location
??
877 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
878 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
880 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
881 unit
: sampledValueTemplate
.unit
,
883 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
884 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
885 measurand
: sampledValueTemplate
.measurand
,
887 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
888 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
889 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
893 private static checkMeasurandPowerDivider(
894 chargingStation
: ChargingStation
,
895 measurandType
: OCPP16MeterValueMeasurand
,
897 if (isUndefined(chargingStation
.powerDivider
)) {
898 const errMsg
= `MeterValues measurand ${
899 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
900 }: powerDivider is undefined`;
901 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
902 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
903 } else if (chargingStation
?.powerDivider
<= 0) {
904 const errMsg
= `MeterValues measurand ${
905 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
906 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
907 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
908 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
912 private static getMeasurandDefaultLocation(
913 measurandType
: OCPP16MeterValueMeasurand
,
914 ): MeterValueLocation
| undefined {
915 switch (measurandType
) {
916 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
917 return MeterValueLocation
.EV
;
921 private static getMeasurandDefaultUnit(
922 measurandType
: OCPP16MeterValueMeasurand
,
923 ): MeterValueUnit
| undefined {
924 switch (measurandType
) {
925 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
926 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
927 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
928 return MeterValueUnit
.AMP
;
929 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
930 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
931 return MeterValueUnit
.WATT_HOUR
;
932 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
933 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
934 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
935 return MeterValueUnit
.WATT
;
936 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
937 return MeterValueUnit
.PERCENT
;
938 case OCPP16MeterValueMeasurand
.VOLTAGE
:
939 return MeterValueUnit
.VOLT
;
943 private static isIdTagLocalAuthorized(chargingStation
: ChargingStation
, idTag
: string): boolean {
945 chargingStation
.getLocalAuthListEnabled() === true &&
946 chargingStation
.hasIdTags() === true &&
948 chargingStation
.idTagsCache
949 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
))
950 ?.find((tag
) => tag
=== idTag
),
955 private static async isIdTagRemoteAuthorized(
956 chargingStation
: ChargingStation
,
958 ): Promise
<boolean> {
959 const authorizeResponse
: OCPP16AuthorizeResponse
=
960 await chargingStation
.ocppRequestService
.requestHandler
<
961 OCPP16AuthorizeRequest
,
962 OCPP16AuthorizeResponse
963 >(chargingStation
, OCPP16RequestCommand
.AUTHORIZE
, {
966 return authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
;