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 ${
108 connector?.transactionId
109 }, value: ${socMinimumValue}/${
110 meterValue.sampledValue[sampledValuesIndex].value
111 }/${socMaximumValue}}`
116 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
119 OCPP16MeterValueMeasurand
.VOLTAGE
121 if (voltageSampledValueTemplate
) {
122 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
123 ? parseInt(voltageSampledValueTemplate
.value
)
124 : chargingStation
.getVoltageOut();
125 const fluctuationPercent
=
126 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
127 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
128 voltageSampledValueTemplateValue
,
132 chargingStation
.getNumberOfPhases() !== 3 ||
133 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
135 meterValue
.sampledValue
.push(
136 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
141 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
144 const phaseLineToNeutralValue
= `L${phase}-N`;
145 const voltagePhaseLineToNeutralSampledValueTemplate
=
146 OCPP16ServiceUtils
.getSampledValueTemplate(
149 OCPP16MeterValueMeasurand
.VOLTAGE
,
150 phaseLineToNeutralValue
as OCPP16MeterValuePhase
152 let voltagePhaseLineToNeutralMeasurandValue
: number;
153 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
154 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
155 voltagePhaseLineToNeutralSampledValueTemplate
.value
156 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
157 : chargingStation
.getVoltageOut();
158 const fluctuationPhaseToNeutralPercent
=
159 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
160 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
161 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
162 voltagePhaseLineToNeutralSampledValueTemplateValue
,
163 fluctuationPhaseToNeutralPercent
166 meterValue
.sampledValue
.push(
167 OCPP16ServiceUtils
.buildSampledValue(
168 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
169 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
171 phaseLineToNeutralValue
as OCPP16MeterValuePhase
174 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
175 const phaseLineToLineValue
= `L${phase}-L${
176 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
177 ? (phase + 1) % chargingStation.getNumberOfPhases()
178 : chargingStation.getNumberOfPhases()
180 const voltagePhaseLineToLineSampledValueTemplate
=
181 OCPP16ServiceUtils
.getSampledValueTemplate(
184 OCPP16MeterValueMeasurand
.VOLTAGE
,
185 phaseLineToLineValue
as OCPP16MeterValuePhase
187 let voltagePhaseLineToLineMeasurandValue
: number;
188 if (voltagePhaseLineToLineSampledValueTemplate
) {
189 const voltagePhaseLineToLineSampledValueTemplateValue
=
190 voltagePhaseLineToLineSampledValueTemplate
.value
191 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
192 : Voltage
.VOLTAGE_400
;
193 const fluctuationPhaseLineToLinePercent
=
194 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
195 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
196 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
197 voltagePhaseLineToLineSampledValueTemplateValue
,
198 fluctuationPhaseLineToLinePercent
201 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
205 meterValue
.sampledValue
.push(
206 OCPP16ServiceUtils
.buildSampledValue(
207 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
208 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
210 phaseLineToLineValue
as OCPP16MeterValuePhase
216 // Power.Active.Import measurand
217 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
220 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
222 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
223 if (chargingStation
.getNumberOfPhases() === 3) {
224 powerPerPhaseSampledValueTemplates
= {
225 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
228 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
229 OCPP16MeterValuePhase
.L1_N
231 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
234 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
235 OCPP16MeterValuePhase
.L2_N
237 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
240 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
241 OCPP16MeterValuePhase
.L3_N
245 if (powerSampledValueTemplate
) {
246 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
248 powerSampledValueTemplate
.measurand
250 const errMsg
= `MeterValues measurand ${
251 powerSampledValueTemplate.measurand ??
252 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
253 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
254 chargingStation.templateFile
255 }, cannot calculate ${
256 powerSampledValueTemplate.measurand ??
257 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
259 const powerMeasurandValues
= {} as MeasurandValues
;
260 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
261 const connectorMaximumAvailablePower
=
262 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
263 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
264 const connectorMaximumPowerPerPhase
= Math.round(
265 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
267 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
) ?? 0;
268 const connectorMinimumPowerPerPhase
= Math.round(
269 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
271 switch (chargingStation
.getCurrentOutType()) {
273 if (chargingStation
.getNumberOfPhases() === 3) {
274 const defaultFluctuatedPowerPerPhase
=
275 powerSampledValueTemplate
.value
&&
276 getRandomFloatFluctuatedRounded(
277 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
278 powerSampledValueTemplate
.value
,
279 connectorMaximumPower
/ unitDivider
,
280 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
281 ) / chargingStation
.getNumberOfPhases(),
282 powerSampledValueTemplate
.fluctuationPercent
??
283 Constants
.DEFAULT_FLUCTUATION_PERCENT
285 const phase1FluctuatedValue
=
286 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
287 getRandomFloatFluctuatedRounded(
288 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
289 powerPerPhaseSampledValueTemplates
.L1
.value
,
290 connectorMaximumPowerPerPhase
/ unitDivider
,
291 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
293 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
294 Constants
.DEFAULT_FLUCTUATION_PERCENT
296 const phase2FluctuatedValue
=
297 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
298 getRandomFloatFluctuatedRounded(
299 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
300 powerPerPhaseSampledValueTemplates
.L2
.value
,
301 connectorMaximumPowerPerPhase
/ unitDivider
,
302 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
304 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
305 Constants
.DEFAULT_FLUCTUATION_PERCENT
307 const phase3FluctuatedValue
=
308 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
309 getRandomFloatFluctuatedRounded(
310 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
311 powerPerPhaseSampledValueTemplates
.L3
.value
,
312 connectorMaximumPowerPerPhase
/ unitDivider
,
313 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
315 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
316 Constants
.DEFAULT_FLUCTUATION_PERCENT
318 powerMeasurandValues
.L1
=
319 phase1FluctuatedValue
??
320 defaultFluctuatedPowerPerPhase
??
321 getRandomFloatRounded(
322 connectorMaximumPowerPerPhase
/ unitDivider
,
323 connectorMinimumPowerPerPhase
/ unitDivider
325 powerMeasurandValues
.L2
=
326 phase2FluctuatedValue
??
327 defaultFluctuatedPowerPerPhase
??
328 getRandomFloatRounded(
329 connectorMaximumPowerPerPhase
/ unitDivider
,
330 connectorMinimumPowerPerPhase
/ unitDivider
332 powerMeasurandValues
.L3
=
333 phase3FluctuatedValue
??
334 defaultFluctuatedPowerPerPhase
??
335 getRandomFloatRounded(
336 connectorMaximumPowerPerPhase
/ unitDivider
,
337 connectorMinimumPowerPerPhase
/ unitDivider
340 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
341 ? getRandomFloatFluctuatedRounded(
342 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
343 powerSampledValueTemplate
.value
,
344 connectorMaximumPower
/ unitDivider
,
345 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
347 powerSampledValueTemplate
.fluctuationPercent
??
348 Constants
.DEFAULT_FLUCTUATION_PERCENT
350 : getRandomFloatRounded(
351 connectorMaximumPower
/ unitDivider
,
352 connectorMinimumPower
/ unitDivider
354 powerMeasurandValues
.L2
= 0;
355 powerMeasurandValues
.L3
= 0;
357 powerMeasurandValues
.allPhases
= roundTo(
358 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
363 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
364 ? getRandomFloatFluctuatedRounded(
365 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
366 powerSampledValueTemplate
.value
,
367 connectorMaximumPower
/ unitDivider
,
368 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
370 powerSampledValueTemplate
.fluctuationPercent
??
371 Constants
.DEFAULT_FLUCTUATION_PERCENT
373 : getRandomFloatRounded(
374 connectorMaximumPower
/ unitDivider
,
375 connectorMinimumPower
/ unitDivider
379 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
380 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
382 meterValue
.sampledValue
.push(
383 OCPP16ServiceUtils
.buildSampledValue(
384 powerSampledValueTemplate
,
385 powerMeasurandValues
.allPhases
388 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
389 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
390 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
392 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
393 connectorMaximumPowerRounded
||
394 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
395 connectorMinimumPowerRounded
||
399 `${chargingStation.logPrefix()} MeterValues measurand ${
400 meterValue.sampledValue[sampledValuesIndex].measurand ??
401 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
402 }: connector id ${connectorId}, transaction id ${
403 connector?.transactionId
404 }, value: ${connectorMinimumPowerRounded}/${
405 meterValue.sampledValue[sampledValuesIndex].value
406 }/${connectorMaximumPowerRounded}`
411 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
414 const phaseValue
= `L${phase}-N`;
415 meterValue
.sampledValue
.push(
416 OCPP16ServiceUtils
.buildSampledValue(
417 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
418 powerSampledValueTemplate
,
419 powerMeasurandValues
[`L${phase}`] as number,
421 phaseValue
as OCPP16MeterValuePhase
424 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
425 const connectorMaximumPowerPerPhaseRounded
= roundTo(
426 connectorMaximumPowerPerPhase
/ unitDivider
,
429 const connectorMinimumPowerPerPhaseRounded
= roundTo(
430 connectorMinimumPowerPerPhase
/ unitDivider
,
434 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
435 connectorMaximumPowerPerPhaseRounded
||
436 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
437 connectorMinimumPowerPerPhaseRounded
||
441 `${chargingStation.logPrefix()} MeterValues measurand ${
442 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
443 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
445 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
446 }, connector id ${connectorId}, transaction id ${
447 connector?.transactionId
448 }, value: ${connectorMinimumPowerPerPhaseRounded}/${
449 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
450 }/${connectorMaximumPowerPerPhaseRounded}`
455 // Current.Import measurand
456 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
459 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
461 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
462 if (chargingStation
.getNumberOfPhases() === 3) {
463 currentPerPhaseSampledValueTemplates
= {
464 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
467 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
468 OCPP16MeterValuePhase
.L1
470 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
473 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
474 OCPP16MeterValuePhase
.L2
476 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
479 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
480 OCPP16MeterValuePhase
.L3
484 if (currentSampledValueTemplate
) {
485 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
487 currentSampledValueTemplate
.measurand
489 const errMsg
= `MeterValues measurand ${
490 currentSampledValueTemplate.measurand ??
491 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
492 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
493 chargingStation.templateFile
494 }, cannot calculate ${
495 currentSampledValueTemplate.measurand ??
496 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
498 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
499 const connectorMaximumAvailablePower
=
500 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
501 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
502 let connectorMaximumAmperage
: number;
503 switch (chargingStation
.getCurrentOutType()) {
505 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
506 chargingStation
.getNumberOfPhases(),
507 connectorMaximumAvailablePower
,
508 chargingStation
.getVoltageOut()
510 if (chargingStation
.getNumberOfPhases() === 3) {
511 const defaultFluctuatedAmperagePerPhase
=
512 currentSampledValueTemplate
.value
&&
513 getRandomFloatFluctuatedRounded(
514 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
515 currentSampledValueTemplate
.value
,
516 connectorMaximumAmperage
,
517 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
519 currentSampledValueTemplate
.fluctuationPercent
??
520 Constants
.DEFAULT_FLUCTUATION_PERCENT
522 const phase1FluctuatedValue
=
523 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
524 getRandomFloatFluctuatedRounded(
525 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
526 currentPerPhaseSampledValueTemplates
.L1
.value
,
527 connectorMaximumAmperage
,
528 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
530 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
531 Constants
.DEFAULT_FLUCTUATION_PERCENT
533 const phase2FluctuatedValue
=
534 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
535 getRandomFloatFluctuatedRounded(
536 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
537 currentPerPhaseSampledValueTemplates
.L2
.value
,
538 connectorMaximumAmperage
,
539 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
541 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
542 Constants
.DEFAULT_FLUCTUATION_PERCENT
544 const phase3FluctuatedValue
=
545 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
546 getRandomFloatFluctuatedRounded(
547 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
548 currentPerPhaseSampledValueTemplates
.L3
.value
,
549 connectorMaximumAmperage
,
550 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
552 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
553 Constants
.DEFAULT_FLUCTUATION_PERCENT
555 currentMeasurandValues
.L1
=
556 phase1FluctuatedValue
??
557 defaultFluctuatedAmperagePerPhase
??
558 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
559 currentMeasurandValues
.L2
=
560 phase2FluctuatedValue
??
561 defaultFluctuatedAmperagePerPhase
??
562 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
563 currentMeasurandValues
.L3
=
564 phase3FluctuatedValue
??
565 defaultFluctuatedAmperagePerPhase
??
566 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
568 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
569 ? getRandomFloatFluctuatedRounded(
570 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
571 currentSampledValueTemplate
.value
,
572 connectorMaximumAmperage
,
573 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
575 currentSampledValueTemplate
.fluctuationPercent
??
576 Constants
.DEFAULT_FLUCTUATION_PERCENT
578 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
579 currentMeasurandValues
.L2
= 0;
580 currentMeasurandValues
.L3
= 0;
582 currentMeasurandValues
.allPhases
= roundTo(
583 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
584 chargingStation
.getNumberOfPhases(),
589 connectorMaximumAmperage
= DCElectricUtils
.amperage(
590 connectorMaximumAvailablePower
,
591 chargingStation
.getVoltageOut()
593 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
594 ? getRandomFloatFluctuatedRounded(
595 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
596 currentSampledValueTemplate
.value
,
597 connectorMaximumAmperage
,
598 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
600 currentSampledValueTemplate
.fluctuationPercent
??
601 Constants
.DEFAULT_FLUCTUATION_PERCENT
603 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
606 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
607 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
609 meterValue
.sampledValue
.push(
610 OCPP16ServiceUtils
.buildSampledValue(
611 currentSampledValueTemplate
,
612 currentMeasurandValues
.allPhases
615 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
617 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
618 connectorMaximumAmperage
||
619 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
620 connectorMinimumAmperage
||
624 `${chargingStation.logPrefix()} MeterValues measurand ${
625 meterValue.sampledValue[sampledValuesIndex].measurand ??
626 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
627 }: connector id ${connectorId}, transaction id ${
628 connector?.transactionId
629 }, value: ${connectorMinimumAmperage}/${
630 meterValue.sampledValue[sampledValuesIndex].value
631 }/${connectorMaximumAmperage}`
636 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
639 const phaseValue
= `L${phase}`;
640 meterValue
.sampledValue
.push(
641 OCPP16ServiceUtils
.buildSampledValue(
642 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
643 currentSampledValueTemplate
,
644 currentMeasurandValues
[phaseValue
] as number,
646 phaseValue
as OCPP16MeterValuePhase
649 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
651 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
652 connectorMaximumAmperage
||
653 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
654 connectorMinimumAmperage
||
658 `${chargingStation.logPrefix()} MeterValues measurand ${
659 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
660 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
662 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
663 }, connector id ${connectorId}, transaction id ${
664 connector?.transactionId
665 }, value: ${connectorMinimumAmperage}/${
666 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
667 }/${connectorMaximumAmperage}`
672 // Energy.Active.Import.Register measurand (default)
673 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
677 if (energySampledValueTemplate
) {
678 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
680 energySampledValueTemplate
.measurand
683 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
684 const connectorMaximumAvailablePower
=
685 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
686 const connectorMaximumEnergyRounded
= roundTo(
687 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
690 const energyValueRounded
= energySampledValueTemplate
.value
691 ? // Cumulate the fluctuated value around the static one
692 getRandomFloatFluctuatedRounded(
693 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
694 energySampledValueTemplate
.value
,
695 connectorMaximumEnergyRounded
,
697 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
698 unitMultiplier
: unitDivider
,
701 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
703 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
704 // Persist previous value on connector
707 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
708 connector
.energyActiveImportRegisterValue
>= 0 &&
709 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
710 connector
.transactionEnergyActiveImportRegisterValue
>= 0
712 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
713 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
715 connector
.energyActiveImportRegisterValue
= 0;
716 connector
.transactionEnergyActiveImportRegisterValue
= 0;
718 meterValue
.sampledValue
.push(
719 OCPP16ServiceUtils
.buildSampledValue(
720 energySampledValueTemplate
,
722 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
728 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
729 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
731 `${chargingStation.logPrefix()} MeterValues measurand ${
732 meterValue.sampledValue[sampledValuesIndex].measurand ??
733 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
734 }: connector id ${connectorId}, transaction id ${
735 connector?.transactionId
736 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${roundTo(
737 interval / (3600 * 1000),
746 public static buildTransactionBeginMeterValue(
747 chargingStation
: ChargingStation
,
750 ): OCPP16MeterValue
{
751 const meterValue
: OCPP16MeterValue
= {
752 timestamp
: new Date(),
755 // Energy.Active.Import.Register measurand (default)
756 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
760 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
761 meterValue
.sampledValue
.push(
762 OCPP16ServiceUtils
.buildSampledValue(
763 sampledValueTemplate
,
764 roundTo((meterStart
?? 0) / unitDivider
, 4),
765 MeterValueContext
.TRANSACTION_BEGIN
771 public static buildTransactionEndMeterValue(
772 chargingStation
: ChargingStation
,
775 ): OCPP16MeterValue
{
776 const meterValue
: OCPP16MeterValue
= {
777 timestamp
: new Date(),
780 // Energy.Active.Import.Register measurand (default)
781 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
785 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
786 meterValue
.sampledValue
.push(
787 OCPP16ServiceUtils
.buildSampledValue(
788 sampledValueTemplate
,
789 roundTo((meterStop
?? 0) / unitDivider
, 4),
790 MeterValueContext
.TRANSACTION_END
796 public static buildTransactionDataMeterValues(
797 transactionBeginMeterValue
: OCPP16MeterValue
,
798 transactionEndMeterValue
: OCPP16MeterValue
799 ): OCPP16MeterValue
[] {
800 const meterValues
: OCPP16MeterValue
[] = [];
801 meterValues
.push(transactionBeginMeterValue
);
802 meterValues
.push(transactionEndMeterValue
);
806 public static setChargingProfile(
807 chargingStation
: ChargingStation
,
809 cp
: OCPP16ChargingProfile
811 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
813 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
815 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
818 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
821 `${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`
823 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
825 let cpReplaced
= false;
826 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
828 .getConnectorStatus(connectorId
)
829 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
831 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
832 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
833 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
835 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
840 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
843 public static parseJsonSchemaFile
<T
extends JsonType
>(
844 relativePath
: string,
847 ): JSONSchemaType
<T
> {
848 return super.parseJsonSchemaFile
<T
>(
850 OCPPVersion
.VERSION_16
,
856 public static async isIdTagAuthorized(
857 chargingStation
: ChargingStation
,
860 ): Promise
<boolean> {
861 let authorized
= false;
862 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
);
863 if (OCPP16ServiceUtils
.isIdTagLocalAuthorized(chargingStation
, idTag
)) {
864 connectorStatus
.localAuthorizeIdTag
= idTag
;
865 connectorStatus
.idTagLocalAuthorized
= true;
867 } else if (chargingStation
.getMustAuthorizeAtRemoteStart() === true) {
868 connectorStatus
.authorizeIdTag
= idTag
;
869 authorized
= await OCPP16ServiceUtils
.isIdTagRemoteAuthorized(chargingStation
, idTag
);
872 `${chargingStation.logPrefix()} The charging station configuration expects authorize at
873 remote start transaction but local authorization or authorize isn't enabled`
879 private static buildSampledValue(
880 sampledValueTemplate
: SampledValueTemplate
,
882 context
?: MeterValueContext
,
883 phase
?: OCPP16MeterValuePhase
884 ): OCPP16SampledValue
{
885 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
886 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
887 const sampledValueLocation
=
888 sampledValueTemplate
?.location
??
889 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
890 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
892 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
893 unit
: sampledValueTemplate
.unit
,
895 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
896 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
897 measurand
: sampledValueTemplate
.measurand
,
899 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
900 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
901 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
905 private static checkMeasurandPowerDivider(
906 chargingStation
: ChargingStation
,
907 measurandType
: OCPP16MeterValueMeasurand
909 if (isUndefined(chargingStation
.powerDivider
)) {
910 const errMsg
= `MeterValues measurand ${
911 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
912 }: powerDivider is undefined`;
913 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
914 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
915 } else if (chargingStation
?.powerDivider
<= 0) {
916 const errMsg
= `MeterValues measurand ${
917 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
918 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
919 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
920 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
924 private static getMeasurandDefaultLocation(
925 measurandType
: OCPP16MeterValueMeasurand
926 ): MeterValueLocation
| undefined {
927 switch (measurandType
) {
928 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
929 return MeterValueLocation
.EV
;
933 private static getMeasurandDefaultUnit(
934 measurandType
: OCPP16MeterValueMeasurand
935 ): MeterValueUnit
| undefined {
936 switch (measurandType
) {
937 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
938 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
939 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
940 return MeterValueUnit
.AMP
;
941 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
942 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
943 return MeterValueUnit
.WATT_HOUR
;
944 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
945 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
946 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
947 return MeterValueUnit
.WATT
;
948 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
949 return MeterValueUnit
.PERCENT
;
950 case OCPP16MeterValueMeasurand
.VOLTAGE
:
951 return MeterValueUnit
.VOLT
;
955 private static isIdTagLocalAuthorized(chargingStation
: ChargingStation
, idTag
: string): boolean {
957 chargingStation
.getLocalAuthListEnabled() === true &&
958 chargingStation
.hasIdTags() === true &&
960 chargingStation
.idTagsCache
961 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
))
962 ?.find((tag
) => tag
=== idTag
)
967 private static async isIdTagRemoteAuthorized(
968 chargingStation
: ChargingStation
,
970 ): Promise
<boolean> {
971 const authorizeResponse
: OCPP16AuthorizeResponse
=
972 await chargingStation
.ocppRequestService
.requestHandler
<
973 OCPP16AuthorizeRequest
,
974 OCPP16AuthorizeResponse
975 >(chargingStation
, OCPP16RequestCommand
.AUTHORIZE
, {
978 return authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
;