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';
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 formatDurationMilliSeconds
,
41 getRandomFloatFluctuatedRounded
,
42 getRandomFloatRounded
,
50 } from
'../../../utils';
51 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
53 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
54 public static checkFeatureProfile(
55 chargingStation
: ChargingStation
,
56 featureProfile
: OCPP16SupportedFeatureProfiles
,
57 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
59 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
61 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
62 OCPP16StandardParametersKey.SupportedFeatureProfiles
70 public static buildMeterValue(
71 chargingStation
: ChargingStation
,
73 transactionId
: number,
77 const meterValue
: OCPP16MeterValue
= {
78 timestamp
: new Date(),
81 const connector
= chargingStation
.getConnectorStatus(connectorId
);
83 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
86 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
88 if (socSampledValueTemplate
) {
89 const socMaximumValue
= 100;
90 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
91 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
92 ? getRandomFloatFluctuatedRounded(
93 parseInt(socSampledValueTemplate
.value
),
94 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
96 : getRandomInteger(socMaximumValue
, socMinimumValue
);
97 meterValue
.sampledValue
.push(
98 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
100 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
102 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
103 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
107 `${chargingStation.logPrefix()} MeterValues measurand ${
108 meterValue.sampledValue[sampledValuesIndex].measurand ??
109 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
110 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
111 meterValue.sampledValue[sampledValuesIndex].value
112 }/${socMaximumValue}}`,
117 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
120 OCPP16MeterValueMeasurand
.VOLTAGE
,
122 if (voltageSampledValueTemplate
) {
123 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
124 ? parseInt(voltageSampledValueTemplate
.value
)
125 : chargingStation
.getVoltageOut();
126 const fluctuationPercent
=
127 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
128 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
129 voltageSampledValueTemplateValue
,
133 chargingStation
.getNumberOfPhases() !== 3 ||
134 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
136 meterValue
.sampledValue
.push(
137 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
142 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
145 const phaseLineToNeutralValue
= `L${phase}-N`;
146 const voltagePhaseLineToNeutralSampledValueTemplate
=
147 OCPP16ServiceUtils
.getSampledValueTemplate(
150 OCPP16MeterValueMeasurand
.VOLTAGE
,
151 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
153 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
154 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
155 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
156 voltagePhaseLineToNeutralSampledValueTemplate
.value
157 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
158 : chargingStation
.getVoltageOut();
159 const fluctuationPhaseToNeutralPercent
=
160 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
161 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
162 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
163 voltagePhaseLineToNeutralSampledValueTemplateValue
,
164 fluctuationPhaseToNeutralPercent
,
167 meterValue
.sampledValue
.push(
168 OCPP16ServiceUtils
.buildSampledValue(
169 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
170 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
172 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
175 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
176 const phaseLineToLineValue
= `L${phase}-L${
177 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
178 ? (phase + 1) % chargingStation.getNumberOfPhases()
179 : chargingStation.getNumberOfPhases()
181 const voltagePhaseLineToLineSampledValueTemplate
=
182 OCPP16ServiceUtils
.getSampledValueTemplate(
185 OCPP16MeterValueMeasurand
.VOLTAGE
,
186 phaseLineToLineValue
as OCPP16MeterValuePhase
,
188 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
189 if (voltagePhaseLineToLineSampledValueTemplate
) {
190 const voltagePhaseLineToLineSampledValueTemplateValue
=
191 voltagePhaseLineToLineSampledValueTemplate
.value
192 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
193 : Voltage
.VOLTAGE_400
;
194 const fluctuationPhaseLineToLinePercent
=
195 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
196 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
197 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
198 voltagePhaseLineToLineSampledValueTemplateValue
,
199 fluctuationPhaseLineToLinePercent
,
202 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
206 meterValue
.sampledValue
.push(
207 OCPP16ServiceUtils
.buildSampledValue(
208 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
209 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
211 phaseLineToLineValue
as OCPP16MeterValuePhase
,
217 // Power.Active.Import measurand
218 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
221 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
223 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
224 if (chargingStation
.getNumberOfPhases() === 3) {
225 powerPerPhaseSampledValueTemplates
= {
226 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
229 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
230 OCPP16MeterValuePhase
.L1_N
,
232 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
235 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
236 OCPP16MeterValuePhase
.L2_N
,
238 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
241 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
242 OCPP16MeterValuePhase
.L3_N
,
246 if (powerSampledValueTemplate
) {
247 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
249 powerSampledValueTemplate
.measurand
!,
251 const errMsg
= `MeterValues measurand ${
252 powerSampledValueTemplate.measurand ??
253 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
254 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
255 chargingStation.templateFile
256 }, cannot calculate ${
257 powerSampledValueTemplate.measurand ??
258 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
260 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
261 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
262 const connectorMaximumAvailablePower
=
263 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
264 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
265 const connectorMaximumPowerPerPhase
= Math.round(
266 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
268 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
!) ?? 0;
269 const connectorMinimumPowerPerPhase
= Math.round(
270 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
272 switch (chargingStation
.getCurrentOutType()) {
274 if (chargingStation
.getNumberOfPhases() === 3) {
275 const defaultFluctuatedPowerPerPhase
=
276 powerSampledValueTemplate
.value
&&
277 getRandomFloatFluctuatedRounded(
278 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
279 powerSampledValueTemplate
.value
,
280 connectorMaximumPower
/ unitDivider
,
281 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
282 ) / chargingStation
.getNumberOfPhases(),
283 powerSampledValueTemplate
.fluctuationPercent
??
284 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
286 const phase1FluctuatedValue
=
287 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
288 getRandomFloatFluctuatedRounded(
289 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
290 powerPerPhaseSampledValueTemplates
.L1
.value
,
291 connectorMaximumPowerPerPhase
/ unitDivider
,
292 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
294 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
295 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
297 const phase2FluctuatedValue
=
298 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
299 getRandomFloatFluctuatedRounded(
300 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
301 powerPerPhaseSampledValueTemplates
.L2
.value
,
302 connectorMaximumPowerPerPhase
/ unitDivider
,
303 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
305 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
306 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
308 const phase3FluctuatedValue
=
309 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
310 getRandomFloatFluctuatedRounded(
311 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
312 powerPerPhaseSampledValueTemplates
.L3
.value
,
313 connectorMaximumPowerPerPhase
/ unitDivider
,
314 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
316 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
317 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
319 powerMeasurandValues
.L1
=
320 (phase1FluctuatedValue
as number) ??
321 (defaultFluctuatedPowerPerPhase
as number) ??
322 getRandomFloatRounded(
323 connectorMaximumPowerPerPhase
/ unitDivider
,
324 connectorMinimumPowerPerPhase
/ unitDivider
,
326 powerMeasurandValues
.L2
=
327 (phase2FluctuatedValue
as number) ??
328 (defaultFluctuatedPowerPerPhase
as number) ??
329 getRandomFloatRounded(
330 connectorMaximumPowerPerPhase
/ unitDivider
,
331 connectorMinimumPowerPerPhase
/ unitDivider
,
333 powerMeasurandValues
.L3
=
334 (phase3FluctuatedValue
as number) ??
335 (defaultFluctuatedPowerPerPhase
as number) ??
336 getRandomFloatRounded(
337 connectorMaximumPowerPerPhase
/ unitDivider
,
338 connectorMinimumPowerPerPhase
/ unitDivider
,
341 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
342 ? getRandomFloatFluctuatedRounded(
343 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
344 powerSampledValueTemplate
.value
,
345 connectorMaximumPower
/ unitDivider
,
346 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
348 powerSampledValueTemplate
.fluctuationPercent
??
349 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
351 : getRandomFloatRounded(
352 connectorMaximumPower
/ unitDivider
,
353 connectorMinimumPower
/ unitDivider
,
355 powerMeasurandValues
.L2
= 0;
356 powerMeasurandValues
.L3
= 0;
358 powerMeasurandValues
.allPhases
= roundTo(
359 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
364 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
365 ? getRandomFloatFluctuatedRounded(
366 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
367 powerSampledValueTemplate
.value
,
368 connectorMaximumPower
/ unitDivider
,
369 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
371 powerSampledValueTemplate
.fluctuationPercent
??
372 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
374 : getRandomFloatRounded(
375 connectorMaximumPower
/ unitDivider
,
376 connectorMinimumPower
/ unitDivider
,
380 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
381 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
383 meterValue
.sampledValue
.push(
384 OCPP16ServiceUtils
.buildSampledValue(
385 powerSampledValueTemplate
,
386 powerMeasurandValues
.allPhases
,
389 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
390 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
391 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
393 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
394 connectorMaximumPowerRounded
||
395 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
396 connectorMinimumPowerRounded
||
400 `${chargingStation.logPrefix()} MeterValues measurand ${
401 meterValue.sampledValue[sampledValuesIndex].measurand ??
402 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
403 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
404 meterValue.sampledValue[sampledValuesIndex].value
405 }/${connectorMaximumPowerRounded}`,
410 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
413 const phaseValue
= `L${phase}-N`;
414 meterValue
.sampledValue
.push(
415 OCPP16ServiceUtils
.buildSampledValue(
416 powerPerPhaseSampledValueTemplates
[
417 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
418 ]! ?? powerSampledValueTemplate
,
419 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
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 ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
447 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
448 }/${connectorMaximumPowerPerPhaseRounded}`,
453 // Current.Import measurand
454 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
457 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
459 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
460 if (chargingStation
.getNumberOfPhases() === 3) {
461 currentPerPhaseSampledValueTemplates
= {
462 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
465 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
466 OCPP16MeterValuePhase
.L1
,
468 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
471 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
472 OCPP16MeterValuePhase
.L2
,
474 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
477 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
478 OCPP16MeterValuePhase
.L3
,
482 if (currentSampledValueTemplate
) {
483 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
485 currentSampledValueTemplate
.measurand
!,
487 const errMsg
= `MeterValues measurand ${
488 currentSampledValueTemplate.measurand ??
489 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
490 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
491 chargingStation.templateFile
492 }, cannot calculate ${
493 currentSampledValueTemplate.measurand ??
494 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
496 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
497 const connectorMaximumAvailablePower
=
498 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
499 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
500 let connectorMaximumAmperage
: number;
501 switch (chargingStation
.getCurrentOutType()) {
503 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
504 chargingStation
.getNumberOfPhases(),
505 connectorMaximumAvailablePower
,
506 chargingStation
.getVoltageOut(),
508 if (chargingStation
.getNumberOfPhases() === 3) {
509 const defaultFluctuatedAmperagePerPhase
=
510 currentSampledValueTemplate
.value
&&
511 getRandomFloatFluctuatedRounded(
512 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
513 currentSampledValueTemplate
.value
,
514 connectorMaximumAmperage
,
515 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
517 currentSampledValueTemplate
.fluctuationPercent
??
518 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
520 const phase1FluctuatedValue
=
521 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
522 getRandomFloatFluctuatedRounded(
523 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
524 currentPerPhaseSampledValueTemplates
.L1
.value
,
525 connectorMaximumAmperage
,
526 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
528 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
529 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
531 const phase2FluctuatedValue
=
532 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
533 getRandomFloatFluctuatedRounded(
534 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
535 currentPerPhaseSampledValueTemplates
.L2
.value
,
536 connectorMaximumAmperage
,
537 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
539 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
540 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
542 const phase3FluctuatedValue
=
543 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
544 getRandomFloatFluctuatedRounded(
545 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
546 currentPerPhaseSampledValueTemplates
.L3
.value
,
547 connectorMaximumAmperage
,
548 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
550 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
551 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
553 currentMeasurandValues
.L1
=
554 (phase1FluctuatedValue
as number) ??
555 (defaultFluctuatedAmperagePerPhase
as number) ??
556 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
557 currentMeasurandValues
.L2
=
558 (phase2FluctuatedValue
as number) ??
559 (defaultFluctuatedAmperagePerPhase
as number) ??
560 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
561 currentMeasurandValues
.L3
=
562 (phase3FluctuatedValue
as number) ??
563 (defaultFluctuatedAmperagePerPhase
as number) ??
564 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
566 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
567 ? getRandomFloatFluctuatedRounded(
568 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
569 currentSampledValueTemplate
.value
,
570 connectorMaximumAmperage
,
571 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
573 currentSampledValueTemplate
.fluctuationPercent
??
574 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
576 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
577 currentMeasurandValues
.L2
= 0;
578 currentMeasurandValues
.L3
= 0;
580 currentMeasurandValues
.allPhases
= roundTo(
581 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
582 chargingStation
.getNumberOfPhases(),
587 connectorMaximumAmperage
= DCElectricUtils
.amperage(
588 connectorMaximumAvailablePower
,
589 chargingStation
.getVoltageOut(),
591 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
592 ? getRandomFloatFluctuatedRounded(
593 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
594 currentSampledValueTemplate
.value
,
595 connectorMaximumAmperage
,
596 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
598 currentSampledValueTemplate
.fluctuationPercent
??
599 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
601 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
604 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
605 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
607 meterValue
.sampledValue
.push(
608 OCPP16ServiceUtils
.buildSampledValue(
609 currentSampledValueTemplate
,
610 currentMeasurandValues
.allPhases
,
613 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
615 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
616 connectorMaximumAmperage
||
617 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
618 connectorMinimumAmperage
||
622 `${chargingStation.logPrefix()} MeterValues measurand ${
623 meterValue.sampledValue[sampledValuesIndex].measurand ??
624 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
625 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
626 meterValue.sampledValue[sampledValuesIndex].value
627 }/${connectorMaximumAmperage}`,
632 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
635 const phaseValue
= `L${phase}`;
636 meterValue
.sampledValue
.push(
637 OCPP16ServiceUtils
.buildSampledValue(
638 currentPerPhaseSampledValueTemplates
[
639 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
640 ]! ?? currentSampledValueTemplate
,
641 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
643 phaseValue
as OCPP16MeterValuePhase
,
646 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
648 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
649 connectorMaximumAmperage
||
650 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
651 connectorMinimumAmperage
||
655 `${chargingStation.logPrefix()} MeterValues measurand ${
656 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
657 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
659 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
660 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
661 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
662 }/${connectorMaximumAmperage}`,
667 // Energy.Active.Import.Register measurand (default)
668 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
672 if (energySampledValueTemplate
) {
673 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
675 energySampledValueTemplate
.measurand
!,
678 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
679 const connectorMaximumAvailablePower
=
680 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
681 const connectorMaximumEnergyRounded
= roundTo(
682 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
685 const energyValueRounded
= energySampledValueTemplate
.value
686 ? // Cumulate the fluctuated value around the static one
687 getRandomFloatFluctuatedRounded(
688 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
689 energySampledValueTemplate
.value
,
690 connectorMaximumEnergyRounded
,
692 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
693 unitMultiplier
: unitDivider
,
696 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
698 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
699 // Persist previous value on connector
702 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
703 connector
.energyActiveImportRegisterValue
! >= 0 &&
704 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
705 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
707 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
708 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
710 connector
.energyActiveImportRegisterValue
= 0;
711 connector
.transactionEnergyActiveImportRegisterValue
= 0;
714 meterValue
.sampledValue
.push(
715 OCPP16ServiceUtils
.buildSampledValue(
716 energySampledValueTemplate
,
718 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
724 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
725 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
727 `${chargingStation.logPrefix()} MeterValues measurand ${
728 meterValue.sampledValue[sampledValuesIndex].measurand ??
729 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
730 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${formatDurationMilliSeconds(
732 )}(${roundTo(interval, 4)}ms)`,
739 public static buildTransactionBeginMeterValue(
740 chargingStation
: ChargingStation
,
743 ): OCPP16MeterValue
{
744 const meterValue
: OCPP16MeterValue
= {
745 timestamp
: new Date(),
748 // Energy.Active.Import.Register measurand (default)
749 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
753 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
754 meterValue
.sampledValue
.push(
755 OCPP16ServiceUtils
.buildSampledValue(
756 sampledValueTemplate
!,
757 roundTo((meterStart
?? 0) / unitDivider
, 4),
758 MeterValueContext
.TRANSACTION_BEGIN
,
764 public static buildTransactionEndMeterValue(
765 chargingStation
: ChargingStation
,
768 ): OCPP16MeterValue
{
769 const meterValue
: OCPP16MeterValue
= {
770 timestamp
: new Date(),
773 // Energy.Active.Import.Register measurand (default)
774 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
778 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
779 meterValue
.sampledValue
.push(
780 OCPP16ServiceUtils
.buildSampledValue(
781 sampledValueTemplate
!,
782 roundTo((meterStop
?? 0) / unitDivider
, 4),
783 MeterValueContext
.TRANSACTION_END
,
789 public static buildTransactionDataMeterValues(
790 transactionBeginMeterValue
: OCPP16MeterValue
,
791 transactionEndMeterValue
: OCPP16MeterValue
,
792 ): OCPP16MeterValue
[] {
793 const meterValues
: OCPP16MeterValue
[] = [];
794 meterValues
.push(transactionBeginMeterValue
);
795 meterValues
.push(transactionEndMeterValue
);
799 public static setChargingProfile(
800 chargingStation
: ChargingStation
,
802 cp
: OCPP16ChargingProfile
,
804 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
806 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
808 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
811 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
814 `${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`,
816 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
818 let cpReplaced
= false;
819 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
821 .getConnectorStatus(connectorId
)
822 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
824 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
825 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
826 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
828 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
833 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
836 public static clearChargingProfiles
= (
837 chargingStation
: ChargingStation
,
838 commandPayload
: ClearChargingProfileRequest
,
839 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
841 let clearedCP
= false;
842 if (isNotEmptyArray(chargingProfiles
)) {
843 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
844 let clearCurrentCP
= false;
845 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
846 clearCurrentCP
= true;
849 !commandPayload
.chargingProfilePurpose
&&
850 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
852 clearCurrentCP
= true;
855 !chargingProfile
.stackLevel
&&
856 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
858 clearCurrentCP
= true;
861 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
862 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
864 clearCurrentCP
= true;
866 if (clearCurrentCP
) {
867 chargingProfiles
.splice(index
, 1);
869 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
879 public static parseJsonSchemaFile
<T
extends JsonType
>(
880 relativePath
: string,
883 ): JSONSchemaType
<T
> {
884 return super.parseJsonSchemaFile
<T
>(
886 OCPPVersion
.VERSION_16
,
892 public static async isIdTagAuthorized(
893 chargingStation
: ChargingStation
,
896 ): Promise
<boolean> {
897 let authorized
= false;
898 const connectorStatus
: ConnectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
899 if (OCPP16ServiceUtils
.isIdTagLocalAuthorized(chargingStation
, idTag
)) {
900 connectorStatus
.localAuthorizeIdTag
= idTag
;
901 connectorStatus
.idTagLocalAuthorized
= true;
903 } else if (chargingStation
.getMustAuthorizeAtRemoteStart() === true) {
904 connectorStatus
.authorizeIdTag
= idTag
;
905 authorized
= await OCPP16ServiceUtils
.isIdTagRemoteAuthorized(chargingStation
, idTag
);
908 `${chargingStation.logPrefix()} The charging station configuration expects authorize at
909 remote start transaction but local authorization or authorize isn't enabled`,
915 private static buildSampledValue(
916 sampledValueTemplate
: SampledValueTemplate
,
918 context
?: MeterValueContext
,
919 phase
?: OCPP16MeterValuePhase
,
920 ): OCPP16SampledValue
{
921 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
922 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
923 const sampledValueLocation
=
924 sampledValueTemplate
?.location
??
925 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
926 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
928 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
929 unit
: sampledValueTemplate
.unit
,
931 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
932 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
933 measurand
: sampledValueTemplate
.measurand
,
935 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
936 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
937 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
938 } as OCPP16SampledValue
;
941 private static checkMeasurandPowerDivider(
942 chargingStation
: ChargingStation
,
943 measurandType
: OCPP16MeterValueMeasurand
,
945 if (isUndefined(chargingStation
.powerDivider
)) {
946 const errMsg
= `MeterValues measurand ${
947 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
948 }: powerDivider is undefined`;
949 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
950 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
951 } else if (chargingStation
?.powerDivider
<= 0) {
952 const errMsg
= `MeterValues measurand ${
953 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
954 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
955 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
956 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
960 private static getMeasurandDefaultLocation(
961 measurandType
: OCPP16MeterValueMeasurand
,
962 ): MeterValueLocation
| undefined {
963 switch (measurandType
) {
964 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
965 return MeterValueLocation
.EV
;
969 private static getMeasurandDefaultUnit(
970 measurandType
: OCPP16MeterValueMeasurand
,
971 ): MeterValueUnit
| undefined {
972 switch (measurandType
) {
973 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
974 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
975 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
976 return MeterValueUnit
.AMP
;
977 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
978 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
979 return MeterValueUnit
.WATT_HOUR
;
980 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
981 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
982 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
983 return MeterValueUnit
.WATT
;
984 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
985 return MeterValueUnit
.PERCENT
;
986 case OCPP16MeterValueMeasurand
.VOLTAGE
:
987 return MeterValueUnit
.VOLT
;
991 private static isIdTagLocalAuthorized(chargingStation
: ChargingStation
, idTag
: string): boolean {
993 chargingStation
.getLocalAuthListEnabled() === true &&
994 chargingStation
.hasIdTags() === true &&
996 chargingStation
.idTagsCache
997 .getIdTags(getIdTagsFile(chargingStation
.stationInfo
)!)
998 ?.find((tag
) => tag
=== idTag
),
1003 private static async isIdTagRemoteAuthorized(
1004 chargingStation
: ChargingStation
,
1006 ): Promise
<boolean> {
1007 const authorizeResponse
: OCPP16AuthorizeResponse
=
1008 await chargingStation
.ocppRequestService
.requestHandler
<
1009 OCPP16AuthorizeRequest
,
1010 OCPP16AuthorizeResponse
1011 >(chargingStation
, OCPP16RequestCommand
.AUTHORIZE
, {
1014 return authorizeResponse
?.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
;