1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
3 import type { JSONSchemaType
} from
'ajv';
7 areIntervalsOverlapping
,
14 import { OCPP16Constants
} from
'./OCPP16Constants';
18 hasReservationExpired
,
19 } from
'../../../charging-station';
20 import { OCPPError
} from
'../../../exception';
22 type ClearChargingProfileRequest
,
27 type MeasurandPerPhaseSampledValueTemplates
,
32 OCPP16AuthorizationStatus
,
33 OCPP16AvailabilityType
,
34 type OCPP16ChangeAvailabilityResponse
,
35 OCPP16ChargePointStatus
,
36 type OCPP16ChargingProfile
,
37 type OCPP16ChargingSchedule
,
38 type OCPP16IncomingRequestCommand
,
39 type OCPP16MeterValue
,
40 OCPP16MeterValueMeasurand
,
41 OCPP16MeterValuePhase
,
43 type OCPP16SampledValue
,
44 OCPP16StandardParametersKey
,
45 OCPP16StopTransactionReason
,
46 type OCPP16SupportedFeatureProfiles
,
48 type SampledValueTemplate
,
49 } from
'../../../types';
56 getRandomFloatFluctuatedRounded
,
57 getRandomFloatRounded
,
65 } from
'../../../utils';
66 import { OCPPServiceUtils
} from
'../OCPPServiceUtils';
68 export class OCPP16ServiceUtils
extends OCPPServiceUtils
{
69 public static checkFeatureProfile(
70 chargingStation
: ChargingStation
,
71 featureProfile
: OCPP16SupportedFeatureProfiles
,
72 command
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
,
74 if (!hasFeatureProfile(chargingStation
, featureProfile
)) {
76 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
77 OCPP16StandardParametersKey.SupportedFeatureProfiles
85 public static buildMeterValue(
86 chargingStation
: ChargingStation
,
88 transactionId
: number,
92 const meterValue
: OCPP16MeterValue
= {
93 timestamp
: new Date(),
96 const connector
= chargingStation
.getConnectorStatus(connectorId
);
98 const socSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
101 OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
,
103 if (socSampledValueTemplate
) {
104 const socMaximumValue
= 100;
105 const socMinimumValue
= socSampledValueTemplate
.minimumValue
?? 0;
106 const socSampledValueTemplateValue
= isNotEmptyString(socSampledValueTemplate
.value
)
107 ? getRandomFloatFluctuatedRounded(
108 parseInt(socSampledValueTemplate
.value
),
109 socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
111 : getRandomInteger(socMaximumValue
, socMinimumValue
);
112 meterValue
.sampledValue
.push(
113 OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
),
115 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
117 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > socMaximumValue
||
118 convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) < socMinimumValue
||
122 `${chargingStation.logPrefix()} MeterValues measurand ${
123 meterValue.sampledValue[sampledValuesIndex].measurand ??
124 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
125 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
126 meterValue.sampledValue[sampledValuesIndex].value
127 }/${socMaximumValue}`,
132 const voltageSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
135 OCPP16MeterValueMeasurand
.VOLTAGE
,
137 if (voltageSampledValueTemplate
) {
138 const voltageSampledValueTemplateValue
= isNotEmptyString(voltageSampledValueTemplate
.value
)
139 ? parseInt(voltageSampledValueTemplate
.value
)
140 : chargingStation
.stationInfo
.voltageOut
!;
141 const fluctuationPercent
=
142 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
143 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
144 voltageSampledValueTemplateValue
,
148 chargingStation
.getNumberOfPhases() !== 3 ||
149 (chargingStation
.getNumberOfPhases() === 3 &&
150 chargingStation
.stationInfo
?.mainVoltageMeterValues
)
152 meterValue
.sampledValue
.push(
153 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
158 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
161 const phaseLineToNeutralValue
= `L${phase}-N`;
162 const voltagePhaseLineToNeutralSampledValueTemplate
=
163 OCPP16ServiceUtils
.getSampledValueTemplate(
166 OCPP16MeterValueMeasurand
.VOLTAGE
,
167 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
169 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
170 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
171 const voltagePhaseLineToNeutralSampledValueTemplateValue
= isNotEmptyString(
172 voltagePhaseLineToNeutralSampledValueTemplate
.value
,
174 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
175 : chargingStation
.stationInfo
.voltageOut
!;
176 const fluctuationPhaseToNeutralPercent
=
177 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
178 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
179 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
180 voltagePhaseLineToNeutralSampledValueTemplateValue
,
181 fluctuationPhaseToNeutralPercent
,
184 meterValue
.sampledValue
.push(
185 OCPP16ServiceUtils
.buildSampledValue(
186 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
187 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
189 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
192 if (chargingStation
.stationInfo
?.phaseLineToLineVoltageMeterValues
) {
193 const phaseLineToLineValue
= `L${phase}-L${
194 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
195 ? (phase + 1) % chargingStation.getNumberOfPhases()
196 : chargingStation.getNumberOfPhases()
198 const voltagePhaseLineToLineValueRounded
= roundTo(
199 Math.sqrt(chargingStation
.getNumberOfPhases()) *
200 chargingStation
.stationInfo
.voltageOut
!,
203 const voltagePhaseLineToLineSampledValueTemplate
=
204 OCPP16ServiceUtils
.getSampledValueTemplate(
207 OCPP16MeterValueMeasurand
.VOLTAGE
,
208 phaseLineToLineValue
as OCPP16MeterValuePhase
,
210 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
211 if (voltagePhaseLineToLineSampledValueTemplate
) {
212 const voltagePhaseLineToLineSampledValueTemplateValue
= isNotEmptyString(
213 voltagePhaseLineToLineSampledValueTemplate
.value
,
215 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
216 : voltagePhaseLineToLineValueRounded
;
217 const fluctuationPhaseLineToLinePercent
=
218 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
219 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
220 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
221 voltagePhaseLineToLineSampledValueTemplateValue
,
222 fluctuationPhaseLineToLinePercent
,
225 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
226 voltagePhaseLineToLineValueRounded
,
229 meterValue
.sampledValue
.push(
230 OCPP16ServiceUtils
.buildSampledValue(
231 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
232 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
234 phaseLineToLineValue
as OCPP16MeterValuePhase
,
240 // Power.Active.Import measurand
241 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
244 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
246 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
247 if (chargingStation
.getNumberOfPhases() === 3) {
248 powerPerPhaseSampledValueTemplates
= {
249 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
252 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
253 OCPP16MeterValuePhase
.L1_N
,
255 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
258 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
259 OCPP16MeterValuePhase
.L2_N
,
261 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
264 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
265 OCPP16MeterValuePhase
.L3_N
,
269 if (powerSampledValueTemplate
) {
270 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
272 powerSampledValueTemplate
.measurand
!,
274 const errMsg
= `MeterValues measurand ${
275 powerSampledValueTemplate.measurand ??
276 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
277 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
278 chargingStation.templateFile
279 }, cannot calculate ${
280 powerSampledValueTemplate.measurand ??
281 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
283 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
284 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
285 const connectorMaximumAvailablePower
=
286 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
287 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
288 const connectorMaximumPowerPerPhase
= Math.round(
289 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
291 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
?? 0);
292 const connectorMinimumPowerPerPhase
= Math.round(
293 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
295 switch (chargingStation
.stationInfo
?.currentOutType
) {
297 if (chargingStation
.getNumberOfPhases() === 3) {
298 const defaultFluctuatedPowerPerPhase
= isNotEmptyString(powerSampledValueTemplate
.value
)
299 ? getRandomFloatFluctuatedRounded(
300 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
301 powerSampledValueTemplate
.value
,
302 connectorMaximumPower
/ unitDivider
,
303 connectorMinimumPower
/ unitDivider
,
306 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
307 fallbackValue
: connectorMinimumPower
/ unitDivider
,
309 ) / chargingStation
.getNumberOfPhases(),
310 powerSampledValueTemplate
.fluctuationPercent
??
311 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
314 const phase1FluctuatedValue
= isNotEmptyString(
315 powerPerPhaseSampledValueTemplates
.L1
?.value
,
317 ? getRandomFloatFluctuatedRounded(
318 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
319 powerPerPhaseSampledValueTemplates
.L1
?.value
,
320 connectorMaximumPowerPerPhase
/ unitDivider
,
321 connectorMinimumPowerPerPhase
/ unitDivider
,
324 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
325 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
328 powerPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
329 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
332 const phase2FluctuatedValue
= isNotEmptyString(
333 powerPerPhaseSampledValueTemplates
.L2
?.value
,
335 ? getRandomFloatFluctuatedRounded(
336 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
337 powerPerPhaseSampledValueTemplates
.L2
?.value
,
338 connectorMaximumPowerPerPhase
/ unitDivider
,
339 connectorMinimumPowerPerPhase
/ unitDivider
,
342 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
343 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
346 powerPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
347 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
350 const phase3FluctuatedValue
= isNotEmptyString(
351 powerPerPhaseSampledValueTemplates
.L3
?.value
,
353 ? getRandomFloatFluctuatedRounded(
354 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
355 powerPerPhaseSampledValueTemplates
.L3
?.value
,
356 connectorMaximumPowerPerPhase
/ unitDivider
,
357 connectorMinimumPowerPerPhase
/ unitDivider
,
360 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
361 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
364 powerPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
365 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
368 powerMeasurandValues
.L1
=
369 phase1FluctuatedValue
??
370 defaultFluctuatedPowerPerPhase
??
371 getRandomFloatRounded(
372 connectorMaximumPowerPerPhase
/ unitDivider
,
373 connectorMinimumPowerPerPhase
/ unitDivider
,
375 powerMeasurandValues
.L2
=
376 phase2FluctuatedValue
??
377 defaultFluctuatedPowerPerPhase
??
378 getRandomFloatRounded(
379 connectorMaximumPowerPerPhase
/ unitDivider
,
380 connectorMinimumPowerPerPhase
/ unitDivider
,
382 powerMeasurandValues
.L3
=
383 phase3FluctuatedValue
??
384 defaultFluctuatedPowerPerPhase
??
385 getRandomFloatRounded(
386 connectorMaximumPowerPerPhase
/ unitDivider
,
387 connectorMinimumPowerPerPhase
/ unitDivider
,
390 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
391 ? getRandomFloatFluctuatedRounded(
392 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
393 powerSampledValueTemplate
.value
,
394 connectorMaximumPower
/ unitDivider
,
395 connectorMinimumPower
/ unitDivider
,
398 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
399 fallbackValue
: connectorMinimumPower
/ unitDivider
,
402 powerSampledValueTemplate
.fluctuationPercent
??
403 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
405 : getRandomFloatRounded(
406 connectorMaximumPower
/ unitDivider
,
407 connectorMinimumPower
/ unitDivider
,
409 powerMeasurandValues
.L2
= 0;
410 powerMeasurandValues
.L3
= 0;
412 powerMeasurandValues
.allPhases
= roundTo(
413 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
418 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
419 ? getRandomFloatFluctuatedRounded(
420 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
421 powerSampledValueTemplate
.value
,
422 connectorMaximumPower
/ unitDivider
,
423 connectorMinimumPower
/ unitDivider
,
426 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
427 fallbackValue
: connectorMinimumPower
/ unitDivider
,
430 powerSampledValueTemplate
.fluctuationPercent
??
431 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
433 : getRandomFloatRounded(
434 connectorMaximumPower
/ unitDivider
,
435 connectorMinimumPower
/ unitDivider
,
439 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
440 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
442 meterValue
.sampledValue
.push(
443 OCPP16ServiceUtils
.buildSampledValue(
444 powerSampledValueTemplate
,
445 powerMeasurandValues
.allPhases
,
448 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
449 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
450 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
452 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
453 connectorMaximumPowerRounded
||
454 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
455 connectorMinimumPowerRounded
||
459 `${chargingStation.logPrefix()} MeterValues measurand ${
460 meterValue.sampledValue[sampledValuesIndex].measurand ??
461 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
462 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
463 meterValue.sampledValue[sampledValuesIndex].value
464 }/${connectorMaximumPowerRounded}`,
469 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
472 const phaseValue
= `L${phase}-N`;
473 meterValue
.sampledValue
.push(
474 OCPP16ServiceUtils
.buildSampledValue(
475 powerPerPhaseSampledValueTemplates
[
476 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
477 ] ?? powerSampledValueTemplate
,
478 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
480 phaseValue
as OCPP16MeterValuePhase
,
483 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
484 const connectorMaximumPowerPerPhaseRounded
= roundTo(
485 connectorMaximumPowerPerPhase
/ unitDivider
,
488 const connectorMinimumPowerPerPhaseRounded
= roundTo(
489 connectorMinimumPowerPerPhase
/ unitDivider
,
493 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
494 connectorMaximumPowerPerPhaseRounded
||
495 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
496 connectorMinimumPowerPerPhaseRounded
||
500 `${chargingStation.logPrefix()} MeterValues measurand ${
501 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
502 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
504 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
505 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
506 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
507 }/${connectorMaximumPowerPerPhaseRounded}`,
512 // Current.Import measurand
513 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
516 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
518 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
519 if (chargingStation
.getNumberOfPhases() === 3) {
520 currentPerPhaseSampledValueTemplates
= {
521 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
524 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
525 OCPP16MeterValuePhase
.L1
,
527 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
530 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
531 OCPP16MeterValuePhase
.L2
,
533 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
536 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
537 OCPP16MeterValuePhase
.L3
,
541 if (currentSampledValueTemplate
) {
542 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
544 currentSampledValueTemplate
.measurand
!,
546 const errMsg
= `MeterValues measurand ${
547 currentSampledValueTemplate.measurand ??
548 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
549 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
550 chargingStation.templateFile
551 }, cannot calculate ${
552 currentSampledValueTemplate.measurand ??
553 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
555 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
556 const connectorMaximumAvailablePower
=
557 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
558 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
559 let connectorMaximumAmperage
: number;
560 switch (chargingStation
.stationInfo
?.currentOutType
) {
562 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
563 chargingStation
.getNumberOfPhases(),
564 connectorMaximumAvailablePower
,
565 chargingStation
.stationInfo
.voltageOut
!,
567 if (chargingStation
.getNumberOfPhases() === 3) {
568 const defaultFluctuatedAmperagePerPhase
= isNotEmptyString(
569 currentSampledValueTemplate
.value
,
571 ? getRandomFloatFluctuatedRounded(
572 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
573 currentSampledValueTemplate
.value
,
574 connectorMaximumAmperage
,
575 connectorMinimumAmperage
,
578 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
579 fallbackValue
: connectorMinimumAmperage
,
582 currentSampledValueTemplate
.fluctuationPercent
??
583 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
586 const phase1FluctuatedValue
= isNotEmptyString(
587 currentPerPhaseSampledValueTemplates
.L1
?.value
,
589 ? getRandomFloatFluctuatedRounded(
590 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
591 currentPerPhaseSampledValueTemplates
.L1
?.value
,
592 connectorMaximumAmperage
,
593 connectorMinimumAmperage
,
596 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
597 fallbackValue
: connectorMinimumAmperage
,
600 currentPerPhaseSampledValueTemplates
.L1
?.fluctuationPercent
??
601 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
604 const phase2FluctuatedValue
= isNotEmptyString(
605 currentPerPhaseSampledValueTemplates
.L2
?.value
,
607 ? getRandomFloatFluctuatedRounded(
608 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
609 currentPerPhaseSampledValueTemplates
.L2
?.value
,
610 connectorMaximumAmperage
,
611 connectorMinimumAmperage
,
614 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
615 fallbackValue
: connectorMinimumAmperage
,
618 currentPerPhaseSampledValueTemplates
.L2
?.fluctuationPercent
??
619 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
622 const phase3FluctuatedValue
= isNotEmptyString(
623 currentPerPhaseSampledValueTemplates
.L3
?.value
,
625 ? getRandomFloatFluctuatedRounded(
626 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
627 currentPerPhaseSampledValueTemplates
.L3
?.value
,
628 connectorMaximumAmperage
,
629 connectorMinimumAmperage
,
632 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
633 fallbackValue
: connectorMinimumAmperage
,
636 currentPerPhaseSampledValueTemplates
.L3
?.fluctuationPercent
??
637 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
640 currentMeasurandValues
.L1
=
641 phase1FluctuatedValue
??
642 defaultFluctuatedAmperagePerPhase
??
643 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
644 currentMeasurandValues
.L2
=
645 phase2FluctuatedValue
??
646 defaultFluctuatedAmperagePerPhase
??
647 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
648 currentMeasurandValues
.L3
=
649 phase3FluctuatedValue
??
650 defaultFluctuatedAmperagePerPhase
??
651 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
653 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
654 ? getRandomFloatFluctuatedRounded(
655 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
656 currentSampledValueTemplate
.value
,
657 connectorMaximumAmperage
,
658 connectorMinimumAmperage
,
661 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
662 fallbackValue
: connectorMinimumAmperage
,
665 currentSampledValueTemplate
.fluctuationPercent
??
666 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
668 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
669 currentMeasurandValues
.L2
= 0;
670 currentMeasurandValues
.L3
= 0;
672 currentMeasurandValues
.allPhases
= roundTo(
673 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
674 chargingStation
.getNumberOfPhases(),
679 connectorMaximumAmperage
= DCElectricUtils
.amperage(
680 connectorMaximumAvailablePower
,
681 chargingStation
.stationInfo
.voltageOut
!,
683 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
684 ? getRandomFloatFluctuatedRounded(
685 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
686 currentSampledValueTemplate
.value
,
687 connectorMaximumAmperage
,
688 connectorMinimumAmperage
,
691 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
692 fallbackValue
: connectorMinimumAmperage
,
695 currentSampledValueTemplate
.fluctuationPercent
??
696 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
698 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
701 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
702 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
704 meterValue
.sampledValue
.push(
705 OCPP16ServiceUtils
.buildSampledValue(
706 currentSampledValueTemplate
,
707 currentMeasurandValues
.allPhases
,
710 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
712 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
713 connectorMaximumAmperage
||
714 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
715 connectorMinimumAmperage
||
719 `${chargingStation.logPrefix()} MeterValues measurand ${
720 meterValue.sampledValue[sampledValuesIndex].measurand ??
721 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
722 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
723 meterValue.sampledValue[sampledValuesIndex].value
724 }/${connectorMaximumAmperage}`,
729 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
732 const phaseValue
= `L${phase}`;
733 meterValue
.sampledValue
.push(
734 OCPP16ServiceUtils
.buildSampledValue(
735 currentPerPhaseSampledValueTemplates
[
736 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
737 ] ?? currentSampledValueTemplate
,
738 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
740 phaseValue
as OCPP16MeterValuePhase
,
743 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
745 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
746 connectorMaximumAmperage
||
747 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
748 connectorMinimumAmperage
||
752 `${chargingStation.logPrefix()} MeterValues measurand ${
753 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
754 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
756 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
757 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
758 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
759 }/${connectorMaximumAmperage}`,
764 // Energy.Active.Import.Register measurand (default)
765 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
769 if (energySampledValueTemplate
) {
770 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
772 energySampledValueTemplate
.measurand
!,
775 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
776 const connectorMaximumAvailablePower
=
777 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
778 const connectorMaximumEnergyRounded
= roundTo(
779 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
782 const connectorMinimumEnergyRounded
= roundTo(
783 energySampledValueTemplate
.minimumValue
?? 0,
786 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
787 ? getRandomFloatFluctuatedRounded(
788 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
789 energySampledValueTemplate
.value
,
790 connectorMaximumEnergyRounded
,
791 connectorMinimumEnergyRounded
,
793 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
794 fallbackValue
: connectorMinimumEnergyRounded
,
795 unitMultiplier
: unitDivider
,
798 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
800 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
);
801 // Persist previous value on connector
804 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
805 connector
.energyActiveImportRegisterValue
! >= 0 &&
806 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
807 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
809 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
810 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
812 connector
.energyActiveImportRegisterValue
= 0;
813 connector
.transactionEnergyActiveImportRegisterValue
= 0;
816 meterValue
.sampledValue
.push(
817 OCPP16ServiceUtils
.buildSampledValue(
818 energySampledValueTemplate
,
820 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
826 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
828 energyValueRounded
> connectorMaximumEnergyRounded
||
829 energyValueRounded
< connectorMinimumEnergyRounded
||
833 `${chargingStation.logPrefix()} MeterValues measurand ${
834 meterValue.sampledValue[sampledValuesIndex].measurand ??
835 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
836 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
843 public static buildTransactionBeginMeterValue(
844 chargingStation
: ChargingStation
,
847 ): OCPP16MeterValue
{
848 const meterValue
: OCPP16MeterValue
= {
849 timestamp
: new Date(),
852 // Energy.Active.Import.Register measurand (default)
853 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
857 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
858 meterValue
.sampledValue
.push(
859 OCPP16ServiceUtils
.buildSampledValue(
860 sampledValueTemplate
!,
861 roundTo((meterStart
?? 0) / unitDivider
, 4),
862 MeterValueContext
.TRANSACTION_BEGIN
,
868 public static buildTransactionEndMeterValue(
869 chargingStation
: ChargingStation
,
872 ): OCPP16MeterValue
{
873 const meterValue
: OCPP16MeterValue
= {
874 timestamp
: new Date(),
877 // Energy.Active.Import.Register measurand (default)
878 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
882 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
883 meterValue
.sampledValue
.push(
884 OCPP16ServiceUtils
.buildSampledValue(
885 sampledValueTemplate
!,
886 roundTo((meterStop
?? 0) / unitDivider
, 4),
887 MeterValueContext
.TRANSACTION_END
,
893 public static buildTransactionDataMeterValues(
894 transactionBeginMeterValue
: OCPP16MeterValue
,
895 transactionEndMeterValue
: OCPP16MeterValue
,
896 ): OCPP16MeterValue
[] {
897 const meterValues
: OCPP16MeterValue
[] = [];
898 meterValues
.push(transactionBeginMeterValue
);
899 meterValues
.push(transactionEndMeterValue
);
903 public static remoteStopTransaction
= async (
904 chargingStation
: ChargingStation
,
906 ): Promise
<GenericResponse
> => {
907 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
910 OCPP16ChargePointStatus
.Finishing
,
912 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
914 OCPP16StopTransactionReason
.REMOTE
,
916 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
917 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
919 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
922 public static changeAvailability
= async (
923 chargingStation
: ChargingStation
,
924 connectorIds
: number[],
925 chargePointStatus
: OCPP16ChargePointStatus
,
926 availabilityType
: OCPP16AvailabilityType
,
927 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
928 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
929 for (const connectorId
of connectorIds
) {
930 let response
: OCPP16ChangeAvailabilityResponse
=
931 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
932 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
933 if (connectorStatus
?.transactionStarted
=== true) {
934 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
936 connectorStatus
.availability
= availabilityType
;
937 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
938 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
944 responses
.push(response
);
946 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
947 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
949 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
952 public static setChargingProfile(
953 chargingStation
: ChargingStation
,
955 cp
: OCPP16ChargingProfile
,
957 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
959 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
961 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
964 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
967 `${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`,
969 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
971 let cpReplaced
= false;
972 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
974 .getConnectorStatus(connectorId
)
975 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
977 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
978 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
979 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
981 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
986 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
989 public static clearChargingProfiles
= (
990 chargingStation
: ChargingStation
,
991 commandPayload
: ClearChargingProfileRequest
,
992 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
994 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
;
995 let clearedCP
= false;
996 if (isNotEmptyArray(chargingProfiles
)) {
997 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
998 let clearCurrentCP
= false;
999 if (chargingProfile
.chargingProfileId
=== id
) {
1000 clearCurrentCP
= true;
1002 if (!chargingProfilePurpose
&& chargingProfile
.stackLevel
=== stackLevel
) {
1003 clearCurrentCP
= true;
1005 if (!stackLevel
&& chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
) {
1006 clearCurrentCP
= true;
1009 chargingProfile
.stackLevel
=== stackLevel
&&
1010 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
1012 clearCurrentCP
= true;
1014 if (clearCurrentCP
) {
1015 chargingProfiles
.splice(index
, 1);
1017 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
1027 public static composeChargingSchedules
= (
1028 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
1029 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
1030 compositeInterval
: Interval
,
1031 ): OCPP16ChargingSchedule
| undefined => {
1032 if (!chargingScheduleHigher
&& !chargingScheduleLower
) {
1035 if (chargingScheduleHigher
&& !chargingScheduleLower
) {
1036 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
);
1038 if (!chargingScheduleHigher
&& chargingScheduleLower
) {
1039 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
);
1041 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
1042 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
);
1043 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
1044 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
);
1045 const compositeChargingScheduleHigherInterval
: Interval
= {
1046 start
: compositeChargingScheduleHigher
!.startSchedule
!,
1048 compositeChargingScheduleHigher
!.startSchedule
!,
1049 compositeChargingScheduleHigher
!.duration
!,
1052 const compositeChargingScheduleLowerInterval
: Interval
= {
1053 start
: compositeChargingScheduleLower
!.startSchedule
!,
1055 compositeChargingScheduleLower
!.startSchedule
!,
1056 compositeChargingScheduleLower
!.duration
!,
1059 const higherFirst
= isBefore(
1060 compositeChargingScheduleHigherInterval
.start
,
1061 compositeChargingScheduleLowerInterval
.start
,
1064 !areIntervalsOverlapping(
1065 compositeChargingScheduleHigherInterval
,
1066 compositeChargingScheduleLowerInterval
,
1070 ...compositeChargingScheduleLower
,
1071 ...compositeChargingScheduleHigher
!,
1072 startSchedule
: higherFirst
1073 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1074 : (compositeChargingScheduleLowerInterval
.start
as Date),
1075 duration
: higherFirst
1076 ? differenceInSeconds(
1077 compositeChargingScheduleLowerInterval
.end
,
1078 compositeChargingScheduleHigherInterval
.start
,
1080 : differenceInSeconds(
1081 compositeChargingScheduleHigherInterval
.end
,
1082 compositeChargingScheduleLowerInterval
.start
,
1084 chargingSchedulePeriod
: [
1085 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1088 startPeriod
: higherFirst
1090 : schedulePeriod
.startPeriod
+
1091 differenceInSeconds(
1092 compositeChargingScheduleHigherInterval
.start
,
1093 compositeChargingScheduleLowerInterval
.start
,
1097 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1100 startPeriod
: higherFirst
1101 ? schedulePeriod
.startPeriod
+
1102 differenceInSeconds(
1103 compositeChargingScheduleLowerInterval
.start
,
1104 compositeChargingScheduleHigherInterval
.start
,
1109 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1113 ...compositeChargingScheduleLower
,
1114 ...compositeChargingScheduleHigher
!,
1115 startSchedule
: higherFirst
1116 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1117 : (compositeChargingScheduleLowerInterval
.start
as Date),
1118 duration
: higherFirst
1119 ? differenceInSeconds(
1120 compositeChargingScheduleLowerInterval
.end
,
1121 compositeChargingScheduleHigherInterval
.start
,
1123 : differenceInSeconds(
1124 compositeChargingScheduleHigherInterval
.end
,
1125 compositeChargingScheduleLowerInterval
.start
,
1127 chargingSchedulePeriod
: [
1128 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1131 startPeriod
: higherFirst
1133 : schedulePeriod
.startPeriod
+
1134 differenceInSeconds(
1135 compositeChargingScheduleHigherInterval
.start
,
1136 compositeChargingScheduleLowerInterval
.start
,
1140 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
1141 .filter((schedulePeriod
, index
) => {
1146 compositeChargingScheduleLowerInterval
.start
,
1147 schedulePeriod
.startPeriod
,
1150 start
: compositeChargingScheduleLowerInterval
.start
,
1151 end
: compositeChargingScheduleHigherInterval
.end
,
1159 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
1162 compositeChargingScheduleLowerInterval
.start
,
1163 schedulePeriod
.startPeriod
,
1166 start
: compositeChargingScheduleLowerInterval
.start
,
1167 end
: compositeChargingScheduleHigherInterval
.end
,
1172 compositeChargingScheduleLowerInterval
.start
,
1173 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1176 start
: compositeChargingScheduleLowerInterval
.start
,
1177 end
: compositeChargingScheduleHigherInterval
.end
,
1187 compositeChargingScheduleLowerInterval
.start
,
1188 schedulePeriod
.startPeriod
,
1191 start
: compositeChargingScheduleHigherInterval
.start
,
1192 end
: compositeChargingScheduleLowerInterval
.end
,
1200 .map((schedulePeriod
, index
) => {
1201 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1202 schedulePeriod
.startPeriod
= 0;
1206 startPeriod
: higherFirst
1207 ? schedulePeriod
.startPeriod
+
1208 differenceInSeconds(
1209 compositeChargingScheduleLowerInterval
.start
,
1210 compositeChargingScheduleHigherInterval
.start
,
1215 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1219 public static hasReservation
= (
1220 chargingStation
: ChargingStation
,
1221 connectorId
: number,
1224 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
1225 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
1227 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
1228 OCPP16ChargePointStatus
.Reserved
&&
1229 connectorReservation
&&
1230 !hasReservationExpired(connectorReservation
) &&
1231 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1232 connectorReservation
?.idTag
=== idTag
) ||
1233 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
1234 chargingStationReservation
&&
1235 !hasReservationExpired(chargingStationReservation
) &&
1236 chargingStationReservation
?.idTag
=== idTag
)
1239 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1240 connectorReservation
?? chargingStationReservation
,
1247 public static parseJsonSchemaFile
<T
extends JsonType
>(
1248 relativePath
: string,
1249 moduleName
?: string,
1250 methodName
?: string,
1251 ): JSONSchemaType
<T
> {
1252 return super.parseJsonSchemaFile
<T
>(
1254 OCPPVersion
.VERSION_16
,
1260 private static composeChargingSchedule
= (
1261 chargingSchedule
: OCPP16ChargingSchedule
,
1262 compositeInterval
: Interval
,
1263 ): OCPP16ChargingSchedule
| undefined => {
1264 const chargingScheduleInterval
: Interval
= {
1265 start
: chargingSchedule
.startSchedule
!,
1266 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!),
1268 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
1269 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
);
1270 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
1272 ...chargingSchedule
,
1273 startSchedule
: compositeInterval
.start
as Date,
1274 duration
: differenceInSeconds(
1275 chargingScheduleInterval
.end
,
1276 compositeInterval
.start
as Date,
1278 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
1279 .filter((schedulePeriod
, index
) => {
1282 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1289 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1291 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
1296 chargingScheduleInterval
.start
,
1297 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1306 .map((schedulePeriod
, index
) => {
1307 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1308 schedulePeriod
.startPeriod
= 0;
1310 return schedulePeriod
;
1314 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
1316 ...chargingSchedule
,
1317 duration
: differenceInSeconds(
1318 compositeInterval
.end
as Date,
1319 chargingScheduleInterval
.start
,
1321 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
1323 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1329 return chargingSchedule
;
1333 private static buildSampledValue(
1334 sampledValueTemplate
: SampledValueTemplate
,
1336 context
?: MeterValueContext
,
1337 phase
?: OCPP16MeterValuePhase
,
1338 ): OCPP16SampledValue
{
1339 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1340 const sampledValueLocation
=
1341 sampledValueTemplate
?.location
??
1342 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1343 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1345 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1346 unit
: sampledValueTemplate
.unit
,
1348 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1349 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1350 measurand
: sampledValueTemplate
.measurand
,
1352 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1353 ...(!isNullOrUndefined(value
) && { value
: value
.toString() }),
1354 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1355 } as OCPP16SampledValue
;
1358 private static checkMeasurandPowerDivider(
1359 chargingStation
: ChargingStation
,
1360 measurandType
: OCPP16MeterValueMeasurand
,
1362 if (isUndefined(chargingStation
.powerDivider
)) {
1363 const errMsg
= `MeterValues measurand ${
1364 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1365 }: powerDivider is undefined`;
1366 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1367 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1368 } else if (chargingStation
?.powerDivider
<= 0) {
1369 const errMsg
= `MeterValues measurand ${
1370 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1371 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1372 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1373 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1377 private static getMeasurandDefaultLocation(
1378 measurandType
: OCPP16MeterValueMeasurand
,
1379 ): MeterValueLocation
| undefined {
1380 switch (measurandType
) {
1381 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1382 return MeterValueLocation
.EV
;
1386 // private static getMeasurandDefaultUnit(
1387 // measurandType: OCPP16MeterValueMeasurand,
1388 // ): MeterValueUnit | undefined {
1389 // switch (measurandType) {
1390 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1391 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1392 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1393 // return MeterValueUnit.AMP;
1394 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1395 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1396 // return MeterValueUnit.WATT_HOUR;
1397 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1398 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1399 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1400 // return MeterValueUnit.WATT;
1401 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1402 // return MeterValueUnit.PERCENT;
1403 // case OCPP16MeterValueMeasurand.VOLTAGE:
1404 // return MeterValueUnit.VOLT;