feat: ensure measurand min value are taken into account
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
CommitLineData
edd13439 1// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
c8eeb62b 2
130783a7 3import type { JSONSchemaType } from 'ajv';
ef9e3b33
JB
4import {
5 addSeconds,
6 areIntervalsOverlapping,
7 differenceInSeconds,
8 isAfter,
9 isBefore,
10 isWithinInterval,
11} from 'date-fns';
130783a7 12
366f75f6 13import { OCPP16Constants } from './OCPP16Constants';
90aceaf6
JB
14import {
15 type ChargingStation,
16 hasFeatureProfile,
17 hasReservationExpired,
18} from '../../../charging-station';
268a74bb 19import { OCPPError } from '../../../exception';
e7aeea18 20import {
73d87be1 21 type ClearChargingProfileRequest,
268a74bb
JB
22 CurrentType,
23 ErrorType,
d19b10a8 24 type GenericResponse,
268a74bb
JB
25 type JsonType,
26 type MeasurandPerPhaseSampledValueTemplates,
27 type MeasurandValues,
e7aeea18
JB
28 MeterValueContext,
29 MeterValueLocation,
30 MeterValueUnit,
d19b10a8 31 OCPP16AuthorizationStatus,
366f75f6
JB
32 OCPP16AvailabilityType,
33 type OCPP16ChangeAvailabilityResponse,
34 OCPP16ChargePointStatus,
268a74bb 35 type OCPP16ChargingProfile,
ef9e3b33 36 type OCPP16ChargingSchedule,
268a74bb 37 type OCPP16IncomingRequestCommand,
27782dbc 38 type OCPP16MeterValue,
e7aeea18
JB
39 OCPP16MeterValueMeasurand,
40 OCPP16MeterValuePhase,
370ae4ee 41 OCPP16RequestCommand,
268a74bb
JB
42 type OCPP16SampledValue,
43 OCPP16StandardParametersKey,
d19b10a8 44 OCPP16StopTransactionReason,
268a74bb
JB
45 type OCPP16SupportedFeatureProfiles,
46 OCPPVersion,
47 type SampledValueTemplate,
268a74bb 48} from '../../../types';
9bf0ef23
JB
49import {
50 ACElectricUtils,
51 Constants,
52 DCElectricUtils,
53 convertToFloat,
54 convertToInt,
55 getRandomFloatFluctuatedRounded,
56 getRandomFloatRounded,
57 getRandomInteger,
58 isNotEmptyArray,
5a47f72c 59 isNotEmptyString,
9bf0ef23
JB
60 isNullOrUndefined,
61 isUndefined,
62 logger,
63 roundTo,
64} from '../../../utils';
4c3c0d59 65import { OCPPServiceUtils } from '../OCPPServiceUtils';
6ed92bc1 66
7bc31f9c 67export class OCPP16ServiceUtils extends OCPPServiceUtils {
370ae4ee
JB
68 public static checkFeatureProfile(
69 chargingStation: ChargingStation,
70 featureProfile: OCPP16SupportedFeatureProfiles,
5edd8ba0 71 command: OCPP16RequestCommand | OCPP16IncomingRequestCommand,
370ae4ee 72 ): boolean {
d8093be1 73 if (!hasFeatureProfile(chargingStation, featureProfile)) {
370ae4ee
JB
74 logger.warn(
75 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
76 OCPP16StandardParametersKey.SupportedFeatureProfiles
5edd8ba0 77 } in configuration`,
370ae4ee
JB
78 );
79 return false;
80 }
81 return true;
82 }
83
78085c42
JB
84 public static buildMeterValue(
85 chargingStation: ChargingStation,
86 connectorId: number,
87 transactionId: number,
88 interval: number,
5edd8ba0 89 debug = false,
78085c42
JB
90 ): OCPP16MeterValue {
91 const meterValue: OCPP16MeterValue = {
c38f0ced 92 timestamp: new Date(),
78085c42
JB
93 sampledValue: [],
94 };
95 const connector = chargingStation.getConnectorStatus(connectorId);
96 // SoC measurand
ed3d2808 97 const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 98 chargingStation,
78085c42 99 connectorId,
5edd8ba0 100 OCPP16MeterValueMeasurand.STATE_OF_CHARGE,
78085c42
JB
101 );
102 if (socSampledValueTemplate) {
860ef183
JB
103 const socMaximumValue = 100;
104 const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0;
5a47f72c 105 const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value)
9bf0ef23 106 ? getRandomFloatFluctuatedRounded(
78085c42 107 parseInt(socSampledValueTemplate.value),
5edd8ba0 108 socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 109 )
9bf0ef23 110 : getRandomInteger(socMaximumValue, socMinimumValue);
78085c42 111 meterValue.sampledValue.push(
5edd8ba0 112 OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue),
78085c42
JB
113 );
114 const sampledValuesIndex = meterValue.sampledValue.length - 1;
860ef183 115 if (
9bf0ef23
JB
116 convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue ||
117 convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue ||
860ef183
JB
118 debug
119 ) {
78085c42
JB
120 logger.error(
121 `${chargingStation.logPrefix()} MeterValues measurand ${
122 meterValue.sampledValue[sampledValuesIndex].measurand ??
123 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5edd8ba0 124 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
78085c42 125 meterValue.sampledValue[sampledValuesIndex].value
9ff486f4 126 }/${socMaximumValue}`,
78085c42
JB
127 );
128 }
129 }
130 // Voltage measurand
ed3d2808 131 const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 132 chargingStation,
78085c42 133 connectorId,
5edd8ba0 134 OCPP16MeterValueMeasurand.VOLTAGE,
78085c42
JB
135 );
136 if (voltageSampledValueTemplate) {
5a47f72c 137 const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value)
78085c42 138 ? parseInt(voltageSampledValueTemplate.value)
5398cecf 139 : chargingStation.stationInfo.voltageOut!;
78085c42
JB
140 const fluctuationPercent =
141 voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT;
9bf0ef23 142 const voltageMeasurandValue = getRandomFloatFluctuatedRounded(
78085c42 143 voltageSampledValueTemplateValue,
5edd8ba0 144 fluctuationPercent,
78085c42
JB
145 );
146 if (
147 chargingStation.getNumberOfPhases() !== 3 ||
5398cecf
JB
148 (chargingStation.getNumberOfPhases() === 3 &&
149 chargingStation.stationInfo?.mainVoltageMeterValues)
78085c42
JB
150 ) {
151 meterValue.sampledValue.push(
5edd8ba0 152 OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue),
78085c42
JB
153 );
154 }
155 for (
156 let phase = 1;
157 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
158 phase++
159 ) {
160 const phaseLineToNeutralValue = `L${phase}-N`;
161 const voltagePhaseLineToNeutralSampledValueTemplate =
ed3d2808 162 OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 163 chargingStation,
78085c42
JB
164 connectorId,
165 OCPP16MeterValueMeasurand.VOLTAGE,
5edd8ba0 166 phaseLineToNeutralValue as OCPP16MeterValuePhase,
78085c42 167 );
e1d9a0f4 168 let voltagePhaseLineToNeutralMeasurandValue: number | undefined;
78085c42 169 if (voltagePhaseLineToNeutralSampledValueTemplate) {
5a47f72c
JB
170 const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString(
171 voltagePhaseLineToNeutralSampledValueTemplate.value,
172 )
173 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
174 : chargingStation.stationInfo.voltageOut!;
78085c42
JB
175 const fluctuationPhaseToNeutralPercent =
176 voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
177 Constants.DEFAULT_FLUCTUATION_PERCENT;
9bf0ef23 178 voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded(
78085c42 179 voltagePhaseLineToNeutralSampledValueTemplateValue,
5edd8ba0 180 fluctuationPhaseToNeutralPercent,
78085c42
JB
181 );
182 }
183 meterValue.sampledValue.push(
184 OCPP16ServiceUtils.buildSampledValue(
185 voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
186 voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
72092cfc 187 undefined,
5edd8ba0
JB
188 phaseLineToNeutralValue as OCPP16MeterValuePhase,
189 ),
78085c42 190 );
5398cecf 191 if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) {
78085c42
JB
192 const phaseLineToLineValue = `L${phase}-L${
193 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
194 ? (phase + 1) % chargingStation.getNumberOfPhases()
195 : chargingStation.getNumberOfPhases()
196 }`;
4c149643
JB
197 const voltagePhaseLineToLineValueRounded = roundTo(
198 Math.sqrt(chargingStation.getNumberOfPhases()) *
199 chargingStation.stationInfo.voltageOut!,
200 2,
201 );
78085c42 202 const voltagePhaseLineToLineSampledValueTemplate =
ed3d2808 203 OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 204 chargingStation,
78085c42
JB
205 connectorId,
206 OCPP16MeterValueMeasurand.VOLTAGE,
5edd8ba0 207 phaseLineToLineValue as OCPP16MeterValuePhase,
78085c42 208 );
e1d9a0f4 209 let voltagePhaseLineToLineMeasurandValue: number | undefined;
78085c42 210 if (voltagePhaseLineToLineSampledValueTemplate) {
5a47f72c
JB
211 const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString(
212 voltagePhaseLineToLineSampledValueTemplate.value,
213 )
214 ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
4c149643 215 : voltagePhaseLineToLineValueRounded;
78085c42
JB
216 const fluctuationPhaseLineToLinePercent =
217 voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
218 Constants.DEFAULT_FLUCTUATION_PERCENT;
9bf0ef23 219 voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
78085c42 220 voltagePhaseLineToLineSampledValueTemplateValue,
5edd8ba0 221 fluctuationPhaseLineToLinePercent,
78085c42
JB
222 );
223 }
9bf0ef23 224 const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
4c149643 225 voltagePhaseLineToLineValueRounded,
5edd8ba0 226 fluctuationPercent,
78085c42
JB
227 );
228 meterValue.sampledValue.push(
229 OCPP16ServiceUtils.buildSampledValue(
230 voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
231 voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
72092cfc 232 undefined,
5edd8ba0
JB
233 phaseLineToLineValue as OCPP16MeterValuePhase,
234 ),
78085c42
JB
235 );
236 }
237 }
238 }
239 // Power.Active.Import measurand
ed3d2808 240 const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 241 chargingStation,
78085c42 242 connectorId,
5edd8ba0 243 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
78085c42 244 );
abe9e9dd 245 let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
78085c42
JB
246 if (chargingStation.getNumberOfPhases() === 3) {
247 powerPerPhaseSampledValueTemplates = {
ed3d2808 248 L1: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 249 chargingStation,
78085c42
JB
250 connectorId,
251 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
5edd8ba0 252 OCPP16MeterValuePhase.L1_N,
78085c42 253 ),
ed3d2808 254 L2: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 255 chargingStation,
78085c42
JB
256 connectorId,
257 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
5edd8ba0 258 OCPP16MeterValuePhase.L2_N,
78085c42 259 ),
ed3d2808 260 L3: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 261 chargingStation,
78085c42
JB
262 connectorId,
263 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
5edd8ba0 264 OCPP16MeterValuePhase.L3_N,
78085c42
JB
265 ),
266 };
267 }
268 if (powerSampledValueTemplate) {
269 OCPP16ServiceUtils.checkMeasurandPowerDivider(
270 chargingStation,
e1d9a0f4 271 powerSampledValueTemplate.measurand!,
78085c42 272 );
fc040c43 273 const errMsg = `MeterValues measurand ${
78085c42
JB
274 powerSampledValueTemplate.measurand ??
275 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5398cecf 276 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
2484ac1e 277 chargingStation.templateFile
78085c42
JB
278 }, cannot calculate ${
279 powerSampledValueTemplate.measurand ??
280 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
281 } measurand value`;
e1d9a0f4 282 const powerMeasurandValues: MeasurandValues = {} as MeasurandValues;
78085c42 283 const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
1b6498ba
JB
284 const connectorMaximumAvailablePower =
285 chargingStation.getConnectorMaximumAvailablePower(connectorId);
286 const connectorMaximumPower = Math.round(connectorMaximumAvailablePower);
ad8537a7 287 const connectorMaximumPowerPerPhase = Math.round(
5edd8ba0 288 connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(),
78085c42 289 );
d71ce3fa 290 const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue ?? 0);
860ef183 291 const connectorMinimumPowerPerPhase = Math.round(
5edd8ba0 292 connectorMinimumPower / chargingStation.getNumberOfPhases(),
860ef183 293 );
5398cecf 294 switch (chargingStation.stationInfo?.currentOutType) {
78085c42
JB
295 case CurrentType.AC:
296 if (chargingStation.getNumberOfPhases() === 3) {
297 const defaultFluctuatedPowerPerPhase =
298 powerSampledValueTemplate.value &&
9bf0ef23 299 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
300 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
301 powerSampledValueTemplate.value,
302 connectorMaximumPower / unitDivider,
d71ce3fa 303 connectorMinimumPower / unitDivider,
5398cecf
JB
304 {
305 limitationEnabled:
306 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 307 fallbackValue: connectorMinimumPower / unitDivider,
5398cecf 308 },
34464008 309 ) / chargingStation.getNumberOfPhases(),
78085c42 310 powerSampledValueTemplate.fluctuationPercent ??
5edd8ba0 311 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
312 );
313 const phase1FluctuatedValue =
e1d9a0f4 314 powerPerPhaseSampledValueTemplates.L1?.value &&
9bf0ef23 315 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
316 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
317 powerPerPhaseSampledValueTemplates.L1.value,
318 connectorMaximumPowerPerPhase / unitDivider,
d71ce3fa 319 connectorMinimumPowerPerPhase / unitDivider,
5398cecf
JB
320 {
321 limitationEnabled:
322 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 323 fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
5398cecf 324 },
34464008 325 ),
78085c42 326 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
5edd8ba0 327 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
328 );
329 const phase2FluctuatedValue =
e1d9a0f4 330 powerPerPhaseSampledValueTemplates.L2?.value &&
9bf0ef23 331 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
332 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
333 powerPerPhaseSampledValueTemplates.L2.value,
334 connectorMaximumPowerPerPhase / unitDivider,
d71ce3fa 335 connectorMinimumPowerPerPhase / unitDivider,
5398cecf
JB
336 {
337 limitationEnabled:
338 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 339 fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
5398cecf 340 },
34464008 341 ),
78085c42 342 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
5edd8ba0 343 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
344 );
345 const phase3FluctuatedValue =
e1d9a0f4 346 powerPerPhaseSampledValueTemplates.L3?.value &&
9bf0ef23 347 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
348 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
349 powerPerPhaseSampledValueTemplates.L3.value,
350 connectorMaximumPowerPerPhase / unitDivider,
d71ce3fa 351 connectorMinimumPowerPerPhase / unitDivider,
5398cecf
JB
352 {
353 limitationEnabled:
354 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 355 fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
5398cecf 356 },
34464008 357 ),
78085c42 358 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
5edd8ba0 359 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
360 );
361 powerMeasurandValues.L1 =
e1d9a0f4
JB
362 (phase1FluctuatedValue as number) ??
363 (defaultFluctuatedPowerPerPhase as number) ??
9bf0ef23 364 getRandomFloatRounded(
860ef183 365 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 366 connectorMinimumPowerPerPhase / unitDivider,
860ef183 367 );
78085c42 368 powerMeasurandValues.L2 =
e1d9a0f4
JB
369 (phase2FluctuatedValue as number) ??
370 (defaultFluctuatedPowerPerPhase as number) ??
9bf0ef23 371 getRandomFloatRounded(
860ef183 372 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 373 connectorMinimumPowerPerPhase / unitDivider,
860ef183 374 );
78085c42 375 powerMeasurandValues.L3 =
e1d9a0f4
JB
376 (phase3FluctuatedValue as number) ??
377 (defaultFluctuatedPowerPerPhase as number) ??
9bf0ef23 378 getRandomFloatRounded(
860ef183 379 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 380 connectorMinimumPowerPerPhase / unitDivider,
860ef183 381 );
78085c42 382 } else {
856e8f67 383 powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value)
9bf0ef23 384 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
385 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
386 powerSampledValueTemplate.value,
387 connectorMaximumPower / unitDivider,
d71ce3fa 388 connectorMinimumPower / unitDivider,
5398cecf
JB
389 {
390 limitationEnabled:
391 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 392 fallbackValue: connectorMinimumPower / unitDivider,
5398cecf 393 },
34464008 394 ),
78085c42 395 powerSampledValueTemplate.fluctuationPercent ??
5edd8ba0 396 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 397 )
9bf0ef23 398 : getRandomFloatRounded(
860ef183 399 connectorMaximumPower / unitDivider,
5edd8ba0 400 connectorMinimumPower / unitDivider,
860ef183 401 );
78085c42
JB
402 powerMeasurandValues.L2 = 0;
403 powerMeasurandValues.L3 = 0;
404 }
9bf0ef23 405 powerMeasurandValues.allPhases = roundTo(
78085c42 406 powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
5edd8ba0 407 2,
78085c42
JB
408 );
409 break;
410 case CurrentType.DC:
5a47f72c 411 powerMeasurandValues.allPhases = isNotEmptyString(powerSampledValueTemplate.value)
9bf0ef23 412 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
413 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
414 powerSampledValueTemplate.value,
415 connectorMaximumPower / unitDivider,
d71ce3fa 416 connectorMinimumPower / unitDivider,
5398cecf
JB
417 {
418 limitationEnabled:
419 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 420 fallbackValue: connectorMinimumPower / unitDivider,
5398cecf 421 },
34464008 422 ),
78085c42 423 powerSampledValueTemplate.fluctuationPercent ??
5edd8ba0 424 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 425 )
9bf0ef23 426 : getRandomFloatRounded(
860ef183 427 connectorMaximumPower / unitDivider,
5edd8ba0 428 connectorMinimumPower / unitDivider,
860ef183 429 );
78085c42
JB
430 break;
431 default:
fc040c43 432 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
78085c42
JB
433 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
434 }
435 meterValue.sampledValue.push(
436 OCPP16ServiceUtils.buildSampledValue(
437 powerSampledValueTemplate,
5edd8ba0
JB
438 powerMeasurandValues.allPhases,
439 ),
78085c42
JB
440 );
441 const sampledValuesIndex = meterValue.sampledValue.length - 1;
9bf0ef23
JB
442 const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2);
443 const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2);
78085c42 444 if (
9bf0ef23 445 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
ad8537a7 446 connectorMaximumPowerRounded ||
9bf0ef23 447 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
860ef183 448 connectorMinimumPowerRounded ||
78085c42
JB
449 debug
450 ) {
451 logger.error(
452 `${chargingStation.logPrefix()} MeterValues measurand ${
453 meterValue.sampledValue[sampledValuesIndex].measurand ??
454 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5edd8ba0 455 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
78085c42 456 meterValue.sampledValue[sampledValuesIndex].value
5edd8ba0 457 }/${connectorMaximumPowerRounded}`,
78085c42
JB
458 );
459 }
460 for (
461 let phase = 1;
462 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
463 phase++
464 ) {
465 const phaseValue = `L${phase}-N`;
466 meterValue.sampledValue.push(
467 OCPP16ServiceUtils.buildSampledValue(
a37fc6dc
JB
468 powerPerPhaseSampledValueTemplates[
469 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
470 ]! ?? powerSampledValueTemplate,
471 powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
72092cfc 472 undefined,
5edd8ba0
JB
473 phaseValue as OCPP16MeterValuePhase,
474 ),
78085c42
JB
475 );
476 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
9bf0ef23 477 const connectorMaximumPowerPerPhaseRounded = roundTo(
ad8537a7 478 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 479 2,
ad8537a7 480 );
9bf0ef23 481 const connectorMinimumPowerPerPhaseRounded = roundTo(
860ef183 482 connectorMinimumPowerPerPhase / unitDivider,
5edd8ba0 483 2,
860ef183 484 );
78085c42 485 if (
9bf0ef23 486 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
ad8537a7 487 connectorMaximumPowerPerPhaseRounded ||
9bf0ef23 488 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
860ef183 489 connectorMinimumPowerPerPhaseRounded ||
78085c42
JB
490 debug
491 ) {
492 logger.error(
493 `${chargingStation.logPrefix()} MeterValues measurand ${
494 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
495 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
496 }: phase ${
497 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
5edd8ba0 498 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
78085c42 499 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
5edd8ba0 500 }/${connectorMaximumPowerPerPhaseRounded}`,
78085c42
JB
501 );
502 }
503 }
504 }
505 // Current.Import measurand
ed3d2808 506 const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 507 chargingStation,
78085c42 508 connectorId,
5edd8ba0 509 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
78085c42 510 );
abe9e9dd 511 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
78085c42
JB
512 if (chargingStation.getNumberOfPhases() === 3) {
513 currentPerPhaseSampledValueTemplates = {
ed3d2808 514 L1: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 515 chargingStation,
78085c42
JB
516 connectorId,
517 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 518 OCPP16MeterValuePhase.L1,
78085c42 519 ),
ed3d2808 520 L2: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 521 chargingStation,
78085c42
JB
522 connectorId,
523 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 524 OCPP16MeterValuePhase.L2,
78085c42 525 ),
ed3d2808 526 L3: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 527 chargingStation,
78085c42
JB
528 connectorId,
529 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 530 OCPP16MeterValuePhase.L3,
78085c42
JB
531 ),
532 };
533 }
534 if (currentSampledValueTemplate) {
535 OCPP16ServiceUtils.checkMeasurandPowerDivider(
536 chargingStation,
e1d9a0f4 537 currentSampledValueTemplate.measurand!,
78085c42 538 );
fc040c43 539 const errMsg = `MeterValues measurand ${
78085c42
JB
540 currentSampledValueTemplate.measurand ??
541 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5398cecf 542 }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
2484ac1e 543 chargingStation.templateFile
78085c42
JB
544 }, cannot calculate ${
545 currentSampledValueTemplate.measurand ??
546 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
547 } measurand value`;
abe9e9dd 548 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
1b6498ba
JB
549 const connectorMaximumAvailablePower =
550 chargingStation.getConnectorMaximumAvailablePower(connectorId);
860ef183 551 const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
ad8537a7 552 let connectorMaximumAmperage: number;
5398cecf 553 switch (chargingStation.stationInfo?.currentOutType) {
78085c42 554 case CurrentType.AC:
ad8537a7 555 connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
78085c42 556 chargingStation.getNumberOfPhases(),
1b6498ba 557 connectorMaximumAvailablePower,
5398cecf 558 chargingStation.stationInfo.voltageOut!,
78085c42
JB
559 );
560 if (chargingStation.getNumberOfPhases() === 3) {
561 const defaultFluctuatedAmperagePerPhase =
562 currentSampledValueTemplate.value &&
9bf0ef23 563 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
564 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
565 currentSampledValueTemplate.value,
566 connectorMaximumAmperage,
d71ce3fa 567 connectorMinimumAmperage,
5398cecf
JB
568 {
569 limitationEnabled:
570 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 571 fallbackValue: connectorMinimumAmperage,
5398cecf 572 },
7bc31f9c 573 ),
78085c42 574 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 575 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
576 );
577 const phase1FluctuatedValue =
e1d9a0f4 578 currentPerPhaseSampledValueTemplates.L1?.value &&
9bf0ef23 579 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
580 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
581 currentPerPhaseSampledValueTemplates.L1.value,
582 connectorMaximumAmperage,
d71ce3fa 583 connectorMinimumAmperage,
5398cecf
JB
584 {
585 limitationEnabled:
586 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 587 fallbackValue: connectorMinimumAmperage,
5398cecf 588 },
34464008 589 ),
78085c42 590 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
5edd8ba0 591 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
592 );
593 const phase2FluctuatedValue =
e1d9a0f4 594 currentPerPhaseSampledValueTemplates.L2?.value &&
9bf0ef23 595 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
596 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
597 currentPerPhaseSampledValueTemplates.L2.value,
598 connectorMaximumAmperage,
d71ce3fa 599 connectorMinimumAmperage,
5398cecf
JB
600 {
601 limitationEnabled:
602 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 603 fallbackValue: connectorMinimumAmperage,
5398cecf 604 },
34464008 605 ),
78085c42 606 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
5edd8ba0 607 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
608 );
609 const phase3FluctuatedValue =
e1d9a0f4 610 currentPerPhaseSampledValueTemplates.L3?.value &&
9bf0ef23 611 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
612 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
613 currentPerPhaseSampledValueTemplates.L3.value,
614 connectorMaximumAmperage,
d71ce3fa 615 connectorMinimumAmperage,
5398cecf
JB
616 {
617 limitationEnabled:
618 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 619 fallbackValue: connectorMinimumAmperage,
5398cecf 620 },
34464008 621 ),
78085c42 622 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
5edd8ba0 623 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
624 );
625 currentMeasurandValues.L1 =
e1d9a0f4
JB
626 (phase1FluctuatedValue as number) ??
627 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 628 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42 629 currentMeasurandValues.L2 =
e1d9a0f4
JB
630 (phase2FluctuatedValue as number) ??
631 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 632 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42 633 currentMeasurandValues.L3 =
e1d9a0f4
JB
634 (phase3FluctuatedValue as number) ??
635 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 636 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42 637 } else {
5a47f72c 638 currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value)
9bf0ef23 639 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
640 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
641 currentSampledValueTemplate.value,
642 connectorMaximumAmperage,
d71ce3fa 643 connectorMinimumAmperage,
5398cecf
JB
644 {
645 limitationEnabled:
646 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 647 fallbackValue: connectorMinimumAmperage,
5398cecf 648 },
7bc31f9c 649 ),
78085c42 650 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 651 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 652 )
9bf0ef23 653 : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42
JB
654 currentMeasurandValues.L2 = 0;
655 currentMeasurandValues.L3 = 0;
656 }
9bf0ef23 657 currentMeasurandValues.allPhases = roundTo(
78085c42
JB
658 (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
659 chargingStation.getNumberOfPhases(),
5edd8ba0 660 2,
78085c42
JB
661 );
662 break;
663 case CurrentType.DC:
ad8537a7 664 connectorMaximumAmperage = DCElectricUtils.amperage(
1b6498ba 665 connectorMaximumAvailablePower,
5398cecf 666 chargingStation.stationInfo.voltageOut!,
78085c42 667 );
5a47f72c 668 currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value)
9bf0ef23 669 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
670 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
671 currentSampledValueTemplate.value,
672 connectorMaximumAmperage,
d71ce3fa 673 connectorMinimumAmperage,
5398cecf
JB
674 {
675 limitationEnabled:
676 chargingStation.stationInfo?.customValueLimitationMeterValues,
d71ce3fa 677 fallbackValue: connectorMinimumAmperage,
5398cecf 678 },
7bc31f9c 679 ),
78085c42 680 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 681 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 682 )
9bf0ef23 683 : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42
JB
684 break;
685 default:
fc040c43 686 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
78085c42
JB
687 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
688 }
689 meterValue.sampledValue.push(
690 OCPP16ServiceUtils.buildSampledValue(
691 currentSampledValueTemplate,
5edd8ba0
JB
692 currentMeasurandValues.allPhases,
693 ),
78085c42
JB
694 );
695 const sampledValuesIndex = meterValue.sampledValue.length - 1;
696 if (
9bf0ef23 697 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
ad8537a7 698 connectorMaximumAmperage ||
9bf0ef23 699 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
860ef183 700 connectorMinimumAmperage ||
78085c42
JB
701 debug
702 ) {
703 logger.error(
704 `${chargingStation.logPrefix()} MeterValues measurand ${
705 meterValue.sampledValue[sampledValuesIndex].measurand ??
706 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5edd8ba0 707 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
78085c42 708 meterValue.sampledValue[sampledValuesIndex].value
5edd8ba0 709 }/${connectorMaximumAmperage}`,
78085c42
JB
710 );
711 }
712 for (
713 let phase = 1;
714 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
715 phase++
716 ) {
717 const phaseValue = `L${phase}`;
718 meterValue.sampledValue.push(
719 OCPP16ServiceUtils.buildSampledValue(
a37fc6dc
JB
720 currentPerPhaseSampledValueTemplates[
721 phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
722 ]! ?? currentSampledValueTemplate,
723 currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
72092cfc 724 undefined,
5edd8ba0
JB
725 phaseValue as OCPP16MeterValuePhase,
726 ),
78085c42
JB
727 );
728 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
729 if (
9bf0ef23 730 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
ad8537a7 731 connectorMaximumAmperage ||
9bf0ef23 732 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
860ef183 733 connectorMinimumAmperage ||
78085c42
JB
734 debug
735 ) {
736 logger.error(
737 `${chargingStation.logPrefix()} MeterValues measurand ${
738 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
739 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
740 }: phase ${
741 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
5edd8ba0 742 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
78085c42 743 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
5edd8ba0 744 }/${connectorMaximumAmperage}`,
78085c42
JB
745 );
746 }
747 }
748 }
749 // Energy.Active.Import.Register measurand (default)
ed3d2808 750 const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 751 chargingStation,
5edd8ba0 752 connectorId,
492cf6ab 753 );
78085c42
JB
754 if (energySampledValueTemplate) {
755 OCPP16ServiceUtils.checkMeasurandPowerDivider(
756 chargingStation,
e1d9a0f4 757 energySampledValueTemplate.measurand!,
78085c42
JB
758 );
759 const unitDivider =
760 energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
1b6498ba
JB
761 const connectorMaximumAvailablePower =
762 chargingStation.getConnectorMaximumAvailablePower(connectorId);
9bf0ef23 763 const connectorMaximumEnergyRounded = roundTo(
1b6498ba 764 (connectorMaximumAvailablePower * interval) / (3600 * 1000),
5edd8ba0 765 2,
78085c42 766 );
d71ce3fa
JB
767 const connectorMinimumEnergyRounded = roundTo(
768 energySampledValueTemplate.minimumValue ?? 0,
769 2,
770 );
77684af8 771 const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value)
78085c42 772 ? // Cumulate the fluctuated value around the static one
9bf0ef23 773 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
774 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
775 energySampledValueTemplate.value,
776 connectorMaximumEnergyRounded,
d71ce3fa 777 connectorMinimumEnergyRounded,
7bc31f9c 778 {
5398cecf 779 limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues,
7bc31f9c 780 unitMultiplier: unitDivider,
d71ce3fa 781 fallbackValue: connectorMinimumEnergyRounded,
5edd8ba0 782 },
34464008 783 ),
5edd8ba0 784 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 785 )
d71ce3fa 786 : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded);
78085c42 787 // Persist previous value on connector
e1d9a0f4
JB
788 if (connector) {
789 if (
790 isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
791 connector.energyActiveImportRegisterValue! >= 0 &&
792 isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
793 connector.transactionEnergyActiveImportRegisterValue! >= 0
794 ) {
795 connector.energyActiveImportRegisterValue! += energyValueRounded;
796 connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded;
797 } else {
798 connector.energyActiveImportRegisterValue = 0;
799 connector.transactionEnergyActiveImportRegisterValue = 0;
800 }
78085c42
JB
801 }
802 meterValue.sampledValue.push(
803 OCPP16ServiceUtils.buildSampledValue(
804 energySampledValueTemplate,
9bf0ef23 805 roundTo(
78085c42
JB
806 chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
807 unitDivider,
5edd8ba0
JB
808 2,
809 ),
810 ),
78085c42
JB
811 );
812 const sampledValuesIndex = meterValue.sampledValue.length - 1;
d71ce3fa
JB
813 if (
814 energyValueRounded > connectorMaximumEnergyRounded ||
815 energyValueRounded < connectorMinimumEnergyRounded ||
816 debug
817 ) {
78085c42
JB
818 logger.error(
819 `${chargingStation.logPrefix()} MeterValues measurand ${
820 meterValue.sampledValue[sampledValuesIndex].measurand ??
821 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
d71ce3fa 822 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
78085c42
JB
823 );
824 }
825 }
826 return meterValue;
827 }
828
e7aeea18
JB
829 public static buildTransactionBeginMeterValue(
830 chargingStation: ChargingStation,
831 connectorId: number,
5edd8ba0 832 meterStart: number,
e7aeea18 833 ): OCPP16MeterValue {
fd0c36fa 834 const meterValue: OCPP16MeterValue = {
c38f0ced 835 timestamp: new Date(),
fd0c36fa
JB
836 sampledValue: [],
837 };
9ccca265 838 // Energy.Active.Import.Register measurand (default)
ed3d2808 839 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 840 chargingStation,
5edd8ba0 841 connectorId,
492cf6ab 842 );
9ccca265 843 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
844 meterValue.sampledValue.push(
845 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 846 sampledValueTemplate!,
9bf0ef23 847 roundTo((meterStart ?? 0) / unitDivider, 4),
5edd8ba0
JB
848 MeterValueContext.TRANSACTION_BEGIN,
849 ),
e7aeea18 850 );
fd0c36fa
JB
851 return meterValue;
852 }
853
e7aeea18
JB
854 public static buildTransactionEndMeterValue(
855 chargingStation: ChargingStation,
856 connectorId: number,
5edd8ba0 857 meterStop: number,
e7aeea18 858 ): OCPP16MeterValue {
fd0c36fa 859 const meterValue: OCPP16MeterValue = {
c38f0ced 860 timestamp: new Date(),
fd0c36fa
JB
861 sampledValue: [],
862 };
9ccca265 863 // Energy.Active.Import.Register measurand (default)
ed3d2808 864 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 865 chargingStation,
5edd8ba0 866 connectorId,
492cf6ab 867 );
9ccca265 868 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
869 meterValue.sampledValue.push(
870 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 871 sampledValueTemplate!,
9bf0ef23 872 roundTo((meterStop ?? 0) / unitDivider, 4),
5edd8ba0
JB
873 MeterValueContext.TRANSACTION_END,
874 ),
e7aeea18 875 );
fd0c36fa
JB
876 return meterValue;
877 }
878
e7aeea18
JB
879 public static buildTransactionDataMeterValues(
880 transactionBeginMeterValue: OCPP16MeterValue,
5edd8ba0 881 transactionEndMeterValue: OCPP16MeterValue,
e7aeea18 882 ): OCPP16MeterValue[] {
fd0c36fa
JB
883 const meterValues: OCPP16MeterValue[] = [];
884 meterValues.push(transactionBeginMeterValue);
885 meterValues.push(transactionEndMeterValue);
886 return meterValues;
887 }
7bc31f9c 888
d19b10a8
JB
889 public static remoteStopTransaction = async (
890 chargingStation: ChargingStation,
891 connectorId: number,
892 ): Promise<GenericResponse> => {
893 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
894 chargingStation,
895 connectorId,
896 OCPP16ChargePointStatus.Finishing,
897 );
898 const stopResponse = await chargingStation.stopTransactionOnConnector(
899 connectorId,
900 OCPP16StopTransactionReason.REMOTE,
901 );
902 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
903 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
904 }
905 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
906 };
907
366f75f6
JB
908 public static changeAvailability = async (
909 chargingStation: ChargingStation,
225e32b0 910 connectorIds: number[],
366f75f6
JB
911 chargePointStatus: OCPP16ChargePointStatus,
912 availabilityType: OCPP16AvailabilityType,
913 ): Promise<OCPP16ChangeAvailabilityResponse> => {
225e32b0
JB
914 const responses: OCPP16ChangeAvailabilityResponse[] = [];
915 for (const connectorId of connectorIds) {
916 let response: OCPP16ChangeAvailabilityResponse =
917 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
918 const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
919 if (connectorStatus?.transactionStarted === true) {
920 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
921 }
922 connectorStatus.availability = availabilityType;
923 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
924 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
925 chargingStation,
926 connectorId,
927 chargePointStatus,
928 );
929 }
930 responses.push(response);
366f75f6 931 }
3b0ed034 932 if (responses.includes(OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED)) {
225e32b0 933 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
366f75f6 934 }
225e32b0 935 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
366f75f6
JB
936 };
937
ed3d2808
JB
938 public static setChargingProfile(
939 chargingStation: ChargingStation,
940 connectorId: number,
5edd8ba0 941 cp: OCPP16ChargingProfile,
ed3d2808 942 ): void {
9bf0ef23 943 if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808 944 logger.error(
5edd8ba0 945 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
ed3d2808 946 );
e1d9a0f4 947 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808 948 }
72092cfc
JB
949 if (
950 Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
951 ) {
ed3d2808 952 logger.error(
bbb55ee4 953 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type deferred initialization`,
ed3d2808 954 );
e1d9a0f4 955 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808
JB
956 }
957 let cpReplaced = false;
9bf0ef23 958 if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808
JB
959 chargingStation
960 .getConnectorStatus(connectorId)
72092cfc 961 ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
ed3d2808
JB
962 if (
963 chargingProfile.chargingProfileId === cp.chargingProfileId ||
964 (chargingProfile.stackLevel === cp.stackLevel &&
965 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
966 ) {
e1d9a0f4 967 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp;
ed3d2808
JB
968 cpReplaced = true;
969 }
970 });
971 }
72092cfc 972 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
ed3d2808
JB
973 }
974
73d87be1
JB
975 public static clearChargingProfiles = (
976 chargingStation: ChargingStation,
977 commandPayload: ClearChargingProfileRequest,
978 chargingProfiles: OCPP16ChargingProfile[] | undefined,
979 ): boolean => {
0d1f33ba 980 const { id, chargingProfilePurpose, stackLevel } = commandPayload;
73d87be1
JB
981 let clearedCP = false;
982 if (isNotEmptyArray(chargingProfiles)) {
983 chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
984 let clearCurrentCP = false;
0d1f33ba 985 if (chargingProfile.chargingProfileId === id) {
73d87be1
JB
986 clearCurrentCP = true;
987 }
0d1f33ba 988 if (!chargingProfilePurpose && chargingProfile.stackLevel === stackLevel) {
73d87be1
JB
989 clearCurrentCP = true;
990 }
0d1f33ba 991 if (!stackLevel && chargingProfile.chargingProfilePurpose === chargingProfilePurpose) {
73d87be1
JB
992 clearCurrentCP = true;
993 }
994 if (
0d1f33ba
JB
995 chargingProfile.stackLevel === stackLevel &&
996 chargingProfile.chargingProfilePurpose === chargingProfilePurpose
73d87be1
JB
997 ) {
998 clearCurrentCP = true;
999 }
1000 if (clearCurrentCP) {
1001 chargingProfiles.splice(index, 1);
1002 logger.debug(
1003 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
1004 chargingProfile,
1005 );
1006 clearedCP = true;
1007 }
1008 });
1009 }
1010 return clearedCP;
1011 };
1012
ef9e3b33 1013 public static composeChargingSchedules = (
4abf6441
JB
1014 chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
1015 chargingScheduleLower: OCPP16ChargingSchedule | undefined,
d632062f 1016 compositeInterval: Interval,
ef9e3b33 1017 ): OCPP16ChargingSchedule | undefined => {
4abf6441 1018 if (!chargingScheduleHigher && !chargingScheduleLower) {
ef9e3b33
JB
1019 return undefined;
1020 }
4abf6441 1021 if (chargingScheduleHigher && !chargingScheduleLower) {
d632062f 1022 return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval);
ef9e3b33 1023 }
4abf6441 1024 if (!chargingScheduleHigher && chargingScheduleLower) {
d632062f 1025 return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval);
ef9e3b33 1026 }
4abf6441 1027 const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined =
d632062f 1028 OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval);
4abf6441 1029 const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined =
d632062f 1030 OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval);
4abf6441
JB
1031 const compositeChargingScheduleHigherInterval: Interval = {
1032 start: compositeChargingScheduleHigher!.startSchedule!,
ef9e3b33 1033 end: addSeconds(
4abf6441
JB
1034 compositeChargingScheduleHigher!.startSchedule!,
1035 compositeChargingScheduleHigher!.duration!,
ef9e3b33
JB
1036 ),
1037 };
4abf6441
JB
1038 const compositeChargingScheduleLowerInterval: Interval = {
1039 start: compositeChargingScheduleLower!.startSchedule!,
ef9e3b33 1040 end: addSeconds(
4abf6441
JB
1041 compositeChargingScheduleLower!.startSchedule!,
1042 compositeChargingScheduleLower!.duration!,
ef9e3b33
JB
1043 ),
1044 };
4abf6441
JB
1045 const higherFirst = isBefore(
1046 compositeChargingScheduleHigherInterval.start,
1047 compositeChargingScheduleLowerInterval.start,
1048 );
ef9e3b33
JB
1049 if (
1050 !areIntervalsOverlapping(
4abf6441
JB
1051 compositeChargingScheduleHigherInterval,
1052 compositeChargingScheduleLowerInterval,
ef9e3b33
JB
1053 )
1054 ) {
1055 return {
4abf6441
JB
1056 ...compositeChargingScheduleLower,
1057 ...compositeChargingScheduleHigher!,
1058 startSchedule: higherFirst
1059 ? (compositeChargingScheduleHigherInterval.start as Date)
1060 : (compositeChargingScheduleLowerInterval.start as Date),
1061 duration: higherFirst
1062 ? differenceInSeconds(
1063 compositeChargingScheduleLowerInterval.end,
1064 compositeChargingScheduleHigherInterval.start,
1065 )
1066 : differenceInSeconds(
1067 compositeChargingScheduleHigherInterval.end,
1068 compositeChargingScheduleLowerInterval.start,
1069 ),
1070 chargingSchedulePeriod: [
1071 ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => {
1072 return {
1073 ...schedulePeriod,
1074 startPeriod: higherFirst
1075 ? 0
1076 : schedulePeriod.startPeriod +
1077 differenceInSeconds(
1078 compositeChargingScheduleHigherInterval.start,
1079 compositeChargingScheduleLowerInterval.start,
1080 ),
1081 };
1082 }),
1083 ...compositeChargingScheduleLower!.chargingSchedulePeriod.map((schedulePeriod) => {
1084 return {
1085 ...schedulePeriod,
1086 startPeriod: higherFirst
1087 ? schedulePeriod.startPeriod +
1088 differenceInSeconds(
1089 compositeChargingScheduleLowerInterval.start,
1090 compositeChargingScheduleHigherInterval.start,
1091 )
1092 : 0,
1093 };
1094 }),
1095 ].sort((a, b) => a.startPeriod - b.startPeriod),
ef9e3b33
JB
1096 };
1097 }
4abf6441
JB
1098 return {
1099 ...compositeChargingScheduleLower,
1100 ...compositeChargingScheduleHigher!,
1101 startSchedule: higherFirst
1102 ? (compositeChargingScheduleHigherInterval.start as Date)
1103 : (compositeChargingScheduleLowerInterval.start as Date),
1104 duration: higherFirst
1105 ? differenceInSeconds(
1106 compositeChargingScheduleLowerInterval.end,
1107 compositeChargingScheduleHigherInterval.start,
1108 )
1109 : differenceInSeconds(
1110 compositeChargingScheduleHigherInterval.end,
1111 compositeChargingScheduleLowerInterval.start,
1112 ),
1113 chargingSchedulePeriod: [
1114 ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => {
1115 return {
1116 ...schedulePeriod,
1117 startPeriod: higherFirst
1118 ? 0
1119 : schedulePeriod.startPeriod +
1120 differenceInSeconds(
1121 compositeChargingScheduleHigherInterval.start,
1122 compositeChargingScheduleLowerInterval.start,
1123 ),
1124 };
1125 }),
1126 ...compositeChargingScheduleLower!.chargingSchedulePeriod
c4ab56ba 1127 .filter((schedulePeriod, index) => {
4abf6441
JB
1128 if (
1129 higherFirst &&
1130 isWithinInterval(
1131 addSeconds(
1132 compositeChargingScheduleLowerInterval.start,
1133 schedulePeriod.startPeriod,
1134 ),
1135 {
1136 start: compositeChargingScheduleLowerInterval.start,
1137 end: compositeChargingScheduleHigherInterval.end,
1138 },
1139 )
1140 ) {
1141 return false;
1142 }
c4ab56ba
JB
1143 if (
1144 higherFirst &&
1145 index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 &&
1146 !isWithinInterval(
1147 addSeconds(
1148 compositeChargingScheduleLowerInterval.start,
1149 schedulePeriod.startPeriod,
1150 ),
1151 {
1152 start: compositeChargingScheduleLowerInterval.start,
1153 end: compositeChargingScheduleHigherInterval.end,
1154 },
1155 ) &&
1156 isWithinInterval(
1157 addSeconds(
1158 compositeChargingScheduleLowerInterval.start,
1159 compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod,
1160 ),
1161 {
1162 start: compositeChargingScheduleLowerInterval.start,
1163 end: compositeChargingScheduleHigherInterval.end,
1164 },
1165 )
1166 ) {
c4ab56ba
JB
1167 return false;
1168 }
4abf6441
JB
1169 if (
1170 !higherFirst &&
1171 isWithinInterval(
1172 addSeconds(
1173 compositeChargingScheduleLowerInterval.start,
1174 schedulePeriod.startPeriod,
1175 ),
1176 {
1177 start: compositeChargingScheduleHigherInterval.start,
1178 end: compositeChargingScheduleLowerInterval.end,
1179 },
1180 )
1181 ) {
1182 return false;
1183 }
1184 return true;
1185 })
0e14e1d4
JB
1186 .map((schedulePeriod, index) => {
1187 if (index === 0 && schedulePeriod.startPeriod !== 0) {
1188 schedulePeriod.startPeriod = 0;
1189 }
4abf6441
JB
1190 return {
1191 ...schedulePeriod,
1192 startPeriod: higherFirst
1193 ? schedulePeriod.startPeriod +
1194 differenceInSeconds(
1195 compositeChargingScheduleLowerInterval.start,
1196 compositeChargingScheduleHigherInterval.start,
1197 )
1198 : 0,
1199 };
1200 }),
1201 ].sort((a, b) => a.startPeriod - b.startPeriod),
1202 };
ef9e3b33
JB
1203 };
1204
90aceaf6
JB
1205 public static hasReservation = (
1206 chargingStation: ChargingStation,
1207 connectorId: number,
1208 idTag: string,
1209 ): boolean => {
1210 const connectorReservation = chargingStation.getReservationBy('connectorId', connectorId);
1211 const chargingStationReservation = chargingStation.getReservationBy('connectorId', 0);
1212 if (
1213 (chargingStation.getConnectorStatus(connectorId)?.status ===
1214 OCPP16ChargePointStatus.Reserved &&
1215 connectorReservation &&
56563a3c 1216 !hasReservationExpired(connectorReservation) &&
90aceaf6 1217 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
56563a3c 1218 connectorReservation?.idTag === idTag) ||
90aceaf6
JB
1219 (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved &&
1220 chargingStationReservation &&
56563a3c
JB
1221 !hasReservationExpired(chargingStationReservation) &&
1222 chargingStationReservation?.idTag === idTag)
90aceaf6 1223 ) {
88499f52
JB
1224 logger.debug(
1225 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1226 connectorReservation ?? chargingStationReservation,
1227 );
56563a3c 1228 return true;
90aceaf6 1229 }
56563a3c 1230 return false;
90aceaf6
JB
1231 };
1232
1b271a54
JB
1233 public static parseJsonSchemaFile<T extends JsonType>(
1234 relativePath: string,
1235 moduleName?: string,
5edd8ba0 1236 methodName?: string,
1b271a54 1237 ): JSONSchemaType<T> {
7164966d 1238 return super.parseJsonSchemaFile<T>(
51022aa0 1239 relativePath,
1b271a54
JB
1240 OCPPVersion.VERSION_16,
1241 moduleName,
5edd8ba0 1242 methodName,
7164966d 1243 );
130783a7
JB
1244 }
1245
ef9e3b33
JB
1246 private static composeChargingSchedule = (
1247 chargingSchedule: OCPP16ChargingSchedule,
d632062f 1248 compositeInterval: Interval,
ef9e3b33
JB
1249 ): OCPP16ChargingSchedule | undefined => {
1250 const chargingScheduleInterval: Interval = {
1251 start: chargingSchedule.startSchedule!,
1252 end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
1253 };
d632062f 1254 if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
ef9e3b33 1255 chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod);
d632062f 1256 if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
ef9e3b33
JB
1257 return {
1258 ...chargingSchedule,
d632062f
JB
1259 startSchedule: compositeInterval.start as Date,
1260 duration: differenceInSeconds(
1261 chargingScheduleInterval.end,
1262 compositeInterval.start as Date,
1263 ),
0e14e1d4
JB
1264 chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod
1265 .filter((schedulePeriod, index) => {
ef9e3b33
JB
1266 if (
1267 isWithinInterval(
1268 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
d632062f 1269 compositeInterval,
ef9e3b33
JB
1270 )
1271 ) {
1272 return true;
1273 }
1274 if (
1275 index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
1276 !isWithinInterval(
1277 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
d632062f 1278 compositeInterval,
ef9e3b33
JB
1279 ) &&
1280 isWithinInterval(
1281 addSeconds(
1282 chargingScheduleInterval.start,
1283 chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod,
1284 ),
d632062f 1285 compositeInterval,
ef9e3b33
JB
1286 )
1287 ) {
ef9e3b33
JB
1288 return true;
1289 }
1290 return false;
0e14e1d4
JB
1291 })
1292 .map((schedulePeriod, index) => {
1293 if (index === 0 && schedulePeriod.startPeriod !== 0) {
1294 schedulePeriod.startPeriod = 0;
1295 }
1296 return schedulePeriod;
1297 }),
ef9e3b33
JB
1298 };
1299 }
d632062f 1300 if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
ef9e3b33
JB
1301 return {
1302 ...chargingSchedule,
d632062f
JB
1303 duration: differenceInSeconds(
1304 compositeInterval.end as Date,
1305 chargingScheduleInterval.start,
1306 ),
ef9e3b33
JB
1307 chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter((schedulePeriod) =>
1308 isWithinInterval(
1309 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
d632062f 1310 compositeInterval,
ef9e3b33
JB
1311 ),
1312 ),
1313 };
1314 }
1315 return chargingSchedule;
1316 }
1317 };
1318
7bc31f9c
JB
1319 private static buildSampledValue(
1320 sampledValueTemplate: SampledValueTemplate,
1321 value: number,
1322 context?: MeterValueContext,
5edd8ba0 1323 phase?: OCPP16MeterValuePhase,
7bc31f9c 1324 ): OCPP16SampledValue {
4ed03b6e
JB
1325 const sampledValueValue = value ?? sampledValueTemplate?.value;
1326 const sampledValueContext = context ?? sampledValueTemplate?.context;
7bc31f9c
JB
1327 const sampledValueLocation =
1328 sampledValueTemplate?.location ??
e1d9a0f4 1329 OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
4ed03b6e 1330 const sampledValuePhase = phase ?? sampledValueTemplate?.phase;
7bc31f9c 1331 return {
9bf0ef23 1332 ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
7bc31f9c
JB
1333 unit: sampledValueTemplate.unit,
1334 }),
9bf0ef23
JB
1335 ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
1336 ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
7bc31f9c
JB
1337 measurand: sampledValueTemplate.measurand,
1338 }),
9bf0ef23
JB
1339 ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
1340 ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
1341 ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
e1d9a0f4 1342 } as OCPP16SampledValue;
7bc31f9c
JB
1343 }
1344
1345 private static checkMeasurandPowerDivider(
1346 chargingStation: ChargingStation,
5edd8ba0 1347 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c 1348 ): void {
9bf0ef23 1349 if (isUndefined(chargingStation.powerDivider)) {
fc040c43 1350 const errMsg = `MeterValues measurand ${
7bc31f9c
JB
1351 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1352 }: powerDivider is undefined`;
fc040c43 1353 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c 1354 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
fa7bccf4 1355 } else if (chargingStation?.powerDivider <= 0) {
fc040c43 1356 const errMsg = `MeterValues measurand ${
7bc31f9c 1357 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
fa7bccf4 1358 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
fc040c43 1359 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c
JB
1360 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
1361 }
1362 }
1363
1364 private static getMeasurandDefaultLocation(
5edd8ba0 1365 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
1366 ): MeterValueLocation | undefined {
1367 switch (measurandType) {
1368 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1369 return MeterValueLocation.EV;
1370 }
1371 }
1372
3b0ed034
JB
1373 // private static getMeasurandDefaultUnit(
1374 // measurandType: OCPP16MeterValueMeasurand,
1375 // ): MeterValueUnit | undefined {
1376 // switch (measurandType) {
1377 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1378 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1379 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1380 // return MeterValueUnit.AMP;
1381 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1382 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1383 // return MeterValueUnit.WATT_HOUR;
1384 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1385 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1386 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1387 // return MeterValueUnit.WATT;
1388 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1389 // return MeterValueUnit.PERCENT;
1390 // case OCPP16MeterValueMeasurand.VOLTAGE:
1391 // return MeterValueUnit.VOLT;
1392 // }
1393 // }
6ed92bc1 1394}