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
,
48 } from
'../../../types';
55 getRandomFloatFluctuatedRounded
,
56 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
= isNotEmptyString(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
= isNotEmptyString(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
= isNotEmptyString(
171 voltagePhaseLineToNeutralSampledValueTemplate
.value
,
173 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
174 : chargingStation
.stationInfo
.voltageOut
!;
175 const fluctuationPhaseToNeutralPercent
=
176 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
177 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
178 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
179 voltagePhaseLineToNeutralSampledValueTemplateValue
,
180 fluctuationPhaseToNeutralPercent
,
183 meterValue
.sampledValue
.push(
184 OCPP16ServiceUtils
.buildSampledValue(
185 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
186 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
188 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
191 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
) {
192 const phaseLineToLineValue
= `L${phase}-L${
193 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
194 ? (phase + 1) % chargingStation.getNumberOfPhases()
195 : chargingStation.getNumberOfPhases()
197 const voltagePhaseLineToLineSampledValueTemplate
=
198 OCPP16ServiceUtils
.getSampledValueTemplate(
201 OCPP16MeterValueMeasurand
.VOLTAGE
,
202 phaseLineToLineValue
as OCPP16MeterValuePhase
,
204 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
205 if (voltagePhaseLineToLineSampledValueTemplate
) {
206 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
207 voltagePhaseLineToLineSampledValueTemplate
.value
,
209 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
211 Math.sqrt(chargingStation
.getNumberOfPhases()) *
212 chargingStation
.stationInfo
.voltageOut
!,
215 const fluctuationPhaseLineToLinePercent
=
216 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
217 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
218 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
219 voltagePhaseLineToLineSampledValueTemplateValue
,
220 fluctuationPhaseLineToLinePercent
,
223 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
224 Math.sqrt(chargingStation
.getNumberOfPhases()) *
225 chargingStation
.stationInfo
.voltageOut
!,
228 meterValue
.sampledValue
.push(
229 OCPP16ServiceUtils
.buildSampledValue(
230 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
231 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
233 phaseLineToLineValue
as OCPP16MeterValuePhase
,
239 // Power.Active.Import measurand
240 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
243 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
245 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
246 if (chargingStation
.getNumberOfPhases() === 3) {
247 powerPerPhaseSampledValueTemplates
= {
248 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
251 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
252 OCPP16MeterValuePhase
.L1_N
,
254 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
257 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
258 OCPP16MeterValuePhase
.L2_N
,
260 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
263 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
264 OCPP16MeterValuePhase
.L3_N
,
268 if (powerSampledValueTemplate
) {
269 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
271 powerSampledValueTemplate
.measurand
!,
273 const errMsg
= `MeterValues measurand ${
274 powerSampledValueTemplate.measurand ??
275 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
276 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
277 chargingStation.templateFile
278 }, cannot calculate ${
279 powerSampledValueTemplate.measurand ??
280 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
282 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
283 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
284 const connectorMaximumAvailablePower
=
285 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
286 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
287 const connectorMaximumPowerPerPhase
= Math.round(
288 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
290 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
!) ?? 0;
291 const connectorMinimumPowerPerPhase
= Math.round(
292 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
294 switch (chargingStation
.stationInfo
?.currentOutType
) {
296 if (chargingStation
.getNumberOfPhases() === 3) {
297 const defaultFluctuatedPowerPerPhase
=
298 powerSampledValueTemplate
.value
&&
299 getRandomFloatFluctuatedRounded(
300 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
301 powerSampledValueTemplate
.value
,
302 connectorMaximumPower
/ unitDivider
,
305 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
307 ) / chargingStation
.getNumberOfPhases(),
308 powerSampledValueTemplate
.fluctuationPercent
??
309 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
311 const phase1FluctuatedValue
=
312 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
313 getRandomFloatFluctuatedRounded(
314 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
315 powerPerPhaseSampledValueTemplates
.L1
.value
,
316 connectorMaximumPowerPerPhase
/ unitDivider
,
319 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
322 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
323 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
325 const phase2FluctuatedValue
=
326 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
327 getRandomFloatFluctuatedRounded(
328 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
329 powerPerPhaseSampledValueTemplates
.L2
.value
,
330 connectorMaximumPowerPerPhase
/ unitDivider
,
333 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
336 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
337 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
339 const phase3FluctuatedValue
=
340 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
341 getRandomFloatFluctuatedRounded(
342 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
343 powerPerPhaseSampledValueTemplates
.L3
.value
,
344 connectorMaximumPowerPerPhase
/ unitDivider
,
347 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
350 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
351 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
353 powerMeasurandValues
.L1
=
354 (phase1FluctuatedValue
as number) ??
355 (defaultFluctuatedPowerPerPhase
as number) ??
356 getRandomFloatRounded(
357 connectorMaximumPowerPerPhase
/ unitDivider
,
358 connectorMinimumPowerPerPhase
/ unitDivider
,
360 powerMeasurandValues
.L2
=
361 (phase2FluctuatedValue
as number) ??
362 (defaultFluctuatedPowerPerPhase
as number) ??
363 getRandomFloatRounded(
364 connectorMaximumPowerPerPhase
/ unitDivider
,
365 connectorMinimumPowerPerPhase
/ unitDivider
,
367 powerMeasurandValues
.L3
=
368 (phase3FluctuatedValue
as number) ??
369 (defaultFluctuatedPowerPerPhase
as number) ??
370 getRandomFloatRounded(
371 connectorMaximumPowerPerPhase
/ unitDivider
,
372 connectorMinimumPowerPerPhase
/ unitDivider
,
375 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
376 ? getRandomFloatFluctuatedRounded(
377 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
378 powerSampledValueTemplate
.value
,
379 connectorMaximumPower
/ unitDivider
,
382 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
385 powerSampledValueTemplate
.fluctuationPercent
??
386 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
388 : getRandomFloatRounded(
389 connectorMaximumPower
/ unitDivider
,
390 connectorMinimumPower
/ unitDivider
,
392 powerMeasurandValues
.L2
= 0;
393 powerMeasurandValues
.L3
= 0;
395 powerMeasurandValues
.allPhases
= roundTo(
396 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
401 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
402 ? getRandomFloatFluctuatedRounded(
403 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
404 powerSampledValueTemplate
.value
,
405 connectorMaximumPower
/ unitDivider
,
408 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
411 powerSampledValueTemplate
.fluctuationPercent
??
412 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
414 : getRandomFloatRounded(
415 connectorMaximumPower
/ unitDivider
,
416 connectorMinimumPower
/ unitDivider
,
420 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
421 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
423 meterValue
.sampledValue
.push(
424 OCPP16ServiceUtils
.buildSampledValue(
425 powerSampledValueTemplate
,
426 powerMeasurandValues
.allPhases
,
429 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
430 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
431 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
433 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
434 connectorMaximumPowerRounded
||
435 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
436 connectorMinimumPowerRounded
||
440 `${chargingStation.logPrefix()} MeterValues measurand ${
441 meterValue.sampledValue[sampledValuesIndex].measurand ??
442 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
443 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
444 meterValue.sampledValue[sampledValuesIndex].value
445 }/${connectorMaximumPowerRounded}`,
450 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
453 const phaseValue
= `L${phase}-N`;
454 meterValue
.sampledValue
.push(
455 OCPP16ServiceUtils
.buildSampledValue(
456 powerPerPhaseSampledValueTemplates
[
457 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
458 ]! ?? powerSampledValueTemplate
,
459 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
461 phaseValue
as OCPP16MeterValuePhase
,
464 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
465 const connectorMaximumPowerPerPhaseRounded
= roundTo(
466 connectorMaximumPowerPerPhase
/ unitDivider
,
469 const connectorMinimumPowerPerPhaseRounded
= roundTo(
470 connectorMinimumPowerPerPhase
/ unitDivider
,
474 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
475 connectorMaximumPowerPerPhaseRounded
||
476 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
477 connectorMinimumPowerPerPhaseRounded
||
481 `${chargingStation.logPrefix()} MeterValues measurand ${
482 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
483 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
485 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
486 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
487 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
488 }/${connectorMaximumPowerPerPhaseRounded}`,
493 // Current.Import measurand
494 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
497 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
499 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
500 if (chargingStation
.getNumberOfPhases() === 3) {
501 currentPerPhaseSampledValueTemplates
= {
502 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
505 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
506 OCPP16MeterValuePhase
.L1
,
508 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
511 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
512 OCPP16MeterValuePhase
.L2
,
514 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
517 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
518 OCPP16MeterValuePhase
.L3
,
522 if (currentSampledValueTemplate
) {
523 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
525 currentSampledValueTemplate
.measurand
!,
527 const errMsg
= `MeterValues measurand ${
528 currentSampledValueTemplate.measurand ??
529 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
530 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
531 chargingStation.templateFile
532 }, cannot calculate ${
533 currentSampledValueTemplate.measurand ??
534 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
536 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
537 const connectorMaximumAvailablePower
=
538 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
539 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
540 let connectorMaximumAmperage
: number;
541 switch (chargingStation
.stationInfo
?.currentOutType
) {
543 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
544 chargingStation
.getNumberOfPhases(),
545 connectorMaximumAvailablePower
,
546 chargingStation
.stationInfo
.voltageOut
!,
548 if (chargingStation
.getNumberOfPhases() === 3) {
549 const defaultFluctuatedAmperagePerPhase
=
550 currentSampledValueTemplate
.value
&&
551 getRandomFloatFluctuatedRounded(
552 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
553 currentSampledValueTemplate
.value
,
554 connectorMaximumAmperage
,
557 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
560 currentSampledValueTemplate
.fluctuationPercent
??
561 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
563 const phase1FluctuatedValue
=
564 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
565 getRandomFloatFluctuatedRounded(
566 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
567 currentPerPhaseSampledValueTemplates
.L1
.value
,
568 connectorMaximumAmperage
,
571 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
574 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
575 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
577 const phase2FluctuatedValue
=
578 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
579 getRandomFloatFluctuatedRounded(
580 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
581 currentPerPhaseSampledValueTemplates
.L2
.value
,
582 connectorMaximumAmperage
,
585 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
588 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
589 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
591 const phase3FluctuatedValue
=
592 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
593 getRandomFloatFluctuatedRounded(
594 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
595 currentPerPhaseSampledValueTemplates
.L3
.value
,
596 connectorMaximumAmperage
,
599 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
602 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
603 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
605 currentMeasurandValues
.L1
=
606 (phase1FluctuatedValue
as number) ??
607 (defaultFluctuatedAmperagePerPhase
as number) ??
608 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
609 currentMeasurandValues
.L2
=
610 (phase2FluctuatedValue
as number) ??
611 (defaultFluctuatedAmperagePerPhase
as number) ??
612 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
613 currentMeasurandValues
.L3
=
614 (phase3FluctuatedValue
as number) ??
615 (defaultFluctuatedAmperagePerPhase
as number) ??
616 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
618 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
619 ? getRandomFloatFluctuatedRounded(
620 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
621 currentSampledValueTemplate
.value
,
622 connectorMaximumAmperage
,
625 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
628 currentSampledValueTemplate
.fluctuationPercent
??
629 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
631 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
632 currentMeasurandValues
.L2
= 0;
633 currentMeasurandValues
.L3
= 0;
635 currentMeasurandValues
.allPhases
= roundTo(
636 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
637 chargingStation
.getNumberOfPhases(),
642 connectorMaximumAmperage
= DCElectricUtils
.amperage(
643 connectorMaximumAvailablePower
,
644 chargingStation
.stationInfo
.voltageOut
!,
646 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
647 ? getRandomFloatFluctuatedRounded(
648 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
649 currentSampledValueTemplate
.value
,
650 connectorMaximumAmperage
,
653 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
656 currentSampledValueTemplate
.fluctuationPercent
??
657 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
659 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
662 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
663 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
665 meterValue
.sampledValue
.push(
666 OCPP16ServiceUtils
.buildSampledValue(
667 currentSampledValueTemplate
,
668 currentMeasurandValues
.allPhases
,
671 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
673 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
674 connectorMaximumAmperage
||
675 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
676 connectorMinimumAmperage
||
680 `${chargingStation.logPrefix()} MeterValues measurand ${
681 meterValue.sampledValue[sampledValuesIndex].measurand ??
682 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
683 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
684 meterValue.sampledValue[sampledValuesIndex].value
685 }/${connectorMaximumAmperage}`,
690 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
693 const phaseValue
= `L${phase}`;
694 meterValue
.sampledValue
.push(
695 OCPP16ServiceUtils
.buildSampledValue(
696 currentPerPhaseSampledValueTemplates
[
697 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
698 ]! ?? currentSampledValueTemplate
,
699 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
701 phaseValue
as OCPP16MeterValuePhase
,
704 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
706 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
707 connectorMaximumAmperage
||
708 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
709 connectorMinimumAmperage
||
713 `${chargingStation.logPrefix()} MeterValues measurand ${
714 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
715 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
717 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
718 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
719 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
720 }/${connectorMaximumAmperage}`,
725 // Energy.Active.Import.Register measurand (default)
726 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
730 if (energySampledValueTemplate
) {
731 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
733 energySampledValueTemplate
.measurand
!,
736 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
737 const connectorMaximumAvailablePower
=
738 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
739 const connectorMaximumEnergyRounded
= roundTo(
740 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
743 const energyValueRounded
= energySampledValueTemplate
.value
744 ? // Cumulate the fluctuated value around the static one
745 getRandomFloatFluctuatedRounded(
746 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
747 energySampledValueTemplate
.value
,
748 connectorMaximumEnergyRounded
,
750 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
751 unitMultiplier
: unitDivider
,
754 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
756 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
757 // Persist previous value on connector
760 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
761 connector
.energyActiveImportRegisterValue
! >= 0 &&
762 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
763 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
765 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
766 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
768 connector
.energyActiveImportRegisterValue
= 0;
769 connector
.transactionEnergyActiveImportRegisterValue
= 0;
772 meterValue
.sampledValue
.push(
773 OCPP16ServiceUtils
.buildSampledValue(
774 energySampledValueTemplate
,
776 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
782 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
783 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
785 `${chargingStation.logPrefix()} MeterValues measurand ${
786 meterValue.sampledValue[sampledValuesIndex].measurand ??
787 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
788 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
795 public static buildTransactionBeginMeterValue(
796 chargingStation
: ChargingStation
,
799 ): OCPP16MeterValue
{
800 const meterValue
: OCPP16MeterValue
= {
801 timestamp
: new Date(),
804 // Energy.Active.Import.Register measurand (default)
805 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
809 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
810 meterValue
.sampledValue
.push(
811 OCPP16ServiceUtils
.buildSampledValue(
812 sampledValueTemplate
!,
813 roundTo((meterStart
?? 0) / unitDivider
, 4),
814 MeterValueContext
.TRANSACTION_BEGIN
,
820 public static buildTransactionEndMeterValue(
821 chargingStation
: ChargingStation
,
824 ): OCPP16MeterValue
{
825 const meterValue
: OCPP16MeterValue
= {
826 timestamp
: new Date(),
829 // Energy.Active.Import.Register measurand (default)
830 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
834 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
835 meterValue
.sampledValue
.push(
836 OCPP16ServiceUtils
.buildSampledValue(
837 sampledValueTemplate
!,
838 roundTo((meterStop
?? 0) / unitDivider
, 4),
839 MeterValueContext
.TRANSACTION_END
,
845 public static buildTransactionDataMeterValues(
846 transactionBeginMeterValue
: OCPP16MeterValue
,
847 transactionEndMeterValue
: OCPP16MeterValue
,
848 ): OCPP16MeterValue
[] {
849 const meterValues
: OCPP16MeterValue
[] = [];
850 meterValues
.push(transactionBeginMeterValue
);
851 meterValues
.push(transactionEndMeterValue
);
855 public static remoteStopTransaction
= async (
856 chargingStation
: ChargingStation
,
858 ): Promise
<GenericResponse
> => {
859 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
862 OCPP16ChargePointStatus
.Finishing
,
864 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
866 OCPP16StopTransactionReason
.REMOTE
,
868 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
869 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
871 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
874 public static changeAvailability
= async (
875 chargingStation
: ChargingStation
,
876 connectorIds
: number[],
877 chargePointStatus
: OCPP16ChargePointStatus
,
878 availabilityType
: OCPP16AvailabilityType
,
879 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
880 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
881 for (const connectorId
of connectorIds
) {
882 let response
: OCPP16ChangeAvailabilityResponse
=
883 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
884 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
885 if (connectorStatus
?.transactionStarted
=== true) {
886 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
888 connectorStatus
.availability
= availabilityType
;
889 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
890 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
896 responses
.push(response
);
898 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
899 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
901 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
904 public static setChargingProfile(
905 chargingStation
: ChargingStation
,
907 cp
: OCPP16ChargingProfile
,
909 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
911 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
913 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
916 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
919 `${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`,
921 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
923 let cpReplaced
= false;
924 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
926 .getConnectorStatus(connectorId
)
927 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
929 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
930 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
931 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
933 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
938 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
941 public static clearChargingProfiles
= (
942 chargingStation
: ChargingStation
,
943 commandPayload
: ClearChargingProfileRequest
,
944 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
946 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
;
947 let clearedCP
= false;
948 if (isNotEmptyArray(chargingProfiles
)) {
949 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
950 let clearCurrentCP
= false;
951 if (chargingProfile
.chargingProfileId
=== id
) {
952 clearCurrentCP
= true;
954 if (!chargingProfilePurpose
&& chargingProfile
.stackLevel
=== stackLevel
) {
955 clearCurrentCP
= true;
957 if (!stackLevel
&& chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
) {
958 clearCurrentCP
= true;
961 chargingProfile
.stackLevel
=== stackLevel
&&
962 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
964 clearCurrentCP
= true;
966 if (clearCurrentCP
) {
967 chargingProfiles
.splice(index
, 1);
969 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
979 public static composeChargingSchedules
= (
980 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
981 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
982 compositeInterval
: Interval
,
983 ): OCPP16ChargingSchedule
| undefined => {
984 if (!chargingScheduleHigher
&& !chargingScheduleLower
) {
987 if (chargingScheduleHigher
&& !chargingScheduleLower
) {
988 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
);
990 if (!chargingScheduleHigher
&& chargingScheduleLower
) {
991 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
);
993 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
994 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
);
995 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
996 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
);
997 const compositeChargingScheduleHigherInterval
: Interval
= {
998 start
: compositeChargingScheduleHigher
!.startSchedule
!,
1000 compositeChargingScheduleHigher
!.startSchedule
!,
1001 compositeChargingScheduleHigher
!.duration
!,
1004 const compositeChargingScheduleLowerInterval
: Interval
= {
1005 start
: compositeChargingScheduleLower
!.startSchedule
!,
1007 compositeChargingScheduleLower
!.startSchedule
!,
1008 compositeChargingScheduleLower
!.duration
!,
1011 const higherFirst
= isBefore(
1012 compositeChargingScheduleHigherInterval
.start
,
1013 compositeChargingScheduleLowerInterval
.start
,
1016 !areIntervalsOverlapping(
1017 compositeChargingScheduleHigherInterval
,
1018 compositeChargingScheduleLowerInterval
,
1022 ...compositeChargingScheduleLower
,
1023 ...compositeChargingScheduleHigher
!,
1024 startSchedule
: higherFirst
1025 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1026 : (compositeChargingScheduleLowerInterval
.start
as Date),
1027 duration
: higherFirst
1028 ? differenceInSeconds(
1029 compositeChargingScheduleLowerInterval
.end
,
1030 compositeChargingScheduleHigherInterval
.start
,
1032 : differenceInSeconds(
1033 compositeChargingScheduleHigherInterval
.end
,
1034 compositeChargingScheduleLowerInterval
.start
,
1036 chargingSchedulePeriod
: [
1037 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1040 startPeriod
: higherFirst
1042 : schedulePeriod
.startPeriod
+
1043 differenceInSeconds(
1044 compositeChargingScheduleHigherInterval
.start
,
1045 compositeChargingScheduleLowerInterval
.start
,
1049 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1052 startPeriod
: higherFirst
1053 ? schedulePeriod
.startPeriod
+
1054 differenceInSeconds(
1055 compositeChargingScheduleLowerInterval
.start
,
1056 compositeChargingScheduleHigherInterval
.start
,
1061 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1065 ...compositeChargingScheduleLower
,
1066 ...compositeChargingScheduleHigher
!,
1067 startSchedule
: higherFirst
1068 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1069 : (compositeChargingScheduleLowerInterval
.start
as Date),
1070 duration
: higherFirst
1071 ? differenceInSeconds(
1072 compositeChargingScheduleLowerInterval
.end
,
1073 compositeChargingScheduleHigherInterval
.start
,
1075 : differenceInSeconds(
1076 compositeChargingScheduleHigherInterval
.end
,
1077 compositeChargingScheduleLowerInterval
.start
,
1079 chargingSchedulePeriod
: [
1080 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1083 startPeriod
: higherFirst
1085 : schedulePeriod
.startPeriod
+
1086 differenceInSeconds(
1087 compositeChargingScheduleHigherInterval
.start
,
1088 compositeChargingScheduleLowerInterval
.start
,
1092 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
1093 .filter((schedulePeriod
, index
) => {
1098 compositeChargingScheduleLowerInterval
.start
,
1099 schedulePeriod
.startPeriod
,
1102 start
: compositeChargingScheduleLowerInterval
.start
,
1103 end
: compositeChargingScheduleHigherInterval
.end
,
1111 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
1114 compositeChargingScheduleLowerInterval
.start
,
1115 schedulePeriod
.startPeriod
,
1118 start
: compositeChargingScheduleLowerInterval
.start
,
1119 end
: compositeChargingScheduleHigherInterval
.end
,
1124 compositeChargingScheduleLowerInterval
.start
,
1125 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1128 start
: compositeChargingScheduleLowerInterval
.start
,
1129 end
: compositeChargingScheduleHigherInterval
.end
,
1139 compositeChargingScheduleLowerInterval
.start
,
1140 schedulePeriod
.startPeriod
,
1143 start
: compositeChargingScheduleHigherInterval
.start
,
1144 end
: compositeChargingScheduleLowerInterval
.end
,
1152 .map((schedulePeriod
, index
) => {
1153 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1154 schedulePeriod
.startPeriod
= 0;
1158 startPeriod
: higherFirst
1159 ? schedulePeriod
.startPeriod
+
1160 differenceInSeconds(
1161 compositeChargingScheduleLowerInterval
.start
,
1162 compositeChargingScheduleHigherInterval
.start
,
1167 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1171 public static hasReservation
= (
1172 chargingStation
: ChargingStation
,
1173 connectorId
: number,
1176 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
1177 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
1179 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
1180 OCPP16ChargePointStatus
.Reserved
&&
1181 connectorReservation
&&
1182 !hasReservationExpired(connectorReservation
) &&
1183 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1184 connectorReservation
?.idTag
=== idTag
) ||
1185 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
1186 chargingStationReservation
&&
1187 !hasReservationExpired(chargingStationReservation
) &&
1188 chargingStationReservation
?.idTag
=== idTag
)
1191 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1192 connectorReservation
?? chargingStationReservation
,
1199 public static parseJsonSchemaFile
<T
extends JsonType
>(
1200 relativePath
: string,
1201 moduleName
?: string,
1202 methodName
?: string,
1203 ): JSONSchemaType
<T
> {
1204 return super.parseJsonSchemaFile
<T
>(
1206 OCPPVersion
.VERSION_16
,
1212 private static composeChargingSchedule
= (
1213 chargingSchedule
: OCPP16ChargingSchedule
,
1214 compositeInterval
: Interval
,
1215 ): OCPP16ChargingSchedule
| undefined => {
1216 const chargingScheduleInterval
: Interval
= {
1217 start
: chargingSchedule
.startSchedule
!,
1218 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!),
1220 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
1221 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
);
1222 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
1224 ...chargingSchedule
,
1225 startSchedule
: compositeInterval
.start
as Date,
1226 duration
: differenceInSeconds(
1227 chargingScheduleInterval
.end
,
1228 compositeInterval
.start
as Date,
1230 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
1231 .filter((schedulePeriod
, index
) => {
1234 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1241 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1243 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
1248 chargingScheduleInterval
.start
,
1249 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1258 .map((schedulePeriod
, index
) => {
1259 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1260 schedulePeriod
.startPeriod
= 0;
1262 return schedulePeriod
;
1266 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
1268 ...chargingSchedule
,
1269 duration
: differenceInSeconds(
1270 compositeInterval
.end
as Date,
1271 chargingScheduleInterval
.start
,
1273 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
1275 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1281 return chargingSchedule
;
1285 private static buildSampledValue(
1286 sampledValueTemplate
: SampledValueTemplate
,
1288 context
?: MeterValueContext
,
1289 phase
?: OCPP16MeterValuePhase
,
1290 ): OCPP16SampledValue
{
1291 const sampledValueValue
= value
?? sampledValueTemplate
?.value
;
1292 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1293 const sampledValueLocation
=
1294 sampledValueTemplate
?.location
??
1295 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1296 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1298 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1299 unit
: sampledValueTemplate
.unit
,
1301 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1302 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1303 measurand
: sampledValueTemplate
.measurand
,
1305 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1306 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
1307 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1308 } as OCPP16SampledValue
;
1311 private static checkMeasurandPowerDivider(
1312 chargingStation
: ChargingStation
,
1313 measurandType
: OCPP16MeterValueMeasurand
,
1315 if (isUndefined(chargingStation
.powerDivider
)) {
1316 const errMsg
= `MeterValues measurand ${
1317 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1318 }: powerDivider is undefined`;
1319 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1320 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1321 } else if (chargingStation
?.powerDivider
<= 0) {
1322 const errMsg
= `MeterValues measurand ${
1323 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1324 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1325 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1326 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1330 private static getMeasurandDefaultLocation(
1331 measurandType
: OCPP16MeterValueMeasurand
,
1332 ): MeterValueLocation
| undefined {
1333 switch (measurandType
) {
1334 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1335 return MeterValueLocation
.EV
;
1339 // private static getMeasurandDefaultUnit(
1340 // measurandType: OCPP16MeterValueMeasurand,
1341 // ): MeterValueUnit | undefined {
1342 // switch (measurandType) {
1343 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1344 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1345 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1346 // return MeterValueUnit.AMP;
1347 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1348 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1349 // return MeterValueUnit.WATT_HOUR;
1350 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1351 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1352 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1353 // return MeterValueUnit.WATT;
1354 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1355 // return MeterValueUnit.PERCENT;
1356 // case OCPP16MeterValueMeasurand.VOLTAGE:
1357 // return MeterValueUnit.VOLT;