1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import path from
'node:path';
4 import { fileURLToPath
} from
'node:url';
6 import type { JSONSchemaType
} from
'ajv';
8 import type { ChargingStation
} from
'../../../charging-station';
9 import { OCPPError
} from
'../../../exception';
14 type MeasurandPerPhaseSampledValueTemplates
,
19 OCPP16ChargePointErrorCode
,
20 type OCPP16ChargePointStatus
,
21 type OCPP16ChargingProfile
,
22 type OCPP16IncomingRequestCommand
,
23 type OCPP16MeterValue
,
24 OCPP16MeterValueMeasurand
,
25 OCPP16MeterValuePhase
,
27 type OCPP16SampledValue
,
28 OCPP16StandardParametersKey
,
29 type OCPP16StatusNotificationRequest
,
30 type OCPP16StatusNotificationResponse
,
31 type OCPP16SupportedFeatureProfiles
,
33 type SampledValueTemplate
,
35 } from
'../../../types';
36 import { ACElectricUtils
, Constants
, DCElectricUtils
, Utils
, logger
} from
'../../../utils';
37 import { OCPP16Constants
, OCPPServiceUtils
} from
'../internal';
39 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
40 public static checkFeatureProfile(
41 chargingStation
: ChargingStation
,
42 featureProfile
: OCPP16SupportedFeatureProfiles
,
43 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
45 if (!chargingStation
.hasFeatureProfile(featureProfile
)) {
47 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
48 OCPP16StandardParametersKey.SupportedFeatureProfiles
56 public static buildMeterValue(
57 chargingStation
: ChargingStation
,
59 transactionId
: number,
63 const meterValue
: OCPP16MeterValue
= {
64 timestamp
: new Date(),
67 const connector
= chargingStation
.getConnectorStatus(connectorId
);
69 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
72 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
74 if (socSampledValueTemplate
) {
75 const socMaximumValue
= 100;
76 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
77 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
78 ? Utils
.getRandomFloatFluctuatedRounded(
79 parseInt(socSampledValueTemplate
.value
),
80 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
82 : Utils
.getRandomInteger(socMaximumValue
, socMinimumValue
);
83 meterValue
.sampledValue
.push(
84 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
)
86 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
88 Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
89 Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
93 `${chargingStation.logPrefix()} MeterValues measurand ${
94 meterValue.sampledValue[sampledValuesIndex].measurand ??
95 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
96 }: connectorId ${connectorId}, transaction ${
97 connector?.transactionId
98 }, value: ${socMinimumValue}/${
99 meterValue.sampledValue[sampledValuesIndex].value
100 }/${socMaximumValue}}`
105 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
108 OCPP16MeterValueMeasurand
.VOLTAGE
110 if (voltageSampledValueTemplate
) {
111 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
112 ? parseInt(voltageSampledValueTemplate
.value
)
113 : chargingStation
.getVoltageOut();
114 const fluctuationPercent
=
115 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
116 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
117 voltageSampledValueTemplateValue
,
121 chargingStation
.getNumberOfPhases() !== 3 ||
122 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
124 meterValue
.sampledValue
.push(
125 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
)
130 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
133 const phaseLineToNeutralValue
= `L${phase}-N`;
134 const voltagePhaseLineToNeutralSampledValueTemplate
=
135 OCPP16ServiceUtils
.getSampledValueTemplate(
138 OCPP16MeterValueMeasurand
.VOLTAGE
,
139 phaseLineToNeutralValue
as OCPP16MeterValuePhase
141 let voltagePhaseLineToNeutralMeasurandValue
: number;
142 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
143 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
144 voltagePhaseLineToNeutralSampledValueTemplate
.value
145 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
146 : chargingStation
.getVoltageOut();
147 const fluctuationPhaseToNeutralPercent
=
148 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
149 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
150 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
151 voltagePhaseLineToNeutralSampledValueTemplateValue
,
152 fluctuationPhaseToNeutralPercent
155 meterValue
.sampledValue
.push(
156 OCPP16ServiceUtils
.buildSampledValue(
157 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
158 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
160 phaseLineToNeutralValue
as OCPP16MeterValuePhase
163 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
164 const phaseLineToLineValue
= `L${phase}-L${
165 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
166 ? (phase + 1) % chargingStation.getNumberOfPhases()
167 : chargingStation.getNumberOfPhases()
169 const voltagePhaseLineToLineSampledValueTemplate
=
170 OCPP16ServiceUtils
.getSampledValueTemplate(
173 OCPP16MeterValueMeasurand
.VOLTAGE
,
174 phaseLineToLineValue
as OCPP16MeterValuePhase
176 let voltagePhaseLineToLineMeasurandValue
: number;
177 if (voltagePhaseLineToLineSampledValueTemplate
) {
178 const voltagePhaseLineToLineSampledValueTemplateValue
=
179 voltagePhaseLineToLineSampledValueTemplate
.value
180 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
181 : Voltage
.VOLTAGE_400
;
182 const fluctuationPhaseLineToLinePercent
=
183 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
184 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
185 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
186 voltagePhaseLineToLineSampledValueTemplateValue
,
187 fluctuationPhaseLineToLinePercent
190 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(
194 meterValue
.sampledValue
.push(
195 OCPP16ServiceUtils
.buildSampledValue(
196 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
197 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
199 phaseLineToLineValue
as OCPP16MeterValuePhase
205 // Power.Active.Import measurand
206 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
209 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
211 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
212 if (chargingStation
.getNumberOfPhases() === 3) {
213 powerPerPhaseSampledValueTemplates
= {
214 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
217 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
218 OCPP16MeterValuePhase
.L1_N
220 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
223 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
224 OCPP16MeterValuePhase
.L2_N
226 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
229 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
230 OCPP16MeterValuePhase
.L3_N
234 if (powerSampledValueTemplate
) {
235 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
237 powerSampledValueTemplate
.measurand
239 const errMsg
= `MeterValues measurand ${
240 powerSampledValueTemplate.measurand ??
241 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
242 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
243 chargingStation.templateFile
244 }, cannot calculate ${
245 powerSampledValueTemplate.measurand ??
246 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
248 const powerMeasurandValues
= {} as MeasurandValues
;
249 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
250 const connectorMaximumAvailablePower
=
251 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
252 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
253 const connectorMaximumPowerPerPhase
= Math.round(
254 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases()
256 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
) ?? 0;
257 const connectorMinimumPowerPerPhase
= Math.round(
258 connectorMinimumPower
/ chargingStation
.getNumberOfPhases()
260 switch (chargingStation
.getCurrentOutType()) {
262 if (chargingStation
.getNumberOfPhases() === 3) {
263 const defaultFluctuatedPowerPerPhase
=
264 powerSampledValueTemplate
.value
&&
265 Utils
.getRandomFloatFluctuatedRounded(
266 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
267 powerSampledValueTemplate
.value
,
268 connectorMaximumPower
/ unitDivider
,
269 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
270 ) / chargingStation
.getNumberOfPhases(),
271 powerSampledValueTemplate
.fluctuationPercent
??
272 Constants
.DEFAULT_FLUCTUATION_PERCENT
274 const phase1FluctuatedValue
=
275 powerPerPhaseSampledValueTemplates
?.L1
?.value
&&
276 Utils
.getRandomFloatFluctuatedRounded(
277 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
278 powerPerPhaseSampledValueTemplates
.L1
.value
,
279 connectorMaximumPowerPerPhase
/ unitDivider
,
280 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
282 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
283 Constants
.DEFAULT_FLUCTUATION_PERCENT
285 const phase2FluctuatedValue
=
286 powerPerPhaseSampledValueTemplates
?.L2
?.value
&&
287 Utils
.getRandomFloatFluctuatedRounded(
288 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
289 powerPerPhaseSampledValueTemplates
.L2
.value
,
290 connectorMaximumPowerPerPhase
/ unitDivider
,
291 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
293 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
294 Constants
.DEFAULT_FLUCTUATION_PERCENT
296 const phase3FluctuatedValue
=
297 powerPerPhaseSampledValueTemplates
?.L3
?.value
&&
298 Utils
.getRandomFloatFluctuatedRounded(
299 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
300 powerPerPhaseSampledValueTemplates
.L3
.value
,
301 connectorMaximumPowerPerPhase
/ unitDivider
,
302 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
304 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
305 Constants
.DEFAULT_FLUCTUATION_PERCENT
307 powerMeasurandValues
.L1
=
308 phase1FluctuatedValue
??
309 defaultFluctuatedPowerPerPhase
??
310 Utils
.getRandomFloatRounded(
311 connectorMaximumPowerPerPhase
/ unitDivider
,
312 connectorMinimumPowerPerPhase
/ unitDivider
314 powerMeasurandValues
.L2
=
315 phase2FluctuatedValue
??
316 defaultFluctuatedPowerPerPhase
??
317 Utils
.getRandomFloatRounded(
318 connectorMaximumPowerPerPhase
/ unitDivider
,
319 connectorMinimumPowerPerPhase
/ unitDivider
321 powerMeasurandValues
.L3
=
322 phase3FluctuatedValue
??
323 defaultFluctuatedPowerPerPhase
??
324 Utils
.getRandomFloatRounded(
325 connectorMaximumPowerPerPhase
/ unitDivider
,
326 connectorMinimumPowerPerPhase
/ unitDivider
329 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
330 ? Utils
.getRandomFloatFluctuatedRounded(
331 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
332 powerSampledValueTemplate
.value
,
333 connectorMaximumPower
/ unitDivider
,
334 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
336 powerSampledValueTemplate
.fluctuationPercent
??
337 Constants
.DEFAULT_FLUCTUATION_PERCENT
339 : Utils
.getRandomFloatRounded(
340 connectorMaximumPower
/ unitDivider
,
341 connectorMinimumPower
/ unitDivider
343 powerMeasurandValues
.L2
= 0;
344 powerMeasurandValues
.L3
= 0;
346 powerMeasurandValues
.allPhases
= Utils
.roundTo(
347 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
352 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
353 ? Utils
.getRandomFloatFluctuatedRounded(
354 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
355 powerSampledValueTemplate
.value
,
356 connectorMaximumPower
/ unitDivider
,
357 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
359 powerSampledValueTemplate
.fluctuationPercent
??
360 Constants
.DEFAULT_FLUCTUATION_PERCENT
362 : Utils
.getRandomFloatRounded(
363 connectorMaximumPower
/ unitDivider
,
364 connectorMinimumPower
/ unitDivider
368 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
369 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
371 meterValue
.sampledValue
.push(
372 OCPP16ServiceUtils
.buildSampledValue(
373 powerSampledValueTemplate
,
374 powerMeasurandValues
.allPhases
377 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
378 const connectorMaximumPowerRounded
= Utils
.roundTo(connectorMaximumPower
/ unitDivider
, 2);
379 const connectorMinimumPowerRounded
= Utils
.roundTo(connectorMinimumPower
/ unitDivider
, 2);
381 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
382 connectorMaximumPowerRounded
||
383 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
384 connectorMinimumPowerRounded
||
388 `${chargingStation.logPrefix()} MeterValues measurand ${
389 meterValue.sampledValue[sampledValuesIndex].measurand ??
390 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
391 }: connectorId ${connectorId}, transaction ${
392 connector?.transactionId
393 }, value: ${connectorMinimumPowerRounded}/${
394 meterValue.sampledValue[sampledValuesIndex].value
395 }/${connectorMaximumPowerRounded}`
400 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
403 const phaseValue
= `L${phase}-N`;
404 meterValue
.sampledValue
.push(
405 OCPP16ServiceUtils
.buildSampledValue(
406 (powerPerPhaseSampledValueTemplates
[`L${phase}`] as SampledValueTemplate
) ??
407 powerSampledValueTemplate
,
408 powerMeasurandValues
[`L${phase}`] as number,
410 phaseValue
as OCPP16MeterValuePhase
413 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
414 const connectorMaximumPowerPerPhaseRounded
= Utils
.roundTo(
415 connectorMaximumPowerPerPhase
/ unitDivider
,
418 const connectorMinimumPowerPerPhaseRounded
= Utils
.roundTo(
419 connectorMinimumPowerPerPhase
/ unitDivider
,
423 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
424 connectorMaximumPowerPerPhaseRounded
||
425 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
426 connectorMinimumPowerPerPhaseRounded
||
430 `${chargingStation.logPrefix()} MeterValues measurand ${
431 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
432 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
434 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
435 }, connectorId ${connectorId}, transaction ${
436 connector?.transactionId
437 }, value: ${connectorMinimumPowerPerPhaseRounded}/${
438 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
439 }/${connectorMaximumPowerPerPhaseRounded}`
444 // Current.Import measurand
445 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
448 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
450 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
451 if (chargingStation
.getNumberOfPhases() === 3) {
452 currentPerPhaseSampledValueTemplates
= {
453 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
456 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
457 OCPP16MeterValuePhase
.L1
459 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
462 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
463 OCPP16MeterValuePhase
.L2
465 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
468 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
469 OCPP16MeterValuePhase
.L3
473 if (currentSampledValueTemplate
) {
474 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
476 currentSampledValueTemplate
.measurand
478 const errMsg
= `MeterValues measurand ${
479 currentSampledValueTemplate.measurand ??
480 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
481 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
482 chargingStation.templateFile
483 }, cannot calculate ${
484 currentSampledValueTemplate.measurand ??
485 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
487 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
488 const connectorMaximumAvailablePower
=
489 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
490 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
491 let connectorMaximumAmperage
: number;
492 switch (chargingStation
.getCurrentOutType()) {
494 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
495 chargingStation
.getNumberOfPhases(),
496 connectorMaximumAvailablePower
,
497 chargingStation
.getVoltageOut()
499 if (chargingStation
.getNumberOfPhases() === 3) {
500 const defaultFluctuatedAmperagePerPhase
=
501 currentSampledValueTemplate
.value
&&
502 Utils
.getRandomFloatFluctuatedRounded(
503 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
504 currentSampledValueTemplate
.value
,
505 connectorMaximumAmperage
,
506 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
508 currentSampledValueTemplate
.fluctuationPercent
??
509 Constants
.DEFAULT_FLUCTUATION_PERCENT
511 const phase1FluctuatedValue
=
512 currentPerPhaseSampledValueTemplates
?.L1
?.value
&&
513 Utils
.getRandomFloatFluctuatedRounded(
514 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
515 currentPerPhaseSampledValueTemplates
.L1
.value
,
516 connectorMaximumAmperage
,
517 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
519 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
520 Constants
.DEFAULT_FLUCTUATION_PERCENT
522 const phase2FluctuatedValue
=
523 currentPerPhaseSampledValueTemplates
?.L2
?.value
&&
524 Utils
.getRandomFloatFluctuatedRounded(
525 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
526 currentPerPhaseSampledValueTemplates
.L2
.value
,
527 connectorMaximumAmperage
,
528 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
530 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
531 Constants
.DEFAULT_FLUCTUATION_PERCENT
533 const phase3FluctuatedValue
=
534 currentPerPhaseSampledValueTemplates
?.L3
?.value
&&
535 Utils
.getRandomFloatFluctuatedRounded(
536 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
537 currentPerPhaseSampledValueTemplates
.L3
.value
,
538 connectorMaximumAmperage
,
539 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
541 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
542 Constants
.DEFAULT_FLUCTUATION_PERCENT
544 currentMeasurandValues
.L1
=
545 phase1FluctuatedValue
??
546 defaultFluctuatedAmperagePerPhase
??
547 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
548 currentMeasurandValues
.L2
=
549 phase2FluctuatedValue
??
550 defaultFluctuatedAmperagePerPhase
??
551 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
552 currentMeasurandValues
.L3
=
553 phase3FluctuatedValue
??
554 defaultFluctuatedAmperagePerPhase
??
555 Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
557 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
558 ? Utils
.getRandomFloatFluctuatedRounded(
559 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
560 currentSampledValueTemplate
.value
,
561 connectorMaximumAmperage
,
562 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
564 currentSampledValueTemplate
.fluctuationPercent
??
565 Constants
.DEFAULT_FLUCTUATION_PERCENT
567 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
568 currentMeasurandValues
.L2
= 0;
569 currentMeasurandValues
.L3
= 0;
571 currentMeasurandValues
.allPhases
= Utils
.roundTo(
572 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
573 chargingStation
.getNumberOfPhases(),
578 connectorMaximumAmperage
= DCElectricUtils
.amperage(
579 connectorMaximumAvailablePower
,
580 chargingStation
.getVoltageOut()
582 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
583 ? Utils
.getRandomFloatFluctuatedRounded(
584 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
585 currentSampledValueTemplate
.value
,
586 connectorMaximumAmperage
,
587 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() }
589 currentSampledValueTemplate
.fluctuationPercent
??
590 Constants
.DEFAULT_FLUCTUATION_PERCENT
592 : Utils
.getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
595 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
596 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
598 meterValue
.sampledValue
.push(
599 OCPP16ServiceUtils
.buildSampledValue(
600 currentSampledValueTemplate
,
601 currentMeasurandValues
.allPhases
604 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
606 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
607 connectorMaximumAmperage
||
608 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
609 connectorMinimumAmperage
||
613 `${chargingStation.logPrefix()} MeterValues measurand ${
614 meterValue.sampledValue[sampledValuesIndex].measurand ??
615 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
616 }: connectorId ${connectorId}, transaction ${
617 connector?.transactionId
618 }, value: ${connectorMinimumAmperage}/${
619 meterValue.sampledValue[sampledValuesIndex].value
620 }/${connectorMaximumAmperage}`
625 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
628 const phaseValue
= `L${phase}`;
629 meterValue
.sampledValue
.push(
630 OCPP16ServiceUtils
.buildSampledValue(
631 (currentPerPhaseSampledValueTemplates
[phaseValue
] as SampledValueTemplate
) ??
632 currentSampledValueTemplate
,
633 currentMeasurandValues
[phaseValue
] as number,
635 phaseValue
as OCPP16MeterValuePhase
638 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
640 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
641 connectorMaximumAmperage
||
642 Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
643 connectorMinimumAmperage
||
647 `${chargingStation.logPrefix()} MeterValues measurand ${
648 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
649 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
651 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
652 }, connectorId ${connectorId}, transaction ${
653 connector?.transactionId
654 }, value: ${connectorMinimumAmperage}/${
655 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
656 }/${connectorMaximumAmperage}`
661 // Energy.Active.Import.Register measurand (default)
662 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
666 if (energySampledValueTemplate
) {
667 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
669 energySampledValueTemplate
.measurand
672 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
673 const connectorMaximumAvailablePower
=
674 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
675 const connectorMaximumEnergyRounded
= Utils
.roundTo(
676 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
679 const energyValueRounded
= energySampledValueTemplate
.value
680 ? // Cumulate the fluctuated value around the static one
681 Utils
.getRandomFloatFluctuatedRounded(
682 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
683 energySampledValueTemplate
.value
,
684 connectorMaximumEnergyRounded
,
686 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
687 unitMultiplier
: unitDivider
,
690 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
692 : Utils
.getRandomFloatRounded(connectorMaximumEnergyRounded
);
693 // Persist previous value on connector
696 Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
697 connector
.energyActiveImportRegisterValue
>= 0 &&
698 Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
699 connector
.transactionEnergyActiveImportRegisterValue
>= 0
701 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
702 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
704 connector
.energyActiveImportRegisterValue
= 0;
705 connector
.transactionEnergyActiveImportRegisterValue
= 0;
707 meterValue
.sampledValue
.push(
708 OCPP16ServiceUtils
.buildSampledValue(
709 energySampledValueTemplate
,
711 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
717 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
718 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
720 `${chargingStation.logPrefix()} MeterValues measurand ${
721 meterValue.sampledValue[sampledValuesIndex].measurand ??
722 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
723 }: connectorId ${connectorId}, transaction ${
724 connector?.transactionId
725 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
726 interval / (3600 * 1000),
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 Utils
.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 Utils
.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 setChargingProfile(
796 chargingStation
: ChargingStation
,
798 cp
: OCPP16ChargingProfile
801 Utils
.isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)
804 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
806 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
809 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
812 `${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`
814 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
= [];
816 let cpReplaced
= false;
817 if (Utils
.isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
819 .getConnectorStatus(connectorId
)
820 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
822 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
823 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
824 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
826 chargingStation
.getConnectorStatus(connectorId
).chargingProfiles
[index
] = cp
;
831 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
834 public static parseJsonSchemaFile
<T
extends JsonType
>(
835 relativePath
: string,
838 ): JSONSchemaType
<T
> {
839 return super.parseJsonSchemaFile
<T
>(
840 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), relativePath
),
841 OCPPVersion
.VERSION_16
,
847 public static async sendAndSetConnectorStatus(
848 chargingStation
: ChargingStation
,
850 status: OCPP16ChargePointStatus
,
851 errorCode
: OCPP16ChargePointErrorCode
= OCPP16ChargePointErrorCode
.NO_ERROR
853 OCPP16ServiceUtils
.checkConnectorStatusTransition(chargingStation
, connectorId
, status);
854 await chargingStation
.ocppRequestService
.requestHandler
<
855 OCPP16StatusNotificationRequest
,
856 OCPP16StatusNotificationResponse
857 >(chargingStation
, OCPP16RequestCommand
.STATUS_NOTIFICATION
, {
862 chargingStation
.getConnectorStatus(connectorId
).status = status;
865 private static checkConnectorStatusTransition(
866 chargingStation
: ChargingStation
,
868 status: OCPP16ChargePointStatus
872 !OCPP16Constants
.OCPP16ChargePointStatusChargingStationTransition
.has([
873 chargingStation
.getConnectorStatus(connectorId
).status as OCPP16ChargePointStatus
,
878 `${chargingStation.logPrefix()} Connector ${connectorId} status transition from ${
879 chargingStation.getConnectorStatus(connectorId).status
880 } to ${status} is not allowed`
884 !OCPP16Constants
.OCPP16ChargePointStatusConnectorTransition
.has([
885 chargingStation
.getConnectorStatus(connectorId
).status as OCPP16ChargePointStatus
,
890 `${chargingStation.logPrefix()} Connector ${connectorId} status transition from ${
891 chargingStation.getConnectorStatus(connectorId).status
892 } to ${status} is not allowed`
899 private static buildSampledValue(
900 sampledValueTemplate
: SampledValueTemplate
,
902 context
?: MeterValueContext
,
903 phase
?: OCPP16MeterValuePhase
904 ): OCPP16SampledValue
{
905 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
906 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
907 const sampledValueLocation
=
908 sampledValueTemplate
?.location
??
909 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
?.measurand
?? null);
910 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
912 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.unit
) && {
913 unit
: sampledValueTemplate
.unit
,
915 ...(!Utils
.isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
916 ...(!Utils
.isNullOrUndefined(sampledValueTemplate
.measurand
) && {
917 measurand
: sampledValueTemplate
.measurand
,
919 ...(!Utils
.isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
920 ...(!Utils
.isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
921 ...(!Utils
.isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
925 private static checkMeasurandPowerDivider(
926 chargingStation
: ChargingStation
,
927 measurandType
: OCPP16MeterValueMeasurand
929 if (Utils
.isUndefined(chargingStation
.powerDivider
)) {
930 const errMsg
= `MeterValues measurand ${
931 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
932 }: powerDivider is undefined`;
933 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
934 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
935 } else if (chargingStation
?.powerDivider
<= 0) {
936 const errMsg
= `MeterValues measurand ${
937 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
938 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
939 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
940 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
944 private static getMeasurandDefaultLocation(
945 measurandType
: OCPP16MeterValueMeasurand
946 ): MeterValueLocation
| undefined {
947 switch (measurandType
) {
948 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
949 return MeterValueLocation
.EV
;
953 private static getMeasurandDefaultUnit(
954 measurandType
: OCPP16MeterValueMeasurand
955 ): MeterValueUnit
| undefined {
956 switch (measurandType
) {
957 case OCPP16MeterValueMeasurand
.CURRENT_EXPORT
:
958 case OCPP16MeterValueMeasurand
.CURRENT_IMPORT
:
959 case OCPP16MeterValueMeasurand
.CURRENT_OFFERED
:
960 return MeterValueUnit
.AMP
;
961 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_EXPORT_REGISTER
:
962 case OCPP16MeterValueMeasurand
.ENERGY_ACTIVE_IMPORT_REGISTER
:
963 return MeterValueUnit
.WATT_HOUR
;
964 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_EXPORT
:
965 case OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
:
966 case OCPP16MeterValueMeasurand
.POWER_OFFERED
:
967 return MeterValueUnit
.WATT
;
968 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
969 return MeterValueUnit
.PERCENT
;
970 case OCPP16MeterValueMeasurand
.VOLTAGE
:
971 return MeterValueUnit
.VOLT
;