1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import fs from
'node:fs';
4 import path from
'node:path';
5 import { fileURLToPath
} from
'node:url';
7 import type { JSONSchemaType
} from
'ajv';
9 import OCPPError from
'../../../exception/OCPPError';
10 import { CurrentType
, Voltage
} from
'../../../types/ChargingStationTemplate';
11 import { FileType
} from
'../../../types/FileType';
12 import type { JsonType
} from
'../../../types/JsonType';
14 MeasurandPerPhaseSampledValueTemplates
,
16 } from
'../../../types/MeasurandPerPhaseSampledValueTemplates';
17 import type { MeasurandValues
} from
'../../../types/MeasurandValues';
18 import type { OCPP16ChargingProfile
} from
'../../../types/ocpp/1.6/ChargingProfile';
20 OCPP16StandardParametersKey
,
21 OCPP16SupportedFeatureProfiles
,
22 } from
'../../../types/ocpp/1.6/Configuration';
27 type OCPP16MeterValue
,
28 OCPP16MeterValueMeasurand
,
29 OCPP16MeterValuePhase
,
30 type OCPP16SampledValue
,
31 } from
'../../../types/ocpp/1.6/MeterValues';
33 type OCPP16IncomingRequestCommand
,
35 } from
'../../../types/ocpp/1.6/Requests';
36 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
37 import { OCPPVersion
} from
'../../../types/ocpp/OCPPVersion';
38 import Constants from
'../../../utils/Constants';
39 import { ACElectricUtils
, DCElectricUtils
} from
'../../../utils/ElectricUtils';
40 import FileUtils from
'../../../utils/FileUtils';
41 import logger from
'../../../utils/Logger';
42 import Utils from
'../../../utils/Utils';
43 import type ChargingStation from
'../../ChargingStation';
44 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
46 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
47 public static checkFeatureProfile(
48 chargingStation
: ChargingStation
,
49 featureProfile
: OCPP16SupportedFeatureProfiles
,
50 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
52 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
54 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
55 OCPP16StandardParametersKey.SupportedFeatureProfiles
63 public static buildMeterValue(
64 chargingStation
: ChargingStation
,
66 transactionId
: number,
70 const meterValue
: OCPP16MeterValue
= {
71 timestamp
: new Date(),
74 const connector
= chargingStation
.getConnectorStatus(connectorId
);
76 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
79 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
81 if (socSampledValueTemplate
) {
82 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
83 ? Utils
.getRandomFloatFluctuatedRounded(
84 parseInt(socSampledValueTemplate
.value
),
85 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
87 : Utils
.getRandomInteger(100);
88 meterValue
.sampledValue
.push(
89 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
91 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
92 if (Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > 100 || debug
) {
94 `${chargingStation.logPrefix()} MeterValues measurand ${
95 meterValue.sampledValue[sampledValuesIndex].measurand ??
96 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
97 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
98 meterValue.sampledValue[sampledValuesIndex].value
104 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
107 OCPP16MeterValueMeasurand
.VOLTAGE
109 if (voltageSampledValueTemplate
) {
110 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
111 ? parseInt(voltageSampledValueTemplate
.value
)
112 : chargingStation
.getVoltageOut();
113 const fluctuationPercent
=
114 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
115 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
116 voltageSampledValueTemplateValue
,
120 chargingStation
.getNumberOfPhases() !== 3 ||
121 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
123 meterValue
.sampledValue
.push(
124 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
129 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
132 const phaseLineToNeutralValue
= `L${phase}-N`;
133 const voltagePhaseLineToNeutralSampledValueTemplate
=
134 OCPP16ServiceUtils
.getSampledValueTemplate(
137 OCPP16MeterValueMeasurand
.VOLTAGE
,
138 phaseLineToNeutralValue
as OCPP16MeterValuePhase
140 let voltagePhaseLineToNeutralMeasurandValue
: number;
141 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
142 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
143 voltagePhaseLineToNeutralSampledValueTemplate
.value
144 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
145 : chargingStation
.getVoltageOut();
146 const fluctuationPhaseToNeutralPercent
=
147 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
148 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
149 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
150 voltagePhaseLineToNeutralSampledValueTemplateValue
,
151 fluctuationPhaseToNeutralPercent
154 meterValue
.sampledValue
.push(
155 OCPP16ServiceUtils
.buildSampledValue(
156 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
157 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
159 phaseLineToNeutralValue
as OCPP16MeterValuePhase
162 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
163 const phaseLineToLineValue
= `L${phase}-L${
164 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
165 ? (phase + 1) % chargingStation.getNumberOfPhases()
166 : chargingStation.getNumberOfPhases()
168 const voltagePhaseLineToLineSampledValueTemplate
=
169 OCPP16ServiceUtils
.getSampledValueTemplate(
172 OCPP16MeterValueMeasurand
.VOLTAGE
,
173 phaseLineToLineValue
as OCPP16MeterValuePhase
175 let voltagePhaseLineToLineMeasurandValue
: number;
176 if (voltagePhaseLineToLineSampledValueTemplate
) {
177 const voltagePhaseLineToLineSampledValueTemplateValue
=
178 voltagePhaseLineToLineSampledValueTemplate
.value
179 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
180 : Voltage
.VOLTAGE_400
;
181 const fluctuationPhaseLineToLinePercent
=
182 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
183 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
184 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
185 voltagePhaseLineToLineSampledValueTemplateValue
,
186 fluctuationPhaseLineToLinePercent
189 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
193 meterValue
.sampledValue
.push(
194 OCPP16ServiceUtils
.buildSampledValue(
195 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
196 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
198 phaseLineToLineValue
as OCPP16MeterValuePhase
204 // Power.Active.Import measurand
205 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
208 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
210 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
211 if (chargingStation
.getNumberOfPhases() === 3) {
212 powerPerPhaseSampledValueTemplates
= {
213 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
216 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
217 OCPP16MeterValuePhase
.L1_N
219 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
222 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
223 OCPP16MeterValuePhase
.L2_N
225 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
228 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
229 OCPP16MeterValuePhase
.L3_N
233 if (powerSampledValueTemplate
) {
234 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
236 powerSampledValueTemplate
.measurand
238 const errMsg
= `MeterValues measurand ${
239 powerSampledValueTemplate.measurand ??
240 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
241 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
242 chargingStation.templateFile
243 }, cannot calculate ${
244 powerSampledValueTemplate.measurand ??
245 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
247 const powerMeasurandValues
= {} as MeasurandValues
;
248 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
249 const connectorMaximumAvailablePower
=
250 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
251 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
252 const connectorMaximumPowerPerPhase
= Math.round(
253 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
255 switch (chargingStation
.getCurrentOutType()) {
257 if (chargingStation
.getNumberOfPhases() === 3) {
258 const defaultFluctuatedPowerPerPhase
=
259 powerSampledValueTemplate
.value
&&
260 Utils
.getRandomFloatFluctuatedRounded(
261 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
262 powerSampledValueTemplate
.value
,
263 connectorMaximumPower
/ unitDivider
,
264 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
265 ) / chargingStation
.getNumberOfPhases(),
266 powerSampledValueTemplate
.fluctuationPercent
??
267 Constants
.DEFAULT_FLUCTUATION_PERCENT
269 const phase1FluctuatedValue
=
270 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
271 Utils
.getRandomFloatFluctuatedRounded(
272 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
273 powerPerPhaseSampledValueTemplates
.L1
.value
,
274 connectorMaximumPowerPerPhase
/ unitDivider
,
275 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
277 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
278 Constants
.DEFAULT_FLUCTUATION_PERCENT
280 const phase2FluctuatedValue
=
281 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
282 Utils
.getRandomFloatFluctuatedRounded(
283 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
284 powerPerPhaseSampledValueTemplates
.L2
.value
,
285 connectorMaximumPowerPerPhase
/ unitDivider
,
286 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
288 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
289 Constants
.DEFAULT_FLUCTUATION_PERCENT
291 const phase3FluctuatedValue
=
292 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
293 Utils
.getRandomFloatFluctuatedRounded(
294 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
295 powerPerPhaseSampledValueTemplates
.L3
.value
,
296 connectorMaximumPowerPerPhase
/ unitDivider
,
297 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
299 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
300 Constants
.DEFAULT_FLUCTUATION_PERCENT
302 powerMeasurandValues
.L1
=
303 phase1FluctuatedValue
??
304 defaultFluctuatedPowerPerPhase
??
305 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
306 powerMeasurandValues
.L2
=
307 phase2FluctuatedValue
??
308 defaultFluctuatedPowerPerPhase
??
309 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
310 powerMeasurandValues
.L3
=
311 phase3FluctuatedValue
??
312 defaultFluctuatedPowerPerPhase
??
313 Utils
.getRandomFloatRounded(connectorMaximumPowerPerPhase
/ unitDivider
);
315 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
316 ? Utils
.getRandomFloatFluctuatedRounded(
317 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
318 powerSampledValueTemplate
.value
,
319 connectorMaximumPower
/ unitDivider
,
320 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
322 powerSampledValueTemplate
.fluctuationPercent
??
323 Constants
.DEFAULT_FLUCTUATION_PERCENT
325 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
326 powerMeasurandValues
.L2
= 0;
327 powerMeasurandValues
.L3
= 0;
329 powerMeasurandValues
.allPhases
= Utils
.roundTo(
330 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
335 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
336 ? Utils
.getRandomFloatFluctuatedRounded(
337 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
338 powerSampledValueTemplate
.value
,
339 connectorMaximumPower
/ unitDivider
,
340 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
342 powerSampledValueTemplate
.fluctuationPercent
??
343 Constants
.DEFAULT_FLUCTUATION_PERCENT
345 : Utils
.getRandomFloatRounded(connectorMaximumPower
/ unitDivider
);
348 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
349 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
351 meterValue
.sampledValue
.push(
352 OCPP16ServiceUtils
.buildSampledValue(
353 powerSampledValueTemplate
,
354 powerMeasurandValues
.allPhases
357 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
358 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
360 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
361 connectorMaximumPowerRounded
||
365 `${chargingStation.logPrefix()} MeterValues measurand ${
366 meterValue.sampledValue[sampledValuesIndex].measurand ??
367 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
368 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
369 meterValue.sampledValue[sampledValuesIndex].value
370 }/${connectorMaximumPowerRounded}`
375 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
378 const phaseValue
= `L${phase}-N`;
379 meterValue
.sampledValue
.push(
380 OCPP16ServiceUtils
.buildSampledValue(
381 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
382 powerSampledValueTemplate
,
383 powerMeasurandValues
[`L${phase}`] as number,
385 phaseValue
as OCPP16MeterValuePhase
388 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
389 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
390 connectorMaximumPowerPerPhase
/ unitDivider
,
394 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
395 connectorMaximumPowerPerPhaseRounded
||
399 `${chargingStation.logPrefix()} MeterValues measurand ${
400 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
401 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
403 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
404 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
405 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
406 }/${connectorMaximumPowerPerPhaseRounded}`
411 // Current.Import measurand
412 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
415 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
417 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
418 if (chargingStation
.getNumberOfPhases() === 3) {
419 currentPerPhaseSampledValueTemplates
= {
420 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
423 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
424 OCPP16MeterValuePhase
.L1
426 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
429 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
430 OCPP16MeterValuePhase
.L2
432 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
435 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
436 OCPP16MeterValuePhase
.L3
440 if (currentSampledValueTemplate
) {
441 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
443 currentSampledValueTemplate
.measurand
445 const errMsg
= `MeterValues measurand ${
446 currentSampledValueTemplate.measurand ??
447 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
448 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
449 chargingStation.templateFile
450 }, cannot calculate ${
451 currentSampledValueTemplate.measurand ??
452 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
454 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
455 const connectorMaximumAvailablePower
=
456 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
457 let connectorMaximumAmperage
: number;
458 switch (chargingStation
.getCurrentOutType()) {
460 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
461 chargingStation
.getNumberOfPhases(),
462 connectorMaximumAvailablePower
,
463 chargingStation
.getVoltageOut()
465 if (chargingStation
.getNumberOfPhases() === 3) {
466 const defaultFluctuatedAmperagePerPhase
=
467 currentSampledValueTemplate
.value
&&
468 Utils
.getRandomFloatFluctuatedRounded(
469 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
470 currentSampledValueTemplate
.value
,
471 connectorMaximumAmperage
,
472 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
474 currentSampledValueTemplate
.fluctuationPercent
??
475 Constants
.DEFAULT_FLUCTUATION_PERCENT
477 const phase1FluctuatedValue
=
478 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
479 Utils
.getRandomFloatFluctuatedRounded(
480 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
481 currentPerPhaseSampledValueTemplates
.L1
.value
,
482 connectorMaximumAmperage
,
483 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
485 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
486 Constants
.DEFAULT_FLUCTUATION_PERCENT
488 const phase2FluctuatedValue
=
489 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
490 Utils
.getRandomFloatFluctuatedRounded(
491 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
492 currentPerPhaseSampledValueTemplates
.L2
.value
,
493 connectorMaximumAmperage
,
494 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
496 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
497 Constants
.DEFAULT_FLUCTUATION_PERCENT
499 const phase3FluctuatedValue
=
500 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
501 Utils
.getRandomFloatFluctuatedRounded(
502 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
503 currentPerPhaseSampledValueTemplates
.L3
.value
,
504 connectorMaximumAmperage
,
505 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
507 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
508 Constants
.DEFAULT_FLUCTUATION_PERCENT
510 currentMeasurandValues
.L1
=
511 phase1FluctuatedValue
??
512 defaultFluctuatedAmperagePerPhase
??
513 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
514 currentMeasurandValues
.L2
=
515 phase2FluctuatedValue
??
516 defaultFluctuatedAmperagePerPhase
??
517 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
518 currentMeasurandValues
.L3
=
519 phase3FluctuatedValue
??
520 defaultFluctuatedAmperagePerPhase
??
521 Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
523 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
524 ? Utils
.getRandomFloatFluctuatedRounded(
525 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
526 currentSampledValueTemplate
.value
,
527 connectorMaximumAmperage
,
528 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
530 currentSampledValueTemplate
.fluctuationPercent
??
531 Constants
.DEFAULT_FLUCTUATION_PERCENT
533 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
534 currentMeasurandValues
.L2
= 0;
535 currentMeasurandValues
.L3
= 0;
537 currentMeasurandValues
.allPhases
= Utils
.roundTo(
538 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
539 chargingStation
.getNumberOfPhases(),
544 connectorMaximumAmperage
= DCElectricUtils
.amperage(
545 connectorMaximumAvailablePower
,
546 chargingStation
.getVoltageOut()
548 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
549 ? Utils
.getRandomFloatFluctuatedRounded(
550 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
551 currentSampledValueTemplate
.value
,
552 connectorMaximumAmperage
,
553 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
555 currentSampledValueTemplate
.fluctuationPercent
??
556 Constants
.DEFAULT_FLUCTUATION_PERCENT
558 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
);
561 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
562 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
564 meterValue
.sampledValue
.push(
565 OCPP16ServiceUtils
.buildSampledValue(
566 currentSampledValueTemplate
,
567 currentMeasurandValues
.allPhases
570 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
572 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
573 connectorMaximumAmperage
||
577 `${chargingStation.logPrefix()} MeterValues measurand ${
578 meterValue.sampledValue[sampledValuesIndex].measurand ??
579 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
580 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
581 meterValue.sampledValue[sampledValuesIndex].value
582 }/${connectorMaximumAmperage}`
587 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
590 const phaseValue
= `L${phase}`;
591 meterValue
.sampledValue
.push(
592 OCPP16ServiceUtils
.buildSampledValue(
593 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
594 currentSampledValueTemplate
,
595 currentMeasurandValues
[phaseValue
] as number,
597 phaseValue
as OCPP16MeterValuePhase
600 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
602 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
603 connectorMaximumAmperage
||
607 `${chargingStation.logPrefix()} MeterValues measurand ${
608 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
609 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
611 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
612 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
613 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
614 }/${connectorMaximumAmperage}`
619 // Energy.Active.Import.Register measurand (default)
620 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
624 if (energySampledValueTemplate
) {
625 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
627 energySampledValueTemplate
.measurand
630 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
631 const connectorMaximumAvailablePower
=
632 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
633 const connectorMaximumEnergyRounded
= Utils
.roundTo(
634 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
637 const energyValueRounded
= energySampledValueTemplate
.value
638 ? // Cumulate the fluctuated value around the static one
639 Utils
.getRandomFloatFluctuatedRounded(
640 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
641 energySampledValueTemplate
.value
,
642 connectorMaximumEnergyRounded
,
644 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
645 unitMultiplier
: unitDivider
,
648 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
650 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
651 // Persist previous value on connector
654 Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
655 connector
.energyActiveImportRegisterValue
>= 0 &&
656 Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
657 connector
.transactionEnergyActiveImportRegisterValue
>= 0
659 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
660 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
662 connector
.energyActiveImportRegisterValue
= 0;
663 connector
.transactionEnergyActiveImportRegisterValue
= 0;
665 meterValue
.sampledValue
.push(
666 OCPP16ServiceUtils
.buildSampledValue(
667 energySampledValueTemplate
,
669 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
675 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
676 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
678 `${chargingStation.logPrefix()} MeterValues measurand ${
679 meterValue.sampledValue[sampledValuesIndex].measurand ??
680 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
681 }: connectorId ${connectorId}, transaction ${
682 connector?.transactionId
683 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
684 interval / (3600 * 1000),
693 public static buildTransactionBeginMeterValue(
694 chargingStation
: ChargingStation
,
697 ): OCPP16MeterValue
{
698 const meterValue
: OCPP16MeterValue
= {
699 timestamp
: new Date(),
702 // Energy.Active.Import.Register measurand (default)
703 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
707 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
708 meterValue
.sampledValue
.push(
709 OCPP16ServiceUtils
.buildSampledValue(
710 sampledValueTemplate
,
711 Utils
.roundTo((meterStart
?? 0) / unitDivider
, 4),
712 MeterValueContext
.TRANSACTION_BEGIN
718 public static buildTransactionEndMeterValue(
719 chargingStation
: ChargingStation
,
722 ): OCPP16MeterValue
{
723 const meterValue
: OCPP16MeterValue
= {
724 timestamp
: new Date(),
727 // Energy.Active.Import.Register measurand (default)
728 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
732 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
733 meterValue
.sampledValue
.push(
734 OCPP16ServiceUtils
.buildSampledValue(
735 sampledValueTemplate
,
736 Utils
.roundTo((meterStop
?? 0) / unitDivider
, 4),
737 MeterValueContext
.TRANSACTION_END
743 public static buildTransactionDataMeterValues(
744 transactionBeginMeterValue
: OCPP16MeterValue
,
745 transactionEndMeterValue
: OCPP16MeterValue
746 ): OCPP16MeterValue
[] {
747 const meterValues
: OCPP16MeterValue
[] = [];
748 meterValues
.push(transactionBeginMeterValue
);
749 meterValues
.push(transactionEndMeterValue
);
753 public static setChargingProfile(
754 chargingStation
: ChargingStation
,
756 cp
: OCPP16ChargingProfile
759 Utils
.isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)
762 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
764 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
767 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
770 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
772 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
774 let cpReplaced
= false;
775 if (!Utils
.isEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
777 .getConnectorStatus(connectorId
)
778 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
780 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
781 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
782 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
784 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
789 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
792 public static parseJsonSchemaFile
<T
extends JsonType
>(relativePath
: string): JSONSchemaType
<T
> {
793 const filePath
= path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), relativePath
);
795 return JSON
.parse(fs
.readFileSync(filePath
, 'utf8')) as JSONSchemaType
<T
>;
797 FileUtils
.handleFileException(
798 OCPPServiceUtils
.logPrefix(OCPPVersion
.VERSION_16
),
801 error
as NodeJS
.ErrnoException
,
802 { throwError
: false }
807 private static buildSampledValue(
808 sampledValueTemplate
: SampledValueTemplate
,
810 context
?: MeterValueContext
,
811 phase
?: OCPP16MeterValuePhase
812 ): OCPP16SampledValue
{
813 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
814 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
815 const sampledValueLocation
=
816 sampledValueTemplate
?.location
??
817 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
818 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
820 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
821 unit
: sampledValueTemplate
.unit
,
823 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
824 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
825 measurand
: sampledValueTemplate
.measurand
,
827 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
828 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
829 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
833 private static checkMeasurandPowerDivider(
834 chargingStation
: ChargingStation
,
835 measurandType
: OCPP16MeterValueMeasurand
837 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
838 const errMsg
= `MeterValues measurand ${
839 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
840 }: powerDivider is undefined`;
841 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
842 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
843 } else if (chargingStation
?.powerDivider
<= 0) {
844 const errMsg
= `MeterValues measurand ${
845 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
846 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
847 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
848 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
852 private static getMeasurandDefaultLocation(
853 measurandType
: OCPP16MeterValueMeasurand
854 ): MeterValueLocation
| undefined {
855 switch (measurandType
) {
856 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
857 return MeterValueLocation
.EV
;
861 private static getMeasurandDefaultUnit(
862 measurandType
: OCPP16MeterValueMeasurand
863 ): MeterValueUnit
| undefined {
864 switch (measurandType
) {
865 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
866 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
867 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
868 return MeterValueUnit
.AMP
;
869 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
870 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
871 return MeterValueUnit
.WATT_HOUR
;
872 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
873 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
874 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
875 return MeterValueUnit
.WATT
;
876 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
877 return MeterValueUnit
.PERCENT
;
878 case OCPP16MeterValueMeasurand
.VOLTAGE
:
879 return MeterValueUnit
.VOLT
;