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