1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
5 import { type ChargingStation
, getIdTagsFile
, hasFeatureProfile
} from
'../../../charging-station';
6 import { OCPPError
} from
'../../../exception';
8 type ClearChargingProfileRequest
,
13 type MeasurandPerPhaseSampledValueTemplates
,
18 OCPP16AuthorizationStatus
,
19 type OCPP16AuthorizeRequest
,
20 type OCPP16AuthorizeResponse
,
21 type OCPP16ChargingProfile
,
22 type OCPP16IncomingRequestCommand
,
23 type OCPP16MeterValue
,
24 OCPP16MeterValueMeasurand
,
25 OCPP16MeterValuePhase
,
27 type OCPP16SampledValue
,
28 OCPP16StandardParametersKey
,
29 type OCPP16SupportedFeatureProfiles
,
31 type SampledValueTemplate
,
33 } from
'../../../types';
40 getRandomFloatFluctuatedRounded
,
41 getRandomFloatRounded
,
49 } from
'../../../utils';
50 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
52 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
53 public static checkFeatureProfile(
54 chargingStation
: ChargingStation
,
55 featureProfile
: OCPP16SupportedFeatureProfiles
,
56 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
58 if (!hasFeatureProfile(chargingStation
, featureProfile
)) {
60 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
61 OCPP16StandardParametersKey.SupportedFeatureProfiles
69 public static buildMeterValue(
70 chargingStation
: ChargingStation
,
72 transactionId
: number,
76 const meterValue
: OCPP16MeterValue
= {
77 timestamp
: new Date(),
80 const connector
= chargingStation
.getConnectorStatus(connectorId
);
82 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
85 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
87 if (socSampledValueTemplate
) {
88 const socMaximumValue
= 100;
89 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
90 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
91 ? getRandomFloatFluctuatedRounded(
92 parseInt(socSampledValueTemplate
.value
),
93 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
95 : getRandomInteger(socMaximumValue
, socMinimumValue
);
96 meterValue
.sampledValue
.push(
97 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
99 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
101 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
102 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
106 `${chargingStation.logPrefix()} MeterValues measurand ${
107 meterValue.sampledValue[sampledValuesIndex].measurand ??
108 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
109 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, 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 | undefined;
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 | undefined;
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
: MeasurandValues
= {} 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
as number) ??
320 (defaultFluctuatedPowerPerPhase
as number) ??
321 getRandomFloatRounded(
322 connectorMaximumPowerPerPhase
/ unitDivider
,
323 connectorMinimumPowerPerPhase
/ unitDivider
,
325 powerMeasurandValues
.L2
=
326 (phase2FluctuatedValue
as number) ??
327 (defaultFluctuatedPowerPerPhase
as number) ??
328 getRandomFloatRounded(
329 connectorMaximumPowerPerPhase
/ unitDivider
,
330 connectorMinimumPowerPerPhase
/ unitDivider
,
332 powerMeasurandValues
.L3
=
333 (phase3FluctuatedValue
as number) ??
334 (defaultFluctuatedPowerPerPhase
as number) ??
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 ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
403 meterValue.sampledValue[sampledValuesIndex].value
404 }/${connectorMaximumPowerRounded}`,
409 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
412 const phaseValue
= `L${phase}-N`;
413 meterValue
.sampledValue
.push(
414 OCPP16ServiceUtils
.buildSampledValue(
415 powerPerPhaseSampledValueTemplates
[
416 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
417 ]! ?? powerSampledValueTemplate
,
418 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
420 phaseValue
as OCPP16MeterValuePhase
,
423 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
424 const connectorMaximumPowerPerPhaseRounded
= roundTo(
425 connectorMaximumPowerPerPhase
/ unitDivider
,
428 const connectorMinimumPowerPerPhaseRounded
= roundTo(
429 connectorMinimumPowerPerPhase
/ unitDivider
,
433 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
434 connectorMaximumPowerPerPhaseRounded
||
435 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
436 connectorMinimumPowerPerPhaseRounded
||
440 `${chargingStation.logPrefix()} MeterValues measurand ${
441 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
442 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
444 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
445 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
446 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
447 }/${connectorMaximumPowerPerPhaseRounded}`,
452 // Current.Import measurand
453 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
456 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
458 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
459 if (chargingStation
.getNumberOfPhases() === 3) {
460 currentPerPhaseSampledValueTemplates
= {
461 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
464 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
465 OCPP16MeterValuePhase
.L1
,
467 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
470 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
471 OCPP16MeterValuePhase
.L2
,
473 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
476 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
477 OCPP16MeterValuePhase
.L3
,
481 if (currentSampledValueTemplate
) {
482 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
484 currentSampledValueTemplate
.measurand
!,
486 const errMsg
= `MeterValues measurand ${
487 currentSampledValueTemplate.measurand ??
488 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
489 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
490 chargingStation.templateFile
491 }, cannot calculate ${
492 currentSampledValueTemplate.measurand ??
493 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
495 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
496 const connectorMaximumAvailablePower
=
497 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
498 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
499 let connectorMaximumAmperage
: number;
500 switch (chargingStation
.getCurrentOutType()) {
502 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
503 chargingStation
.getNumberOfPhases(),
504 connectorMaximumAvailablePower
,
505 chargingStation
.getVoltageOut(),
507 if (chargingStation
.getNumberOfPhases() === 3) {
508 const defaultFluctuatedAmperagePerPhase
=
509 currentSampledValueTemplate
.value
&&
510 getRandomFloatFluctuatedRounded(
511 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
512 currentSampledValueTemplate
.value
,
513 connectorMaximumAmperage
,
514 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
516 currentSampledValueTemplate
.fluctuationPercent
??
517 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
519 const phase1FluctuatedValue
=
520 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
521 getRandomFloatFluctuatedRounded(
522 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
523 currentPerPhaseSampledValueTemplates
.L1
.value
,
524 connectorMaximumAmperage
,
525 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
527 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
528 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
530 const phase2FluctuatedValue
=
531 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
532 getRandomFloatFluctuatedRounded(
533 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
534 currentPerPhaseSampledValueTemplates
.L2
.value
,
535 connectorMaximumAmperage
,
536 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
538 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
539 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
541 const phase3FluctuatedValue
=
542 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
543 getRandomFloatFluctuatedRounded(
544 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
545 currentPerPhaseSampledValueTemplates
.L3
.value
,
546 connectorMaximumAmperage
,
547 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
549 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
550 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
552 currentMeasurandValues
.L1
=
553 (phase1FluctuatedValue
as number) ??
554 (defaultFluctuatedAmperagePerPhase
as number) ??
555 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
556 currentMeasurandValues
.L2
=
557 (phase2FluctuatedValue
as number) ??
558 (defaultFluctuatedAmperagePerPhase
as number) ??
559 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
560 currentMeasurandValues
.L3
=
561 (phase3FluctuatedValue
as number) ??
562 (defaultFluctuatedAmperagePerPhase
as number) ??
563 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
565 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
566 ? getRandomFloatFluctuatedRounded(
567 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
568 currentSampledValueTemplate
.value
,
569 connectorMaximumAmperage
,
570 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
572 currentSampledValueTemplate
.fluctuationPercent
??
573 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
575 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
576 currentMeasurandValues
.L2
= 0;
577 currentMeasurandValues
.L3
= 0;
579 currentMeasurandValues
.allPhases
= roundTo(
580 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
581 chargingStation
.getNumberOfPhases(),
586 connectorMaximumAmperage
= DCElectricUtils
.amperage(
587 connectorMaximumAvailablePower
,
588 chargingStation
.getVoltageOut(),
590 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
591 ? getRandomFloatFluctuatedRounded(
592 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
593 currentSampledValueTemplate
.value
,
594 connectorMaximumAmperage
,
595 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
597 currentSampledValueTemplate
.fluctuationPercent
??
598 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
600 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
603 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
604 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
606 meterValue
.sampledValue
.push(
607 OCPP16ServiceUtils
.buildSampledValue(
608 currentSampledValueTemplate
,
609 currentMeasurandValues
.allPhases
,
612 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
614 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
615 connectorMaximumAmperage
||
616 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
617 connectorMinimumAmperage
||
621 `${chargingStation.logPrefix()} MeterValues measurand ${
622 meterValue.sampledValue[sampledValuesIndex].measurand ??
623 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
624 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
625 meterValue.sampledValue[sampledValuesIndex].value
626 }/${connectorMaximumAmperage}`,
631 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
634 const phaseValue
= `L${phase}`;
635 meterValue
.sampledValue
.push(
636 OCPP16ServiceUtils
.buildSampledValue(
637 currentPerPhaseSampledValueTemplates
[
638 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
639 ]! ?? currentSampledValueTemplate
,
640 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
642 phaseValue
as OCPP16MeterValuePhase
,
645 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
647 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
648 connectorMaximumAmperage
||
649 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
650 connectorMinimumAmperage
||
654 `${chargingStation.logPrefix()} MeterValues measurand ${
655 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
656 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
658 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
659 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
660 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
661 }/${connectorMaximumAmperage}`,
666 // Energy.Active.Import.Register measurand (default)
667 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
671 if (energySampledValueTemplate
) {
672 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
674 energySampledValueTemplate
.measurand
!,
677 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
678 const connectorMaximumAvailablePower
=
679 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
680 const connectorMaximumEnergyRounded
= roundTo(
681 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
684 const energyValueRounded
= energySampledValueTemplate
.value
685 ? // Cumulate the fluctuated value around the static one
686 getRandomFloatFluctuatedRounded(
687 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
688 energySampledValueTemplate
.value
,
689 connectorMaximumEnergyRounded
,
691 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
692 unitMultiplier
: unitDivider
,
695 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
697 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
698 // Persist previous value on connector
701 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
702 connector
.energyActiveImportRegisterValue
! >= 0 &&
703 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
704 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
706 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
707 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
709 connector
.energyActiveImportRegisterValue
= 0;
710 connector
.transactionEnergyActiveImportRegisterValue
= 0;
713 meterValue
.sampledValue
.push(
714 OCPP16ServiceUtils
.buildSampledValue(
715 energySampledValueTemplate
,
717 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
723 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
724 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
726 `${chargingStation.logPrefix()} MeterValues measurand ${
727 meterValue.sampledValue[sampledValuesIndex].measurand ??
728 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
729 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
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 clearChargingProfiles
= (
834 chargingStation
: ChargingStation
,
835 commandPayload
: ClearChargingProfileRequest
,
836 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
838 let clearedCP
= false;
839 if (isNotEmptyArray(chargingProfiles
)) {
840 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
841 let clearCurrentCP
= false;
842 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
843 clearCurrentCP
= true;
846 !commandPayload
.chargingProfilePurpose
&&
847 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
849 clearCurrentCP
= true;
852 !chargingProfile
.stackLevel
&&
853 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
855 clearCurrentCP
= true;
858 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
859 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
861 clearCurrentCP
= true;
863 if (clearCurrentCP
) {
864 chargingProfiles
.splice(index
, 1);
866 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
876 public static parseJsonSchemaFile
<T
extends JsonType
>(
877 relativePath
: string,
880 ): JSONSchemaType
<T
> {
881 return super.parseJsonSchemaFile
<T
>(
883 OCPPVersion
.VERSION_16
,
889 public static async isIdTagAuthorized(
890 chargingStation
: ChargingStation
,
893 ): Promise
<boolean> {
894 let authorized
= false;
895 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
896 if (OCPP16ServiceUtils
.isIdTagLocalAuthorized(chargingStation
, idTag
)) {
897 connectorStatus
.localAuthorizeIdTag
= idTag
;
898 connectorStatus
.idTagLocalAuthorized
= true;
901 authorized
= await OCPP16ServiceUtils
.isIdTagRemoteAuthorized(chargingStation
, idTag
);
902 if (authorized
&& isNullOrUndefined(connectorStatus
.authorizeIdTag
)) {
904 `${chargingStation.logPrefix()} IdTag ${idTag} is not set as authorized remotely, applying deferred initialization`,
906 connectorStatus
.authorizeIdTag
= idTag
;
912 private static buildSampledValue(
913 sampledValueTemplate
: SampledValueTemplate
,
915 context
?: MeterValueContext
,
916 phase
?: OCPP16MeterValuePhase
,
917 ): OCPP16SampledValue
{
918 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
919 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
920 const sampledValueLocation
=
921 sampledValueTemplate
?.location
??
922 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
923 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
925 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
926 unit
: sampledValueTemplate
.unit
,
928 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
929 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
930 measurand
: sampledValueTemplate
.measurand
,
932 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
933 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
934 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
935 } as OCPP16SampledValue
;
938 private static checkMeasurandPowerDivider(
939 chargingStation
: ChargingStation
,
940 measurandType
: OCPP16MeterValueMeasurand
,
942 if (isUndefined(chargingStation
.powerDivider
)) {
943 const errMsg
= `MeterValues measurand ${
944 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
945 }: powerDivider is undefined`;
946 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
947 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
948 } else if (chargingStation
?.powerDivider
<= 0) {
949 const errMsg
= `MeterValues measurand ${
950 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
951 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
952 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
953 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
957 private static getMeasurandDefaultLocation(
958 measurandType
: OCPP16MeterValueMeasurand
,
959 ): MeterValueLocation
| undefined {
960 switch (measurandType
) {
961 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
962 return MeterValueLocation
.EV
;
966 private static getMeasurandDefaultUnit(
967 measurandType
: OCPP16MeterValueMeasurand
,
968 ): MeterValueUnit
| undefined {
969 switch (measurandType
) {
970 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
971 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
972 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
973 return MeterValueUnit
.AMP
;
974 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
975 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
976 return MeterValueUnit
.WATT_HOUR
;
977 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
978 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
979 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
980 return MeterValueUnit
.WATT
;
981 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
982 return MeterValueUnit
.PERCENT
;
983 case OCPP16MeterValueMeasurand
.VOLTAGE
:
984 return MeterValueUnit
.VOLT
;
988 private static isIdTagLocalAuthorized(chargingStation
: ChargingStation
, idTag
: string): boolean {
990 chargingStation
.getLocalAuthListEnabled() === true &&
991 chargingStation
.hasIdTags() === true &&
993 chargingStation
.idTagsCache
994 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
995 ?.find((tag
) => tag
=== idTag
),
1000 private static async isIdTagRemoteAuthorized(
1001 chargingStation
: ChargingStation
,
1003 ): Promise
<boolean> {
1004 const authorizeResponse
: OCPP16AuthorizeResponse
=
1005 await chargingStation
.ocppRequestService
.requestHandler
<
1006 OCPP16AuthorizeRequest
,
1007 OCPP16AuthorizeResponse
1008 >(chargingStation
, OCPP16RequestCommand
.AUTHORIZE
, {
1011 return authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
;