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
,
305 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
306 defaultValue
: connectorMinimumPower
/ unitDivider
,
308 ) / chargingStation
.getNumberOfPhases(),
309 powerSampledValueTemplate
.fluctuationPercent
??
310 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
312 const phase1FluctuatedValue
=
313 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
314 getRandomFloatFluctuatedRounded(
315 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
316 powerPerPhaseSampledValueTemplates
.L1
.value
,
317 connectorMaximumPowerPerPhase
/ unitDivider
,
320 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
321 defaultValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
324 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
325 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
327 const phase2FluctuatedValue
=
328 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
329 getRandomFloatFluctuatedRounded(
330 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
331 powerPerPhaseSampledValueTemplates
.L2
.value
,
332 connectorMaximumPowerPerPhase
/ unitDivider
,
335 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
336 defaultValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
339 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
340 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
342 const phase3FluctuatedValue
=
343 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
344 getRandomFloatFluctuatedRounded(
345 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
346 powerPerPhaseSampledValueTemplates
.L3
.value
,
347 connectorMaximumPowerPerPhase
/ unitDivider
,
350 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
351 defaultValue
: connectorMinimumPowerPerPhase
/ unitDivider
,
354 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
355 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
357 powerMeasurandValues
.L1
=
358 (phase1FluctuatedValue
as number) ??
359 (defaultFluctuatedPowerPerPhase
as number) ??
360 getRandomFloatRounded(
361 connectorMaximumPowerPerPhase
/ unitDivider
,
362 connectorMinimumPowerPerPhase
/ unitDivider
,
364 powerMeasurandValues
.L2
=
365 (phase2FluctuatedValue
as number) ??
366 (defaultFluctuatedPowerPerPhase
as number) ??
367 getRandomFloatRounded(
368 connectorMaximumPowerPerPhase
/ unitDivider
,
369 connectorMinimumPowerPerPhase
/ unitDivider
,
371 powerMeasurandValues
.L3
=
372 (phase3FluctuatedValue
as number) ??
373 (defaultFluctuatedPowerPerPhase
as number) ??
374 getRandomFloatRounded(
375 connectorMaximumPowerPerPhase
/ unitDivider
,
376 connectorMinimumPowerPerPhase
/ unitDivider
,
379 powerMeasurandValues
.L1
= isNotEmptyString(powerSampledValueTemplate
.value
)
380 ? getRandomFloatFluctuatedRounded(
381 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
382 powerSampledValueTemplate
.value
,
383 connectorMaximumPower
/ unitDivider
,
386 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
387 defaultValue
: connectorMinimumPower
/ unitDivider
,
390 powerSampledValueTemplate
.fluctuationPercent
??
391 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
393 : getRandomFloatRounded(
394 connectorMaximumPower
/ unitDivider
,
395 connectorMinimumPower
/ unitDivider
,
397 powerMeasurandValues
.L2
= 0;
398 powerMeasurandValues
.L3
= 0;
400 powerMeasurandValues
.allPhases
= roundTo(
401 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
406 powerMeasurandValues
.allPhases
= isNotEmptyString(powerSampledValueTemplate
.value
)
407 ? getRandomFloatFluctuatedRounded(
408 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
409 powerSampledValueTemplate
.value
,
410 connectorMaximumPower
/ unitDivider
,
413 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
414 defaultValue
: connectorMinimumPower
/ unitDivider
,
417 powerSampledValueTemplate
.fluctuationPercent
??
418 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
420 : getRandomFloatRounded(
421 connectorMaximumPower
/ unitDivider
,
422 connectorMinimumPower
/ unitDivider
,
426 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
427 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
429 meterValue
.sampledValue
.push(
430 OCPP16ServiceUtils
.buildSampledValue(
431 powerSampledValueTemplate
,
432 powerMeasurandValues
.allPhases
,
435 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
436 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
437 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
439 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
440 connectorMaximumPowerRounded
||
441 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
442 connectorMinimumPowerRounded
||
446 `${chargingStation.logPrefix()} MeterValues measurand ${
447 meterValue.sampledValue[sampledValuesIndex].measurand ??
448 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
449 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
450 meterValue.sampledValue[sampledValuesIndex].value
451 }/${connectorMaximumPowerRounded}`,
456 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
459 const phaseValue
= `L${phase}-N`;
460 meterValue
.sampledValue
.push(
461 OCPP16ServiceUtils
.buildSampledValue(
462 powerPerPhaseSampledValueTemplates
[
463 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
464 ]! ?? powerSampledValueTemplate
,
465 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
467 phaseValue
as OCPP16MeterValuePhase
,
470 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
471 const connectorMaximumPowerPerPhaseRounded
= roundTo(
472 connectorMaximumPowerPerPhase
/ unitDivider
,
475 const connectorMinimumPowerPerPhaseRounded
= roundTo(
476 connectorMinimumPowerPerPhase
/ unitDivider
,
480 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
481 connectorMaximumPowerPerPhaseRounded
||
482 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
483 connectorMinimumPowerPerPhaseRounded
||
487 `${chargingStation.logPrefix()} MeterValues measurand ${
488 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
489 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
491 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
492 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
493 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
494 }/${connectorMaximumPowerPerPhaseRounded}`,
499 // Current.Import measurand
500 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
503 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
505 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
506 if (chargingStation
.getNumberOfPhases() === 3) {
507 currentPerPhaseSampledValueTemplates
= {
508 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
511 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
512 OCPP16MeterValuePhase
.L1
,
514 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
517 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
518 OCPP16MeterValuePhase
.L2
,
520 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
523 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
524 OCPP16MeterValuePhase
.L3
,
528 if (currentSampledValueTemplate
) {
529 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
531 currentSampledValueTemplate
.measurand
!,
533 const errMsg
= `MeterValues measurand ${
534 currentSampledValueTemplate.measurand ??
535 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
536 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
537 chargingStation.templateFile
538 }, cannot calculate ${
539 currentSampledValueTemplate.measurand ??
540 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
542 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
543 const connectorMaximumAvailablePower
=
544 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
545 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
546 let connectorMaximumAmperage
: number;
547 switch (chargingStation
.stationInfo
?.currentOutType
) {
549 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
550 chargingStation
.getNumberOfPhases(),
551 connectorMaximumAvailablePower
,
552 chargingStation
.stationInfo
.voltageOut
!,
554 if (chargingStation
.getNumberOfPhases() === 3) {
555 const defaultFluctuatedAmperagePerPhase
=
556 currentSampledValueTemplate
.value
&&
557 getRandomFloatFluctuatedRounded(
558 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
559 currentSampledValueTemplate
.value
,
560 connectorMaximumAmperage
,
563 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
564 defaultValue
: connectorMinimumAmperage
,
567 currentSampledValueTemplate
.fluctuationPercent
??
568 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
570 const phase1FluctuatedValue
=
571 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
572 getRandomFloatFluctuatedRounded(
573 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
574 currentPerPhaseSampledValueTemplates
.L1
.value
,
575 connectorMaximumAmperage
,
578 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
579 defaultValue
: connectorMinimumAmperage
,
582 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
583 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
585 const phase2FluctuatedValue
=
586 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
587 getRandomFloatFluctuatedRounded(
588 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
589 currentPerPhaseSampledValueTemplates
.L2
.value
,
590 connectorMaximumAmperage
,
593 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
594 defaultValue
: connectorMinimumAmperage
,
597 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
598 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
600 const phase3FluctuatedValue
=
601 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
602 getRandomFloatFluctuatedRounded(
603 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
604 currentPerPhaseSampledValueTemplates
.L3
.value
,
605 connectorMaximumAmperage
,
608 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
609 defaultValue
: connectorMinimumAmperage
,
612 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
613 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
615 currentMeasurandValues
.L1
=
616 (phase1FluctuatedValue
as number) ??
617 (defaultFluctuatedAmperagePerPhase
as number) ??
618 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
619 currentMeasurandValues
.L2
=
620 (phase2FluctuatedValue
as number) ??
621 (defaultFluctuatedAmperagePerPhase
as number) ??
622 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
623 currentMeasurandValues
.L3
=
624 (phase3FluctuatedValue
as number) ??
625 (defaultFluctuatedAmperagePerPhase
as number) ??
626 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
628 currentMeasurandValues
.L1
= isNotEmptyString(currentSampledValueTemplate
.value
)
629 ? getRandomFloatFluctuatedRounded(
630 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
631 currentSampledValueTemplate
.value
,
632 connectorMaximumAmperage
,
635 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
636 defaultValue
: connectorMinimumAmperage
,
639 currentSampledValueTemplate
.fluctuationPercent
??
640 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
642 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
643 currentMeasurandValues
.L2
= 0;
644 currentMeasurandValues
.L3
= 0;
646 currentMeasurandValues
.allPhases
= roundTo(
647 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
648 chargingStation
.getNumberOfPhases(),
653 connectorMaximumAmperage
= DCElectricUtils
.amperage(
654 connectorMaximumAvailablePower
,
655 chargingStation
.stationInfo
.voltageOut
!,
657 currentMeasurandValues
.allPhases
= isNotEmptyString(currentSampledValueTemplate
.value
)
658 ? getRandomFloatFluctuatedRounded(
659 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
660 currentSampledValueTemplate
.value
,
661 connectorMaximumAmperage
,
664 chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
665 defaultValue
: connectorMinimumAmperage
,
668 currentSampledValueTemplate
.fluctuationPercent
??
669 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
671 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
674 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
675 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
677 meterValue
.sampledValue
.push(
678 OCPP16ServiceUtils
.buildSampledValue(
679 currentSampledValueTemplate
,
680 currentMeasurandValues
.allPhases
,
683 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
685 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
686 connectorMaximumAmperage
||
687 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
688 connectorMinimumAmperage
||
692 `${chargingStation.logPrefix()} MeterValues measurand ${
693 meterValue.sampledValue[sampledValuesIndex].measurand ??
694 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
695 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
696 meterValue.sampledValue[sampledValuesIndex].value
697 }/${connectorMaximumAmperage}`,
702 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
705 const phaseValue
= `L${phase}`;
706 meterValue
.sampledValue
.push(
707 OCPP16ServiceUtils
.buildSampledValue(
708 currentPerPhaseSampledValueTemplates
[
709 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
710 ]! ?? currentSampledValueTemplate
,
711 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
713 phaseValue
as OCPP16MeterValuePhase
,
716 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
718 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
719 connectorMaximumAmperage
||
720 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
721 connectorMinimumAmperage
||
725 `${chargingStation.logPrefix()} MeterValues measurand ${
726 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
727 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
729 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
730 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
731 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
732 }/${connectorMaximumAmperage}`,
737 // Energy.Active.Import.Register measurand (default)
738 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
742 if (energySampledValueTemplate
) {
743 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
745 energySampledValueTemplate
.measurand
!,
748 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
749 const connectorMaximumAvailablePower
=
750 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
751 const connectorMaximumEnergyRounded
= roundTo(
752 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
755 const energyValueRounded
= isNotEmptyString(energySampledValueTemplate
.value
)
756 ? // Cumulate the fluctuated value around the static one
757 getRandomFloatFluctuatedRounded(
758 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
759 energySampledValueTemplate
.value
,
760 connectorMaximumEnergyRounded
,
762 limitationEnabled
: chargingStation
.stationInfo
?.customValueLimitationMeterValues
,
763 unitMultiplier
: unitDivider
,
766 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
768 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
769 // Persist previous value on connector
772 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
773 connector
.energyActiveImportRegisterValue
! >= 0 &&
774 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
775 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
777 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
778 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
780 connector
.energyActiveImportRegisterValue
= 0;
781 connector
.transactionEnergyActiveImportRegisterValue
= 0;
784 meterValue
.sampledValue
.push(
785 OCPP16ServiceUtils
.buildSampledValue(
786 energySampledValueTemplate
,
788 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
794 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
795 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
797 `${chargingStation.logPrefix()} MeterValues measurand ${
798 meterValue.sampledValue[sampledValuesIndex].measurand ??
799 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
800 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
807 public static buildTransactionBeginMeterValue(
808 chargingStation
: ChargingStation
,
811 ): OCPP16MeterValue
{
812 const meterValue
: OCPP16MeterValue
= {
813 timestamp
: new Date(),
816 // Energy.Active.Import.Register measurand (default)
817 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
821 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
822 meterValue
.sampledValue
.push(
823 OCPP16ServiceUtils
.buildSampledValue(
824 sampledValueTemplate
!,
825 roundTo((meterStart
?? 0) / unitDivider
, 4),
826 MeterValueContext
.TRANSACTION_BEGIN
,
832 public static buildTransactionEndMeterValue(
833 chargingStation
: ChargingStation
,
836 ): OCPP16MeterValue
{
837 const meterValue
: OCPP16MeterValue
= {
838 timestamp
: new Date(),
841 // Energy.Active.Import.Register measurand (default)
842 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
846 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
847 meterValue
.sampledValue
.push(
848 OCPP16ServiceUtils
.buildSampledValue(
849 sampledValueTemplate
!,
850 roundTo((meterStop
?? 0) / unitDivider
, 4),
851 MeterValueContext
.TRANSACTION_END
,
857 public static buildTransactionDataMeterValues(
858 transactionBeginMeterValue
: OCPP16MeterValue
,
859 transactionEndMeterValue
: OCPP16MeterValue
,
860 ): OCPP16MeterValue
[] {
861 const meterValues
: OCPP16MeterValue
[] = [];
862 meterValues
.push(transactionBeginMeterValue
);
863 meterValues
.push(transactionEndMeterValue
);
867 public static remoteStopTransaction
= async (
868 chargingStation
: ChargingStation
,
870 ): Promise
<GenericResponse
> => {
871 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
874 OCPP16ChargePointStatus
.Finishing
,
876 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
878 OCPP16StopTransactionReason
.REMOTE
,
880 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
881 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
883 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
886 public static changeAvailability
= async (
887 chargingStation
: ChargingStation
,
888 connectorIds
: number[],
889 chargePointStatus
: OCPP16ChargePointStatus
,
890 availabilityType
: OCPP16AvailabilityType
,
891 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
892 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
893 for (const connectorId
of connectorIds
) {
894 let response
: OCPP16ChangeAvailabilityResponse
=
895 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
896 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
897 if (connectorStatus
?.transactionStarted
=== true) {
898 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
900 connectorStatus
.availability
= availabilityType
;
901 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
902 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
908 responses
.push(response
);
910 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
911 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
913 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
916 public static setChargingProfile(
917 chargingStation
: ChargingStation
,
919 cp
: OCPP16ChargingProfile
,
921 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
923 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
925 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
928 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
931 `${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`,
933 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
935 let cpReplaced
= false;
936 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
938 .getConnectorStatus(connectorId
)
939 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
941 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
942 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
943 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
945 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
950 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
953 public static clearChargingProfiles
= (
954 chargingStation
: ChargingStation
,
955 commandPayload
: ClearChargingProfileRequest
,
956 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
958 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
;
959 let clearedCP
= false;
960 if (isNotEmptyArray(chargingProfiles
)) {
961 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
962 let clearCurrentCP
= false;
963 if (chargingProfile
.chargingProfileId
=== id
) {
964 clearCurrentCP
= true;
966 if (!chargingProfilePurpose
&& chargingProfile
.stackLevel
=== stackLevel
) {
967 clearCurrentCP
= true;
969 if (!stackLevel
&& chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
) {
970 clearCurrentCP
= true;
973 chargingProfile
.stackLevel
=== stackLevel
&&
974 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
976 clearCurrentCP
= true;
978 if (clearCurrentCP
) {
979 chargingProfiles
.splice(index
, 1);
981 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
991 public static composeChargingSchedules
= (
992 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
993 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
994 compositeInterval
: Interval
,
995 ): OCPP16ChargingSchedule
| undefined => {
996 if (!chargingScheduleHigher
&& !chargingScheduleLower
) {
999 if (chargingScheduleHigher
&& !chargingScheduleLower
) {
1000 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
);
1002 if (!chargingScheduleHigher
&& chargingScheduleLower
) {
1003 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
);
1005 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
1006 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
);
1007 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
1008 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
);
1009 const compositeChargingScheduleHigherInterval
: Interval
= {
1010 start
: compositeChargingScheduleHigher
!.startSchedule
!,
1012 compositeChargingScheduleHigher
!.startSchedule
!,
1013 compositeChargingScheduleHigher
!.duration
!,
1016 const compositeChargingScheduleLowerInterval
: Interval
= {
1017 start
: compositeChargingScheduleLower
!.startSchedule
!,
1019 compositeChargingScheduleLower
!.startSchedule
!,
1020 compositeChargingScheduleLower
!.duration
!,
1023 const higherFirst
= isBefore(
1024 compositeChargingScheduleHigherInterval
.start
,
1025 compositeChargingScheduleLowerInterval
.start
,
1028 !areIntervalsOverlapping(
1029 compositeChargingScheduleHigherInterval
,
1030 compositeChargingScheduleLowerInterval
,
1034 ...compositeChargingScheduleLower
,
1035 ...compositeChargingScheduleHigher
!,
1036 startSchedule
: higherFirst
1037 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1038 : (compositeChargingScheduleLowerInterval
.start
as Date),
1039 duration
: higherFirst
1040 ? differenceInSeconds(
1041 compositeChargingScheduleLowerInterval
.end
,
1042 compositeChargingScheduleHigherInterval
.start
,
1044 : differenceInSeconds(
1045 compositeChargingScheduleHigherInterval
.end
,
1046 compositeChargingScheduleLowerInterval
.start
,
1048 chargingSchedulePeriod
: [
1049 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1052 startPeriod
: higherFirst
1054 : schedulePeriod
.startPeriod
+
1055 differenceInSeconds(
1056 compositeChargingScheduleHigherInterval
.start
,
1057 compositeChargingScheduleLowerInterval
.start
,
1061 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1064 startPeriod
: higherFirst
1065 ? schedulePeriod
.startPeriod
+
1066 differenceInSeconds(
1067 compositeChargingScheduleLowerInterval
.start
,
1068 compositeChargingScheduleHigherInterval
.start
,
1073 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1077 ...compositeChargingScheduleLower
,
1078 ...compositeChargingScheduleHigher
!,
1079 startSchedule
: higherFirst
1080 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1081 : (compositeChargingScheduleLowerInterval
.start
as Date),
1082 duration
: higherFirst
1083 ? differenceInSeconds(
1084 compositeChargingScheduleLowerInterval
.end
,
1085 compositeChargingScheduleHigherInterval
.start
,
1087 : differenceInSeconds(
1088 compositeChargingScheduleHigherInterval
.end
,
1089 compositeChargingScheduleLowerInterval
.start
,
1091 chargingSchedulePeriod
: [
1092 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1095 startPeriod
: higherFirst
1097 : schedulePeriod
.startPeriod
+
1098 differenceInSeconds(
1099 compositeChargingScheduleHigherInterval
.start
,
1100 compositeChargingScheduleLowerInterval
.start
,
1104 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
1105 .filter((schedulePeriod
, index
) => {
1110 compositeChargingScheduleLowerInterval
.start
,
1111 schedulePeriod
.startPeriod
,
1114 start
: compositeChargingScheduleLowerInterval
.start
,
1115 end
: compositeChargingScheduleHigherInterval
.end
,
1123 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
1126 compositeChargingScheduleLowerInterval
.start
,
1127 schedulePeriod
.startPeriod
,
1130 start
: compositeChargingScheduleLowerInterval
.start
,
1131 end
: compositeChargingScheduleHigherInterval
.end
,
1136 compositeChargingScheduleLowerInterval
.start
,
1137 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1140 start
: compositeChargingScheduleLowerInterval
.start
,
1141 end
: compositeChargingScheduleHigherInterval
.end
,
1151 compositeChargingScheduleLowerInterval
.start
,
1152 schedulePeriod
.startPeriod
,
1155 start
: compositeChargingScheduleHigherInterval
.start
,
1156 end
: compositeChargingScheduleLowerInterval
.end
,
1164 .map((schedulePeriod
, index
) => {
1165 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1166 schedulePeriod
.startPeriod
= 0;
1170 startPeriod
: higherFirst
1171 ? schedulePeriod
.startPeriod
+
1172 differenceInSeconds(
1173 compositeChargingScheduleLowerInterval
.start
,
1174 compositeChargingScheduleHigherInterval
.start
,
1179 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1183 public static hasReservation
= (
1184 chargingStation
: ChargingStation
,
1185 connectorId
: number,
1188 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
1189 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
1191 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
1192 OCPP16ChargePointStatus
.Reserved
&&
1193 connectorReservation
&&
1194 !hasReservationExpired(connectorReservation
) &&
1195 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1196 connectorReservation
?.idTag
=== idTag
) ||
1197 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
1198 chargingStationReservation
&&
1199 !hasReservationExpired(chargingStationReservation
) &&
1200 chargingStationReservation
?.idTag
=== idTag
)
1203 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1204 connectorReservation
?? chargingStationReservation
,
1211 public static parseJsonSchemaFile
<T
extends JsonType
>(
1212 relativePath
: string,
1213 moduleName
?: string,
1214 methodName
?: string,
1215 ): JSONSchemaType
<T
> {
1216 return super.parseJsonSchemaFile
<T
>(
1218 OCPPVersion
.VERSION_16
,
1224 private static composeChargingSchedule
= (
1225 chargingSchedule
: OCPP16ChargingSchedule
,
1226 compositeInterval
: Interval
,
1227 ): OCPP16ChargingSchedule
| undefined => {
1228 const chargingScheduleInterval
: Interval
= {
1229 start
: chargingSchedule
.startSchedule
!,
1230 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!),
1232 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
1233 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
);
1234 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
1236 ...chargingSchedule
,
1237 startSchedule
: compositeInterval
.start
as Date,
1238 duration
: differenceInSeconds(
1239 chargingScheduleInterval
.end
,
1240 compositeInterval
.start
as Date,
1242 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
1243 .filter((schedulePeriod
, index
) => {
1246 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1253 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1255 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
1260 chargingScheduleInterval
.start
,
1261 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1270 .map((schedulePeriod
, index
) => {
1271 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1272 schedulePeriod
.startPeriod
= 0;
1274 return schedulePeriod
;
1278 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
1280 ...chargingSchedule
,
1281 duration
: differenceInSeconds(
1282 compositeInterval
.end
as Date,
1283 chargingScheduleInterval
.start
,
1285 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
1287 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1293 return chargingSchedule
;
1297 private static buildSampledValue(
1298 sampledValueTemplate
: SampledValueTemplate
,
1300 context
?: MeterValueContext
,
1301 phase
?: OCPP16MeterValuePhase
,
1302 ): OCPP16SampledValue
{
1303 const sampledValueValue
= value
?? sampledValueTemplate
?.value
;
1304 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1305 const sampledValueLocation
=
1306 sampledValueTemplate
?.location
??
1307 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1308 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1310 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1311 unit
: sampledValueTemplate
.unit
,
1313 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1314 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1315 measurand
: sampledValueTemplate
.measurand
,
1317 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1318 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
1319 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1320 } as OCPP16SampledValue
;
1323 private static checkMeasurandPowerDivider(
1324 chargingStation
: ChargingStation
,
1325 measurandType
: OCPP16MeterValueMeasurand
,
1327 if (isUndefined(chargingStation
.powerDivider
)) {
1328 const errMsg
= `MeterValues measurand ${
1329 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1330 }: powerDivider is undefined`;
1331 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1332 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1333 } else if (chargingStation
?.powerDivider
<= 0) {
1334 const errMsg
= `MeterValues measurand ${
1335 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1336 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1337 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1338 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1342 private static getMeasurandDefaultLocation(
1343 measurandType
: OCPP16MeterValueMeasurand
,
1344 ): MeterValueLocation
| undefined {
1345 switch (measurandType
) {
1346 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1347 return MeterValueLocation
.EV
;
1351 // private static getMeasurandDefaultUnit(
1352 // measurandType: OCPP16MeterValueMeasurand,
1353 // ): MeterValueUnit | undefined {
1354 // switch (measurandType) {
1355 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1356 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1357 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1358 // return MeterValueUnit.AMP;
1359 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1360 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1361 // return MeterValueUnit.WATT_HOUR;
1362 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1363 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1364 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1365 // return MeterValueUnit.WATT;
1366 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1367 // return MeterValueUnit.PERCENT;
1368 // case OCPP16MeterValueMeasurand.VOLTAGE:
1369 // return MeterValueUnit.VOLT;