1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
5 import { OCPP16Constants
} from
'./OCPP16Constants';
10 } from
'../../../charging-station';
11 import { OCPPError
} from
'../../../exception';
13 type ClearChargingProfileRequest
,
18 type MeasurandPerPhaseSampledValueTemplates
,
23 OCPP16AuthorizationStatus
,
24 OCPP16AvailabilityType
,
25 type OCPP16ChangeAvailabilityResponse
,
26 OCPP16ChargePointStatus
,
27 type OCPP16ChargingProfile
,
28 type OCPP16IncomingRequestCommand
,
29 type OCPP16MeterValue
,
30 OCPP16MeterValueMeasurand
,
31 OCPP16MeterValuePhase
,
33 type OCPP16SampledValue
,
34 OCPP16StandardParametersKey
,
35 OCPP16StopTransactionReason
,
36 type OCPP16SupportedFeatureProfiles
,
38 type SampledValueTemplate
,
40 } from
'../../../types';
47 getRandomFloatFluctuatedRounded
,
48 getRandomFloatRounded
,
55 } from
'../../../utils';
56 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
58 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
59 public static checkFeatureProfile(
60 chargingStation
: ChargingStation
,
61 featureProfile
: OCPP16SupportedFeatureProfiles
,
62 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
64 if (!hasFeatureProfile(chargingStation
, featureProfile
)) {
66 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
67 OCPP16StandardParametersKey.SupportedFeatureProfiles
75 public static buildMeterValue(
76 chargingStation
: ChargingStation
,
78 transactionId
: number,
82 const meterValue
: OCPP16MeterValue
= {
83 timestamp
: new Date(),
86 const connector
= chargingStation
.getConnectorStatus(connectorId
);
88 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
91 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
93 if (socSampledValueTemplate
) {
94 const socMaximumValue
= 100;
95 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
96 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
97 ? getRandomFloatFluctuatedRounded(
98 parseInt(socSampledValueTemplate
.value
),
99 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
101 : getRandomInteger(socMaximumValue
, socMinimumValue
);
102 meterValue
.sampledValue
.push(
103 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
105 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
107 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
108 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
112 `${chargingStation.logPrefix()} MeterValues measurand ${
113 meterValue.sampledValue[sampledValuesIndex].measurand ??
114 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
115 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
116 meterValue.sampledValue[sampledValuesIndex].value
117 }/${socMaximumValue}}`,
122 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
125 OCPP16MeterValueMeasurand
.VOLTAGE
,
127 if (voltageSampledValueTemplate
) {
128 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
129 ? parseInt(voltageSampledValueTemplate
.value
)
130 : chargingStation
.getVoltageOut();
131 const fluctuationPercent
=
132 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
133 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
134 voltageSampledValueTemplateValue
,
138 chargingStation
.getNumberOfPhases() !== 3 ||
139 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
141 meterValue
.sampledValue
.push(
142 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
147 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
150 const phaseLineToNeutralValue
= `L${phase}-N`;
151 const voltagePhaseLineToNeutralSampledValueTemplate
=
152 OCPP16ServiceUtils
.getSampledValueTemplate(
155 OCPP16MeterValueMeasurand
.VOLTAGE
,
156 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
158 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
159 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
160 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
161 voltagePhaseLineToNeutralSampledValueTemplate
.value
162 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
163 : chargingStation
.getVoltageOut();
164 const fluctuationPhaseToNeutralPercent
=
165 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
166 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
167 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
168 voltagePhaseLineToNeutralSampledValueTemplateValue
,
169 fluctuationPhaseToNeutralPercent
,
172 meterValue
.sampledValue
.push(
173 OCPP16ServiceUtils
.buildSampledValue(
174 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
175 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
177 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
180 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
181 const phaseLineToLineValue
= `L${phase}-L${
182 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
183 ? (phase + 1) % chargingStation.getNumberOfPhases()
184 : chargingStation.getNumberOfPhases()
186 const voltagePhaseLineToLineSampledValueTemplate
=
187 OCPP16ServiceUtils
.getSampledValueTemplate(
190 OCPP16MeterValueMeasurand
.VOLTAGE
,
191 phaseLineToLineValue
as OCPP16MeterValuePhase
,
193 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
194 if (voltagePhaseLineToLineSampledValueTemplate
) {
195 const voltagePhaseLineToLineSampledValueTemplateValue
=
196 voltagePhaseLineToLineSampledValueTemplate
.value
197 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
198 : Voltage
.VOLTAGE_400
;
199 const fluctuationPhaseLineToLinePercent
=
200 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
201 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
202 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
203 voltagePhaseLineToLineSampledValueTemplateValue
,
204 fluctuationPhaseLineToLinePercent
,
207 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
211 meterValue
.sampledValue
.push(
212 OCPP16ServiceUtils
.buildSampledValue(
213 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
214 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
216 phaseLineToLineValue
as OCPP16MeterValuePhase
,
222 // Power.Active.Import measurand
223 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
226 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
228 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
229 if (chargingStation
.getNumberOfPhases() === 3) {
230 powerPerPhaseSampledValueTemplates
= {
231 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
234 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
235 OCPP16MeterValuePhase
.L1_N
,
237 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
240 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
241 OCPP16MeterValuePhase
.L2_N
,
243 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
246 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
247 OCPP16MeterValuePhase
.L3_N
,
251 if (powerSampledValueTemplate
) {
252 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
254 powerSampledValueTemplate
.measurand
!,
256 const errMsg
= `MeterValues measurand ${
257 powerSampledValueTemplate.measurand ??
258 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
259 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
260 chargingStation.templateFile
261 }, cannot calculate ${
262 powerSampledValueTemplate.measurand ??
263 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
265 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
266 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
267 const connectorMaximumAvailablePower
=
268 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
269 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
270 const connectorMaximumPowerPerPhase
= Math.round(
271 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
273 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
!) ?? 0;
274 const connectorMinimumPowerPerPhase
= Math.round(
275 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
277 switch (chargingStation
.getCurrentOutType()) {
279 if (chargingStation
.getNumberOfPhases() === 3) {
280 const defaultFluctuatedPowerPerPhase
=
281 powerSampledValueTemplate
.value
&&
282 getRandomFloatFluctuatedRounded(
283 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
284 powerSampledValueTemplate
.value
,
285 connectorMaximumPower
/ unitDivider
,
286 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
287 ) / chargingStation
.getNumberOfPhases(),
288 powerSampledValueTemplate
.fluctuationPercent
??
289 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
291 const phase1FluctuatedValue
=
292 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
293 getRandomFloatFluctuatedRounded(
294 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
295 powerPerPhaseSampledValueTemplates
.L1
.value
,
296 connectorMaximumPowerPerPhase
/ unitDivider
,
297 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
299 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
300 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
302 const phase2FluctuatedValue
=
303 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
304 getRandomFloatFluctuatedRounded(
305 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
306 powerPerPhaseSampledValueTemplates
.L2
.value
,
307 connectorMaximumPowerPerPhase
/ unitDivider
,
308 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
310 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
311 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
313 const phase3FluctuatedValue
=
314 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
315 getRandomFloatFluctuatedRounded(
316 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
317 powerPerPhaseSampledValueTemplates
.L3
.value
,
318 connectorMaximumPowerPerPhase
/ unitDivider
,
319 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
321 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
322 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
324 powerMeasurandValues
.L1
=
325 (phase1FluctuatedValue
as number) ??
326 (defaultFluctuatedPowerPerPhase
as number) ??
327 getRandomFloatRounded(
328 connectorMaximumPowerPerPhase
/ unitDivider
,
329 connectorMinimumPowerPerPhase
/ unitDivider
,
331 powerMeasurandValues
.L2
=
332 (phase2FluctuatedValue
as number) ??
333 (defaultFluctuatedPowerPerPhase
as number) ??
334 getRandomFloatRounded(
335 connectorMaximumPowerPerPhase
/ unitDivider
,
336 connectorMinimumPowerPerPhase
/ unitDivider
,
338 powerMeasurandValues
.L3
=
339 (phase3FluctuatedValue
as number) ??
340 (defaultFluctuatedPowerPerPhase
as number) ??
341 getRandomFloatRounded(
342 connectorMaximumPowerPerPhase
/ unitDivider
,
343 connectorMinimumPowerPerPhase
/ unitDivider
,
346 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
347 ? getRandomFloatFluctuatedRounded(
348 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
349 powerSampledValueTemplate
.value
,
350 connectorMaximumPower
/ unitDivider
,
351 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
353 powerSampledValueTemplate
.fluctuationPercent
??
354 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
356 : getRandomFloatRounded(
357 connectorMaximumPower
/ unitDivider
,
358 connectorMinimumPower
/ unitDivider
,
360 powerMeasurandValues
.L2
= 0;
361 powerMeasurandValues
.L3
= 0;
363 powerMeasurandValues
.allPhases
= roundTo(
364 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
369 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
370 ? getRandomFloatFluctuatedRounded(
371 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
372 powerSampledValueTemplate
.value
,
373 connectorMaximumPower
/ unitDivider
,
374 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
376 powerSampledValueTemplate
.fluctuationPercent
??
377 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
379 : getRandomFloatRounded(
380 connectorMaximumPower
/ unitDivider
,
381 connectorMinimumPower
/ unitDivider
,
385 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
386 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
388 meterValue
.sampledValue
.push(
389 OCPP16ServiceUtils
.buildSampledValue(
390 powerSampledValueTemplate
,
391 powerMeasurandValues
.allPhases
,
394 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
395 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
396 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
398 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
399 connectorMaximumPowerRounded
||
400 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
401 connectorMinimumPowerRounded
||
405 `${chargingStation.logPrefix()} MeterValues measurand ${
406 meterValue.sampledValue[sampledValuesIndex].measurand ??
407 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
408 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
409 meterValue.sampledValue[sampledValuesIndex].value
410 }/${connectorMaximumPowerRounded}`,
415 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
418 const phaseValue
= `L${phase}-N`;
419 meterValue
.sampledValue
.push(
420 OCPP16ServiceUtils
.buildSampledValue(
421 powerPerPhaseSampledValueTemplates
[
422 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
423 ]! ?? powerSampledValueTemplate
,
424 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
426 phaseValue
as OCPP16MeterValuePhase
,
429 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
430 const connectorMaximumPowerPerPhaseRounded
= roundTo(
431 connectorMaximumPowerPerPhase
/ unitDivider
,
434 const connectorMinimumPowerPerPhaseRounded
= roundTo(
435 connectorMinimumPowerPerPhase
/ unitDivider
,
439 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
440 connectorMaximumPowerPerPhaseRounded
||
441 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
442 connectorMinimumPowerPerPhaseRounded
||
446 `${chargingStation.logPrefix()} MeterValues measurand ${
447 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
448 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
450 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
451 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
452 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
453 }/${connectorMaximumPowerPerPhaseRounded}`,
458 // Current.Import measurand
459 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
462 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
464 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
465 if (chargingStation
.getNumberOfPhases() === 3) {
466 currentPerPhaseSampledValueTemplates
= {
467 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
470 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
471 OCPP16MeterValuePhase
.L1
,
473 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
476 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
477 OCPP16MeterValuePhase
.L2
,
479 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
482 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
483 OCPP16MeterValuePhase
.L3
,
487 if (currentSampledValueTemplate
) {
488 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
490 currentSampledValueTemplate
.measurand
!,
492 const errMsg
= `MeterValues measurand ${
493 currentSampledValueTemplate.measurand ??
494 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
495 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
496 chargingStation.templateFile
497 }, cannot calculate ${
498 currentSampledValueTemplate.measurand ??
499 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
501 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
502 const connectorMaximumAvailablePower
=
503 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
504 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
505 let connectorMaximumAmperage
: number;
506 switch (chargingStation
.getCurrentOutType()) {
508 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
509 chargingStation
.getNumberOfPhases(),
510 connectorMaximumAvailablePower
,
511 chargingStation
.getVoltageOut(),
513 if (chargingStation
.getNumberOfPhases() === 3) {
514 const defaultFluctuatedAmperagePerPhase
=
515 currentSampledValueTemplate
.value
&&
516 getRandomFloatFluctuatedRounded(
517 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
518 currentSampledValueTemplate
.value
,
519 connectorMaximumAmperage
,
520 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
522 currentSampledValueTemplate
.fluctuationPercent
??
523 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
525 const phase1FluctuatedValue
=
526 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
527 getRandomFloatFluctuatedRounded(
528 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
529 currentPerPhaseSampledValueTemplates
.L1
.value
,
530 connectorMaximumAmperage
,
531 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
533 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
534 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
536 const phase2FluctuatedValue
=
537 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
538 getRandomFloatFluctuatedRounded(
539 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
540 currentPerPhaseSampledValueTemplates
.L2
.value
,
541 connectorMaximumAmperage
,
542 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
544 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
545 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
547 const phase3FluctuatedValue
=
548 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
549 getRandomFloatFluctuatedRounded(
550 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
551 currentPerPhaseSampledValueTemplates
.L3
.value
,
552 connectorMaximumAmperage
,
553 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
555 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
556 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
558 currentMeasurandValues
.L1
=
559 (phase1FluctuatedValue
as number) ??
560 (defaultFluctuatedAmperagePerPhase
as number) ??
561 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
562 currentMeasurandValues
.L2
=
563 (phase2FluctuatedValue
as number) ??
564 (defaultFluctuatedAmperagePerPhase
as number) ??
565 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
566 currentMeasurandValues
.L3
=
567 (phase3FluctuatedValue
as number) ??
568 (defaultFluctuatedAmperagePerPhase
as number) ??
569 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
571 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
572 ? getRandomFloatFluctuatedRounded(
573 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
574 currentSampledValueTemplate
.value
,
575 connectorMaximumAmperage
,
576 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
578 currentSampledValueTemplate
.fluctuationPercent
??
579 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
581 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
582 currentMeasurandValues
.L2
= 0;
583 currentMeasurandValues
.L3
= 0;
585 currentMeasurandValues
.allPhases
= roundTo(
586 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
587 chargingStation
.getNumberOfPhases(),
592 connectorMaximumAmperage
= DCElectricUtils
.amperage(
593 connectorMaximumAvailablePower
,
594 chargingStation
.getVoltageOut(),
596 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
597 ? getRandomFloatFluctuatedRounded(
598 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
599 currentSampledValueTemplate
.value
,
600 connectorMaximumAmperage
,
601 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
603 currentSampledValueTemplate
.fluctuationPercent
??
604 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
606 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
609 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
610 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
612 meterValue
.sampledValue
.push(
613 OCPP16ServiceUtils
.buildSampledValue(
614 currentSampledValueTemplate
,
615 currentMeasurandValues
.allPhases
,
618 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
620 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
621 connectorMaximumAmperage
||
622 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
623 connectorMinimumAmperage
||
627 `${chargingStation.logPrefix()} MeterValues measurand ${
628 meterValue.sampledValue[sampledValuesIndex].measurand ??
629 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
630 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
631 meterValue.sampledValue[sampledValuesIndex].value
632 }/${connectorMaximumAmperage}`,
637 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
640 const phaseValue
= `L${phase}`;
641 meterValue
.sampledValue
.push(
642 OCPP16ServiceUtils
.buildSampledValue(
643 currentPerPhaseSampledValueTemplates
[
644 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
645 ]! ?? currentSampledValueTemplate
,
646 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
648 phaseValue
as OCPP16MeterValuePhase
,
651 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
653 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
654 connectorMaximumAmperage
||
655 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
656 connectorMinimumAmperage
||
660 `${chargingStation.logPrefix()} MeterValues measurand ${
661 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
662 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
664 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
665 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
666 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
667 }/${connectorMaximumAmperage}`,
672 // Energy.Active.Import.Register measurand (default)
673 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
677 if (energySampledValueTemplate
) {
678 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
680 energySampledValueTemplate
.measurand
!,
683 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
684 const connectorMaximumAvailablePower
=
685 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
686 const connectorMaximumEnergyRounded
= roundTo(
687 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
690 const energyValueRounded
= energySampledValueTemplate
.value
691 ? // Cumulate the fluctuated value around the static one
692 getRandomFloatFluctuatedRounded(
693 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
694 energySampledValueTemplate
.value
,
695 connectorMaximumEnergyRounded
,
697 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
698 unitMultiplier
: unitDivider
,
701 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
703 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
704 // Persist previous value on connector
707 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
708 connector
.energyActiveImportRegisterValue
! >= 0 &&
709 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
710 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
712 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
713 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
715 connector
.energyActiveImportRegisterValue
= 0;
716 connector
.transactionEnergyActiveImportRegisterValue
= 0;
719 meterValue
.sampledValue
.push(
720 OCPP16ServiceUtils
.buildSampledValue(
721 energySampledValueTemplate
,
723 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
729 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
730 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
732 `${chargingStation.logPrefix()} MeterValues measurand ${
733 meterValue.sampledValue[sampledValuesIndex].measurand ??
734 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
735 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
742 public static buildTransactionBeginMeterValue(
743 chargingStation
: ChargingStation
,
746 ): OCPP16MeterValue
{
747 const meterValue
: OCPP16MeterValue
= {
748 timestamp
: new Date(),
751 // Energy.Active.Import.Register measurand (default)
752 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
756 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
757 meterValue
.sampledValue
.push(
758 OCPP16ServiceUtils
.buildSampledValue(
759 sampledValueTemplate
!,
760 roundTo((meterStart
?? 0) / unitDivider
, 4),
761 MeterValueContext
.TRANSACTION_BEGIN
,
767 public static buildTransactionEndMeterValue(
768 chargingStation
: ChargingStation
,
771 ): OCPP16MeterValue
{
772 const meterValue
: OCPP16MeterValue
= {
773 timestamp
: new Date(),
776 // Energy.Active.Import.Register measurand (default)
777 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
781 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
782 meterValue
.sampledValue
.push(
783 OCPP16ServiceUtils
.buildSampledValue(
784 sampledValueTemplate
!,
785 roundTo((meterStop
?? 0) / unitDivider
, 4),
786 MeterValueContext
.TRANSACTION_END
,
792 public static buildTransactionDataMeterValues(
793 transactionBeginMeterValue
: OCPP16MeterValue
,
794 transactionEndMeterValue
: OCPP16MeterValue
,
795 ): OCPP16MeterValue
[] {
796 const meterValues
: OCPP16MeterValue
[] = [];
797 meterValues
.push(transactionBeginMeterValue
);
798 meterValues
.push(transactionEndMeterValue
);
802 public static remoteStopTransaction
= async (
803 chargingStation
: ChargingStation
,
805 ): Promise
<GenericResponse
> => {
806 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
809 OCPP16ChargePointStatus
.Finishing
,
811 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
813 OCPP16StopTransactionReason
.REMOTE
,
815 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
816 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
818 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
821 public static changeAvailability
= async (
822 chargingStation
: ChargingStation
,
823 connectorIds
: number[],
824 chargePointStatus
: OCPP16ChargePointStatus
,
825 availabilityType
: OCPP16AvailabilityType
,
826 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
827 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
828 for (const connectorId
of connectorIds
) {
829 let response
: OCPP16ChangeAvailabilityResponse
=
830 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
831 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
832 if (connectorStatus
?.transactionStarted
=== true) {
833 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
835 connectorStatus
.availability
= availabilityType
;
836 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
837 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
843 responses
.push(response
);
845 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
846 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
848 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
851 public static setChargingProfile(
852 chargingStation
: ChargingStation
,
854 cp
: OCPP16ChargingProfile
,
856 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
858 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
860 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
863 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
866 `${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`,
868 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
870 let cpReplaced
= false;
871 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
873 .getConnectorStatus(connectorId
)
874 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
876 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
877 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
878 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
880 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
885 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
888 public static clearChargingProfiles
= (
889 chargingStation
: ChargingStation
,
890 commandPayload
: ClearChargingProfileRequest
,
891 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
893 let clearedCP
= false;
894 if (isNotEmptyArray(chargingProfiles
)) {
895 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
896 let clearCurrentCP
= false;
897 if (chargingProfile
.chargingProfileId
=== commandPayload
.id
) {
898 clearCurrentCP
= true;
901 !commandPayload
.chargingProfilePurpose
&&
902 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
904 clearCurrentCP
= true;
907 !chargingProfile
.stackLevel
&&
908 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
910 clearCurrentCP
= true;
913 chargingProfile
.stackLevel
=== commandPayload
.stackLevel
&&
914 chargingProfile
.chargingProfilePurpose
=== commandPayload
.chargingProfilePurpose
916 clearCurrentCP
= true;
918 if (clearCurrentCP
) {
919 chargingProfiles
.splice(index
, 1);
921 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
931 public static hasReservation
= (
932 chargingStation
: ChargingStation
,
936 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
937 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
939 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
940 OCPP16ChargePointStatus
.Reserved
&&
941 connectorReservation
&&
942 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
943 (hasReservationExpired(connectorReservation
) || connectorReservation
?.idTag
!== idTag
)) ||
944 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
945 chargingStationReservation
&&
946 (hasReservationExpired(chargingStationReservation
) ||
947 chargingStationReservation
?.idTag
!== idTag
))
954 public static parseJsonSchemaFile
<T
extends JsonType
>(
955 relativePath
: string,
958 ): JSONSchemaType
<T
> {
959 return super.parseJsonSchemaFile
<T
>(
961 OCPPVersion
.VERSION_16
,
967 private static buildSampledValue(
968 sampledValueTemplate
: SampledValueTemplate
,
970 context
?: MeterValueContext
,
971 phase
?: OCPP16MeterValuePhase
,
972 ): OCPP16SampledValue
{
973 const sampledValueValue
= value
?? sampledValueTemplate
?.value
?? null;
974 const sampledValueContext
= context
?? sampledValueTemplate
?.context
?? null;
975 const sampledValueLocation
=
976 sampledValueTemplate
?.location
??
977 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
978 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
?? null;
980 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
981 unit
: sampledValueTemplate
.unit
,
983 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
984 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
985 measurand
: sampledValueTemplate
.measurand
,
987 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
988 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
989 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
990 } as OCPP16SampledValue
;
993 private static checkMeasurandPowerDivider(
994 chargingStation
: ChargingStation
,
995 measurandType
: OCPP16MeterValueMeasurand
,
997 if (isUndefined(chargingStation
.powerDivider
)) {
998 const errMsg
= `MeterValues measurand ${
999 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1000 }: powerDivider is undefined`;
1001 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1002 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1003 } else if (chargingStation
?.powerDivider
<= 0) {
1004 const errMsg
= `MeterValues measurand ${
1005 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1006 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1007 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1008 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1012 private static getMeasurandDefaultLocation(
1013 measurandType
: OCPP16MeterValueMeasurand
,
1014 ): MeterValueLocation
| undefined {
1015 switch (measurandType
) {
1016 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1017 return MeterValueLocation
.EV
;
1021 // private static getMeasurandDefaultUnit(
1022 // measurandType: OCPP16MeterValueMeasurand,
1023 // ): MeterValueUnit | undefined {
1024 // switch (measurandType) {
1025 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1026 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1027 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1028 // return MeterValueUnit.AMP;
1029 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1030 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1031 // return MeterValueUnit.WATT_HOUR;
1032 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1033 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1034 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1035 // return MeterValueUnit.WATT;
1036 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1037 // return MeterValueUnit.PERCENT;
1038 // case OCPP16MeterValueMeasurand.VOLTAGE:
1039 // return MeterValueUnit.VOLT;