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
=
298 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
,
313 const phase1FluctuatedValue
=
314 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
315 getRandomFloatFluctuatedRounded(
316 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
317 powerPerPhaseSampledValueTemplates
.L1
.value
,
318 connectorMaximumPowerPerPhase
/ unitDivider
,
319 connectorMinimumPowerPerPhase
/ unitDivider
,
322 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
323 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
326 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
327 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
329 const phase2FluctuatedValue
=
330 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
331 getRandomFloatFluctuatedRounded(
332 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
333 powerPerPhaseSampledValueTemplates
.L2
.value
,
334 connectorMaximumPowerPerPhase
/ unitDivider
,
335 connectorMinimumPowerPerPhase
/ unitDivider
,
338 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
339 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
342 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
343 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
345 const phase3FluctuatedValue
=
346 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
347 getRandomFloatFluctuatedRounded(
348 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
349 powerPerPhaseSampledValueTemplates
.L3
.value
,
350 connectorMaximumPowerPerPhase
/ unitDivider
,
351 connectorMinimumPowerPerPhase
/ unitDivider
,
354 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
355 fallbackValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
358 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
359 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
361 powerMeasurandValues
.L1
=
362 (phase1FluctuatedValue
as number) ??
363 (defaultFluctuatedPowerPerPhase
as number) ??
364 getRandomFloatRounded(
365 connectorMaximumPowerPerPhase
/ unitDivider
,
366 connectorMinimumPowerPerPhase
/ unitDivider
,
368 powerMeasurandValues
.L2
=
369 (phase2FluctuatedValue
as number) ??
370 (defaultFluctuatedPowerPerPhase
as number) ??
371 getRandomFloatRounded(
372 connectorMaximumPowerPerPhase
/ unitDivider
,
373 connectorMinimumPowerPerPhase
/ unitDivider
,
375 powerMeasurandValues
.L3
=
376 (phase3FluctuatedValue
as number) ??
377 (defaultFluctuatedPowerPerPhase
as number) ??
378 getRandomFloatRounded(
379 connectorMaximumPowerPerPhase
/ unitDivider
,
380 connectorMinimumPowerPerPhase
/ unitDivider
,
383 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
384 ? getRandomFloatFluctuatedRounded(
385 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
386 powerSampledValueTemplate
.value
,
387 connectorMaximumPower
/ unitDivider
,
388 connectorMinimumPower
/ unitDivider
,
391 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
392 fallbackValue
: connectorMinimumPower
/ unitDivider
,
395 powerSampledValueTemplate
.fluctuationPercent
??
396 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
398 : getRandomFloatRounded(
399 connectorMaximumPower
/ unitDivider
,
400 connectorMinimumPower
/ unitDivider
,
402 powerMeasurandValues
.L2
= 0;
403 powerMeasurandValues
.L3
= 0;
405 powerMeasurandValues
.allPhases
= roundTo(
406 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
411 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
412 ? getRandomFloatFluctuatedRounded(
413 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
414 powerSampledValueTemplate
.value
,
415 connectorMaximumPower
/ unitDivider
,
416 connectorMinimumPower
/ unitDivider
,
419 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
420 fallbackValue
: connectorMinimumPower
/ unitDivider
,
423 powerSampledValueTemplate
.fluctuationPercent
??
424 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
426 : getRandomFloatRounded(
427 connectorMaximumPower
/ unitDivider
,
428 connectorMinimumPower
/ unitDivider
,
432 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
433 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
435 meterValue
.sampledValue
.push(
436 OCPP16ServiceUtils
.buildSampledValue(
437 powerSampledValueTemplate
,
438 powerMeasurandValues
.allPhases
,
441 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
442 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
443 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
445 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
446 connectorMaximumPowerRounded
||
447 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
448 connectorMinimumPowerRounded
||
452 `${chargingStation.logPrefix()} MeterValues measurand ${
453 meterValue.sampledValue[sampledValuesIndex].measurand ??
454 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
455 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
456 meterValue.sampledValue[sampledValuesIndex].value
457 }/${connectorMaximumPowerRounded}`,
462 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
465 const phaseValue
= `L${phase}-N`;
466 meterValue
.sampledValue
.push(
467 OCPP16ServiceUtils
.buildSampledValue(
468 powerPerPhaseSampledValueTemplates
[
469 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
470 ]! ?? powerSampledValueTemplate
,
471 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
473 phaseValue
as OCPP16MeterValuePhase
,
476 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
477 const connectorMaximumPowerPerPhaseRounded
= roundTo(
478 connectorMaximumPowerPerPhase
/ unitDivider
,
481 const connectorMinimumPowerPerPhaseRounded
= roundTo(
482 connectorMinimumPowerPerPhase
/ unitDivider
,
486 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
487 connectorMaximumPowerPerPhaseRounded
||
488 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
489 connectorMinimumPowerPerPhaseRounded
||
493 `${chargingStation.logPrefix()} MeterValues measurand ${
494 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
495 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
497 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
498 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
499 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
500 }/${connectorMaximumPowerPerPhaseRounded}`,
505 // Current.Import measurand
506 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
509 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
511 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
512 if (chargingStation
.getNumberOfPhases() === 3) {
513 currentPerPhaseSampledValueTemplates
= {
514 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
517 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
518 OCPP16MeterValuePhase
.L1
,
520 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
523 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
524 OCPP16MeterValuePhase
.L2
,
526 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
529 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
530 OCPP16MeterValuePhase
.L3
,
534 if (currentSampledValueTemplate
) {
535 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
537 currentSampledValueTemplate
.measurand
!,
539 const errMsg
= `MeterValues measurand ${
540 currentSampledValueTemplate.measurand ??
541 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
542 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
543 chargingStation.templateFile
544 }, cannot calculate ${
545 currentSampledValueTemplate.measurand ??
546 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
548 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
549 const connectorMaximumAvailablePower
=
550 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
551 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
552 let connectorMaximumAmperage
: number;
553 switch (chargingStation
.stationInfo
?.currentOutType
) {
555 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
556 chargingStation
.getNumberOfPhases(),
557 connectorMaximumAvailablePower
,
558 chargingStation
.stationInfo
.voltageOut
!,
560 if (chargingStation
.getNumberOfPhases() === 3) {
561 const defaultFluctuatedAmperagePerPhase
=
562 currentSampledValueTemplate
.value
&&
563 getRandomFloatFluctuatedRounded(
564 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
565 currentSampledValueTemplate
.value
,
566 connectorMaximumAmperage
,
567 connectorMinimumAmperage
,
570 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
571 fallbackValue
: connectorMinimumAmperage
,
574 currentSampledValueTemplate
.fluctuationPercent
??
575 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
577 const phase1FluctuatedValue
=
578 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
579 getRandomFloatFluctuatedRounded(
580 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
581 currentPerPhaseSampledValueTemplates
.L1
.value
,
582 connectorMaximumAmperage
,
583 connectorMinimumAmperage
,
586 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
587 fallbackValue
: connectorMinimumAmperage
,
590 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
591 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
593 const phase2FluctuatedValue
=
594 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
595 getRandomFloatFluctuatedRounded(
596 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
597 currentPerPhaseSampledValueTemplates
.L2
.value
,
598 connectorMaximumAmperage
,
599 connectorMinimumAmperage
,
602 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
603 fallbackValue
: connectorMinimumAmperage
,
606 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
607 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
609 const phase3FluctuatedValue
=
610 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
611 getRandomFloatFluctuatedRounded(
612 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
613 currentPerPhaseSampledValueTemplates
.L3
.value
,
614 connectorMaximumAmperage
,
615 connectorMinimumAmperage
,
618 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
619 fallbackValue
: connectorMinimumAmperage
,
622 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
623 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
625 currentMeasurandValues
.L1
=
626 (phase1FluctuatedValue
as number) ??
627 (defaultFluctuatedAmperagePerPhase
as number) ??
628 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
629 currentMeasurandValues
.L2
=
630 (phase2FluctuatedValue
as number) ??
631 (defaultFluctuatedAmperagePerPhase
as number) ??
632 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
633 currentMeasurandValues
.L3
=
634 (phase3FluctuatedValue
as number) ??
635 (defaultFluctuatedAmperagePerPhase
as number) ??
636 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
638 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
639 ? getRandomFloatFluctuatedRounded(
640 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
641 currentSampledValueTemplate
.value
,
642 connectorMaximumAmperage
,
643 connectorMinimumAmperage
,
646 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
647 fallbackValue
: connectorMinimumAmperage
,
650 currentSampledValueTemplate
.fluctuationPercent
??
651 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
653 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
654 currentMeasurandValues
.L2
= 0;
655 currentMeasurandValues
.L3
= 0;
657 currentMeasurandValues
.allPhases
= roundTo(
658 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
659 chargingStation
.getNumberOfPhases(),
664 connectorMaximumAmperage
= DCElectricUtils
.amperage(
665 connectorMaximumAvailablePower
,
666 chargingStation
.stationInfo
.voltageOut
!,
668 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
669 ? getRandomFloatFluctuatedRounded(
670 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
671 currentSampledValueTemplate
.value
,
672 connectorMaximumAmperage
,
673 connectorMinimumAmperage
,
676 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
677 fallbackValue
: connectorMinimumAmperage
,
680 currentSampledValueTemplate
.fluctuationPercent
??
681 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
683 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
686 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
687 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
689 meterValue
.sampledValue
.push(
690 OCPP16ServiceUtils
.buildSampledValue(
691 currentSampledValueTemplate
,
692 currentMeasurandValues
.allPhases
,
695 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
697 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
698 connectorMaximumAmperage
||
699 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
700 connectorMinimumAmperage
||
704 `${chargingStation.logPrefix()} MeterValues measurand ${
705 meterValue.sampledValue[sampledValuesIndex].measurand ??
706 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
707 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
708 meterValue.sampledValue[sampledValuesIndex].value
709 }/${connectorMaximumAmperage}`,
714 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
717 const phaseValue
= `L${phase}`;
718 meterValue
.sampledValue
.push(
719 OCPP16ServiceUtils
.buildSampledValue(
720 currentPerPhaseSampledValueTemplates
[
721 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
722 ]! ?? currentSampledValueTemplate
,
723 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
725 phaseValue
as OCPP16MeterValuePhase
,
728 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
730 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
731 connectorMaximumAmperage
||
732 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
733 connectorMinimumAmperage
||
737 `${chargingStation.logPrefix()} MeterValues measurand ${
738 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
739 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
741 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
742 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
743 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
744 }/${connectorMaximumAmperage}`,
749 // Energy.Active.Import.Register measurand (default)
750 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
754 if (energySampledValueTemplate
) {
755 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
757 energySampledValueTemplate
.measurand
!,
760 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
761 const connectorMaximumAvailablePower
=
762 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
763 const connectorMaximumEnergyRounded
= roundTo(
764 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
767 const connectorMinimumEnergyRounded
= roundTo(
768 energySampledValueTemplate
.minimumValue
?? 0,
771 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
772 ? // Cumulate the fluctuated value around the static one
773 getRandomFloatFluctuatedRounded(
774 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
775 energySampledValueTemplate
.value
,
776 connectorMaximumEnergyRounded
,
777 connectorMinimumEnergyRounded
,
779 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
780 unitMultiplier
: unitDivider
,
781 fallbackValue
: connectorMinimumEnergyRounded
,
784 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
786 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
);
787 // Persist previous value on connector
790 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
791 connector
.energyActiveImportRegisterValue
! >= 0 &&
792 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
793 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
795 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
796 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
798 connector
.energyActiveImportRegisterValue
= 0;
799 connector
.transactionEnergyActiveImportRegisterValue
= 0;
802 meterValue
.sampledValue
.push(
803 OCPP16ServiceUtils
.buildSampledValue(
804 energySampledValueTemplate
,
806 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
812 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
814 energyValueRounded
> connectorMaximumEnergyRounded
||
815 energyValueRounded
< connectorMinimumEnergyRounded
||
819 `${chargingStation.logPrefix()} MeterValues measurand ${
820 meterValue.sampledValue[sampledValuesIndex].measurand ??
821 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
822 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
829 public static buildTransactionBeginMeterValue(
830 chargingStation
: ChargingStation
,
833 ): OCPP16MeterValue
{
834 const meterValue
: OCPP16MeterValue
= {
835 timestamp
: new Date(),
838 // Energy.Active.Import.Register measurand (default)
839 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
843 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
844 meterValue
.sampledValue
.push(
845 OCPP16ServiceUtils
.buildSampledValue(
846 sampledValueTemplate
!,
847 roundTo((meterStart
?? 0) / unitDivider
, 4),
848 MeterValueContext
.TRANSACTION_BEGIN
,
854 public static buildTransactionEndMeterValue(
855 chargingStation
: ChargingStation
,
858 ): OCPP16MeterValue
{
859 const meterValue
: OCPP16MeterValue
= {
860 timestamp
: new Date(),
863 // Energy.Active.Import.Register measurand (default)
864 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
868 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
869 meterValue
.sampledValue
.push(
870 OCPP16ServiceUtils
.buildSampledValue(
871 sampledValueTemplate
!,
872 roundTo((meterStop
?? 0) / unitDivider
, 4),
873 MeterValueContext
.TRANSACTION_END
,
879 public static buildTransactionDataMeterValues(
880 transactionBeginMeterValue
: OCPP16MeterValue
,
881 transactionEndMeterValue
: OCPP16MeterValue
,
882 ): OCPP16MeterValue
[] {
883 const meterValues
: OCPP16MeterValue
[] = [];
884 meterValues
.push(transactionBeginMeterValue
);
885 meterValues
.push(transactionEndMeterValue
);
889 public static remoteStopTransaction
= async (
890 chargingStation
: ChargingStation
,
892 ): Promise
<GenericResponse
> => {
893 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
896 OCPP16ChargePointStatus
.Finishing
,
898 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
900 OCPP16StopTransactionReason
.REMOTE
,
902 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
903 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
905 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
908 public static changeAvailability
= async (
909 chargingStation
: ChargingStation
,
910 connectorIds
: number[],
911 chargePointStatus
: OCPP16ChargePointStatus
,
912 availabilityType
: OCPP16AvailabilityType
,
913 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
914 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
915 for (const connectorId
of connectorIds
) {
916 let response
: OCPP16ChangeAvailabilityResponse
=
917 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
918 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
919 if (connectorStatus
?.transactionStarted
=== true) {
920 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
922 connectorStatus
.availability
= availabilityType
;
923 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
924 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
930 responses
.push(response
);
932 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
933 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
935 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
938 public static setChargingProfile(
939 chargingStation
: ChargingStation
,
941 cp
: OCPP16ChargingProfile
,
943 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
945 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
947 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
950 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
953 `${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`,
955 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
957 let cpReplaced
= false;
958 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
960 .getConnectorStatus(connectorId
)
961 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
963 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
964 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
965 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
967 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
972 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
975 public static clearChargingProfiles
= (
976 chargingStation
: ChargingStation
,
977 commandPayload
: ClearChargingProfileRequest
,
978 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
980 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
;
981 let clearedCP
= false;
982 if (isNotEmptyArray(chargingProfiles
)) {
983 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
984 let clearCurrentCP
= false;
985 if (chargingProfile
.chargingProfileId
=== id
) {
986 clearCurrentCP
= true;
988 if (!chargingProfilePurpose
&& chargingProfile
.stackLevel
=== stackLevel
) {
989 clearCurrentCP
= true;
991 if (!stackLevel
&& chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
) {
992 clearCurrentCP
= true;
995 chargingProfile
.stackLevel
=== stackLevel
&&
996 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
998 clearCurrentCP
= true;
1000 if (clearCurrentCP
) {
1001 chargingProfiles
.splice(index
, 1);
1003 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
1013 public static composeChargingSchedules
= (
1014 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
1015 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
1016 compositeInterval
: Interval
,
1017 ): OCPP16ChargingSchedule
| undefined => {
1018 if (!chargingScheduleHigher
&& !chargingScheduleLower
) {
1021 if (chargingScheduleHigher
&& !chargingScheduleLower
) {
1022 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
);
1024 if (!chargingScheduleHigher
&& chargingScheduleLower
) {
1025 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
);
1027 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
1028 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
);
1029 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
1030 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
);
1031 const compositeChargingScheduleHigherInterval
: Interval
= {
1032 start
: compositeChargingScheduleHigher
!.startSchedule
!,
1034 compositeChargingScheduleHigher
!.startSchedule
!,
1035 compositeChargingScheduleHigher
!.duration
!,
1038 const compositeChargingScheduleLowerInterval
: Interval
= {
1039 start
: compositeChargingScheduleLower
!.startSchedule
!,
1041 compositeChargingScheduleLower
!.startSchedule
!,
1042 compositeChargingScheduleLower
!.duration
!,
1045 const higherFirst
= isBefore(
1046 compositeChargingScheduleHigherInterval
.start
,
1047 compositeChargingScheduleLowerInterval
.start
,
1050 !areIntervalsOverlapping(
1051 compositeChargingScheduleHigherInterval
,
1052 compositeChargingScheduleLowerInterval
,
1056 ...compositeChargingScheduleLower
,
1057 ...compositeChargingScheduleHigher
!,
1058 startSchedule
: higherFirst
1059 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1060 : (compositeChargingScheduleLowerInterval
.start
as Date),
1061 duration
: higherFirst
1062 ? differenceInSeconds(
1063 compositeChargingScheduleLowerInterval
.end
,
1064 compositeChargingScheduleHigherInterval
.start
,
1066 : differenceInSeconds(
1067 compositeChargingScheduleHigherInterval
.end
,
1068 compositeChargingScheduleLowerInterval
.start
,
1070 chargingSchedulePeriod
: [
1071 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1074 startPeriod
: higherFirst
1076 : schedulePeriod
.startPeriod
+
1077 differenceInSeconds(
1078 compositeChargingScheduleHigherInterval
.start
,
1079 compositeChargingScheduleLowerInterval
.start
,
1083 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1086 startPeriod
: higherFirst
1087 ? schedulePeriod
.startPeriod
+
1088 differenceInSeconds(
1089 compositeChargingScheduleLowerInterval
.start
,
1090 compositeChargingScheduleHigherInterval
.start
,
1095 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1099 ...compositeChargingScheduleLower
,
1100 ...compositeChargingScheduleHigher
!,
1101 startSchedule
: higherFirst
1102 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1103 : (compositeChargingScheduleLowerInterval
.start
as Date),
1104 duration
: higherFirst
1105 ? differenceInSeconds(
1106 compositeChargingScheduleLowerInterval
.end
,
1107 compositeChargingScheduleHigherInterval
.start
,
1109 : differenceInSeconds(
1110 compositeChargingScheduleHigherInterval
.end
,
1111 compositeChargingScheduleLowerInterval
.start
,
1113 chargingSchedulePeriod
: [
1114 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1117 startPeriod
: higherFirst
1119 : schedulePeriod
.startPeriod
+
1120 differenceInSeconds(
1121 compositeChargingScheduleHigherInterval
.start
,
1122 compositeChargingScheduleLowerInterval
.start
,
1126 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
1127 .filter((schedulePeriod
, index
) => {
1132 compositeChargingScheduleLowerInterval
.start
,
1133 schedulePeriod
.startPeriod
,
1136 start
: compositeChargingScheduleLowerInterval
.start
,
1137 end
: compositeChargingScheduleHigherInterval
.end
,
1145 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
1148 compositeChargingScheduleLowerInterval
.start
,
1149 schedulePeriod
.startPeriod
,
1152 start
: compositeChargingScheduleLowerInterval
.start
,
1153 end
: compositeChargingScheduleHigherInterval
.end
,
1158 compositeChargingScheduleLowerInterval
.start
,
1159 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1162 start
: compositeChargingScheduleLowerInterval
.start
,
1163 end
: compositeChargingScheduleHigherInterval
.end
,
1173 compositeChargingScheduleLowerInterval
.start
,
1174 schedulePeriod
.startPeriod
,
1177 start
: compositeChargingScheduleHigherInterval
.start
,
1178 end
: compositeChargingScheduleLowerInterval
.end
,
1186 .map((schedulePeriod
, index
) => {
1187 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1188 schedulePeriod
.startPeriod
= 0;
1192 startPeriod
: higherFirst
1193 ? schedulePeriod
.startPeriod
+
1194 differenceInSeconds(
1195 compositeChargingScheduleLowerInterval
.start
,
1196 compositeChargingScheduleHigherInterval
.start
,
1201 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1205 public static hasReservation
= (
1206 chargingStation
: ChargingStation
,
1207 connectorId
: number,
1210 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
1211 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
1213 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
1214 OCPP16ChargePointStatus
.Reserved
&&
1215 connectorReservation
&&
1216 !hasReservationExpired(connectorReservation
) &&
1217 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1218 connectorReservation
?.idTag
=== idTag
) ||
1219 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
1220 chargingStationReservation
&&
1221 !hasReservationExpired(chargingStationReservation
) &&
1222 chargingStationReservation
?.idTag
=== idTag
)
1225 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1226 connectorReservation
?? chargingStationReservation
,
1233 public static parseJsonSchemaFile
<T
extends JsonType
>(
1234 relativePath
: string,
1235 moduleName
?: string,
1236 methodName
?: string,
1237 ): JSONSchemaType
<T
> {
1238 return super.parseJsonSchemaFile
<T
>(
1240 OCPPVersion
.VERSION_16
,
1246 private static composeChargingSchedule
= (
1247 chargingSchedule
: OCPP16ChargingSchedule
,
1248 compositeInterval
: Interval
,
1249 ): OCPP16ChargingSchedule
| undefined => {
1250 const chargingScheduleInterval
: Interval
= {
1251 start
: chargingSchedule
.startSchedule
!,
1252 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!),
1254 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
1255 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
);
1256 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
1258 ...chargingSchedule
,
1259 startSchedule
: compositeInterval
.start
as Date,
1260 duration
: differenceInSeconds(
1261 chargingScheduleInterval
.end
,
1262 compositeInterval
.start
as Date,
1264 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
1265 .filter((schedulePeriod
, index
) => {
1268 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1275 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1277 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
1282 chargingScheduleInterval
.start
,
1283 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1292 .map((schedulePeriod
, index
) => {
1293 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1294 schedulePeriod
.startPeriod
= 0;
1296 return schedulePeriod
;
1300 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
1302 ...chargingSchedule
,
1303 duration
: differenceInSeconds(
1304 compositeInterval
.end
as Date,
1305 chargingScheduleInterval
.start
,
1307 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
1309 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1315 return chargingSchedule
;
1319 private static buildSampledValue(
1320 sampledValueTemplate
: SampledValueTemplate
,
1322 context
?: MeterValueContext
,
1323 phase
?: OCPP16MeterValuePhase
,
1324 ): OCPP16SampledValue
{
1325 const sampledValueValue
= value
?? sampledValueTemplate
?.value
;
1326 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1327 const sampledValueLocation
=
1328 sampledValueTemplate
?.location
??
1329 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1330 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1332 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1333 unit
: sampledValueTemplate
.unit
,
1335 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1336 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1337 measurand
: sampledValueTemplate
.measurand
,
1339 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1340 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
1341 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1342 } as OCPP16SampledValue
;
1345 private static checkMeasurandPowerDivider(
1346 chargingStation
: ChargingStation
,
1347 measurandType
: OCPP16MeterValueMeasurand
,
1349 if (isUndefined(chargingStation
.powerDivider
)) {
1350 const errMsg
= `MeterValues measurand ${
1351 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1352 }: powerDivider is undefined`;
1353 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1354 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1355 } else if (chargingStation
?.powerDivider
<= 0) {
1356 const errMsg
= `MeterValues measurand ${
1357 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1358 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1359 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1360 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1364 private static getMeasurandDefaultLocation(
1365 measurandType
: OCPP16MeterValueMeasurand
,
1366 ): MeterValueLocation
| undefined {
1367 switch (measurandType
) {
1368 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1369 return MeterValueLocation
.EV
;
1373 // private static getMeasurandDefaultUnit(
1374 // measurandType: OCPP16MeterValueMeasurand,
1375 // ): MeterValueUnit | undefined {
1376 // switch (measurandType) {
1377 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1378 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1379 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1380 // return MeterValueUnit.AMP;
1381 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1382 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1383 // return MeterValueUnit.WATT_HOUR;
1384 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1385 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1386 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1387 // return MeterValueUnit.WATT;
1388 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1389 // return MeterValueUnit.PERCENT;
1390 // case OCPP16MeterValueMeasurand.VOLTAGE:
1391 // return MeterValueUnit.VOLT;