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
,
49 } from
'../../../types';
56 getRandomFloatFluctuatedRounded
,
57 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
= 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
= voltageSampledValueTemplate
.value
138 ? parseInt(voltageSampledValueTemplate
.value
)
139 : chargingStation
.getVoltageOut();
140 const fluctuationPercent
=
141 voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
142 const voltageMeasurandValue
= getRandomFloatFluctuatedRounded(
143 voltageSampledValueTemplateValue
,
147 chargingStation
.getNumberOfPhases() !== 3 ||
148 (chargingStation
.getNumberOfPhases() === 3 && chargingStation
.getMainVoltageMeterValues())
150 meterValue
.sampledValue
.push(
151 OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
),
156 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
159 const phaseLineToNeutralValue
= `L${phase}-N`;
160 const voltagePhaseLineToNeutralSampledValueTemplate
=
161 OCPP16ServiceUtils
.getSampledValueTemplate(
164 OCPP16MeterValueMeasurand
.VOLTAGE
,
165 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
167 let voltagePhaseLineToNeutralMeasurandValue
: number | undefined;
168 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
169 const voltagePhaseLineToNeutralSampledValueTemplateValue
=
170 voltagePhaseLineToNeutralSampledValueTemplate
.value
171 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
)
172 : chargingStation
.getVoltageOut();
173 const fluctuationPhaseToNeutralPercent
=
174 voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
??
175 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
176 voltagePhaseLineToNeutralMeasurandValue
= getRandomFloatFluctuatedRounded(
177 voltagePhaseLineToNeutralSampledValueTemplateValue
,
178 fluctuationPhaseToNeutralPercent
,
181 meterValue
.sampledValue
.push(
182 OCPP16ServiceUtils
.buildSampledValue(
183 voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
184 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
,
186 phaseLineToNeutralValue
as OCPP16MeterValuePhase
,
189 if (chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
190 const phaseLineToLineValue
= `L${phase}-L${
191 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
192 ? (phase + 1) % chargingStation.getNumberOfPhases()
193 : chargingStation.getNumberOfPhases()
195 const voltagePhaseLineToLineSampledValueTemplate
=
196 OCPP16ServiceUtils
.getSampledValueTemplate(
199 OCPP16MeterValueMeasurand
.VOLTAGE
,
200 phaseLineToLineValue
as OCPP16MeterValuePhase
,
202 let voltagePhaseLineToLineMeasurandValue
: number | undefined;
203 if (voltagePhaseLineToLineSampledValueTemplate
) {
204 const voltagePhaseLineToLineSampledValueTemplateValue
=
205 voltagePhaseLineToLineSampledValueTemplate
.value
206 ? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
)
207 : Voltage
.VOLTAGE_400
;
208 const fluctuationPhaseLineToLinePercent
=
209 voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
??
210 Constants
.DEFAULT_FLUCTUATION_PERCENT
;
211 voltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
212 voltagePhaseLineToLineSampledValueTemplateValue
,
213 fluctuationPhaseLineToLinePercent
,
216 const defaultVoltagePhaseLineToLineMeasurandValue
= getRandomFloatFluctuatedRounded(
220 meterValue
.sampledValue
.push(
221 OCPP16ServiceUtils
.buildSampledValue(
222 voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
223 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
,
225 phaseLineToLineValue
as OCPP16MeterValuePhase
,
231 // Power.Active.Import measurand
232 const powerSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
235 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
237 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
238 if (chargingStation
.getNumberOfPhases() === 3) {
239 powerPerPhaseSampledValueTemplates
= {
240 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
243 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
244 OCPP16MeterValuePhase
.L1_N
,
246 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
249 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
250 OCPP16MeterValuePhase
.L2_N
,
252 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
255 OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
,
256 OCPP16MeterValuePhase
.L3_N
,
260 if (powerSampledValueTemplate
) {
261 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
263 powerSampledValueTemplate
.measurand
!,
265 const errMsg
= `MeterValues measurand ${
266 powerSampledValueTemplate.measurand ??
267 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
268 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
269 chargingStation.templateFile
270 }, cannot calculate ${
271 powerSampledValueTemplate.measurand ??
272 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
274 const powerMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
275 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
276 const connectorMaximumAvailablePower
=
277 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
278 const connectorMaximumPower
= Math.round(connectorMaximumAvailablePower
);
279 const connectorMaximumPowerPerPhase
= Math.round(
280 connectorMaximumAvailablePower
/ chargingStation
.getNumberOfPhases(),
282 const connectorMinimumPower
= Math.round(powerSampledValueTemplate
.minimumValue
!) ?? 0;
283 const connectorMinimumPowerPerPhase
= Math.round(
284 connectorMinimumPower
/ chargingStation
.getNumberOfPhases(),
286 switch (chargingStation
.getCurrentOutType()) {
288 if (chargingStation
.getNumberOfPhases() === 3) {
289 const defaultFluctuatedPowerPerPhase
=
290 powerSampledValueTemplate
.value
&&
291 getRandomFloatFluctuatedRounded(
292 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
293 powerSampledValueTemplate
.value
,
294 connectorMaximumPower
/ unitDivider
,
295 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
296 ) / chargingStation
.getNumberOfPhases(),
297 powerSampledValueTemplate
.fluctuationPercent
??
298 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
300 const phase1FluctuatedValue
=
301 powerPerPhaseSampledValueTemplates
.L1
?.value
&&
302 getRandomFloatFluctuatedRounded(
303 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
304 powerPerPhaseSampledValueTemplates
.L1
.value
,
305 connectorMaximumPowerPerPhase
/ unitDivider
,
306 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
308 powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
309 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
311 const phase2FluctuatedValue
=
312 powerPerPhaseSampledValueTemplates
.L2
?.value
&&
313 getRandomFloatFluctuatedRounded(
314 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
315 powerPerPhaseSampledValueTemplates
.L2
.value
,
316 connectorMaximumPowerPerPhase
/ unitDivider
,
317 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
319 powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
320 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
322 const phase3FluctuatedValue
=
323 powerPerPhaseSampledValueTemplates
.L3
?.value
&&
324 getRandomFloatFluctuatedRounded(
325 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
326 powerPerPhaseSampledValueTemplates
.L3
.value
,
327 connectorMaximumPowerPerPhase
/ unitDivider
,
328 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
330 powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
331 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
333 powerMeasurandValues
.L1
=
334 (phase1FluctuatedValue
as number) ??
335 (defaultFluctuatedPowerPerPhase
as number) ??
336 getRandomFloatRounded(
337 connectorMaximumPowerPerPhase
/ unitDivider
,
338 connectorMinimumPowerPerPhase
/ unitDivider
,
340 powerMeasurandValues
.L2
=
341 (phase2FluctuatedValue
as number) ??
342 (defaultFluctuatedPowerPerPhase
as number) ??
343 getRandomFloatRounded(
344 connectorMaximumPowerPerPhase
/ unitDivider
,
345 connectorMinimumPowerPerPhase
/ unitDivider
,
347 powerMeasurandValues
.L3
=
348 (phase3FluctuatedValue
as number) ??
349 (defaultFluctuatedPowerPerPhase
as number) ??
350 getRandomFloatRounded(
351 connectorMaximumPowerPerPhase
/ unitDivider
,
352 connectorMinimumPowerPerPhase
/ unitDivider
,
355 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
356 ? getRandomFloatFluctuatedRounded(
357 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
358 powerSampledValueTemplate
.value
,
359 connectorMaximumPower
/ unitDivider
,
360 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
362 powerSampledValueTemplate
.fluctuationPercent
??
363 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
365 : getRandomFloatRounded(
366 connectorMaximumPower
/ unitDivider
,
367 connectorMinimumPower
/ unitDivider
,
369 powerMeasurandValues
.L2
= 0;
370 powerMeasurandValues
.L3
= 0;
372 powerMeasurandValues
.allPhases
= roundTo(
373 powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
,
378 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
379 ? getRandomFloatFluctuatedRounded(
380 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
381 powerSampledValueTemplate
.value
,
382 connectorMaximumPower
/ unitDivider
,
383 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
385 powerSampledValueTemplate
.fluctuationPercent
??
386 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
388 : getRandomFloatRounded(
389 connectorMaximumPower
/ unitDivider
,
390 connectorMinimumPower
/ unitDivider
,
394 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
395 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
397 meterValue
.sampledValue
.push(
398 OCPP16ServiceUtils
.buildSampledValue(
399 powerSampledValueTemplate
,
400 powerMeasurandValues
.allPhases
,
403 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
404 const connectorMaximumPowerRounded
= roundTo(connectorMaximumPower
/ unitDivider
, 2);
405 const connectorMinimumPowerRounded
= roundTo(connectorMinimumPower
/ unitDivider
, 2);
407 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
408 connectorMaximumPowerRounded
||
409 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
410 connectorMinimumPowerRounded
||
414 `${chargingStation.logPrefix()} MeterValues measurand ${
415 meterValue.sampledValue[sampledValuesIndex].measurand ??
416 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
417 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
418 meterValue.sampledValue[sampledValuesIndex].value
419 }/${connectorMaximumPowerRounded}`,
424 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
427 const phaseValue
= `L${phase}-N`;
428 meterValue
.sampledValue
.push(
429 OCPP16ServiceUtils
.buildSampledValue(
430 powerPerPhaseSampledValueTemplates
[
431 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
432 ]! ?? powerSampledValueTemplate
,
433 powerMeasurandValues
[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
],
435 phaseValue
as OCPP16MeterValuePhase
,
438 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
439 const connectorMaximumPowerPerPhaseRounded
= roundTo(
440 connectorMaximumPowerPerPhase
/ unitDivider
,
443 const connectorMinimumPowerPerPhaseRounded
= roundTo(
444 connectorMinimumPowerPerPhase
/ unitDivider
,
448 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
449 connectorMaximumPowerPerPhaseRounded
||
450 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
451 connectorMinimumPowerPerPhaseRounded
||
455 `${chargingStation.logPrefix()} MeterValues measurand ${
456 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
457 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
459 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
460 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
461 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
462 }/${connectorMaximumPowerPerPhaseRounded}`,
467 // Current.Import measurand
468 const currentSampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
471 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
473 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
474 if (chargingStation
.getNumberOfPhases() === 3) {
475 currentPerPhaseSampledValueTemplates
= {
476 L1
: OCPP16ServiceUtils
.getSampledValueTemplate(
479 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
480 OCPP16MeterValuePhase
.L1
,
482 L2
: OCPP16ServiceUtils
.getSampledValueTemplate(
485 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
486 OCPP16MeterValuePhase
.L2
,
488 L3
: OCPP16ServiceUtils
.getSampledValueTemplate(
491 OCPP16MeterValueMeasurand
.CURRENT_IMPORT
,
492 OCPP16MeterValuePhase
.L3
,
496 if (currentSampledValueTemplate
) {
497 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
499 currentSampledValueTemplate
.measurand
!,
501 const errMsg
= `MeterValues measurand ${
502 currentSampledValueTemplate.measurand ??
503 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
504 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
505 chargingStation.templateFile
506 }, cannot calculate ${
507 currentSampledValueTemplate.measurand ??
508 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
510 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
511 const connectorMaximumAvailablePower
=
512 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
513 const connectorMinimumAmperage
= currentSampledValueTemplate
.minimumValue
?? 0;
514 let connectorMaximumAmperage
: number;
515 switch (chargingStation
.getCurrentOutType()) {
517 connectorMaximumAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(
518 chargingStation
.getNumberOfPhases(),
519 connectorMaximumAvailablePower
,
520 chargingStation
.getVoltageOut(),
522 if (chargingStation
.getNumberOfPhases() === 3) {
523 const defaultFluctuatedAmperagePerPhase
=
524 currentSampledValueTemplate
.value
&&
525 getRandomFloatFluctuatedRounded(
526 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
527 currentSampledValueTemplate
.value
,
528 connectorMaximumAmperage
,
529 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
531 currentSampledValueTemplate
.fluctuationPercent
??
532 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
534 const phase1FluctuatedValue
=
535 currentPerPhaseSampledValueTemplates
.L1
?.value
&&
536 getRandomFloatFluctuatedRounded(
537 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
538 currentPerPhaseSampledValueTemplates
.L1
.value
,
539 connectorMaximumAmperage
,
540 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
542 currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
??
543 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
545 const phase2FluctuatedValue
=
546 currentPerPhaseSampledValueTemplates
.L2
?.value
&&
547 getRandomFloatFluctuatedRounded(
548 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
549 currentPerPhaseSampledValueTemplates
.L2
.value
,
550 connectorMaximumAmperage
,
551 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
553 currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
??
554 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
556 const phase3FluctuatedValue
=
557 currentPerPhaseSampledValueTemplates
.L3
?.value
&&
558 getRandomFloatFluctuatedRounded(
559 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
560 currentPerPhaseSampledValueTemplates
.L3
.value
,
561 connectorMaximumAmperage
,
562 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
564 currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
??
565 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
567 currentMeasurandValues
.L1
=
568 (phase1FluctuatedValue
as number) ??
569 (defaultFluctuatedAmperagePerPhase
as number) ??
570 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
571 currentMeasurandValues
.L2
=
572 (phase2FluctuatedValue
as number) ??
573 (defaultFluctuatedAmperagePerPhase
as number) ??
574 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
575 currentMeasurandValues
.L3
=
576 (phase3FluctuatedValue
as number) ??
577 (defaultFluctuatedAmperagePerPhase
as number) ??
578 getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
580 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
581 ? getRandomFloatFluctuatedRounded(
582 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
583 currentSampledValueTemplate
.value
,
584 connectorMaximumAmperage
,
585 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
587 currentSampledValueTemplate
.fluctuationPercent
??
588 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
590 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
591 currentMeasurandValues
.L2
= 0;
592 currentMeasurandValues
.L3
= 0;
594 currentMeasurandValues
.allPhases
= roundTo(
595 (currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) /
596 chargingStation
.getNumberOfPhases(),
601 connectorMaximumAmperage
= DCElectricUtils
.amperage(
602 connectorMaximumAvailablePower
,
603 chargingStation
.getVoltageOut(),
605 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
606 ? getRandomFloatFluctuatedRounded(
607 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
608 currentSampledValueTemplate
.value
,
609 connectorMaximumAmperage
,
610 { limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues() },
612 currentSampledValueTemplate
.fluctuationPercent
??
613 Constants
.DEFAULT_FLUCTUATION_PERCENT
,
615 : getRandomFloatRounded(connectorMaximumAmperage
, connectorMinimumAmperage
);
618 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
619 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
621 meterValue
.sampledValue
.push(
622 OCPP16ServiceUtils
.buildSampledValue(
623 currentSampledValueTemplate
,
624 currentMeasurandValues
.allPhases
,
627 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
629 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) >
630 connectorMaximumAmperage
||
631 convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) <
632 connectorMinimumAmperage
||
636 `${chargingStation.logPrefix()} MeterValues measurand ${
637 meterValue.sampledValue[sampledValuesIndex].measurand ??
638 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
639 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
640 meterValue.sampledValue[sampledValuesIndex].value
641 }/${connectorMaximumAmperage}`,
646 chargingStation
.getNumberOfPhases() === 3 && phase
<= chargingStation
.getNumberOfPhases();
649 const phaseValue
= `L${phase}`;
650 meterValue
.sampledValue
.push(
651 OCPP16ServiceUtils
.buildSampledValue(
652 currentPerPhaseSampledValueTemplates
[
653 phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
654 ]! ?? currentSampledValueTemplate
,
655 currentMeasurandValues
[phaseValue
as keyof MeasurandPerPhaseSampledValueTemplates
],
657 phaseValue
as OCPP16MeterValuePhase
,
660 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
662 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) >
663 connectorMaximumAmperage
||
664 convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) <
665 connectorMinimumAmperage
||
669 `${chargingStation.logPrefix()} MeterValues measurand ${
670 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
671 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
673 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
674 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
675 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
676 }/${connectorMaximumAmperage}`,
681 // Energy.Active.Import.Register measurand (default)
682 const energySampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
686 if (energySampledValueTemplate
) {
687 OCPP16ServiceUtils
.checkMeasurandPowerDivider(
689 energySampledValueTemplate
.measurand
!,
692 energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
693 const connectorMaximumAvailablePower
=
694 chargingStation
.getConnectorMaximumAvailablePower(connectorId
);
695 const connectorMaximumEnergyRounded
= roundTo(
696 (connectorMaximumAvailablePower
* interval
) / (3600 * 1000),
699 const energyValueRounded
= energySampledValueTemplate
.value
700 ? // Cumulate the fluctuated value around the static one
701 getRandomFloatFluctuatedRounded(
702 OCPP16ServiceUtils
.getLimitFromSampledValueTemplateCustomValue(
703 energySampledValueTemplate
.value
,
704 connectorMaximumEnergyRounded
,
706 limitationEnabled
: chargingStation
.getCustomValueLimitationMeterValues(),
707 unitMultiplier
: unitDivider
,
710 energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
,
712 : getRandomFloatRounded(connectorMaximumEnergyRounded
);
713 // Persist previous value on connector
716 isNullOrUndefined(connector
.energyActiveImportRegisterValue
) === false &&
717 connector
.energyActiveImportRegisterValue
! >= 0 &&
718 isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) === false &&
719 connector
.transactionEnergyActiveImportRegisterValue
! >= 0
721 connector
.energyActiveImportRegisterValue
! += energyValueRounded
;
722 connector
.transactionEnergyActiveImportRegisterValue
! += energyValueRounded
;
724 connector
.energyActiveImportRegisterValue
= 0;
725 connector
.transactionEnergyActiveImportRegisterValue
= 0;
728 meterValue
.sampledValue
.push(
729 OCPP16ServiceUtils
.buildSampledValue(
730 energySampledValueTemplate
,
732 chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) /
738 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
739 if (energyValueRounded
> connectorMaximumEnergyRounded
|| debug
) {
741 `${chargingStation.logPrefix()} MeterValues measurand ${
742 meterValue.sampledValue[sampledValuesIndex].measurand ??
743 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
744 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
751 public static buildTransactionBeginMeterValue(
752 chargingStation
: ChargingStation
,
755 ): OCPP16MeterValue
{
756 const meterValue
: OCPP16MeterValue
= {
757 timestamp
: new Date(),
760 // Energy.Active.Import.Register measurand (default)
761 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
765 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
766 meterValue
.sampledValue
.push(
767 OCPP16ServiceUtils
.buildSampledValue(
768 sampledValueTemplate
!,
769 roundTo((meterStart
?? 0) / unitDivider
, 4),
770 MeterValueContext
.TRANSACTION_BEGIN
,
776 public static buildTransactionEndMeterValue(
777 chargingStation
: ChargingStation
,
780 ): OCPP16MeterValue
{
781 const meterValue
: OCPP16MeterValue
= {
782 timestamp
: new Date(),
785 // Energy.Active.Import.Register measurand (default)
786 const sampledValueTemplate
= OCPP16ServiceUtils
.getSampledValueTemplate(
790 const unitDivider
= sampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
791 meterValue
.sampledValue
.push(
792 OCPP16ServiceUtils
.buildSampledValue(
793 sampledValueTemplate
!,
794 roundTo((meterStop
?? 0) / unitDivider
, 4),
795 MeterValueContext
.TRANSACTION_END
,
801 public static buildTransactionDataMeterValues(
802 transactionBeginMeterValue
: OCPP16MeterValue
,
803 transactionEndMeterValue
: OCPP16MeterValue
,
804 ): OCPP16MeterValue
[] {
805 const meterValues
: OCPP16MeterValue
[] = [];
806 meterValues
.push(transactionBeginMeterValue
);
807 meterValues
.push(transactionEndMeterValue
);
811 public static remoteStopTransaction
= async (
812 chargingStation
: ChargingStation
,
814 ): Promise
<GenericResponse
> => {
815 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
818 OCPP16ChargePointStatus
.Finishing
,
820 const stopResponse
= await chargingStation
.stopTransactionOnConnector(
822 OCPP16StopTransactionReason
.REMOTE
,
824 if (stopResponse
.idTagInfo
?.status === OCPP16AuthorizationStatus
.ACCEPTED
) {
825 return OCPP16Constants
.OCPP_RESPONSE_ACCEPTED
;
827 return OCPP16Constants
.OCPP_RESPONSE_REJECTED
;
830 public static changeAvailability
= async (
831 chargingStation
: ChargingStation
,
832 connectorIds
: number[],
833 chargePointStatus
: OCPP16ChargePointStatus
,
834 availabilityType
: OCPP16AvailabilityType
,
835 ): Promise
<OCPP16ChangeAvailabilityResponse
> => {
836 const responses
: OCPP16ChangeAvailabilityResponse
[] = [];
837 for (const connectorId
of connectorIds
) {
838 let response
: OCPP16ChangeAvailabilityResponse
=
839 OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
840 const connectorStatus
= chargingStation
.getConnectorStatus(connectorId
)!;
841 if (connectorStatus
?.transactionStarted
=== true) {
842 response
= OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
844 connectorStatus
.availability
= availabilityType
;
845 if (response
=== OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
) {
846 await OCPP16ServiceUtils
.sendAndSetConnectorStatus(
852 responses
.push(response
);
854 if (responses
.includes(OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
)) {
855 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
;
857 return OCPP16Constants
.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
;
860 public static setChargingProfile(
861 chargingStation
: ChargingStation
,
863 cp
: OCPP16ChargingProfile
,
865 if (isNullOrUndefined(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
867 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
869 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
872 Array.isArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
) === false
875 `${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`,
877 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
= [];
879 let cpReplaced
= false;
880 if (isNotEmptyArray(chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
)) {
882 .getConnectorStatus(connectorId
)
883 ?.chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
885 chargingProfile
.chargingProfileId
=== cp
.chargingProfileId
||
886 (chargingProfile
.stackLevel
=== cp
.stackLevel
&&
887 chargingProfile
.chargingProfilePurpose
=== cp
.chargingProfilePurpose
)
889 chargingStation
.getConnectorStatus(connectorId
)!.chargingProfiles
![index
] = cp
;
894 !cpReplaced
&& chargingStation
.getConnectorStatus(connectorId
)?.chargingProfiles
?.push(cp
);
897 public static clearChargingProfiles
= (
898 chargingStation
: ChargingStation
,
899 commandPayload
: ClearChargingProfileRequest
,
900 chargingProfiles
: OCPP16ChargingProfile
[] | undefined,
902 const { id
, chargingProfilePurpose
, stackLevel
} = commandPayload
;
903 let clearedCP
= false;
904 if (isNotEmptyArray(chargingProfiles
)) {
905 chargingProfiles
?.forEach((chargingProfile
: OCPP16ChargingProfile
, index
: number) => {
906 let clearCurrentCP
= false;
907 if (chargingProfile
.chargingProfileId
=== id
) {
908 clearCurrentCP
= true;
910 if (!chargingProfilePurpose
&& chargingProfile
.stackLevel
=== stackLevel
) {
911 clearCurrentCP
= true;
913 if (!stackLevel
&& chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
) {
914 clearCurrentCP
= true;
917 chargingProfile
.stackLevel
=== stackLevel
&&
918 chargingProfile
.chargingProfilePurpose
=== chargingProfilePurpose
920 clearCurrentCP
= true;
922 if (clearCurrentCP
) {
923 chargingProfiles
.splice(index
, 1);
925 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
935 public static composeChargingSchedules
= (
936 chargingScheduleHigher
: OCPP16ChargingSchedule
| undefined,
937 chargingScheduleLower
: OCPP16ChargingSchedule
| undefined,
938 compositeInterval
: Interval
,
939 ): OCPP16ChargingSchedule
| undefined => {
940 if (!chargingScheduleHigher
&& !chargingScheduleLower
) {
943 if (chargingScheduleHigher
&& !chargingScheduleLower
) {
944 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
, compositeInterval
);
946 if (!chargingScheduleHigher
&& chargingScheduleLower
) {
947 return OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
, compositeInterval
);
949 const compositeChargingScheduleHigher
: OCPP16ChargingSchedule
| undefined =
950 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleHigher
!, compositeInterval
);
951 const compositeChargingScheduleLower
: OCPP16ChargingSchedule
| undefined =
952 OCPP16ServiceUtils
.composeChargingSchedule(chargingScheduleLower
!, compositeInterval
);
953 const compositeChargingScheduleHigherInterval
: Interval
= {
954 start
: compositeChargingScheduleHigher
!.startSchedule
!,
956 compositeChargingScheduleHigher
!.startSchedule
!,
957 compositeChargingScheduleHigher
!.duration
!,
960 const compositeChargingScheduleLowerInterval
: Interval
= {
961 start
: compositeChargingScheduleLower
!.startSchedule
!,
963 compositeChargingScheduleLower
!.startSchedule
!,
964 compositeChargingScheduleLower
!.duration
!,
967 const higherFirst
= isBefore(
968 compositeChargingScheduleHigherInterval
.start
,
969 compositeChargingScheduleLowerInterval
.start
,
972 !areIntervalsOverlapping(
973 compositeChargingScheduleHigherInterval
,
974 compositeChargingScheduleLowerInterval
,
978 ...compositeChargingScheduleLower
,
979 ...compositeChargingScheduleHigher
!,
980 startSchedule
: higherFirst
981 ? (compositeChargingScheduleHigherInterval
.start
as Date)
982 : (compositeChargingScheduleLowerInterval
.start
as Date),
983 duration
: higherFirst
984 ? differenceInSeconds(
985 compositeChargingScheduleLowerInterval
.end
,
986 compositeChargingScheduleHigherInterval
.start
,
988 : differenceInSeconds(
989 compositeChargingScheduleHigherInterval
.end
,
990 compositeChargingScheduleLowerInterval
.start
,
992 chargingSchedulePeriod
: [
993 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
996 startPeriod
: higherFirst
998 : schedulePeriod
.startPeriod
+
1000 compositeChargingScheduleHigherInterval
.start
,
1001 compositeChargingScheduleLowerInterval
.start
,
1005 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1008 startPeriod
: higherFirst
1009 ? schedulePeriod
.startPeriod
+
1010 differenceInSeconds(
1011 compositeChargingScheduleLowerInterval
.start
,
1012 compositeChargingScheduleHigherInterval
.start
,
1017 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1021 ...compositeChargingScheduleLower
,
1022 ...compositeChargingScheduleHigher
!,
1023 startSchedule
: higherFirst
1024 ? (compositeChargingScheduleHigherInterval
.start
as Date)
1025 : (compositeChargingScheduleLowerInterval
.start
as Date),
1026 duration
: higherFirst
1027 ? differenceInSeconds(
1028 compositeChargingScheduleLowerInterval
.end
,
1029 compositeChargingScheduleHigherInterval
.start
,
1031 : differenceInSeconds(
1032 compositeChargingScheduleHigherInterval
.end
,
1033 compositeChargingScheduleLowerInterval
.start
,
1035 chargingSchedulePeriod
: [
1036 ...compositeChargingScheduleHigher
!.chargingSchedulePeriod
.map((schedulePeriod
) => {
1039 startPeriod
: higherFirst
1041 : schedulePeriod
.startPeriod
+
1042 differenceInSeconds(
1043 compositeChargingScheduleHigherInterval
.start
,
1044 compositeChargingScheduleLowerInterval
.start
,
1048 ...compositeChargingScheduleLower
!.chargingSchedulePeriod
1049 .filter((schedulePeriod
, index
) => {
1054 compositeChargingScheduleLowerInterval
.start
,
1055 schedulePeriod
.startPeriod
,
1058 start
: compositeChargingScheduleLowerInterval
.start
,
1059 end
: compositeChargingScheduleHigherInterval
.end
,
1067 index
< compositeChargingScheduleLower
!.chargingSchedulePeriod
.length
- 1 &&
1070 compositeChargingScheduleLowerInterval
.start
,
1071 schedulePeriod
.startPeriod
,
1074 start
: compositeChargingScheduleLowerInterval
.start
,
1075 end
: compositeChargingScheduleHigherInterval
.end
,
1080 compositeChargingScheduleLowerInterval
.start
,
1081 compositeChargingScheduleLower
!.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1084 start
: compositeChargingScheduleLowerInterval
.start
,
1085 end
: compositeChargingScheduleHigherInterval
.end
,
1095 compositeChargingScheduleLowerInterval
.start
,
1096 schedulePeriod
.startPeriod
,
1099 start
: compositeChargingScheduleHigherInterval
.start
,
1100 end
: compositeChargingScheduleLowerInterval
.end
,
1108 .map((schedulePeriod
, index
) => {
1109 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1110 schedulePeriod
.startPeriod
= 0;
1114 startPeriod
: higherFirst
1115 ? schedulePeriod
.startPeriod
+
1116 differenceInSeconds(
1117 compositeChargingScheduleLowerInterval
.start
,
1118 compositeChargingScheduleHigherInterval
.start
,
1123 ].sort((a
, b
) => a
.startPeriod
- b
.startPeriod
),
1127 public static hasReservation
= (
1128 chargingStation
: ChargingStation
,
1129 connectorId
: number,
1132 const connectorReservation
= chargingStation
.getReservationBy('connectorId', connectorId
);
1133 const chargingStationReservation
= chargingStation
.getReservationBy('connectorId', 0);
1135 (chargingStation
.getConnectorStatus(connectorId
)?.status ===
1136 OCPP16ChargePointStatus
.Reserved
&&
1137 connectorReservation
&&
1138 !hasReservationExpired(connectorReservation
) &&
1139 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1140 connectorReservation
?.idTag
=== idTag
) ||
1141 (chargingStation
.getConnectorStatus(0)?.status === OCPP16ChargePointStatus
.Reserved
&&
1142 chargingStationReservation
&&
1143 !hasReservationExpired(chargingStationReservation
) &&
1144 chargingStationReservation
?.idTag
=== idTag
)
1147 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1148 connectorReservation
?? chargingStationReservation
,
1155 public static parseJsonSchemaFile
<T
extends JsonType
>(
1156 relativePath
: string,
1157 moduleName
?: string,
1158 methodName
?: string,
1159 ): JSONSchemaType
<T
> {
1160 return super.parseJsonSchemaFile
<T
>(
1162 OCPPVersion
.VERSION_16
,
1168 private static composeChargingSchedule
= (
1169 chargingSchedule
: OCPP16ChargingSchedule
,
1170 compositeInterval
: Interval
,
1171 ): OCPP16ChargingSchedule
| undefined => {
1172 const chargingScheduleInterval
: Interval
= {
1173 start
: chargingSchedule
.startSchedule
!,
1174 end
: addSeconds(chargingSchedule
.startSchedule
!, chargingSchedule
.duration
!),
1176 if (areIntervalsOverlapping(chargingScheduleInterval
, compositeInterval
)) {
1177 chargingSchedule
.chargingSchedulePeriod
.sort((a
, b
) => a
.startPeriod
- b
.startPeriod
);
1178 if (isBefore(chargingScheduleInterval
.start
, compositeInterval
.start
)) {
1180 ...chargingSchedule
,
1181 startSchedule
: compositeInterval
.start
as Date,
1182 duration
: differenceInSeconds(
1183 chargingScheduleInterval
.end
,
1184 compositeInterval
.start
as Date,
1186 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
1187 .filter((schedulePeriod
, index
) => {
1190 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1197 index
< chargingSchedule
.chargingSchedulePeriod
.length
- 1 &&
1199 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
),
1204 chargingScheduleInterval
.start
,
1205 chargingSchedule
.chargingSchedulePeriod
[index
+ 1].startPeriod
,
1214 .map((schedulePeriod
, index
) => {
1215 if (index
=== 0 && schedulePeriod
.startPeriod
!== 0) {
1216 schedulePeriod
.startPeriod
= 0;
1218 return schedulePeriod
;
1222 if (isAfter(chargingScheduleInterval
.end
, compositeInterval
.end
)) {
1224 ...chargingSchedule
,
1225 duration
: differenceInSeconds(
1226 compositeInterval
.end
as Date,
1227 chargingScheduleInterval
.start
,
1229 chargingSchedulePeriod
: chargingSchedule
.chargingSchedulePeriod
.filter((schedulePeriod
) =>
1231 addSeconds(chargingScheduleInterval
.start
, schedulePeriod
.startPeriod
)!,
1237 return chargingSchedule
;
1241 private static buildSampledValue(
1242 sampledValueTemplate
: SampledValueTemplate
,
1244 context
?: MeterValueContext
,
1245 phase
?: OCPP16MeterValuePhase
,
1246 ): OCPP16SampledValue
{
1247 const sampledValueValue
= value
?? sampledValueTemplate
?.value
;
1248 const sampledValueContext
= context
?? sampledValueTemplate
?.context
;
1249 const sampledValueLocation
=
1250 sampledValueTemplate
?.location
??
1251 OCPP16ServiceUtils
.getMeasurandDefaultLocation(sampledValueTemplate
.measurand
!);
1252 const sampledValuePhase
= phase
?? sampledValueTemplate
?.phase
;
1254 ...(!isNullOrUndefined(sampledValueTemplate
.unit
) && {
1255 unit
: sampledValueTemplate
.unit
,
1257 ...(!isNullOrUndefined(sampledValueContext
) && { context
: sampledValueContext
}),
1258 ...(!isNullOrUndefined(sampledValueTemplate
.measurand
) && {
1259 measurand
: sampledValueTemplate
.measurand
,
1261 ...(!isNullOrUndefined(sampledValueLocation
) && { location
: sampledValueLocation
}),
1262 ...(!isNullOrUndefined(sampledValueValue
) && { value
: sampledValueValue
.toString() }),
1263 ...(!isNullOrUndefined(sampledValuePhase
) && { phase
: sampledValuePhase
}),
1264 } as OCPP16SampledValue
;
1267 private static checkMeasurandPowerDivider(
1268 chargingStation
: ChargingStation
,
1269 measurandType
: OCPP16MeterValueMeasurand
,
1271 if (isUndefined(chargingStation
.powerDivider
)) {
1272 const errMsg
= `MeterValues measurand ${
1273 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1274 }: powerDivider is undefined`;
1275 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1276 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1277 } else if (chargingStation
?.powerDivider
<= 0) {
1278 const errMsg
= `MeterValues measurand ${
1279 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1280 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
1281 logger
.error(`${chargingStation.logPrefix()} ${errMsg}`);
1282 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
1286 private static getMeasurandDefaultLocation(
1287 measurandType
: OCPP16MeterValueMeasurand
,
1288 ): MeterValueLocation
| undefined {
1289 switch (measurandType
) {
1290 case OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
:
1291 return MeterValueLocation
.EV
;
1295 // private static getMeasurandDefaultUnit(
1296 // measurandType: OCPP16MeterValueMeasurand,
1297 // ): MeterValueUnit | undefined {
1298 // switch (measurandType) {
1299 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1300 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1301 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1302 // return MeterValueUnit.AMP;
1303 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1304 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1305 // return MeterValueUnit.WATT_HOUR;
1306 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1307 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1308 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1309 // return MeterValueUnit.WATT;
1310 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1311 // return MeterValueUnit.PERCENT;
1312 // case OCPP16MeterValueMeasurand.VOLTAGE:
1313 // return MeterValueUnit.VOLT;