1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
6 areIntervalsOverlapping
,
13 import { OCPP16Constants
} from
'./OCPP16Constants';
17 hasReservationExpired
,
18 } from
'../../../charging-station';
19 import { OCPPError
} from
'../../../exception';
21 type ClearChargingProfileRequest
,
26 type MeasurandPerPhaseSampledValueTemplates
,
31 OCPP16AuthorizationStatus
,
32 OCPP16AvailabilityType
,
33 type OCPP16ChangeAvailabilityResponse
,
34 OCPP16ChargePointStatus
,
35 type OCPP16ChargingProfile
,
36 type OCPP16ChargingSchedule
,
37 type OCPP16IncomingRequestCommand
,
38 type OCPP16MeterValue
,
39 OCPP16MeterValueMeasurand
,
40 OCPP16MeterValuePhase
,
42 type OCPP16SampledValue
,
43 OCPP16StandardParametersKey
,
44 OCPP16StopTransactionReason
,
45 type OCPP16SupportedFeatureProfiles
,
47 type SampledValueTemplate
,
49 } from
'../../../types';
56 getRandomFloatFluctuatedRounded
,
57 getRandomFloatRounded
,
64 } from
'../../../utils';
65 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
67 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
68 public static checkFeatureProfile(
69 chargingStation
: ChargingStation
,
70 featureProfile
: OCPP16SupportedFeatureProfiles
,
71 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
73 if (!hasFeatureProfile(chargingStation
, featureProfile
)) {
75 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
76 OCPP16StandardParametersKey.SupportedFeatureProfiles
84 public static buildMeterValue(
85 chargingStation
: ChargingStation
,
87 transactionId
: number,
91 const meterValue
: OCPP16MeterValue
= {
92 timestamp
: new Date(),
95 const connector
= chargingStation
.getConnectorStatus(connectorId
);
97 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
100 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
102 if (socSampledValueTemplate
) {
103 const socMaximumValue
= 100;
104 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
105 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
106 ? getRandomFloatFluctuatedRounded(
107 parseInt(socSampledValueTemplate
.value
),
108 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
110 : getRandomInteger(socMaximumValue
, socMinimumValue
);
111 meterValue
.sampledValue
.push(
112 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
114 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
116 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
117 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
121 `${chargingStation.logPrefix()} MeterValues measurand ${
122 meterValue.sampledValue[sampledValuesIndex].measurand ??
123 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
124 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
125 meterValue.sampledValue[sampledValuesIndex].value
126 }/${socMaximumValue}`,
131 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
134 OCPP16MeterValueMeasurand
.VOLTAGE
,
136 if (voltageSampledValueTemplate
) {
137 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
138 ? parseInt(voltageSampledValueTemplate
.value
)
139 : chargingStation
.stationInfo
.voltageOut
!;
140 const fluctuationPercent
=
141 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
142 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
143 voltageSampledValueTemplateValue
,
147 chargingStation
.getNumberOfPhases() !== 3 ||
148 (chargingStation
.getNumberOfPhases() === 3 &&
149 chargingStation
.stationInfo
?.mainVoltageMeterValues
)
151 meterValue
.sampledValue
.push(
152 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
157 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
160 const phaseLineToNeutralValue
= `L${phase}-N`;
161 const voltagePhaseLineToNeutralSampledValueTemplate
=
162 OCPP16ServiceUtils
.getSampledValueTemplate(
165 OCPP16MeterValueMeasurand
.VOLTAGE
,
166 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
168 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
169 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
170 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
171 voltagePhaseLineToNeutralSampledValueTemplate
.value
172 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
173 : chargingStation
.stationInfo
.voltageOut
!;
174 const fluctuationPhaseToNeutralPercent
=
175 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
176 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
177 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
178 voltagePhaseLineToNeutralSampledValueTemplateValue
,
179 fluctuationPhaseToNeutralPercent
,
182 meterValue
.sampledValue
.push(
183 OCPP16ServiceUtils
.buildSampledValue(
184 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
185 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
187 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
190 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
) {
191 const phaseLineToLineValue
= `L${phase}-L${
192 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
193 ? (phase + 1) % chargingStation.getNumberOfPhases()
194 : chargingStation.getNumberOfPhases()
196 const voltagePhaseLineToLineSampledValueTemplate
=
197 OCPP16ServiceUtils
.getSampledValueTemplate(
200 OCPP16MeterValueMeasurand
.VOLTAGE
,
201 phaseLineToLineValue
as OCPP16MeterValuePhase
,
203 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
204 if (voltagePhaseLineToLineSampledValueTemplate
) {
205 const voltagePhaseLineToLineSampledValueTemplateValue
=
206 voltagePhaseLineToLineSampledValueTemplate
.value
207 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
208 : Voltage
.VOLTAGE_400
;
209 const fluctuationPhaseLineToLinePercent
=
210 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
211 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
212 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
213 voltagePhaseLineToLineSampledValueTemplateValue
,
214 fluctuationPhaseLineToLinePercent
,
217 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
221 meterValue
.sampledValue
.push(
222 OCPP16ServiceUtils
.buildSampledValue(
223 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
224 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
226 phaseLineToLineValue
as OCPP16MeterValuePhase
,
232 // Power.Active.Import measurand
233 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
236 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
238 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
239 if (chargingStation
.getNumberOfPhases() === 3) {
240 powerPerPhaseSampledValueTemplates
= {
241 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
244 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
245 OCPP16MeterValuePhase
.L1_N
,
247 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
250 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
251 OCPP16MeterValuePhase
.L2_N
,
253 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
256 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
257 OCPP16MeterValuePhase
.L3_N
,
261 if (powerSampledValueTemplate
) {
262 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
264 powerSampledValueTemplate
.measurand
!,
266 const errMsg
= `MeterValues measurand ${
267 powerSampledValueTemplate.measurand ??
268 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
269 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
270 chargingStation.templateFile
271 }, cannot calculate ${
272 powerSampledValueTemplate.measurand ??
273 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
275 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
276 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
277 const connectorMaximumAvailablePower
=
278 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
279 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
280 const connectorMaximumPowerPerPhase
= Math.round(
281 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
283 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
!) ?? 0;
284 const connectorMinimumPowerPerPhase
= Math.round(
285 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
287 switch (chargingStation
.stationInfo
?.currentOutType
) {
289 if (chargingStation
.getNumberOfPhases() === 3) {
290 const defaultFluctuatedPowerPerPhase
=
291 powerSampledValueTemplate
.value
&&
292 getRandomFloatFluctuatedRounded(
293 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
294 powerSampledValueTemplate
.value
,
295 connectorMaximumPower
/ unitDivider
,
298 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
300 ) / chargingStation
.getNumberOfPhases(),
301 powerSampledValueTemplate
.fluctuationPercent
??
302 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
304 const phase1FluctuatedValue
=
305 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
306 getRandomFloatFluctuatedRounded(
307 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
308 powerPerPhaseSampledValueTemplates
.L1
.value
,
309 connectorMaximumPowerPerPhase
/ unitDivider
,
312 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
315 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
316 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
318 const phase2FluctuatedValue
=
319 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
320 getRandomFloatFluctuatedRounded(
321 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
322 powerPerPhaseSampledValueTemplates
.L2
.value
,
323 connectorMaximumPowerPerPhase
/ unitDivider
,
326 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
329 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
330 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
332 const phase3FluctuatedValue
=
333 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
334 getRandomFloatFluctuatedRounded(
335 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
336 powerPerPhaseSampledValueTemplates
.L3
.value
,
337 connectorMaximumPowerPerPhase
/ unitDivider
,
340 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
343 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
344 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
346 powerMeasurandValues
.L1
=
347 (phase1FluctuatedValue
as number) ??
348 (defaultFluctuatedPowerPerPhase
as number) ??
349 getRandomFloatRounded(
350 connectorMaximumPowerPerPhase
/ unitDivider
,
351 connectorMinimumPowerPerPhase
/ unitDivider
,
353 powerMeasurandValues
.L2
=
354 (phase2FluctuatedValue
as number) ??
355 (defaultFluctuatedPowerPerPhase
as number) ??
356 getRandomFloatRounded(
357 connectorMaximumPowerPerPhase
/ unitDivider
,
358 connectorMinimumPowerPerPhase
/ unitDivider
,
360 powerMeasurandValues
.L3
=
361 (phase3FluctuatedValue
as number) ??
362 (defaultFluctuatedPowerPerPhase
as number) ??
363 getRandomFloatRounded(
364 connectorMaximumPowerPerPhase
/ unitDivider
,
365 connectorMinimumPowerPerPhase
/ unitDivider
,
368 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
369 ? getRandomFloatFluctuatedRounded(
370 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
371 powerSampledValueTemplate
.value
,
372 connectorMaximumPower
/ unitDivider
,
375 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
378 powerSampledValueTemplate
.fluctuationPercent
??
379 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
381 : getRandomFloatRounded(
382 connectorMaximumPower
/ unitDivider
,
383 connectorMinimumPower
/ unitDivider
,
385 powerMeasurandValues
.L2
= 0;
386 powerMeasurandValues
.L3
= 0;
388 powerMeasurandValues
.allPhases
= roundTo(
389 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
394 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
395 ? getRandomFloatFluctuatedRounded(
396 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
397 powerSampledValueTemplate
.value
,
398 connectorMaximumPower
/ unitDivider
,
401 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
404 powerSampledValueTemplate
.fluctuationPercent
??
405 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
407 : getRandomFloatRounded(
408 connectorMaximumPower
/ unitDivider
,
409 connectorMinimumPower
/ unitDivider
,
413 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
414 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
416 meterValue
.sampledValue
.push(
417 OCPP16ServiceUtils
.buildSampledValue(
418 powerSampledValueTemplate
,
419 powerMeasurandValues
.allPhases
,
422 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
423 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
424 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
426 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
427 connectorMaximumPowerRounded
||
428 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
429 connectorMinimumPowerRounded
||
433 `${chargingStation.logPrefix()} MeterValues measurand ${
434 meterValue.sampledValue[sampledValuesIndex].measurand ??
435 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
436 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
437 meterValue.sampledValue[sampledValuesIndex].value
438 }/${connectorMaximumPowerRounded}`,
443 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
446 const phaseValue
= `L${phase}-N`;
447 meterValue
.sampledValue
.push(
448 OCPP16ServiceUtils
.buildSampledValue(
449 powerPerPhaseSampledValueTemplates
[
450 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
451 ]! ?? powerSampledValueTemplate
,
452 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
454 phaseValue
as OCPP16MeterValuePhase
,
457 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
458 const connectorMaximumPowerPerPhaseRounded
= roundTo(
459 connectorMaximumPowerPerPhase
/ unitDivider
,
462 const connectorMinimumPowerPerPhaseRounded
= roundTo(
463 connectorMinimumPowerPerPhase
/ unitDivider
,
467 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
468 connectorMaximumPowerPerPhaseRounded
||
469 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
470 connectorMinimumPowerPerPhaseRounded
||
474 `${chargingStation.logPrefix()} MeterValues measurand ${
475 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
476 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
478 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
479 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
480 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
481 }/${connectorMaximumPowerPerPhaseRounded}`,
486 // Current.Import measurand
487 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
490 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
492 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
493 if (chargingStation
.getNumberOfPhases() === 3) {
494 currentPerPhaseSampledValueTemplates
= {
495 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
498 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
499 OCPP16MeterValuePhase
.L1
,
501 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
504 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
505 OCPP16MeterValuePhase
.L2
,
507 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
510 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
511 OCPP16MeterValuePhase
.L3
,
515 if (currentSampledValueTemplate
) {
516 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
518 currentSampledValueTemplate
.measurand
!,
520 const errMsg
= `MeterValues measurand ${
521 currentSampledValueTemplate.measurand ??
522 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
523 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
524 chargingStation.templateFile
525 }, cannot calculate ${
526 currentSampledValueTemplate.measurand ??
527 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
529 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
530 const connectorMaximumAvailablePower
=
531 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
532 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
533 let connectorMaximumAmperage
: number;
534 switch (chargingStation
.stationInfo
?.currentOutType
) {
536 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
537 chargingStation
.getNumberOfPhases(),
538 connectorMaximumAvailablePower
,
539 chargingStation
.stationInfo
.voltageOut
!,
541 if (chargingStation
.getNumberOfPhases() === 3) {
542 const defaultFluctuatedAmperagePerPhase
=
543 currentSampledValueTemplate
.value
&&
544 getRandomFloatFluctuatedRounded(
545 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
546 currentSampledValueTemplate
.value
,
547 connectorMaximumAmperage
,
550 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
553 currentSampledValueTemplate
.fluctuationPercent
??
554 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
556 const phase1FluctuatedValue
=
557 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
558 getRandomFloatFluctuatedRounded(
559 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
560 currentPerPhaseSampledValueTemplates
.L1
.value
,
561 connectorMaximumAmperage
,
564 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
567 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
568 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
570 const phase2FluctuatedValue
=
571 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
572 getRandomFloatFluctuatedRounded(
573 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
574 currentPerPhaseSampledValueTemplates
.L2
.value
,
575 connectorMaximumAmperage
,
578 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
581 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
582 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
584 const phase3FluctuatedValue
=
585 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
586 getRandomFloatFluctuatedRounded(
587 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
588 currentPerPhaseSampledValueTemplates
.L3
.value
,
589 connectorMaximumAmperage
,
592 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
595 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
596 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
598 currentMeasurandValues
.L1
=
599 (phase1FluctuatedValue
as number) ??
600 (defaultFluctuatedAmperagePerPhase
as number) ??
601 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
602 currentMeasurandValues
.L2
=
603 (phase2FluctuatedValue
as number) ??
604 (defaultFluctuatedAmperagePerPhase
as number) ??
605 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
606 currentMeasurandValues
.L3
=
607 (phase3FluctuatedValue
as number) ??
608 (defaultFluctuatedAmperagePerPhase
as number) ??
609 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
611 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
612 ? getRandomFloatFluctuatedRounded(
613 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
614 currentSampledValueTemplate
.value
,
615 connectorMaximumAmperage
,
618 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
621 currentSampledValueTemplate
.fluctuationPercent
??
622 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
624 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
625 currentMeasurandValues
.L2
= 0;
626 currentMeasurandValues
.L3
= 0;
628 currentMeasurandValues
.allPhases
= roundTo(
629 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
630 chargingStation
.getNumberOfPhases(),
635 connectorMaximumAmperage
= DCElectricUtils
.amperage(
636 connectorMaximumAvailablePower
,
637 chargingStation
.stationInfo
.voltageOut
!,
639 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
640 ? getRandomFloatFluctuatedRounded(
641 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
642 currentSampledValueTemplate
.value
,
643 connectorMaximumAmperage
,
646 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
649 currentSampledValueTemplate
.fluctuationPercent
??
650 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
652 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
655 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
656 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
658 meterValue
.sampledValue
.push(
659 OCPP16ServiceUtils
.buildSampledValue(
660 currentSampledValueTemplate
,
661 currentMeasurandValues
.allPhases
,
664 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
666 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
667 connectorMaximumAmperage
||
668 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
669 connectorMinimumAmperage
||
673 `${chargingStation.logPrefix()} MeterValues measurand ${
674 meterValue.sampledValue[sampledValuesIndex].measurand ??
675 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
676 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
677 meterValue.sampledValue[sampledValuesIndex].value
678 }/${connectorMaximumAmperage}`,
683 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
686 const phaseValue
= `L${phase}`;
687 meterValue
.sampledValue
.push(
688 OCPP16ServiceUtils
.buildSampledValue(
689 currentPerPhaseSampledValueTemplates
[
690 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
691 ]! ?? currentSampledValueTemplate
,
692 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
694 phaseValue
as OCPP16MeterValuePhase
,
697 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
699 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
700 connectorMaximumAmperage
||
701 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
702 connectorMinimumAmperage
||
706 `${chargingStation.logPrefix()} MeterValues measurand ${
707 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
708 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
710 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
711 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
712 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
713 }/${connectorMaximumAmperage}`,
718 // Energy.Active.Import.Register measurand (default)
719 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
723 if (energySampledValueTemplate
) {
724 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
726 energySampledValueTemplate
.measurand
!,
729 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
730 const connectorMaximumAvailablePower
=
731 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
732 const connectorMaximumEnergyRounded
= roundTo(
733 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
736 const energyValueRounded
= energySampledValueTemplate
.value
737 ? // Cumulate the fluctuated value around the static one
738 getRandomFloatFluctuatedRounded(
739 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
740 energySampledValueTemplate
.value
,
741 connectorMaximumEnergyRounded
,
743 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
744 unitMultiplier
: unitDivider
,
747 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
749 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
750 // Persist previous value on connector
753 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
754 connector
.energyActiveImportRegisterValue
! >= 0 &&
755 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
756 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
758 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
759 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
761 connector
.energyActiveImportRegisterValue
= 0;
762 connector
.transactionEnergyActiveImportRegisterValue
= 0;
765 meterValue
.sampledValue
.push(
766 OCPP16ServiceUtils
.buildSampledValue(
767 energySampledValueTemplate
,
769 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
775 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
776 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
778 `${chargingStation.logPrefix()} MeterValues measurand ${
779 meterValue.sampledValue[sampledValuesIndex].measurand ??
780 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
781 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
788 public static buildTransactionBeginMeterValue(
789 chargingStation
: ChargingStation
,
792 ): OCPP16MeterValue
{
793 const meterValue
: OCPP16MeterValue
= {
794 timestamp
: new Date(),
797 // Energy.Active.Import.Register measurand (default)
798 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
802 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
803 meterValue
.sampledValue
.push(
804 OCPP16ServiceUtils
.buildSampledValue(
805 sampledValueTemplate
!,
806 roundTo((meterStart
?? 0) / unitDivider
, 4),
807 MeterValueContext
.TRANSACTION_BEGIN
,
813 public static buildTransactionEndMeterValue(
814 chargingStation
: ChargingStation
,
817 ): OCPP16MeterValue
{
818 const meterValue
: OCPP16MeterValue
= {
819 timestamp
: new Date(),
822 // Energy.Active.Import.Register measurand (default)
823 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
827 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
828 meterValue
.sampledValue
.push(
829 OCPP16ServiceUtils
.buildSampledValue(
830 sampledValueTemplate
!,
831 roundTo((meterStop
?? 0) / unitDivider
, 4),
832 MeterValueContext
.TRANSACTION_END
,
838 public static buildTransactionDataMeterValues(
839 transactionBeginMeterValue
: OCPP16MeterValue
,
840 transactionEndMeterValue
: OCPP16MeterValue
,
841 ): OCPP16MeterValue
[] {
842 const meterValues
: OCPP16MeterValue
[] = [];
843 meterValues
.push(transactionBeginMeterValue
);
844 meterValues
.push(transactionEndMeterValue
);
848 public static remoteStopTransaction
= async (
849 chargingStation
: ChargingStation
,
851 ): Promise
<GenericResponse
> => {
852 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
855 OCPP16ChargePointStatus
.Finishing
,
857 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
859 OCPP16StopTransactionReason
.REMOTE
,
861 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
862 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
864 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
867 public static changeAvailability
= async (
868 chargingStation
: ChargingStation
,
869 connectorIds
: number[],
870 chargePointStatus
: OCPP16ChargePointStatus
,
871 availabilityType
: OCPP16AvailabilityType
,
872 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
873 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
874 for (const connectorId
of connectorIds
) {
875 let response
: OCPP16ChangeAvailabilityResponse
=
876 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
877 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
878 if (connectorStatus
?.transactionStarted
=== true) {
879 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
881 connectorStatus
.availability
= availabilityType
;
882 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
883 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
889 responses
.push(response
);
891 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
892 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
894 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
897 public static setChargingProfile(
898 chargingStation
: ChargingStation
,
900 cp
: OCPP16ChargingProfile
,
902 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
904 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
906 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
909 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
912 `${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 deferred initialization`,
914 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
916 let cpReplaced
= false;
917 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
919 .getConnectorStatus(connectorId
)
920 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
922 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
923 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
924 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
926 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
931 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
934 public static clearChargingProfiles
= (
935 chargingStation
: ChargingStation
,
936 commandPayload
: ClearChargingProfileRequest
,
937 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
939 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
;
940 let clearedCP
= false;
941 if (isNotEmptyArray(chargingProfiles
)) {
942 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
943 let clearCurrentCP
= false;
944 if (chargingProfile
.chargingProfileId
=== id
) {
945 clearCurrentCP
= true;
947 if (!chargingProfilePurpose
&& chargingProfile
.stackLevel
=== stackLevel
) {
948 clearCurrentCP
= true;
950 if (!stackLevel
&& chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
) {
951 clearCurrentCP
= true;
954 chargingProfile
.stackLevel
=== stackLevel
&&
955 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
957 clearCurrentCP
= true;
959 if (clearCurrentCP
) {
960 chargingProfiles
.splice(index
, 1);
962 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
972 public static composeChargingSchedules
= (
973 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
974 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
975 compositeInterval
: Interval
,
976 ): OCPP16ChargingSchedule
| undefined => {
977 if (!chargingScheduleHigher
&& !chargingScheduleLower
) {
980 if (chargingScheduleHigher
&& !chargingScheduleLower
) {
981 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
);
983 if (!chargingScheduleHigher
&& chargingScheduleLower
) {
984 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
);
986 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
987 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
);
988 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
989 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
);
990 const compositeChargingScheduleHigherInterval
: Interval
= {
991 start
: compositeChargingScheduleHigher
!.startSchedule
!,
993 compositeChargingScheduleHigher
!.startSchedule
!,
994 compositeChargingScheduleHigher
!.duration
!,
997 const compositeChargingScheduleLowerInterval
: Interval
= {
998 start
: compositeChargingScheduleLower
!.startSchedule
!,
1000 compositeChargingScheduleLower
!.startSchedule
!,
1001 compositeChargingScheduleLower
!.duration
!,
1004 const higherFirst
= isBefore(
1005 compositeChargingScheduleHigherInterval
.start
,
1006 compositeChargingScheduleLowerInterval
.start
,
1009 !areIntervalsOverlapping(
1010 compositeChargingScheduleHigherInterval
,
1011 compositeChargingScheduleLowerInterval
,
1015 ...compositeChargingScheduleLower
,
1016 ...compositeChargingScheduleHigher
!,
1017 startSchedule
: higherFirst
1018 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1019 : (compositeChargingScheduleLowerInterval
.start
as Date),
1020 duration
: higherFirst
1021 ? differenceInSeconds(
1022 compositeChargingScheduleLowerInterval
.end
,
1023 compositeChargingScheduleHigherInterval
.start
,
1025 : differenceInSeconds(
1026 compositeChargingScheduleHigherInterval
.end
,
1027 compositeChargingScheduleLowerInterval
.start
,
1029 chargingSchedulePeriod
: [
1030 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1033 startPeriod
: higherFirst
1035 : schedulePeriod
.startPeriod
+
1036 differenceInSeconds(
1037 compositeChargingScheduleHigherInterval
.start
,
1038 compositeChargingScheduleLowerInterval
.start
,
1042 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1045 startPeriod
: higherFirst
1046 ? schedulePeriod
.startPeriod
+
1047 differenceInSeconds(
1048 compositeChargingScheduleLowerInterval
.start
,
1049 compositeChargingScheduleHigherInterval
.start
,
1054 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1058 ...compositeChargingScheduleLower
,
1059 ...compositeChargingScheduleHigher
!,
1060 startSchedule
: higherFirst
1061 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1062 : (compositeChargingScheduleLowerInterval
.start
as Date),
1063 duration
: higherFirst
1064 ? differenceInSeconds(
1065 compositeChargingScheduleLowerInterval
.end
,
1066 compositeChargingScheduleHigherInterval
.start
,
1068 : differenceInSeconds(
1069 compositeChargingScheduleHigherInterval
.end
,
1070 compositeChargingScheduleLowerInterval
.start
,
1072 chargingSchedulePeriod
: [
1073 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1076 startPeriod
: higherFirst
1078 : schedulePeriod
.startPeriod
+
1079 differenceInSeconds(
1080 compositeChargingScheduleHigherInterval
.start
,
1081 compositeChargingScheduleLowerInterval
.start
,
1085 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
1086 .filter((schedulePeriod
, index
) => {
1091 compositeChargingScheduleLowerInterval
.start
,
1092 schedulePeriod
.startPeriod
,
1095 start
: compositeChargingScheduleLowerInterval
.start
,
1096 end
: compositeChargingScheduleHigherInterval
.end
,
1104 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
1107 compositeChargingScheduleLowerInterval
.start
,
1108 schedulePeriod
.startPeriod
,
1111 start
: compositeChargingScheduleLowerInterval
.start
,
1112 end
: compositeChargingScheduleHigherInterval
.end
,
1117 compositeChargingScheduleLowerInterval
.start
,
1118 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1121 start
: compositeChargingScheduleLowerInterval
.start
,
1122 end
: compositeChargingScheduleHigherInterval
.end
,
1132 compositeChargingScheduleLowerInterval
.start
,
1133 schedulePeriod
.startPeriod
,
1136 start
: compositeChargingScheduleHigherInterval
.start
,
1137 end
: compositeChargingScheduleLowerInterval
.end
,
1145 .map((schedulePeriod
, index
) => {
1146 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1147 schedulePeriod
.startPeriod
= 0;
1151 startPeriod
: higherFirst
1152 ? schedulePeriod
.startPeriod
+
1153 differenceInSeconds(
1154 compositeChargingScheduleLowerInterval
.start
,
1155 compositeChargingScheduleHigherInterval
.start
,
1160 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1164 public static hasReservation
= (
1165 chargingStation
: ChargingStation
,
1166 connectorId
: number,
1169 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
1170 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
1172 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
1173 OCPP16ChargePointStatus
.Reserved
&&
1174 connectorReservation
&&
1175 !hasReservationExpired(connectorReservation
) &&
1176 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1177 connectorReservation
?.idTag
=== idTag
) ||
1178 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
1179 chargingStationReservation
&&
1180 !hasReservationExpired(chargingStationReservation
) &&
1181 chargingStationReservation
?.idTag
=== idTag
)
1184 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1185 connectorReservation
?? chargingStationReservation
,
1192 public static parseJsonSchemaFile
<T
extends JsonType
>(
1193 relativePath
: string,
1194 moduleName
?: string,
1195 methodName
?: string,
1196 ): JSONSchemaType
<T
> {
1197 return super.parseJsonSchemaFile
<T
>(
1199 OCPPVersion
.VERSION_16
,
1205 private static composeChargingSchedule
= (
1206 chargingSchedule
: OCPP16ChargingSchedule
,
1207 compositeInterval
: Interval
,
1208 ): OCPP16ChargingSchedule
| undefined => {
1209 const chargingScheduleInterval
: Interval
= {
1210 start
: chargingSchedule
.startSchedule
!,
1211 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!),
1213 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
1214 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
);
1215 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
1217 ...chargingSchedule
,
1218 startSchedule
: compositeInterval
.start
as Date,
1219 duration
: differenceInSeconds(
1220 chargingScheduleInterval
.end
,
1221 compositeInterval
.start
as Date,
1223 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
1224 .filter((schedulePeriod
, index
) => {
1227 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1234 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1236 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
1241 chargingScheduleInterval
.start
,
1242 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1251 .map((schedulePeriod
, index
) => {
1252 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1253 schedulePeriod
.startPeriod
= 0;
1255 return schedulePeriod
;
1259 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
1261 ...chargingSchedule
,
1262 duration
: differenceInSeconds(
1263 compositeInterval
.end
as Date,
1264 chargingScheduleInterval
.start
,
1266 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
1268 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1274 return chargingSchedule
;
1278 private static buildSampledValue(
1279 sampledValueTemplate
: SampledValueTemplate
,
1281 context
?: MeterValueContext
,
1282 phase
?: OCPP16MeterValuePhase
,
1283 ): OCPP16SampledValue
{
1284 const sampledValueValue
= value
?? sampledValueTemplate
?.value
;
1285 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1286 const sampledValueLocation
=
1287 sampledValueTemplate
?.location
??
1288 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1289 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1291 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1292 unit
: sampledValueTemplate
.unit
,
1294 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1295 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1296 measurand
: sampledValueTemplate
.measurand
,
1298 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1299 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
1300 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1301 } as OCPP16SampledValue
;
1304 private static checkMeasurandPowerDivider(
1305 chargingStation
: ChargingStation
,
1306 measurandType
: OCPP16MeterValueMeasurand
,
1308 if (isUndefined(chargingStation
.powerDivider
)) {
1309 const errMsg
= `MeterValues measurand ${
1310 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1311 }: powerDivider is undefined`;
1312 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1313 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1314 } else if (chargingStation
?.powerDivider
<= 0) {
1315 const errMsg
= `MeterValues measurand ${
1316 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1317 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1318 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1319 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1323 private static getMeasurandDefaultLocation(
1324 measurandType
: OCPP16MeterValueMeasurand
,
1325 ): MeterValueLocation
| undefined {
1326 switch (measurandType
) {
1327 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1328 return MeterValueLocation
.EV
;
1332 // private static getMeasurandDefaultUnit(
1333 // measurandType: OCPP16MeterValueMeasurand,
1334 // ): MeterValueUnit | undefined {
1335 // switch (measurandType) {
1336 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1337 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1338 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1339 // return MeterValueUnit.AMP;
1340 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1341 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1342 // return MeterValueUnit.WATT_HOUR;
1343 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1344 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1345 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1346 // return MeterValueUnit.WATT;
1347 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1348 // return MeterValueUnit.PERCENT;
1349 // case OCPP16MeterValueMeasurand.VOLTAGE:
1350 // return MeterValueUnit.VOLT;