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 ? getRandomFloatFluctuatedRounded(
773 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
774 energySampledValueTemplate
.value
,
775 connectorMaximumEnergyRounded
,
776 connectorMinimumEnergyRounded
,
778 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
779 unitMultiplier
: unitDivider
,
780 fallbackValue
: connectorMinimumEnergyRounded
,
783 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
785 : getRandomFloatRounded(connectorMaximumEnergyRounded
, connectorMinimumEnergyRounded
);
786 // Persist previous value on connector
789 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
790 connector
.energyActiveImportRegisterValue
! >= 0 &&
791 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
792 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
794 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
795 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
797 connector
.energyActiveImportRegisterValue
= 0;
798 connector
.transactionEnergyActiveImportRegisterValue
= 0;
801 meterValue
.sampledValue
.push(
802 OCPP16ServiceUtils
.buildSampledValue(
803 energySampledValueTemplate
,
805 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
811 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
813 energyValueRounded
> connectorMaximumEnergyRounded
||
814 energyValueRounded
< connectorMinimumEnergyRounded
||
818 `${chargingStation.logPrefix()} MeterValues measurand ${
819 meterValue.sampledValue[sampledValuesIndex].measurand ??
820 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
821 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
828 public static buildTransactionBeginMeterValue(
829 chargingStation
: ChargingStation
,
832 ): OCPP16MeterValue
{
833 const meterValue
: OCPP16MeterValue
= {
834 timestamp
: new Date(),
837 // Energy.Active.Import.Register measurand (default)
838 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
842 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
843 meterValue
.sampledValue
.push(
844 OCPP16ServiceUtils
.buildSampledValue(
845 sampledValueTemplate
!,
846 roundTo((meterStart
?? 0) / unitDivider
, 4),
847 MeterValueContext
.TRANSACTION_BEGIN
,
853 public static buildTransactionEndMeterValue(
854 chargingStation
: ChargingStation
,
857 ): OCPP16MeterValue
{
858 const meterValue
: OCPP16MeterValue
= {
859 timestamp
: new Date(),
862 // Energy.Active.Import.Register measurand (default)
863 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
867 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
868 meterValue
.sampledValue
.push(
869 OCPP16ServiceUtils
.buildSampledValue(
870 sampledValueTemplate
!,
871 roundTo((meterStop
?? 0) / unitDivider
, 4),
872 MeterValueContext
.TRANSACTION_END
,
878 public static buildTransactionDataMeterValues(
879 transactionBeginMeterValue
: OCPP16MeterValue
,
880 transactionEndMeterValue
: OCPP16MeterValue
,
881 ): OCPP16MeterValue
[] {
882 const meterValues
: OCPP16MeterValue
[] = [];
883 meterValues
.push(transactionBeginMeterValue
);
884 meterValues
.push(transactionEndMeterValue
);
888 public static remoteStopTransaction
= async (
889 chargingStation
: ChargingStation
,
891 ): Promise
<GenericResponse
> => {
892 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
895 OCPP16ChargePointStatus
.Finishing
,
897 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
899 OCPP16StopTransactionReason
.REMOTE
,
901 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
902 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
904 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
907 public static changeAvailability
= async (
908 chargingStation
: ChargingStation
,
909 connectorIds
: number[],
910 chargePointStatus
: OCPP16ChargePointStatus
,
911 availabilityType
: OCPP16AvailabilityType
,
912 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
913 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
914 for (const connectorId
of connectorIds
) {
915 let response
: OCPP16ChangeAvailabilityResponse
=
916 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
917 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
918 if (connectorStatus
?.transactionStarted
=== true) {
919 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
921 connectorStatus
.availability
= availabilityType
;
922 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
923 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
929 responses
.push(response
);
931 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
932 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
934 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
937 public static setChargingProfile(
938 chargingStation
: ChargingStation
,
940 cp
: OCPP16ChargingProfile
,
942 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
944 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
946 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
949 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
952 `${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`,
954 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
956 let cpReplaced
= false;
957 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
959 .getConnectorStatus(connectorId
)
960 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
962 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
963 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
964 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
966 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
971 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
974 public static clearChargingProfiles
= (
975 chargingStation
: ChargingStation
,
976 commandPayload
: ClearChargingProfileRequest
,
977 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
979 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
;
980 let clearedCP
= false;
981 if (isNotEmptyArray(chargingProfiles
)) {
982 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
983 let clearCurrentCP
= false;
984 if (chargingProfile
.chargingProfileId
=== id
) {
985 clearCurrentCP
= true;
987 if (!chargingProfilePurpose
&& chargingProfile
.stackLevel
=== stackLevel
) {
988 clearCurrentCP
= true;
990 if (!stackLevel
&& chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
) {
991 clearCurrentCP
= true;
994 chargingProfile
.stackLevel
=== stackLevel
&&
995 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
997 clearCurrentCP
= true;
999 if (clearCurrentCP
) {
1000 chargingProfiles
.splice(index
, 1);
1002 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
1012 public static composeChargingSchedules
= (
1013 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
1014 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
1015 compositeInterval
: Interval
,
1016 ): OCPP16ChargingSchedule
| undefined => {
1017 if (!chargingScheduleHigher
&& !chargingScheduleLower
) {
1020 if (chargingScheduleHigher
&& !chargingScheduleLower
) {
1021 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
);
1023 if (!chargingScheduleHigher
&& chargingScheduleLower
) {
1024 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
);
1026 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
1027 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
);
1028 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
1029 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
);
1030 const compositeChargingScheduleHigherInterval
: Interval
= {
1031 start
: compositeChargingScheduleHigher
!.startSchedule
!,
1033 compositeChargingScheduleHigher
!.startSchedule
!,
1034 compositeChargingScheduleHigher
!.duration
!,
1037 const compositeChargingScheduleLowerInterval
: Interval
= {
1038 start
: compositeChargingScheduleLower
!.startSchedule
!,
1040 compositeChargingScheduleLower
!.startSchedule
!,
1041 compositeChargingScheduleLower
!.duration
!,
1044 const higherFirst
= isBefore(
1045 compositeChargingScheduleHigherInterval
.start
,
1046 compositeChargingScheduleLowerInterval
.start
,
1049 !areIntervalsOverlapping(
1050 compositeChargingScheduleHigherInterval
,
1051 compositeChargingScheduleLowerInterval
,
1055 ...compositeChargingScheduleLower
,
1056 ...compositeChargingScheduleHigher
!,
1057 startSchedule
: higherFirst
1058 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1059 : (compositeChargingScheduleLowerInterval
.start
as Date),
1060 duration
: higherFirst
1061 ? differenceInSeconds(
1062 compositeChargingScheduleLowerInterval
.end
,
1063 compositeChargingScheduleHigherInterval
.start
,
1065 : differenceInSeconds(
1066 compositeChargingScheduleHigherInterval
.end
,
1067 compositeChargingScheduleLowerInterval
.start
,
1069 chargingSchedulePeriod
: [
1070 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1073 startPeriod
: higherFirst
1075 : schedulePeriod
.startPeriod
+
1076 differenceInSeconds(
1077 compositeChargingScheduleHigherInterval
.start
,
1078 compositeChargingScheduleLowerInterval
.start
,
1082 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1085 startPeriod
: higherFirst
1086 ? schedulePeriod
.startPeriod
+
1087 differenceInSeconds(
1088 compositeChargingScheduleLowerInterval
.start
,
1089 compositeChargingScheduleHigherInterval
.start
,
1094 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1098 ...compositeChargingScheduleLower
,
1099 ...compositeChargingScheduleHigher
!,
1100 startSchedule
: higherFirst
1101 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1102 : (compositeChargingScheduleLowerInterval
.start
as Date),
1103 duration
: higherFirst
1104 ? differenceInSeconds(
1105 compositeChargingScheduleLowerInterval
.end
,
1106 compositeChargingScheduleHigherInterval
.start
,
1108 : differenceInSeconds(
1109 compositeChargingScheduleHigherInterval
.end
,
1110 compositeChargingScheduleLowerInterval
.start
,
1112 chargingSchedulePeriod
: [
1113 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1116 startPeriod
: higherFirst
1118 : schedulePeriod
.startPeriod
+
1119 differenceInSeconds(
1120 compositeChargingScheduleHigherInterval
.start
,
1121 compositeChargingScheduleLowerInterval
.start
,
1125 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
1126 .filter((schedulePeriod
, index
) => {
1131 compositeChargingScheduleLowerInterval
.start
,
1132 schedulePeriod
.startPeriod
,
1135 start
: compositeChargingScheduleLowerInterval
.start
,
1136 end
: compositeChargingScheduleHigherInterval
.end
,
1144 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
1147 compositeChargingScheduleLowerInterval
.start
,
1148 schedulePeriod
.startPeriod
,
1151 start
: compositeChargingScheduleLowerInterval
.start
,
1152 end
: compositeChargingScheduleHigherInterval
.end
,
1157 compositeChargingScheduleLowerInterval
.start
,
1158 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1161 start
: compositeChargingScheduleLowerInterval
.start
,
1162 end
: compositeChargingScheduleHigherInterval
.end
,
1172 compositeChargingScheduleLowerInterval
.start
,
1173 schedulePeriod
.startPeriod
,
1176 start
: compositeChargingScheduleHigherInterval
.start
,
1177 end
: compositeChargingScheduleLowerInterval
.end
,
1185 .map((schedulePeriod
, index
) => {
1186 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1187 schedulePeriod
.startPeriod
= 0;
1191 startPeriod
: higherFirst
1192 ? schedulePeriod
.startPeriod
+
1193 differenceInSeconds(
1194 compositeChargingScheduleLowerInterval
.start
,
1195 compositeChargingScheduleHigherInterval
.start
,
1200 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1204 public static hasReservation
= (
1205 chargingStation
: ChargingStation
,
1206 connectorId
: number,
1209 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
1210 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
1212 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
1213 OCPP16ChargePointStatus
.Reserved
&&
1214 connectorReservation
&&
1215 !hasReservationExpired(connectorReservation
) &&
1216 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1217 connectorReservation
?.idTag
=== idTag
) ||
1218 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
1219 chargingStationReservation
&&
1220 !hasReservationExpired(chargingStationReservation
) &&
1221 chargingStationReservation
?.idTag
=== idTag
)
1224 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1225 connectorReservation
?? chargingStationReservation
,
1232 public static parseJsonSchemaFile
<T
extends JsonType
>(
1233 relativePath
: string,
1234 moduleName
?: string,
1235 methodName
?: string,
1236 ): JSONSchemaType
<T
> {
1237 return super.parseJsonSchemaFile
<T
>(
1239 OCPPVersion
.VERSION_16
,
1245 private static composeChargingSchedule
= (
1246 chargingSchedule
: OCPP16ChargingSchedule
,
1247 compositeInterval
: Interval
,
1248 ): OCPP16ChargingSchedule
| undefined => {
1249 const chargingScheduleInterval
: Interval
= {
1250 start
: chargingSchedule
.startSchedule
!,
1251 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!),
1253 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
1254 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
);
1255 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
1257 ...chargingSchedule
,
1258 startSchedule
: compositeInterval
.start
as Date,
1259 duration
: differenceInSeconds(
1260 chargingScheduleInterval
.end
,
1261 compositeInterval
.start
as Date,
1263 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
1264 .filter((schedulePeriod
, index
) => {
1267 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1274 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1276 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
1281 chargingScheduleInterval
.start
,
1282 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1291 .map((schedulePeriod
, index
) => {
1292 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1293 schedulePeriod
.startPeriod
= 0;
1295 return schedulePeriod
;
1299 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
1301 ...chargingSchedule
,
1302 duration
: differenceInSeconds(
1303 compositeInterval
.end
as Date,
1304 chargingScheduleInterval
.start
,
1306 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
1308 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1314 return chargingSchedule
;
1318 private static buildSampledValue(
1319 sampledValueTemplate
: SampledValueTemplate
,
1321 context
?: MeterValueContext
,
1322 phase
?: OCPP16MeterValuePhase
,
1323 ): OCPP16SampledValue
{
1324 const sampledValueValue
= value
?? sampledValueTemplate
?.value
;
1325 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1326 const sampledValueLocation
=
1327 sampledValueTemplate
?.location
??
1328 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1329 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1331 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1332 unit
: sampledValueTemplate
.unit
,
1334 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1335 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1336 measurand
: sampledValueTemplate
.measurand
,
1338 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1339 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
1340 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1341 } as OCPP16SampledValue
;
1344 private static checkMeasurandPowerDivider(
1345 chargingStation
: ChargingStation
,
1346 measurandType
: OCPP16MeterValueMeasurand
,
1348 if (isUndefined(chargingStation
.powerDivider
)) {
1349 const errMsg
= `MeterValues measurand ${
1350 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1351 }: powerDivider is undefined`;
1352 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1353 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1354 } else if (chargingStation
?.powerDivider
<= 0) {
1355 const errMsg
= `MeterValues measurand ${
1356 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1357 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1358 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1359 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1363 private static getMeasurandDefaultLocation(
1364 measurandType
: OCPP16MeterValueMeasurand
,
1365 ): MeterValueLocation
| undefined {
1366 switch (measurandType
) {
1367 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1368 return MeterValueLocation
.EV
;
1372 // private static getMeasurandDefaultUnit(
1373 // measurandType: OCPP16MeterValueMeasurand,
1374 // ): MeterValueUnit | undefined {
1375 // switch (measurandType) {
1376 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1377 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1378 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1379 // return MeterValueUnit.AMP;
1380 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1381 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1382 // return MeterValueUnit.WATT_HOUR;
1383 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1384 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1385 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1386 // return MeterValueUnit.WATT;
1387 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1388 // return MeterValueUnit.PERCENT;
1389 // case OCPP16MeterValueMeasurand.VOLTAGE:
1390 // return MeterValueUnit.VOLT;