1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
5 import { OCPP16Constants
} from
'./OCPP16Constants';
6 import { type ChargingStation
, hasFeatureProfile
} from
'../../../charging-station';
7 import { OCPPError
} from
'../../../exception';
9 type ClearChargingProfileRequest
,
14 type MeasurandPerPhaseSampledValueTemplates
,
19 OCPP16AuthorizationStatus
,
20 OCPP16AvailabilityType
,
21 type OCPP16ChangeAvailabilityResponse
,
22 OCPP16ChargePointStatus
,
23 type OCPP16ChargingProfile
,
24 type OCPP16IncomingRequestCommand
,
25 type OCPP16MeterValue
,
26 OCPP16MeterValueMeasurand
,
27 OCPP16MeterValuePhase
,
29 type OCPP16SampledValue
,
30 OCPP16StandardParametersKey
,
31 OCPP16StopTransactionReason
,
32 type OCPP16SupportedFeatureProfiles
,
34 type SampledValueTemplate
,
36 } from
'../../../types';
43 getRandomFloatFluctuatedRounded
,
44 getRandomFloatRounded
,
51 } from
'../../../utils';
52 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
54 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
55 public static checkFeatureProfile(
56 chargingStation
: ChargingStation
,
57 featureProfile
: OCPP16SupportedFeatureProfiles
,
58 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
60 if (!hasFeatureProfile(chargingStation
, featureProfile
)) {
62 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
63 OCPP16StandardParametersKey.SupportedFeatureProfiles
71 public static buildMeterValue(
72 chargingStation
: ChargingStation
,
74 transactionId
: number,
78 const meterValue
: OCPP16MeterValue
= {
79 timestamp
: new Date(),
82 const connector
= chargingStation
.getConnectorStatus(connectorId
);
84 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
87 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
89 if (socSampledValueTemplate
) {
90 const socMaximumValue
= 100;
91 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
92 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
93 ? getRandomFloatFluctuatedRounded(
94 parseInt(socSampledValueTemplate
.value
),
95 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
97 : getRandomInteger(socMaximumValue
, socMinimumValue
);
98 meterValue
.sampledValue
.push(
99 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
101 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
103 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
104 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
108 `${chargingStation.logPrefix()} MeterValues measurand ${
109 meterValue.sampledValue[sampledValuesIndex].measurand ??
110 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
111 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
112 meterValue.sampledValue[sampledValuesIndex].value
113 }/${socMaximumValue}}`,
118 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
121 OCPP16MeterValueMeasurand
.VOLTAGE
,
123 if (voltageSampledValueTemplate
) {
124 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
125 ? parseInt(voltageSampledValueTemplate
.value
)
126 : chargingStation
.getVoltageOut();
127 const fluctuationPercent
=
128 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
129 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
130 voltageSampledValueTemplateValue
,
134 chargingStation
.getNumberOfPhases() !== 3 ||
135 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
137 meterValue
.sampledValue
.push(
138 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
143 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
146 const phaseLineToNeutralValue
= `L${phase}-N`;
147 const voltagePhaseLineToNeutralSampledValueTemplate
=
148 OCPP16ServiceUtils
.getSampledValueTemplate(
151 OCPP16MeterValueMeasurand
.VOLTAGE
,
152 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
154 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
155 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
156 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
157 voltagePhaseLineToNeutralSampledValueTemplate
.value
158 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
159 : chargingStation
.getVoltageOut();
160 const fluctuationPhaseToNeutralPercent
=
161 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
162 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
163 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
164 voltagePhaseLineToNeutralSampledValueTemplateValue
,
165 fluctuationPhaseToNeutralPercent
,
168 meterValue
.sampledValue
.push(
169 OCPP16ServiceUtils
.buildSampledValue(
170 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
171 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
173 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
176 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
177 const phaseLineToLineValue
= `L${phase}-L${
178 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
179 ? (phase + 1) % chargingStation.getNumberOfPhases()
180 : chargingStation.getNumberOfPhases()
182 const voltagePhaseLineToLineSampledValueTemplate
=
183 OCPP16ServiceUtils
.getSampledValueTemplate(
186 OCPP16MeterValueMeasurand
.VOLTAGE
,
187 phaseLineToLineValue
as OCPP16MeterValuePhase
,
189 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
190 if (voltagePhaseLineToLineSampledValueTemplate
) {
191 const voltagePhaseLineToLineSampledValueTemplateValue
=
192 voltagePhaseLineToLineSampledValueTemplate
.value
193 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
194 : Voltage
.VOLTAGE_400
;
195 const fluctuationPhaseLineToLinePercent
=
196 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
197 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
198 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
199 voltagePhaseLineToLineSampledValueTemplateValue
,
200 fluctuationPhaseLineToLinePercent
,
203 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
207 meterValue
.sampledValue
.push(
208 OCPP16ServiceUtils
.buildSampledValue(
209 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
210 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
212 phaseLineToLineValue
as OCPP16MeterValuePhase
,
218 // Power.Active.Import measurand
219 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
222 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
224 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
225 if (chargingStation
.getNumberOfPhases() === 3) {
226 powerPerPhaseSampledValueTemplates
= {
227 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
230 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
231 OCPP16MeterValuePhase
.L1_N
,
233 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
236 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
237 OCPP16MeterValuePhase
.L2_N
,
239 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
242 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
243 OCPP16MeterValuePhase
.L3_N
,
247 if (powerSampledValueTemplate
) {
248 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
250 powerSampledValueTemplate
.measurand
!,
252 const errMsg
= `MeterValues measurand ${
253 powerSampledValueTemplate.measurand ??
254 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
255 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
256 chargingStation.templateFile
257 }, cannot calculate ${
258 powerSampledValueTemplate.measurand ??
259 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
261 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
262 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
263 const connectorMaximumAvailablePower
=
264 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
265 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
266 const connectorMaximumPowerPerPhase
= Math.round(
267 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
269 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
!) ?? 0;
270 const connectorMinimumPowerPerPhase
= Math.round(
271 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
273 switch (chargingStation
.getCurrentOutType()) {
275 if (chargingStation
.getNumberOfPhases() === 3) {
276 const defaultFluctuatedPowerPerPhase
=
277 powerSampledValueTemplate
.value
&&
278 getRandomFloatFluctuatedRounded(
279 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
280 powerSampledValueTemplate
.value
,
281 connectorMaximumPower
/ unitDivider
,
282 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
283 ) / chargingStation
.getNumberOfPhases(),
284 powerSampledValueTemplate
.fluctuationPercent
??
285 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
287 const phase1FluctuatedValue
=
288 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
289 getRandomFloatFluctuatedRounded(
290 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
291 powerPerPhaseSampledValueTemplates
.L1
.value
,
292 connectorMaximumPowerPerPhase
/ unitDivider
,
293 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
295 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
296 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
298 const phase2FluctuatedValue
=
299 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
300 getRandomFloatFluctuatedRounded(
301 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
302 powerPerPhaseSampledValueTemplates
.L2
.value
,
303 connectorMaximumPowerPerPhase
/ unitDivider
,
304 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
306 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
307 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
309 const phase3FluctuatedValue
=
310 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
311 getRandomFloatFluctuatedRounded(
312 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
313 powerPerPhaseSampledValueTemplates
.L3
.value
,
314 connectorMaximumPowerPerPhase
/ unitDivider
,
315 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
317 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
318 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
320 powerMeasurandValues
.L1
=
321 (phase1FluctuatedValue
as number) ??
322 (defaultFluctuatedPowerPerPhase
as number) ??
323 getRandomFloatRounded(
324 connectorMaximumPowerPerPhase
/ unitDivider
,
325 connectorMinimumPowerPerPhase
/ unitDivider
,
327 powerMeasurandValues
.L2
=
328 (phase2FluctuatedValue
as number) ??
329 (defaultFluctuatedPowerPerPhase
as number) ??
330 getRandomFloatRounded(
331 connectorMaximumPowerPerPhase
/ unitDivider
,
332 connectorMinimumPowerPerPhase
/ unitDivider
,
334 powerMeasurandValues
.L3
=
335 (phase3FluctuatedValue
as number) ??
336 (defaultFluctuatedPowerPerPhase
as number) ??
337 getRandomFloatRounded(
338 connectorMaximumPowerPerPhase
/ unitDivider
,
339 connectorMinimumPowerPerPhase
/ unitDivider
,
342 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
343 ? getRandomFloatFluctuatedRounded(
344 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
345 powerSampledValueTemplate
.value
,
346 connectorMaximumPower
/ unitDivider
,
347 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
349 powerSampledValueTemplate
.fluctuationPercent
??
350 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
352 : getRandomFloatRounded(
353 connectorMaximumPower
/ unitDivider
,
354 connectorMinimumPower
/ unitDivider
,
356 powerMeasurandValues
.L2
= 0;
357 powerMeasurandValues
.L3
= 0;
359 powerMeasurandValues
.allPhases
= roundTo(
360 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
365 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
366 ? getRandomFloatFluctuatedRounded(
367 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
368 powerSampledValueTemplate
.value
,
369 connectorMaximumPower
/ unitDivider
,
370 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
372 powerSampledValueTemplate
.fluctuationPercent
??
373 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
375 : getRandomFloatRounded(
376 connectorMaximumPower
/ unitDivider
,
377 connectorMinimumPower
/ unitDivider
,
381 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
382 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
384 meterValue
.sampledValue
.push(
385 OCPP16ServiceUtils
.buildSampledValue(
386 powerSampledValueTemplate
,
387 powerMeasurandValues
.allPhases
,
390 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
391 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
392 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
394 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
395 connectorMaximumPowerRounded
||
396 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
397 connectorMinimumPowerRounded
||
401 `${chargingStation.logPrefix()} MeterValues measurand ${
402 meterValue.sampledValue[sampledValuesIndex].measurand ??
403 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
404 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
405 meterValue.sampledValue[sampledValuesIndex].value
406 }/${connectorMaximumPowerRounded}`,
411 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
414 const phaseValue
= `L${phase}-N`;
415 meterValue
.sampledValue
.push(
416 OCPP16ServiceUtils
.buildSampledValue(
417 powerPerPhaseSampledValueTemplates
[
418 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
419 ]! ?? powerSampledValueTemplate
,
420 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
422 phaseValue
as OCPP16MeterValuePhase
,
425 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
426 const connectorMaximumPowerPerPhaseRounded
= roundTo(
427 connectorMaximumPowerPerPhase
/ unitDivider
,
430 const connectorMinimumPowerPerPhaseRounded
= roundTo(
431 connectorMinimumPowerPerPhase
/ unitDivider
,
435 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
436 connectorMaximumPowerPerPhaseRounded
||
437 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
438 connectorMinimumPowerPerPhaseRounded
||
442 `${chargingStation.logPrefix()} MeterValues measurand ${
443 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
444 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
446 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
447 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
448 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
449 }/${connectorMaximumPowerPerPhaseRounded}`,
454 // Current.Import measurand
455 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
458 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
460 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
461 if (chargingStation
.getNumberOfPhases() === 3) {
462 currentPerPhaseSampledValueTemplates
= {
463 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
466 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
467 OCPP16MeterValuePhase
.L1
,
469 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
472 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
473 OCPP16MeterValuePhase
.L2
,
475 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
478 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
479 OCPP16MeterValuePhase
.L3
,
483 if (currentSampledValueTemplate
) {
484 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
486 currentSampledValueTemplate
.measurand
!,
488 const errMsg
= `MeterValues measurand ${
489 currentSampledValueTemplate.measurand ??
490 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
491 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
492 chargingStation.templateFile
493 }, cannot calculate ${
494 currentSampledValueTemplate.measurand ??
495 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
497 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
498 const connectorMaximumAvailablePower
=
499 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
500 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
501 let connectorMaximumAmperage
: number;
502 switch (chargingStation
.getCurrentOutType()) {
504 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
505 chargingStation
.getNumberOfPhases(),
506 connectorMaximumAvailablePower
,
507 chargingStation
.getVoltageOut(),
509 if (chargingStation
.getNumberOfPhases() === 3) {
510 const defaultFluctuatedAmperagePerPhase
=
511 currentSampledValueTemplate
.value
&&
512 getRandomFloatFluctuatedRounded(
513 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
514 currentSampledValueTemplate
.value
,
515 connectorMaximumAmperage
,
516 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
518 currentSampledValueTemplate
.fluctuationPercent
??
519 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
521 const phase1FluctuatedValue
=
522 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
523 getRandomFloatFluctuatedRounded(
524 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
525 currentPerPhaseSampledValueTemplates
.L1
.value
,
526 connectorMaximumAmperage
,
527 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
529 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
530 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
532 const phase2FluctuatedValue
=
533 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
534 getRandomFloatFluctuatedRounded(
535 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
536 currentPerPhaseSampledValueTemplates
.L2
.value
,
537 connectorMaximumAmperage
,
538 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
540 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
541 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
543 const phase3FluctuatedValue
=
544 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
545 getRandomFloatFluctuatedRounded(
546 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
547 currentPerPhaseSampledValueTemplates
.L3
.value
,
548 connectorMaximumAmperage
,
549 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
551 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
552 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
554 currentMeasurandValues
.L1
=
555 (phase1FluctuatedValue
as number) ??
556 (defaultFluctuatedAmperagePerPhase
as number) ??
557 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
558 currentMeasurandValues
.L2
=
559 (phase2FluctuatedValue
as number) ??
560 (defaultFluctuatedAmperagePerPhase
as number) ??
561 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
562 currentMeasurandValues
.L3
=
563 (phase3FluctuatedValue
as number) ??
564 (defaultFluctuatedAmperagePerPhase
as number) ??
565 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
567 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
568 ? getRandomFloatFluctuatedRounded(
569 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
570 currentSampledValueTemplate
.value
,
571 connectorMaximumAmperage
,
572 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
574 currentSampledValueTemplate
.fluctuationPercent
??
575 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
577 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
578 currentMeasurandValues
.L2
= 0;
579 currentMeasurandValues
.L3
= 0;
581 currentMeasurandValues
.allPhases
= roundTo(
582 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
583 chargingStation
.getNumberOfPhases(),
588 connectorMaximumAmperage
= DCElectricUtils
.amperage(
589 connectorMaximumAvailablePower
,
590 chargingStation
.getVoltageOut(),
592 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
593 ? getRandomFloatFluctuatedRounded(
594 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
595 currentSampledValueTemplate
.value
,
596 connectorMaximumAmperage
,
597 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
599 currentSampledValueTemplate
.fluctuationPercent
??
600 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
602 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
605 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
606 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
608 meterValue
.sampledValue
.push(
609 OCPP16ServiceUtils
.buildSampledValue(
610 currentSampledValueTemplate
,
611 currentMeasurandValues
.allPhases
,
614 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
616 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
617 connectorMaximumAmperage
||
618 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
619 connectorMinimumAmperage
||
623 `${chargingStation.logPrefix()} MeterValues measurand ${
624 meterValue.sampledValue[sampledValuesIndex].measurand ??
625 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
626 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
627 meterValue.sampledValue[sampledValuesIndex].value
628 }/${connectorMaximumAmperage}`,
633 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
636 const phaseValue
= `L${phase}`;
637 meterValue
.sampledValue
.push(
638 OCPP16ServiceUtils
.buildSampledValue(
639 currentPerPhaseSampledValueTemplates
[
640 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
641 ]! ?? currentSampledValueTemplate
,
642 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
644 phaseValue
as OCPP16MeterValuePhase
,
647 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
649 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
650 connectorMaximumAmperage
||
651 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
652 connectorMinimumAmperage
||
656 `${chargingStation.logPrefix()} MeterValues measurand ${
657 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
658 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
660 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
661 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
662 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
663 }/${connectorMaximumAmperage}`,
668 // Energy.Active.Import.Register measurand (default)
669 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
673 if (energySampledValueTemplate
) {
674 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
676 energySampledValueTemplate
.measurand
!,
679 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
680 const connectorMaximumAvailablePower
=
681 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
682 const connectorMaximumEnergyRounded
= roundTo(
683 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
686 const energyValueRounded
= energySampledValueTemplate
.value
687 ? // Cumulate the fluctuated value around the static one
688 getRandomFloatFluctuatedRounded(
689 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
690 energySampledValueTemplate
.value
,
691 connectorMaximumEnergyRounded
,
693 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
694 unitMultiplier
: unitDivider
,
697 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
699 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
700 // Persist previous value on connector
703 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
704 connector
.energyActiveImportRegisterValue
! >= 0 &&
705 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
706 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
708 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
709 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
711 connector
.energyActiveImportRegisterValue
= 0;
712 connector
.transactionEnergyActiveImportRegisterValue
= 0;
715 meterValue
.sampledValue
.push(
716 OCPP16ServiceUtils
.buildSampledValue(
717 energySampledValueTemplate
,
719 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
725 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
726 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
728 `${chargingStation.logPrefix()} MeterValues measurand ${
729 meterValue.sampledValue[sampledValuesIndex].measurand ??
730 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
731 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
738 public static buildTransactionBeginMeterValue(
739 chargingStation
: ChargingStation
,
742 ): OCPP16MeterValue
{
743 const meterValue
: OCPP16MeterValue
= {
744 timestamp
: new Date(),
747 // Energy.Active.Import.Register measurand (default)
748 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
752 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
753 meterValue
.sampledValue
.push(
754 OCPP16ServiceUtils
.buildSampledValue(
755 sampledValueTemplate
!,
756 roundTo((meterStart
?? 0) / unitDivider
, 4),
757 MeterValueContext
.TRANSACTION_BEGIN
,
763 public static buildTransactionEndMeterValue(
764 chargingStation
: ChargingStation
,
767 ): OCPP16MeterValue
{
768 const meterValue
: OCPP16MeterValue
= {
769 timestamp
: new Date(),
772 // Energy.Active.Import.Register measurand (default)
773 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
777 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
778 meterValue
.sampledValue
.push(
779 OCPP16ServiceUtils
.buildSampledValue(
780 sampledValueTemplate
!,
781 roundTo((meterStop
?? 0) / unitDivider
, 4),
782 MeterValueContext
.TRANSACTION_END
,
788 public static buildTransactionDataMeterValues(
789 transactionBeginMeterValue
: OCPP16MeterValue
,
790 transactionEndMeterValue
: OCPP16MeterValue
,
791 ): OCPP16MeterValue
[] {
792 const meterValues
: OCPP16MeterValue
[] = [];
793 meterValues
.push(transactionBeginMeterValue
);
794 meterValues
.push(transactionEndMeterValue
);
798 public static remoteStopTransaction
= async (
799 chargingStation
: ChargingStation
,
801 ): Promise
<GenericResponse
> => {
802 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
805 OCPP16ChargePointStatus
.Finishing
,
807 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
809 OCPP16StopTransactionReason
.REMOTE
,
811 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
812 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
814 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
817 public static changeAvailability
= async (
818 chargingStation
: ChargingStation
,
819 connectorIds
: number[],
820 chargePointStatus
: OCPP16ChargePointStatus
,
821 availabilityType
: OCPP16AvailabilityType
,
822 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
823 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
824 for (const connectorId
of connectorIds
) {
825 let response
: OCPP16ChangeAvailabilityResponse
=
826 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
827 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
828 if (connectorStatus
?.transactionStarted
=== true) {
829 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
831 connectorStatus
.availability
= availabilityType
;
832 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
833 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
839 responses
.push(response
);
841 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
842 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
844 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
847 public static setChargingProfile(
848 chargingStation
: ChargingStation
,
850 cp
: OCPP16ChargingProfile
,
852 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
854 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
856 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
859 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
862 `${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`,
864 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
866 let cpReplaced
= false;
867 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
869 .getConnectorStatus(connectorId
)
870 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
872 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
873 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
874 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
876 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
881 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
884 public static clearChargingProfiles
= (
885 chargingStation
: ChargingStation
,
886 commandPayload
: ClearChargingProfileRequest
,
887 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
889 let clearedCP
= false;
890 if (isNotEmptyArray(chargingProfiles
)) {
891 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
892 let clearCurrentCP
= false;
893 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
894 clearCurrentCP
= true;
897 !commandPayload
.chargingProfilePurpose
&&
898 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
900 clearCurrentCP
= true;
903 !chargingProfile
.stackLevel
&&
904 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
906 clearCurrentCP
= true;
909 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
910 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
912 clearCurrentCP
= true;
914 if (clearCurrentCP
) {
915 chargingProfiles
.splice(index
, 1);
917 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
927 public static parseJsonSchemaFile
<T
extends JsonType
>(
928 relativePath
: string,
931 ): JSONSchemaType
<T
> {
932 return super.parseJsonSchemaFile
<T
>(
934 OCPPVersion
.VERSION_16
,
940 private static buildSampledValue(
941 sampledValueTemplate
: SampledValueTemplate
,
943 context
?: MeterValueContext
,
944 phase
?: OCPP16MeterValuePhase
,
945 ): OCPP16SampledValue
{
946 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
947 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
948 const sampledValueLocation
=
949 sampledValueTemplate
?.location
??
950 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
951 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
953 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
954 unit
: sampledValueTemplate
.unit
,
956 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
957 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
958 measurand
: sampledValueTemplate
.measurand
,
960 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
961 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
962 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
963 } as OCPP16SampledValue
;
966 private static checkMeasurandPowerDivider(
967 chargingStation
: ChargingStation
,
968 measurandType
: OCPP16MeterValueMeasurand
,
970 if (isUndefined(chargingStation
.powerDivider
)) {
971 const errMsg
= `MeterValues measurand ${
972 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
973 }: powerDivider is undefined`;
974 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
975 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
976 } else if (chargingStation
?.powerDivider
<= 0) {
977 const errMsg
= `MeterValues measurand ${
978 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
979 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
980 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
981 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
985 private static getMeasurandDefaultLocation(
986 measurandType
: OCPP16MeterValueMeasurand
,
987 ): MeterValueLocation
| undefined {
988 switch (measurandType
) {
989 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
990 return MeterValueLocation
.EV
;
994 // private static getMeasurandDefaultUnit(
995 // measurandType: OCPP16MeterValueMeasurand,
996 // ): MeterValueUnit | undefined {
997 // switch (measurandType) {
998 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
999 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1000 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1001 // return MeterValueUnit.AMP;
1002 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1003 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1004 // return MeterValueUnit.WATT_HOUR;
1005 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1006 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1007 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1008 // return MeterValueUnit.WATT;
1009 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1010 // return MeterValueUnit.PERCENT;
1011 // case OCPP16MeterValueMeasurand.VOLTAGE:
1012 // return MeterValueUnit.VOLT;