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 voltagePhaseLineToLineValueRounded
= roundTo(
198 Math.sqrt(chargingStation
.getNumberOfPhases()) *
199 chargingStation
.stationInfo
.voltageOut
!,
202 const voltagePhaseLineToLineSampledValueTemplate
=
203 OCPP16ServiceUtils
.getSampledValueTemplate(
206 OCPP16MeterValueMeasurand
.VOLTAGE
,
207 phaseLineToLineValue
as OCPP16MeterValuePhase
,
209 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
210 if (voltagePhaseLineToLineSampledValueTemplate
) {
211 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
212 voltagePhaseLineToLineSampledValueTemplate
.value
,
214 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
215 : voltagePhaseLineToLineValueRounded
;
216 const fluctuationPhaseLineToLinePercent
=
217 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
218 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
219 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
220 voltagePhaseLineToLineSampledValueTemplateValue
,
221 fluctuationPhaseLineToLinePercent
,
224 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
225 voltagePhaseLineToLineValueRounded
,
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
= isNotEmptyString(powerSampledValueTemplate
.value
)
298 ? getRandomFloatFluctuatedRounded(
299 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
300 powerSampledValueTemplate
.value
,
301 connectorMaximumPower
/ unitDivider
,
302 connectorMinimumPower
/ unitDivider
,
305 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
306 fallbackValue
: connectorMinimumPower
/ unitDivider
,
308 ) / chargingStation
.getNumberOfPhases(),
309 powerSampledValueTemplate
.fluctuationPercent
??
310 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
313 const phase1FluctuatedValue
= isNotEmptyString(
314 powerPerPhaseSampledValueTemplates
.L1
?.value
,
316 ? getRandomFloatFluctuatedRounded(
317 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
318 powerPerPhaseSampledValueTemplates
.L1
?.value
,
319 connectorMaximumPowerPerPhase
/ unitDivider
,
320 connectorMinimumPowerPerPhase
/ unitDivider
,
323 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
324 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
327 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
328 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
331 const phase2FluctuatedValue
= isNotEmptyString(
332 powerPerPhaseSampledValueTemplates
.L2
?.value
,
334 ? getRandomFloatFluctuatedRounded(
335 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
336 powerPerPhaseSampledValueTemplates
.L2
?.value
,
337 connectorMaximumPowerPerPhase
/ unitDivider
,
338 connectorMinimumPowerPerPhase
/ unitDivider
,
341 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
342 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
345 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
346 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
349 const phase3FluctuatedValue
= isNotEmptyString(
350 powerPerPhaseSampledValueTemplates
.L3
?.value
,
352 ? getRandomFloatFluctuatedRounded(
353 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
354 powerPerPhaseSampledValueTemplates
.L3
?.value
,
355 connectorMaximumPowerPerPhase
/ unitDivider
,
356 connectorMinimumPowerPerPhase
/ unitDivider
,
359 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
360 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
363 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
364 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
367 powerMeasurandValues
.L1
=
368 phase1FluctuatedValue
??
369 defaultFluctuatedPowerPerPhase
??
370 getRandomFloatRounded(
371 connectorMaximumPowerPerPhase
/ unitDivider
,
372 connectorMinimumPowerPerPhase
/ unitDivider
,
374 powerMeasurandValues
.L2
=
375 phase2FluctuatedValue
??
376 defaultFluctuatedPowerPerPhase
??
377 getRandomFloatRounded(
378 connectorMaximumPowerPerPhase
/ unitDivider
,
379 connectorMinimumPowerPerPhase
/ unitDivider
,
381 powerMeasurandValues
.L3
=
382 phase3FluctuatedValue
??
383 defaultFluctuatedPowerPerPhase
??
384 getRandomFloatRounded(
385 connectorMaximumPowerPerPhase
/ unitDivider
,
386 connectorMinimumPowerPerPhase
/ unitDivider
,
389 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
390 ? getRandomFloatFluctuatedRounded(
391 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
392 powerSampledValueTemplate
.value
,
393 connectorMaximumPower
/ unitDivider
,
394 connectorMinimumPower
/ unitDivider
,
397 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
398 fallbackValue
: connectorMinimumPower
/ unitDivider
,
401 powerSampledValueTemplate
.fluctuationPercent
??
402 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
404 : getRandomFloatRounded(
405 connectorMaximumPower
/ unitDivider
,
406 connectorMinimumPower
/ unitDivider
,
408 powerMeasurandValues
.L2
= 0;
409 powerMeasurandValues
.L3
= 0;
411 powerMeasurandValues
.allPhases
= roundTo(
412 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
417 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
418 ? getRandomFloatFluctuatedRounded(
419 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
420 powerSampledValueTemplate
.value
,
421 connectorMaximumPower
/ unitDivider
,
422 connectorMinimumPower
/ unitDivider
,
425 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
426 fallbackValue
: connectorMinimumPower
/ unitDivider
,
429 powerSampledValueTemplate
.fluctuationPercent
??
430 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
432 : getRandomFloatRounded(
433 connectorMaximumPower
/ unitDivider
,
434 connectorMinimumPower
/ unitDivider
,
438 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
439 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
441 meterValue
.sampledValue
.push(
442 OCPP16ServiceUtils
.buildSampledValue(
443 powerSampledValueTemplate
,
444 powerMeasurandValues
.allPhases
,
447 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
448 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
449 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
451 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
452 connectorMaximumPowerRounded
||
453 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
454 connectorMinimumPowerRounded
||
458 `${chargingStation.logPrefix()} MeterValues measurand ${
459 meterValue.sampledValue[sampledValuesIndex].measurand ??
460 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
461 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
462 meterValue.sampledValue[sampledValuesIndex].value
463 }/${connectorMaximumPowerRounded}`,
468 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
471 const phaseValue
= `L${phase}-N`;
472 meterValue
.sampledValue
.push(
473 OCPP16ServiceUtils
.buildSampledValue(
474 powerPerPhaseSampledValueTemplates
[
475 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
476 ] ?? powerSampledValueTemplate
,
477 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
479 phaseValue
as OCPP16MeterValuePhase
,
482 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
483 const connectorMaximumPowerPerPhaseRounded
= roundTo(
484 connectorMaximumPowerPerPhase
/ unitDivider
,
487 const connectorMinimumPowerPerPhaseRounded
= roundTo(
488 connectorMinimumPowerPerPhase
/ unitDivider
,
492 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
493 connectorMaximumPowerPerPhaseRounded
||
494 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
495 connectorMinimumPowerPerPhaseRounded
||
499 `${chargingStation.logPrefix()} MeterValues measurand ${
500 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
501 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
503 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
504 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
505 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
506 }/${connectorMaximumPowerPerPhaseRounded}`,
511 // Current.Import measurand
512 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
515 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
517 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
518 if (chargingStation
.getNumberOfPhases() === 3) {
519 currentPerPhaseSampledValueTemplates
= {
520 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
523 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
524 OCPP16MeterValuePhase
.L1
,
526 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
529 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
530 OCPP16MeterValuePhase
.L2
,
532 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
535 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
536 OCPP16MeterValuePhase
.L3
,
540 if (currentSampledValueTemplate
) {
541 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
543 currentSampledValueTemplate
.measurand
!,
545 const errMsg
= `MeterValues measurand ${
546 currentSampledValueTemplate.measurand ??
547 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
548 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
549 chargingStation.templateFile
550 }, cannot calculate ${
551 currentSampledValueTemplate.measurand ??
552 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
554 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
555 const connectorMaximumAvailablePower
=
556 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
557 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
558 let connectorMaximumAmperage
: number;
559 switch (chargingStation
.stationInfo
?.currentOutType
) {
561 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
562 chargingStation
.getNumberOfPhases(),
563 connectorMaximumAvailablePower
,
564 chargingStation
.stationInfo
.voltageOut
!,
566 if (chargingStation
.getNumberOfPhases() === 3) {
567 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
568 currentSampledValueTemplate
.value
,
570 ? getRandomFloatFluctuatedRounded(
571 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
572 currentSampledValueTemplate
.value
,
573 connectorMaximumAmperage
,
574 connectorMinimumAmperage
,
577 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
578 fallbackValue
: connectorMinimumAmperage
,
581 currentSampledValueTemplate
.fluctuationPercent
??
582 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
585 const phase1FluctuatedValue
= isNotEmptyString(
586 currentPerPhaseSampledValueTemplates
.L1
?.value
,
588 ? getRandomFloatFluctuatedRounded(
589 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
590 currentPerPhaseSampledValueTemplates
.L1
?.value
,
591 connectorMaximumAmperage
,
592 connectorMinimumAmperage
,
595 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
596 fallbackValue
: connectorMinimumAmperage
,
599 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
600 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
603 const phase2FluctuatedValue
= isNotEmptyString(
604 currentPerPhaseSampledValueTemplates
.L2
?.value
,
606 ? getRandomFloatFluctuatedRounded(
607 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
608 currentPerPhaseSampledValueTemplates
.L2
?.value
,
609 connectorMaximumAmperage
,
610 connectorMinimumAmperage
,
613 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
614 fallbackValue
: connectorMinimumAmperage
,
617 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
618 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
621 const phase3FluctuatedValue
= isNotEmptyString(
622 currentPerPhaseSampledValueTemplates
.L3
?.value
,
624 ? getRandomFloatFluctuatedRounded(
625 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
626 currentPerPhaseSampledValueTemplates
.L3
?.value
,
627 connectorMaximumAmperage
,
628 connectorMinimumAmperage
,
631 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
632 fallbackValue
: connectorMinimumAmperage
,
635 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
636 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
639 currentMeasurandValues
.L1
=
640 phase1FluctuatedValue
??
641 defaultFluctuatedAmperagePerPhase
??
642 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
643 currentMeasurandValues
.L2
=
644 phase2FluctuatedValue
??
645 defaultFluctuatedAmperagePerPhase
??
646 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
647 currentMeasurandValues
.L3
=
648 phase3FluctuatedValue
??
649 defaultFluctuatedAmperagePerPhase
??
650 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
652 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
653 ? getRandomFloatFluctuatedRounded(
654 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
655 currentSampledValueTemplate
.value
,
656 connectorMaximumAmperage
,
657 connectorMinimumAmperage
,
660 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
661 fallbackValue
: connectorMinimumAmperage
,
664 currentSampledValueTemplate
.fluctuationPercent
??
665 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
667 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
668 currentMeasurandValues
.L2
= 0;
669 currentMeasurandValues
.L3
= 0;
671 currentMeasurandValues
.allPhases
= roundTo(
672 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
673 chargingStation
.getNumberOfPhases(),
678 connectorMaximumAmperage
= DCElectricUtils
.amperage(
679 connectorMaximumAvailablePower
,
680 chargingStation
.stationInfo
.voltageOut
!,
682 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
683 ? getRandomFloatFluctuatedRounded(
684 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
685 currentSampledValueTemplate
.value
,
686 connectorMaximumAmperage
,
687 connectorMinimumAmperage
,
690 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
691 fallbackValue
: connectorMinimumAmperage
,
694 currentSampledValueTemplate
.fluctuationPercent
??
695 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
697 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
700 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
701 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
703 meterValue
.sampledValue
.push(
704 OCPP16ServiceUtils
.buildSampledValue(
705 currentSampledValueTemplate
,
706 currentMeasurandValues
.allPhases
,
709 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
711 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
712 connectorMaximumAmperage
||
713 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
714 connectorMinimumAmperage
||
718 `${chargingStation.logPrefix()} MeterValues measurand ${
719 meterValue.sampledValue[sampledValuesIndex].measurand ??
720 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
721 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
722 meterValue.sampledValue[sampledValuesIndex].value
723 }/${connectorMaximumAmperage}`,
728 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
731 const phaseValue
= `L${phase}`;
732 meterValue
.sampledValue
.push(
733 OCPP16ServiceUtils
.buildSampledValue(
734 currentPerPhaseSampledValueTemplates
[
735 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
736 ] ?? currentSampledValueTemplate
,
737 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
739 phaseValue
as OCPP16MeterValuePhase
,
742 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
744 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
745 connectorMaximumAmperage
||
746 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
747 connectorMinimumAmperage
||
751 `${chargingStation.logPrefix()} MeterValues measurand ${
752 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
753 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
755 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
756 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
757 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
758 }/${connectorMaximumAmperage}`,
763 // Energy.Active.Import.Register measurand (default)
764 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
768 if (energySampledValueTemplate
) {
769 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
771 energySampledValueTemplate
.measurand
!,
774 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
775 const connectorMaximumAvailablePower
=
776 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
777 const connectorMaximumEnergyRounded
= roundTo(
778 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
781 const connectorMinimumEnergyRounded
= roundTo(
782 energySampledValueTemplate
.minimumValue
?? 0,
785 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
786 ? getRandomFloatFluctuatedRounded(
787 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
788 energySampledValueTemplate
.value
,
789 connectorMaximumEnergyRounded
,
790 connectorMinimumEnergyRounded
,
792 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
793 unitMultiplier
: unitDivider
,
794 fallbackValue
: connectorMinimumEnergyRounded
,
797 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
799 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
);
800 // Persist previous value on connector
803 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
804 connector
.energyActiveImportRegisterValue
! >= 0 &&
805 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
806 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
808 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
809 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
811 connector
.energyActiveImportRegisterValue
= 0;
812 connector
.transactionEnergyActiveImportRegisterValue
= 0;
815 meterValue
.sampledValue
.push(
816 OCPP16ServiceUtils
.buildSampledValue(
817 energySampledValueTemplate
,
819 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
825 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
827 energyValueRounded
> connectorMaximumEnergyRounded
||
828 energyValueRounded
< connectorMinimumEnergyRounded
||
832 `${chargingStation.logPrefix()} MeterValues measurand ${
833 meterValue.sampledValue[sampledValuesIndex].measurand ??
834 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
835 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
842 public static buildTransactionBeginMeterValue(
843 chargingStation
: ChargingStation
,
846 ): OCPP16MeterValue
{
847 const meterValue
: OCPP16MeterValue
= {
848 timestamp
: new Date(),
851 // Energy.Active.Import.Register measurand (default)
852 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
856 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
857 meterValue
.sampledValue
.push(
858 OCPP16ServiceUtils
.buildSampledValue(
859 sampledValueTemplate
!,
860 roundTo((meterStart
?? 0) / unitDivider
, 4),
861 MeterValueContext
.TRANSACTION_BEGIN
,
867 public static buildTransactionEndMeterValue(
868 chargingStation
: ChargingStation
,
871 ): OCPP16MeterValue
{
872 const meterValue
: OCPP16MeterValue
= {
873 timestamp
: new Date(),
876 // Energy.Active.Import.Register measurand (default)
877 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
881 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
882 meterValue
.sampledValue
.push(
883 OCPP16ServiceUtils
.buildSampledValue(
884 sampledValueTemplate
!,
885 roundTo((meterStop
?? 0) / unitDivider
, 4),
886 MeterValueContext
.TRANSACTION_END
,
892 public static buildTransactionDataMeterValues(
893 transactionBeginMeterValue
: OCPP16MeterValue
,
894 transactionEndMeterValue
: OCPP16MeterValue
,
895 ): OCPP16MeterValue
[] {
896 const meterValues
: OCPP16MeterValue
[] = [];
897 meterValues
.push(transactionBeginMeterValue
);
898 meterValues
.push(transactionEndMeterValue
);
902 public static remoteStopTransaction
= async (
903 chargingStation
: ChargingStation
,
905 ): Promise
<GenericResponse
> => {
906 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
909 OCPP16ChargePointStatus
.Finishing
,
911 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
913 OCPP16StopTransactionReason
.REMOTE
,
915 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
916 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
918 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
921 public static changeAvailability
= async (
922 chargingStation
: ChargingStation
,
923 connectorIds
: number[],
924 chargePointStatus
: OCPP16ChargePointStatus
,
925 availabilityType
: OCPP16AvailabilityType
,
926 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
927 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
928 for (const connectorId
of connectorIds
) {
929 let response
: OCPP16ChangeAvailabilityResponse
=
930 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
931 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
932 if (connectorStatus
?.transactionStarted
=== true) {
933 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
935 connectorStatus
.availability
= availabilityType
;
936 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
937 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
943 responses
.push(response
);
945 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
946 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
948 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
951 public static setChargingProfile(
952 chargingStation
: ChargingStation
,
954 cp
: OCPP16ChargingProfile
,
956 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
958 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
960 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
963 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
966 `${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`,
968 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
970 let cpReplaced
= false;
971 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
973 .getConnectorStatus(connectorId
)
974 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
976 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
977 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
978 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
980 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
985 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
988 public static clearChargingProfiles
= (
989 chargingStation
: ChargingStation
,
990 commandPayload
: ClearChargingProfileRequest
,
991 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
993 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
;
994 let clearedCP
= false;
995 if (isNotEmptyArray(chargingProfiles
)) {
996 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
997 let clearCurrentCP
= false;
998 if (chargingProfile
.chargingProfileId
=== id
) {
999 clearCurrentCP
= true;
1001 if (!chargingProfilePurpose
&& chargingProfile
.stackLevel
=== stackLevel
) {
1002 clearCurrentCP
= true;
1004 if (!stackLevel
&& chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
) {
1005 clearCurrentCP
= true;
1008 chargingProfile
.stackLevel
=== stackLevel
&&
1009 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
1011 clearCurrentCP
= true;
1013 if (clearCurrentCP
) {
1014 chargingProfiles
.splice(index
, 1);
1016 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
1026 public static composeChargingSchedules
= (
1027 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
1028 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
1029 compositeInterval
: Interval
,
1030 ): OCPP16ChargingSchedule
| undefined => {
1031 if (!chargingScheduleHigher
&& !chargingScheduleLower
) {
1034 if (chargingScheduleHigher
&& !chargingScheduleLower
) {
1035 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
);
1037 if (!chargingScheduleHigher
&& chargingScheduleLower
) {
1038 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
);
1040 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
1041 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
);
1042 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
1043 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
);
1044 const compositeChargingScheduleHigherInterval
: Interval
= {
1045 start
: compositeChargingScheduleHigher
!.startSchedule
!,
1047 compositeChargingScheduleHigher
!.startSchedule
!,
1048 compositeChargingScheduleHigher
!.duration
!,
1051 const compositeChargingScheduleLowerInterval
: Interval
= {
1052 start
: compositeChargingScheduleLower
!.startSchedule
!,
1054 compositeChargingScheduleLower
!.startSchedule
!,
1055 compositeChargingScheduleLower
!.duration
!,
1058 const higherFirst
= isBefore(
1059 compositeChargingScheduleHigherInterval
.start
,
1060 compositeChargingScheduleLowerInterval
.start
,
1063 !areIntervalsOverlapping(
1064 compositeChargingScheduleHigherInterval
,
1065 compositeChargingScheduleLowerInterval
,
1069 ...compositeChargingScheduleLower
,
1070 ...compositeChargingScheduleHigher
!,
1071 startSchedule
: higherFirst
1072 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1073 : (compositeChargingScheduleLowerInterval
.start
as Date),
1074 duration
: higherFirst
1075 ? differenceInSeconds(
1076 compositeChargingScheduleLowerInterval
.end
,
1077 compositeChargingScheduleHigherInterval
.start
,
1079 : differenceInSeconds(
1080 compositeChargingScheduleHigherInterval
.end
,
1081 compositeChargingScheduleLowerInterval
.start
,
1083 chargingSchedulePeriod
: [
1084 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1087 startPeriod
: higherFirst
1089 : schedulePeriod
.startPeriod
+
1090 differenceInSeconds(
1091 compositeChargingScheduleHigherInterval
.start
,
1092 compositeChargingScheduleLowerInterval
.start
,
1096 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1099 startPeriod
: higherFirst
1100 ? schedulePeriod
.startPeriod
+
1101 differenceInSeconds(
1102 compositeChargingScheduleLowerInterval
.start
,
1103 compositeChargingScheduleHigherInterval
.start
,
1108 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1112 ...compositeChargingScheduleLower
,
1113 ...compositeChargingScheduleHigher
!,
1114 startSchedule
: higherFirst
1115 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1116 : (compositeChargingScheduleLowerInterval
.start
as Date),
1117 duration
: higherFirst
1118 ? differenceInSeconds(
1119 compositeChargingScheduleLowerInterval
.end
,
1120 compositeChargingScheduleHigherInterval
.start
,
1122 : differenceInSeconds(
1123 compositeChargingScheduleHigherInterval
.end
,
1124 compositeChargingScheduleLowerInterval
.start
,
1126 chargingSchedulePeriod
: [
1127 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1130 startPeriod
: higherFirst
1132 : schedulePeriod
.startPeriod
+
1133 differenceInSeconds(
1134 compositeChargingScheduleHigherInterval
.start
,
1135 compositeChargingScheduleLowerInterval
.start
,
1139 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
1140 .filter((schedulePeriod
, index
) => {
1145 compositeChargingScheduleLowerInterval
.start
,
1146 schedulePeriod
.startPeriod
,
1149 start
: compositeChargingScheduleLowerInterval
.start
,
1150 end
: compositeChargingScheduleHigherInterval
.end
,
1158 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
1161 compositeChargingScheduleLowerInterval
.start
,
1162 schedulePeriod
.startPeriod
,
1165 start
: compositeChargingScheduleLowerInterval
.start
,
1166 end
: compositeChargingScheduleHigherInterval
.end
,
1171 compositeChargingScheduleLowerInterval
.start
,
1172 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1175 start
: compositeChargingScheduleLowerInterval
.start
,
1176 end
: compositeChargingScheduleHigherInterval
.end
,
1186 compositeChargingScheduleLowerInterval
.start
,
1187 schedulePeriod
.startPeriod
,
1190 start
: compositeChargingScheduleHigherInterval
.start
,
1191 end
: compositeChargingScheduleLowerInterval
.end
,
1199 .map((schedulePeriod
, index
) => {
1200 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1201 schedulePeriod
.startPeriod
= 0;
1205 startPeriod
: higherFirst
1206 ? schedulePeriod
.startPeriod
+
1207 differenceInSeconds(
1208 compositeChargingScheduleLowerInterval
.start
,
1209 compositeChargingScheduleHigherInterval
.start
,
1214 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1218 public static hasReservation
= (
1219 chargingStation
: ChargingStation
,
1220 connectorId
: number,
1223 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
1224 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
1226 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
1227 OCPP16ChargePointStatus
.Reserved
&&
1228 connectorReservation
&&
1229 !hasReservationExpired(connectorReservation
) &&
1230 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1231 connectorReservation
?.idTag
=== idTag
) ||
1232 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
1233 chargingStationReservation
&&
1234 !hasReservationExpired(chargingStationReservation
) &&
1235 chargingStationReservation
?.idTag
=== idTag
)
1238 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1239 connectorReservation
?? chargingStationReservation
,
1246 public static parseJsonSchemaFile
<T
extends JsonType
>(
1247 relativePath
: string,
1248 moduleName
?: string,
1249 methodName
?: string,
1250 ): JSONSchemaType
<T
> {
1251 return super.parseJsonSchemaFile
<T
>(
1253 OCPPVersion
.VERSION_16
,
1259 private static composeChargingSchedule
= (
1260 chargingSchedule
: OCPP16ChargingSchedule
,
1261 compositeInterval
: Interval
,
1262 ): OCPP16ChargingSchedule
| undefined => {
1263 const chargingScheduleInterval
: Interval
= {
1264 start
: chargingSchedule
.startSchedule
!,
1265 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!),
1267 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
1268 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
);
1269 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
1271 ...chargingSchedule
,
1272 startSchedule
: compositeInterval
.start
as Date,
1273 duration
: differenceInSeconds(
1274 chargingScheduleInterval
.end
,
1275 compositeInterval
.start
as Date,
1277 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
1278 .filter((schedulePeriod
, index
) => {
1281 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1288 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1290 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
1295 chargingScheduleInterval
.start
,
1296 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1305 .map((schedulePeriod
, index
) => {
1306 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1307 schedulePeriod
.startPeriod
= 0;
1309 return schedulePeriod
;
1313 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
1315 ...chargingSchedule
,
1316 duration
: differenceInSeconds(
1317 compositeInterval
.end
as Date,
1318 chargingScheduleInterval
.start
,
1320 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
1322 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1328 return chargingSchedule
;
1332 private static buildSampledValue(
1333 sampledValueTemplate
: SampledValueTemplate
,
1335 context
?: MeterValueContext
,
1336 phase
?: OCPP16MeterValuePhase
,
1337 ): OCPP16SampledValue
{
1338 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1339 const sampledValueLocation
=
1340 sampledValueTemplate
?.location
??
1341 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1342 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1344 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1345 unit
: sampledValueTemplate
.unit
,
1347 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1348 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1349 measurand
: sampledValueTemplate
.measurand
,
1351 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1352 ...(!isNullOrUndefined(value
) && { value
: value
.toString() }),
1353 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1354 } as OCPP16SampledValue
;
1357 private static checkMeasurandPowerDivider(
1358 chargingStation
: ChargingStation
,
1359 measurandType
: OCPP16MeterValueMeasurand
,
1361 if (isUndefined(chargingStation
.powerDivider
)) {
1362 const errMsg
= `MeterValues measurand ${
1363 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1364 }: powerDivider is undefined`;
1365 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1366 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1367 } else if (chargingStation
?.powerDivider
<= 0) {
1368 const errMsg
= `MeterValues measurand ${
1369 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1370 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1371 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1372 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1376 private static getMeasurandDefaultLocation(
1377 measurandType
: OCPP16MeterValueMeasurand
,
1378 ): MeterValueLocation
| undefined {
1379 switch (measurandType
) {
1380 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1381 return MeterValueLocation
.EV
;
1385 // private static getMeasurandDefaultUnit(
1386 // measurandType: OCPP16MeterValueMeasurand,
1387 // ): MeterValueUnit | undefined {
1388 // switch (measurandType) {
1389 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1390 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1391 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1392 // return MeterValueUnit.AMP;
1393 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1394 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1395 // return MeterValueUnit.WATT_HOUR;
1396 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1397 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1398 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1399 // return MeterValueUnit.WATT;
1400 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1401 // return MeterValueUnit.PERCENT;
1402 // case OCPP16MeterValueMeasurand.VOLTAGE:
1403 // return MeterValueUnit.VOLT;