build: refine .cfignore
[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)
c6dcc331 772 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
773 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
774 energySampledValueTemplate.value,
775 connectorMaximumEnergyRounded,
d71ce3fa 776 connectorMinimumEnergyRounded,
7bc31f9c 777 {
5398cecf 778 limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues,
7bc31f9c 779 unitMultiplier: unitDivider,
d71ce3fa 780 fallbackValue: connectorMinimumEnergyRounded,
5edd8ba0 781 },
34464008 782 ),
5edd8ba0 783 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 784 )
d71ce3fa 785 : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded);
78085c42 786 // Persist previous value on connector
e1d9a0f4
JB
787 if (connector) {
788 if (
789 isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
790 connector.energyActiveImportRegisterValue! >= 0 &&
791 isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
792 connector.transactionEnergyActiveImportRegisterValue! >= 0
793 ) {
794 connector.energyActiveImportRegisterValue! += energyValueRounded;
795 connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded;
796 } else {
797 connector.energyActiveImportRegisterValue = 0;
798 connector.transactionEnergyActiveImportRegisterValue = 0;
799 }
78085c42
JB
800 }
801 meterValue.sampledValue.push(
802 OCPP16ServiceUtils.buildSampledValue(
803 energySampledValueTemplate,
9bf0ef23 804 roundTo(
78085c42
JB
805 chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
806 unitDivider,
5edd8ba0
JB
807 2,
808 ),
809 ),
78085c42
JB
810 );
811 const sampledValuesIndex = meterValue.sampledValue.length - 1;
d71ce3fa
JB
812 if (
813 energyValueRounded > connectorMaximumEnergyRounded ||
814 energyValueRounded < connectorMinimumEnergyRounded ||
815 debug
816 ) {
78085c42
JB
817 logger.error(
818 `${chargingStation.logPrefix()} MeterValues measurand ${
819 meterValue.sampledValue[sampledValuesIndex].measurand ??
820 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
d71ce3fa 821 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
78085c42
JB
822 );
823 }
824 }
825 return meterValue;
826 }
827
e7aeea18
JB
828 public static buildTransactionBeginMeterValue(
829 chargingStation: ChargingStation,
830 connectorId: number,
5edd8ba0 831 meterStart: number,
e7aeea18 832 ): OCPP16MeterValue {
fd0c36fa 833 const meterValue: OCPP16MeterValue = {
c38f0ced 834 timestamp: new Date(),
fd0c36fa
JB
835 sampledValue: [],
836 };
9ccca265 837 // Energy.Active.Import.Register measurand (default)
ed3d2808 838 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 839 chargingStation,
5edd8ba0 840 connectorId,
492cf6ab 841 );
9ccca265 842 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
843 meterValue.sampledValue.push(
844 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 845 sampledValueTemplate!,
9bf0ef23 846 roundTo((meterStart ?? 0) / unitDivider, 4),
5edd8ba0
JB
847 MeterValueContext.TRANSACTION_BEGIN,
848 ),
e7aeea18 849 );
fd0c36fa
JB
850 return meterValue;
851 }
852
e7aeea18
JB
853 public static buildTransactionEndMeterValue(
854 chargingStation: ChargingStation,
855 connectorId: number,
5edd8ba0 856 meterStop: number,
e7aeea18 857 ): OCPP16MeterValue {
fd0c36fa 858 const meterValue: OCPP16MeterValue = {
c38f0ced 859 timestamp: new Date(),
fd0c36fa
JB
860 sampledValue: [],
861 };
9ccca265 862 // Energy.Active.Import.Register measurand (default)
ed3d2808 863 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 864 chargingStation,
5edd8ba0 865 connectorId,
492cf6ab 866 );
9ccca265 867 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
868 meterValue.sampledValue.push(
869 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 870 sampledValueTemplate!,
9bf0ef23 871 roundTo((meterStop ?? 0) / unitDivider, 4),
5edd8ba0
JB
872 MeterValueContext.TRANSACTION_END,
873 ),
e7aeea18 874 );
fd0c36fa
JB
875 return meterValue;
876 }
877
e7aeea18
JB
878 public static buildTransactionDataMeterValues(
879 transactionBeginMeterValue: OCPP16MeterValue,
5edd8ba0 880 transactionEndMeterValue: OCPP16MeterValue,
e7aeea18 881 ): OCPP16MeterValue[] {
fd0c36fa
JB
882 const meterValues: OCPP16MeterValue[] = [];
883 meterValues.push(transactionBeginMeterValue);
884 meterValues.push(transactionEndMeterValue);
885 return meterValues;
886 }
7bc31f9c 887
d19b10a8
JB
888 public static remoteStopTransaction = async (
889 chargingStation: ChargingStation,
890 connectorId: number,
891 ): Promise<GenericResponse> => {
892 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
893 chargingStation,
894 connectorId,
895 OCPP16ChargePointStatus.Finishing,
896 );
897 const stopResponse = await chargingStation.stopTransactionOnConnector(
898 connectorId,
899 OCPP16StopTransactionReason.REMOTE,
900 );
901 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
902 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
903 }
904 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
905 };
906
366f75f6
JB
907 public static changeAvailability = async (
908 chargingStation: ChargingStation,
225e32b0 909 connectorIds: number[],
366f75f6
JB
910 chargePointStatus: OCPP16ChargePointStatus,
911 availabilityType: OCPP16AvailabilityType,
912 ): Promise<OCPP16ChangeAvailabilityResponse> => {
225e32b0
JB
913 const responses: OCPP16ChangeAvailabilityResponse[] = [];
914 for (const connectorId of connectorIds) {
915 let response: OCPP16ChangeAvailabilityResponse =
916 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
917 const connectorStatus = chargingStation.getConnectorStatus(connectorId)!;
918 if (connectorStatus?.transactionStarted === true) {
919 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
920 }
921 connectorStatus.availability = availabilityType;
922 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
923 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
924 chargingStation,
925 connectorId,
926 chargePointStatus,
927 );
928 }
929 responses.push(response);
366f75f6 930 }
3b0ed034 931 if (responses.includes(OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED)) {
225e32b0 932 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
366f75f6 933 }
225e32b0 934 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
366f75f6
JB
935 };
936
ed3d2808
JB
937 public static setChargingProfile(
938 chargingStation: ChargingStation,
939 connectorId: number,
5edd8ba0 940 cp: OCPP16ChargingProfile,
ed3d2808 941 ): void {
9bf0ef23 942 if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808 943 logger.error(
5edd8ba0 944 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
ed3d2808 945 );
e1d9a0f4 946 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808 947 }
72092cfc
JB
948 if (
949 Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
950 ) {
ed3d2808 951 logger.error(
bbb55ee4 952 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type deferred initialization`,
ed3d2808 953 );
e1d9a0f4 954 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808
JB
955 }
956 let cpReplaced = false;
9bf0ef23 957 if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808
JB
958 chargingStation
959 .getConnectorStatus(connectorId)
72092cfc 960 ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
ed3d2808
JB
961 if (
962 chargingProfile.chargingProfileId === cp.chargingProfileId ||
963 (chargingProfile.stackLevel === cp.stackLevel &&
964 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
965 ) {
e1d9a0f4 966 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp;
ed3d2808
JB
967 cpReplaced = true;
968 }
969 });
970 }
72092cfc 971 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
ed3d2808
JB
972 }
973
73d87be1
JB
974 public static clearChargingProfiles = (
975 chargingStation: ChargingStation,
976 commandPayload: ClearChargingProfileRequest,
977 chargingProfiles: OCPP16ChargingProfile[] | undefined,
978 ): boolean => {
0d1f33ba 979 const { id, chargingProfilePurpose, stackLevel } = commandPayload;
73d87be1
JB
980 let clearedCP = false;
981 if (isNotEmptyArray(chargingProfiles)) {
982 chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
983 let clearCurrentCP = false;
0d1f33ba 984 if (chargingProfile.chargingProfileId === id) {
73d87be1
JB
985 clearCurrentCP = true;
986 }
0d1f33ba 987 if (!chargingProfilePurpose && chargingProfile.stackLevel === stackLevel) {
73d87be1
JB
988 clearCurrentCP = true;
989 }
0d1f33ba 990 if (!stackLevel && chargingProfile.chargingProfilePurpose === chargingProfilePurpose) {
73d87be1
JB
991 clearCurrentCP = true;
992 }
993 if (
0d1f33ba
JB
994 chargingProfile.stackLevel === stackLevel &&
995 chargingProfile.chargingProfilePurpose === chargingProfilePurpose
73d87be1
JB
996 ) {
997 clearCurrentCP = true;
998 }
999 if (clearCurrentCP) {
1000 chargingProfiles.splice(index, 1);
1001 logger.debug(
1002 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
1003 chargingProfile,
1004 );
1005 clearedCP = true;
1006 }
1007 });
1008 }
1009 return clearedCP;
1010 };
1011
ef9e3b33 1012 public static composeChargingSchedules = (
4abf6441
JB
1013 chargingScheduleHigher: OCPP16ChargingSchedule | undefined,
1014 chargingScheduleLower: OCPP16ChargingSchedule | undefined,
d632062f 1015 compositeInterval: Interval,
ef9e3b33 1016 ): OCPP16ChargingSchedule | undefined => {
4abf6441 1017 if (!chargingScheduleHigher && !chargingScheduleLower) {
ef9e3b33
JB
1018 return undefined;
1019 }
4abf6441 1020 if (chargingScheduleHigher && !chargingScheduleLower) {
d632062f 1021 return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval);
ef9e3b33 1022 }
4abf6441 1023 if (!chargingScheduleHigher && chargingScheduleLower) {
d632062f 1024 return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval);
ef9e3b33 1025 }
4abf6441 1026 const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined =
d632062f 1027 OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval);
4abf6441 1028 const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined =
d632062f 1029 OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval);
4abf6441
JB
1030 const compositeChargingScheduleHigherInterval: Interval = {
1031 start: compositeChargingScheduleHigher!.startSchedule!,
ef9e3b33 1032 end: addSeconds(
4abf6441
JB
1033 compositeChargingScheduleHigher!.startSchedule!,
1034 compositeChargingScheduleHigher!.duration!,
ef9e3b33
JB
1035 ),
1036 };
4abf6441
JB
1037 const compositeChargingScheduleLowerInterval: Interval = {
1038 start: compositeChargingScheduleLower!.startSchedule!,
ef9e3b33 1039 end: addSeconds(
4abf6441
JB
1040 compositeChargingScheduleLower!.startSchedule!,
1041 compositeChargingScheduleLower!.duration!,
ef9e3b33
JB
1042 ),
1043 };
4abf6441
JB
1044 const higherFirst = isBefore(
1045 compositeChargingScheduleHigherInterval.start,
1046 compositeChargingScheduleLowerInterval.start,
1047 );
ef9e3b33
JB
1048 if (
1049 !areIntervalsOverlapping(
4abf6441
JB
1050 compositeChargingScheduleHigherInterval,
1051 compositeChargingScheduleLowerInterval,
ef9e3b33
JB
1052 )
1053 ) {
1054 return {
4abf6441
JB
1055 ...compositeChargingScheduleLower,
1056 ...compositeChargingScheduleHigher!,
1057 startSchedule: higherFirst
1058 ? (compositeChargingScheduleHigherInterval.start as Date)
1059 : (compositeChargingScheduleLowerInterval.start as Date),
1060 duration: higherFirst
1061 ? differenceInSeconds(
1062 compositeChargingScheduleLowerInterval.end,
1063 compositeChargingScheduleHigherInterval.start,
1064 )
1065 : differenceInSeconds(
1066 compositeChargingScheduleHigherInterval.end,
1067 compositeChargingScheduleLowerInterval.start,
1068 ),
1069 chargingSchedulePeriod: [
1070 ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => {
1071 return {
1072 ...schedulePeriod,
1073 startPeriod: higherFirst
1074 ? 0
1075 : schedulePeriod.startPeriod +
1076 differenceInSeconds(
1077 compositeChargingScheduleHigherInterval.start,
1078 compositeChargingScheduleLowerInterval.start,
1079 ),
1080 };
1081 }),
1082 ...compositeChargingScheduleLower!.chargingSchedulePeriod.map((schedulePeriod) => {
1083 return {
1084 ...schedulePeriod,
1085 startPeriod: higherFirst
1086 ? schedulePeriod.startPeriod +
1087 differenceInSeconds(
1088 compositeChargingScheduleLowerInterval.start,
1089 compositeChargingScheduleHigherInterval.start,
1090 )
1091 : 0,
1092 };
1093 }),
1094 ].sort((a, b) => a.startPeriod - b.startPeriod),
ef9e3b33
JB
1095 };
1096 }
4abf6441
JB
1097 return {
1098 ...compositeChargingScheduleLower,
1099 ...compositeChargingScheduleHigher!,
1100 startSchedule: higherFirst
1101 ? (compositeChargingScheduleHigherInterval.start as Date)
1102 : (compositeChargingScheduleLowerInterval.start as Date),
1103 duration: higherFirst
1104 ? differenceInSeconds(
1105 compositeChargingScheduleLowerInterval.end,
1106 compositeChargingScheduleHigherInterval.start,
1107 )
1108 : differenceInSeconds(
1109 compositeChargingScheduleHigherInterval.end,
1110 compositeChargingScheduleLowerInterval.start,
1111 ),
1112 chargingSchedulePeriod: [
1113 ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => {
1114 return {
1115 ...schedulePeriod,
1116 startPeriod: higherFirst
1117 ? 0
1118 : schedulePeriod.startPeriod +
1119 differenceInSeconds(
1120 compositeChargingScheduleHigherInterval.start,
1121 compositeChargingScheduleLowerInterval.start,
1122 ),
1123 };
1124 }),
1125 ...compositeChargingScheduleLower!.chargingSchedulePeriod
c4ab56ba 1126 .filter((schedulePeriod, index) => {
4abf6441
JB
1127 if (
1128 higherFirst &&
1129 isWithinInterval(
1130 addSeconds(
1131 compositeChargingScheduleLowerInterval.start,
1132 schedulePeriod.startPeriod,
1133 ),
1134 {
1135 start: compositeChargingScheduleLowerInterval.start,
1136 end: compositeChargingScheduleHigherInterval.end,
1137 },
1138 )
1139 ) {
1140 return false;
1141 }
c4ab56ba
JB
1142 if (
1143 higherFirst &&
1144 index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 &&
1145 !isWithinInterval(
1146 addSeconds(
1147 compositeChargingScheduleLowerInterval.start,
1148 schedulePeriod.startPeriod,
1149 ),
1150 {
1151 start: compositeChargingScheduleLowerInterval.start,
1152 end: compositeChargingScheduleHigherInterval.end,
1153 },
1154 ) &&
1155 isWithinInterval(
1156 addSeconds(
1157 compositeChargingScheduleLowerInterval.start,
1158 compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod,
1159 ),
1160 {
1161 start: compositeChargingScheduleLowerInterval.start,
1162 end: compositeChargingScheduleHigherInterval.end,
1163 },
1164 )
1165 ) {
c4ab56ba
JB
1166 return false;
1167 }
4abf6441
JB
1168 if (
1169 !higherFirst &&
1170 isWithinInterval(
1171 addSeconds(
1172 compositeChargingScheduleLowerInterval.start,
1173 schedulePeriod.startPeriod,
1174 ),
1175 {
1176 start: compositeChargingScheduleHigherInterval.start,
1177 end: compositeChargingScheduleLowerInterval.end,
1178 },
1179 )
1180 ) {
1181 return false;
1182 }
1183 return true;
1184 })
0e14e1d4
JB
1185 .map((schedulePeriod, index) => {
1186 if (index === 0 && schedulePeriod.startPeriod !== 0) {
1187 schedulePeriod.startPeriod = 0;
1188 }
4abf6441
JB
1189 return {
1190 ...schedulePeriod,
1191 startPeriod: higherFirst
1192 ? schedulePeriod.startPeriod +
1193 differenceInSeconds(
1194 compositeChargingScheduleLowerInterval.start,
1195 compositeChargingScheduleHigherInterval.start,
1196 )
1197 : 0,
1198 };
1199 }),
1200 ].sort((a, b) => a.startPeriod - b.startPeriod),
1201 };
ef9e3b33
JB
1202 };
1203
90aceaf6
JB
1204 public static hasReservation = (
1205 chargingStation: ChargingStation,
1206 connectorId: number,
1207 idTag: string,
1208 ): boolean => {
1209 const connectorReservation = chargingStation.getReservationBy('connectorId', connectorId);
1210 const chargingStationReservation = chargingStation.getReservationBy('connectorId', 0);
1211 if (
1212 (chargingStation.getConnectorStatus(connectorId)?.status ===
1213 OCPP16ChargePointStatus.Reserved &&
1214 connectorReservation &&
56563a3c 1215 !hasReservationExpired(connectorReservation) &&
90aceaf6 1216 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
56563a3c 1217 connectorReservation?.idTag === idTag) ||
90aceaf6
JB
1218 (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved &&
1219 chargingStationReservation &&
56563a3c
JB
1220 !hasReservationExpired(chargingStationReservation) &&
1221 chargingStationReservation?.idTag === idTag)
90aceaf6 1222 ) {
88499f52
JB
1223 logger.debug(
1224 `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
1225 connectorReservation ?? chargingStationReservation,
1226 );
56563a3c 1227 return true;
90aceaf6 1228 }
56563a3c 1229 return false;
90aceaf6
JB
1230 };
1231
1b271a54
JB
1232 public static parseJsonSchemaFile<T extends JsonType>(
1233 relativePath: string,
1234 moduleName?: string,
5edd8ba0 1235 methodName?: string,
1b271a54 1236 ): JSONSchemaType<T> {
7164966d 1237 return super.parseJsonSchemaFile<T>(
51022aa0 1238 relativePath,
1b271a54
JB
1239 OCPPVersion.VERSION_16,
1240 moduleName,
5edd8ba0 1241 methodName,
7164966d 1242 );
130783a7
JB
1243 }
1244
ef9e3b33
JB
1245 private static composeChargingSchedule = (
1246 chargingSchedule: OCPP16ChargingSchedule,
d632062f 1247 compositeInterval: Interval,
ef9e3b33
JB
1248 ): OCPP16ChargingSchedule | undefined => {
1249 const chargingScheduleInterval: Interval = {
1250 start: chargingSchedule.startSchedule!,
1251 end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
1252 };
d632062f 1253 if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
ef9e3b33 1254 chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod);
d632062f 1255 if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
ef9e3b33
JB
1256 return {
1257 ...chargingSchedule,
d632062f
JB
1258 startSchedule: compositeInterval.start as Date,
1259 duration: differenceInSeconds(
1260 chargingScheduleInterval.end,
1261 compositeInterval.start as Date,
1262 ),
0e14e1d4
JB
1263 chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod
1264 .filter((schedulePeriod, index) => {
ef9e3b33
JB
1265 if (
1266 isWithinInterval(
1267 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
d632062f 1268 compositeInterval,
ef9e3b33
JB
1269 )
1270 ) {
1271 return true;
1272 }
1273 if (
1274 index < chargingSchedule.chargingSchedulePeriod.length - 1 &&
1275 !isWithinInterval(
1276 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
d632062f 1277 compositeInterval,
ef9e3b33
JB
1278 ) &&
1279 isWithinInterval(
1280 addSeconds(
1281 chargingScheduleInterval.start,
1282 chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod,
1283 ),
d632062f 1284 compositeInterval,
ef9e3b33
JB
1285 )
1286 ) {
ef9e3b33
JB
1287 return true;
1288 }
1289 return false;
0e14e1d4
JB
1290 })
1291 .map((schedulePeriod, index) => {
1292 if (index === 0 && schedulePeriod.startPeriod !== 0) {
1293 schedulePeriod.startPeriod = 0;
1294 }
1295 return schedulePeriod;
1296 }),
ef9e3b33
JB
1297 };
1298 }
d632062f 1299 if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
ef9e3b33
JB
1300 return {
1301 ...chargingSchedule,
d632062f
JB
1302 duration: differenceInSeconds(
1303 compositeInterval.end as Date,
1304 chargingScheduleInterval.start,
1305 ),
ef9e3b33
JB
1306 chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter((schedulePeriod) =>
1307 isWithinInterval(
1308 addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!,
d632062f 1309 compositeInterval,
ef9e3b33
JB
1310 ),
1311 ),
1312 };
1313 }
1314 return chargingSchedule;
1315 }
1316 };
1317
7bc31f9c
JB
1318 private static buildSampledValue(
1319 sampledValueTemplate: SampledValueTemplate,
1320 value: number,
1321 context?: MeterValueContext,
5edd8ba0 1322 phase?: OCPP16MeterValuePhase,
7bc31f9c 1323 ): OCPP16SampledValue {
4ed03b6e
JB
1324 const sampledValueValue = value ?? sampledValueTemplate?.value;
1325 const sampledValueContext = context ?? sampledValueTemplate?.context;
7bc31f9c
JB
1326 const sampledValueLocation =
1327 sampledValueTemplate?.location ??
e1d9a0f4 1328 OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
4ed03b6e 1329 const sampledValuePhase = phase ?? sampledValueTemplate?.phase;
7bc31f9c 1330 return {
9bf0ef23 1331 ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
7bc31f9c
JB
1332 unit: sampledValueTemplate.unit,
1333 }),
9bf0ef23
JB
1334 ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
1335 ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
7bc31f9c
JB
1336 measurand: sampledValueTemplate.measurand,
1337 }),
9bf0ef23
JB
1338 ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
1339 ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
1340 ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
e1d9a0f4 1341 } as OCPP16SampledValue;
7bc31f9c
JB
1342 }
1343
1344 private static checkMeasurandPowerDivider(
1345 chargingStation: ChargingStation,
5edd8ba0 1346 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c 1347 ): void {
9bf0ef23 1348 if (isUndefined(chargingStation.powerDivider)) {
fc040c43 1349 const errMsg = `MeterValues measurand ${
7bc31f9c
JB
1350 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
1351 }: powerDivider is undefined`;
fc040c43 1352 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c 1353 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
fa7bccf4 1354 } else if (chargingStation?.powerDivider <= 0) {
fc040c43 1355 const errMsg = `MeterValues measurand ${
7bc31f9c 1356 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
fa7bccf4 1357 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
fc040c43 1358 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c
JB
1359 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
1360 }
1361 }
1362
1363 private static getMeasurandDefaultLocation(
5edd8ba0 1364 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
1365 ): MeterValueLocation | undefined {
1366 switch (measurandType) {
1367 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1368 return MeterValueLocation.EV;
1369 }
1370 }
1371
3b0ed034
JB
1372 // private static getMeasurandDefaultUnit(
1373 // measurandType: OCPP16MeterValueMeasurand,
1374 // ): MeterValueUnit | undefined {
1375 // switch (measurandType) {
1376 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
1377 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1378 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1379 // return MeterValueUnit.AMP;
1380 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1381 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1382 // return MeterValueUnit.WATT_HOUR;
1383 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1384 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1385 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1386 // return MeterValueUnit.WATT;
1387 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1388 // return MeterValueUnit.PERCENT;
1389 // case OCPP16MeterValueMeasurand.VOLTAGE:
1390 // return MeterValueUnit.VOLT;
1391 // }
1392 // }
6ed92bc1 1393}