fix: fix authorize response handling
[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
JB
3import type { JSONSchemaType } from 'ajv';
4
ae725be3 5import { type ChargingStation, hasFeatureProfile } from '../../../charging-station';
268a74bb 6import { OCPPError } from '../../../exception';
e7aeea18 7import {
73d87be1 8 type ClearChargingProfileRequest,
268a74bb
JB
9 CurrentType,
10 ErrorType,
11 type JsonType,
12 type MeasurandPerPhaseSampledValueTemplates,
13 type MeasurandValues,
e7aeea18
JB
14 MeterValueContext,
15 MeterValueLocation,
16 MeterValueUnit,
268a74bb
JB
17 type OCPP16ChargingProfile,
18 type OCPP16IncomingRequestCommand,
27782dbc 19 type OCPP16MeterValue,
e7aeea18
JB
20 OCPP16MeterValueMeasurand,
21 OCPP16MeterValuePhase,
370ae4ee 22 OCPP16RequestCommand,
268a74bb
JB
23 type OCPP16SampledValue,
24 OCPP16StandardParametersKey,
25 type OCPP16SupportedFeatureProfiles,
26 OCPPVersion,
27 type SampledValueTemplate,
28 Voltage,
29} from '../../../types';
9bf0ef23
JB
30import {
31 ACElectricUtils,
32 Constants,
33 DCElectricUtils,
34 convertToFloat,
35 convertToInt,
36 getRandomFloatFluctuatedRounded,
37 getRandomFloatRounded,
38 getRandomInteger,
39 isNotEmptyArray,
9bf0ef23
JB
40 isNullOrUndefined,
41 isUndefined,
42 logger,
43 roundTo,
44} from '../../../utils';
4c3c0d59 45import { OCPPServiceUtils } from '../OCPPServiceUtils';
6ed92bc1 46
7bc31f9c 47export class OCPP16ServiceUtils extends OCPPServiceUtils {
370ae4ee
JB
48 public static checkFeatureProfile(
49 chargingStation: ChargingStation,
50 featureProfile: OCPP16SupportedFeatureProfiles,
5edd8ba0 51 command: OCPP16RequestCommand | OCPP16IncomingRequestCommand,
370ae4ee 52 ): boolean {
d8093be1 53 if (!hasFeatureProfile(chargingStation, featureProfile)) {
370ae4ee
JB
54 logger.warn(
55 `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
56 OCPP16StandardParametersKey.SupportedFeatureProfiles
5edd8ba0 57 } in configuration`,
370ae4ee
JB
58 );
59 return false;
60 }
61 return true;
62 }
63
78085c42
JB
64 public static buildMeterValue(
65 chargingStation: ChargingStation,
66 connectorId: number,
67 transactionId: number,
68 interval: number,
5edd8ba0 69 debug = false,
78085c42
JB
70 ): OCPP16MeterValue {
71 const meterValue: OCPP16MeterValue = {
c38f0ced 72 timestamp: new Date(),
78085c42
JB
73 sampledValue: [],
74 };
75 const connector = chargingStation.getConnectorStatus(connectorId);
76 // SoC measurand
ed3d2808 77 const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 78 chargingStation,
78085c42 79 connectorId,
5edd8ba0 80 OCPP16MeterValueMeasurand.STATE_OF_CHARGE,
78085c42
JB
81 );
82 if (socSampledValueTemplate) {
860ef183
JB
83 const socMaximumValue = 100;
84 const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0;
78085c42 85 const socSampledValueTemplateValue = socSampledValueTemplate.value
9bf0ef23 86 ? getRandomFloatFluctuatedRounded(
78085c42 87 parseInt(socSampledValueTemplate.value),
5edd8ba0 88 socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 89 )
9bf0ef23 90 : getRandomInteger(socMaximumValue, socMinimumValue);
78085c42 91 meterValue.sampledValue.push(
5edd8ba0 92 OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue),
78085c42
JB
93 );
94 const sampledValuesIndex = meterValue.sampledValue.length - 1;
860ef183 95 if (
9bf0ef23
JB
96 convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue ||
97 convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue ||
860ef183
JB
98 debug
99 ) {
78085c42
JB
100 logger.error(
101 `${chargingStation.logPrefix()} MeterValues measurand ${
102 meterValue.sampledValue[sampledValuesIndex].measurand ??
103 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5edd8ba0 104 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
78085c42 105 meterValue.sampledValue[sampledValuesIndex].value
5edd8ba0 106 }/${socMaximumValue}}`,
78085c42
JB
107 );
108 }
109 }
110 // Voltage measurand
ed3d2808 111 const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 112 chargingStation,
78085c42 113 connectorId,
5edd8ba0 114 OCPP16MeterValueMeasurand.VOLTAGE,
78085c42
JB
115 );
116 if (voltageSampledValueTemplate) {
117 const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value
118 ? parseInt(voltageSampledValueTemplate.value)
119 : chargingStation.getVoltageOut();
120 const fluctuationPercent =
121 voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT;
9bf0ef23 122 const voltageMeasurandValue = getRandomFloatFluctuatedRounded(
78085c42 123 voltageSampledValueTemplateValue,
5edd8ba0 124 fluctuationPercent,
78085c42
JB
125 );
126 if (
127 chargingStation.getNumberOfPhases() !== 3 ||
128 (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues())
129 ) {
130 meterValue.sampledValue.push(
5edd8ba0 131 OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue),
78085c42
JB
132 );
133 }
134 for (
135 let phase = 1;
136 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
137 phase++
138 ) {
139 const phaseLineToNeutralValue = `L${phase}-N`;
140 const voltagePhaseLineToNeutralSampledValueTemplate =
ed3d2808 141 OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 142 chargingStation,
78085c42
JB
143 connectorId,
144 OCPP16MeterValueMeasurand.VOLTAGE,
5edd8ba0 145 phaseLineToNeutralValue as OCPP16MeterValuePhase,
78085c42 146 );
e1d9a0f4 147 let voltagePhaseLineToNeutralMeasurandValue: number | undefined;
78085c42
JB
148 if (voltagePhaseLineToNeutralSampledValueTemplate) {
149 const voltagePhaseLineToNeutralSampledValueTemplateValue =
150 voltagePhaseLineToNeutralSampledValueTemplate.value
151 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
152 : chargingStation.getVoltageOut();
153 const fluctuationPhaseToNeutralPercent =
154 voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
155 Constants.DEFAULT_FLUCTUATION_PERCENT;
9bf0ef23 156 voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded(
78085c42 157 voltagePhaseLineToNeutralSampledValueTemplateValue,
5edd8ba0 158 fluctuationPhaseToNeutralPercent,
78085c42
JB
159 );
160 }
161 meterValue.sampledValue.push(
162 OCPP16ServiceUtils.buildSampledValue(
163 voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
164 voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
72092cfc 165 undefined,
5edd8ba0
JB
166 phaseLineToNeutralValue as OCPP16MeterValuePhase,
167 ),
78085c42
JB
168 );
169 if (chargingStation.getPhaseLineToLineVoltageMeterValues()) {
170 const phaseLineToLineValue = `L${phase}-L${
171 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
172 ? (phase + 1) % chargingStation.getNumberOfPhases()
173 : chargingStation.getNumberOfPhases()
174 }`;
175 const voltagePhaseLineToLineSampledValueTemplate =
ed3d2808 176 OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 177 chargingStation,
78085c42
JB
178 connectorId,
179 OCPP16MeterValueMeasurand.VOLTAGE,
5edd8ba0 180 phaseLineToLineValue as OCPP16MeterValuePhase,
78085c42 181 );
e1d9a0f4 182 let voltagePhaseLineToLineMeasurandValue: number | undefined;
78085c42
JB
183 if (voltagePhaseLineToLineSampledValueTemplate) {
184 const voltagePhaseLineToLineSampledValueTemplateValue =
185 voltagePhaseLineToLineSampledValueTemplate.value
186 ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
187 : Voltage.VOLTAGE_400;
188 const fluctuationPhaseLineToLinePercent =
189 voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
190 Constants.DEFAULT_FLUCTUATION_PERCENT;
9bf0ef23 191 voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
78085c42 192 voltagePhaseLineToLineSampledValueTemplateValue,
5edd8ba0 193 fluctuationPhaseLineToLinePercent,
78085c42
JB
194 );
195 }
9bf0ef23 196 const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded(
78085c42 197 Voltage.VOLTAGE_400,
5edd8ba0 198 fluctuationPercent,
78085c42
JB
199 );
200 meterValue.sampledValue.push(
201 OCPP16ServiceUtils.buildSampledValue(
202 voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
203 voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
72092cfc 204 undefined,
5edd8ba0
JB
205 phaseLineToLineValue as OCPP16MeterValuePhase,
206 ),
78085c42
JB
207 );
208 }
209 }
210 }
211 // Power.Active.Import measurand
ed3d2808 212 const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 213 chargingStation,
78085c42 214 connectorId,
5edd8ba0 215 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
78085c42 216 );
abe9e9dd 217 let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
78085c42
JB
218 if (chargingStation.getNumberOfPhases() === 3) {
219 powerPerPhaseSampledValueTemplates = {
ed3d2808 220 L1: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 221 chargingStation,
78085c42
JB
222 connectorId,
223 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
5edd8ba0 224 OCPP16MeterValuePhase.L1_N,
78085c42 225 ),
ed3d2808 226 L2: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 227 chargingStation,
78085c42
JB
228 connectorId,
229 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
5edd8ba0 230 OCPP16MeterValuePhase.L2_N,
78085c42 231 ),
ed3d2808 232 L3: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 233 chargingStation,
78085c42
JB
234 connectorId,
235 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
5edd8ba0 236 OCPP16MeterValuePhase.L3_N,
78085c42
JB
237 ),
238 };
239 }
240 if (powerSampledValueTemplate) {
241 OCPP16ServiceUtils.checkMeasurandPowerDivider(
242 chargingStation,
e1d9a0f4 243 powerSampledValueTemplate.measurand!,
78085c42 244 );
fc040c43 245 const errMsg = `MeterValues measurand ${
78085c42
JB
246 powerSampledValueTemplate.measurand ??
247 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
248 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
2484ac1e 249 chargingStation.templateFile
78085c42
JB
250 }, cannot calculate ${
251 powerSampledValueTemplate.measurand ??
252 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
253 } measurand value`;
e1d9a0f4 254 const powerMeasurandValues: MeasurandValues = {} as MeasurandValues;
78085c42 255 const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
1b6498ba
JB
256 const connectorMaximumAvailablePower =
257 chargingStation.getConnectorMaximumAvailablePower(connectorId);
258 const connectorMaximumPower = Math.round(connectorMaximumAvailablePower);
ad8537a7 259 const connectorMaximumPowerPerPhase = Math.round(
5edd8ba0 260 connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(),
78085c42 261 );
e1d9a0f4 262 const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue!) ?? 0;
860ef183 263 const connectorMinimumPowerPerPhase = Math.round(
5edd8ba0 264 connectorMinimumPower / chargingStation.getNumberOfPhases(),
860ef183 265 );
78085c42
JB
266 switch (chargingStation.getCurrentOutType()) {
267 case CurrentType.AC:
268 if (chargingStation.getNumberOfPhases() === 3) {
269 const defaultFluctuatedPowerPerPhase =
270 powerSampledValueTemplate.value &&
9bf0ef23 271 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
272 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
273 powerSampledValueTemplate.value,
274 connectorMaximumPower / unitDivider,
5edd8ba0 275 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 276 ) / chargingStation.getNumberOfPhases(),
78085c42 277 powerSampledValueTemplate.fluctuationPercent ??
5edd8ba0 278 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
279 );
280 const phase1FluctuatedValue =
e1d9a0f4 281 powerPerPhaseSampledValueTemplates.L1?.value &&
9bf0ef23 282 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
283 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
284 powerPerPhaseSampledValueTemplates.L1.value,
285 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 286 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 287 ),
78085c42 288 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
5edd8ba0 289 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
290 );
291 const phase2FluctuatedValue =
e1d9a0f4 292 powerPerPhaseSampledValueTemplates.L2?.value &&
9bf0ef23 293 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
294 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
295 powerPerPhaseSampledValueTemplates.L2.value,
296 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 297 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 298 ),
78085c42 299 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
5edd8ba0 300 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
301 );
302 const phase3FluctuatedValue =
e1d9a0f4 303 powerPerPhaseSampledValueTemplates.L3?.value &&
9bf0ef23 304 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
305 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
306 powerPerPhaseSampledValueTemplates.L3.value,
307 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 308 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 309 ),
78085c42 310 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
5edd8ba0 311 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
312 );
313 powerMeasurandValues.L1 =
e1d9a0f4
JB
314 (phase1FluctuatedValue as number) ??
315 (defaultFluctuatedPowerPerPhase as number) ??
9bf0ef23 316 getRandomFloatRounded(
860ef183 317 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 318 connectorMinimumPowerPerPhase / unitDivider,
860ef183 319 );
78085c42 320 powerMeasurandValues.L2 =
e1d9a0f4
JB
321 (phase2FluctuatedValue as number) ??
322 (defaultFluctuatedPowerPerPhase as number) ??
9bf0ef23 323 getRandomFloatRounded(
860ef183 324 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 325 connectorMinimumPowerPerPhase / unitDivider,
860ef183 326 );
78085c42 327 powerMeasurandValues.L3 =
e1d9a0f4
JB
328 (phase3FluctuatedValue as number) ??
329 (defaultFluctuatedPowerPerPhase as number) ??
9bf0ef23 330 getRandomFloatRounded(
860ef183 331 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 332 connectorMinimumPowerPerPhase / unitDivider,
860ef183 333 );
78085c42
JB
334 } else {
335 powerMeasurandValues.L1 = powerSampledValueTemplate.value
9bf0ef23 336 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
337 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
338 powerSampledValueTemplate.value,
339 connectorMaximumPower / unitDivider,
5edd8ba0 340 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 341 ),
78085c42 342 powerSampledValueTemplate.fluctuationPercent ??
5edd8ba0 343 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 344 )
9bf0ef23 345 : getRandomFloatRounded(
860ef183 346 connectorMaximumPower / unitDivider,
5edd8ba0 347 connectorMinimumPower / unitDivider,
860ef183 348 );
78085c42
JB
349 powerMeasurandValues.L2 = 0;
350 powerMeasurandValues.L3 = 0;
351 }
9bf0ef23 352 powerMeasurandValues.allPhases = roundTo(
78085c42 353 powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
5edd8ba0 354 2,
78085c42
JB
355 );
356 break;
357 case CurrentType.DC:
358 powerMeasurandValues.allPhases = powerSampledValueTemplate.value
9bf0ef23 359 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
360 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
361 powerSampledValueTemplate.value,
362 connectorMaximumPower / unitDivider,
5edd8ba0 363 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 364 ),
78085c42 365 powerSampledValueTemplate.fluctuationPercent ??
5edd8ba0 366 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 367 )
9bf0ef23 368 : getRandomFloatRounded(
860ef183 369 connectorMaximumPower / unitDivider,
5edd8ba0 370 connectorMinimumPower / unitDivider,
860ef183 371 );
78085c42
JB
372 break;
373 default:
fc040c43 374 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
78085c42
JB
375 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
376 }
377 meterValue.sampledValue.push(
378 OCPP16ServiceUtils.buildSampledValue(
379 powerSampledValueTemplate,
5edd8ba0
JB
380 powerMeasurandValues.allPhases,
381 ),
78085c42
JB
382 );
383 const sampledValuesIndex = meterValue.sampledValue.length - 1;
9bf0ef23
JB
384 const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2);
385 const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2);
78085c42 386 if (
9bf0ef23 387 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
ad8537a7 388 connectorMaximumPowerRounded ||
9bf0ef23 389 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
860ef183 390 connectorMinimumPowerRounded ||
78085c42
JB
391 debug
392 ) {
393 logger.error(
394 `${chargingStation.logPrefix()} MeterValues measurand ${
395 meterValue.sampledValue[sampledValuesIndex].measurand ??
396 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5edd8ba0 397 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
78085c42 398 meterValue.sampledValue[sampledValuesIndex].value
5edd8ba0 399 }/${connectorMaximumPowerRounded}`,
78085c42
JB
400 );
401 }
402 for (
403 let phase = 1;
404 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
405 phase++
406 ) {
407 const phaseValue = `L${phase}-N`;
408 meterValue.sampledValue.push(
409 OCPP16ServiceUtils.buildSampledValue(
a37fc6dc
JB
410 powerPerPhaseSampledValueTemplates[
411 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
412 ]! ?? powerSampledValueTemplate,
413 powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
72092cfc 414 undefined,
5edd8ba0
JB
415 phaseValue as OCPP16MeterValuePhase,
416 ),
78085c42
JB
417 );
418 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
9bf0ef23 419 const connectorMaximumPowerPerPhaseRounded = roundTo(
ad8537a7 420 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 421 2,
ad8537a7 422 );
9bf0ef23 423 const connectorMinimumPowerPerPhaseRounded = roundTo(
860ef183 424 connectorMinimumPowerPerPhase / unitDivider,
5edd8ba0 425 2,
860ef183 426 );
78085c42 427 if (
9bf0ef23 428 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
ad8537a7 429 connectorMaximumPowerPerPhaseRounded ||
9bf0ef23 430 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
860ef183 431 connectorMinimumPowerPerPhaseRounded ||
78085c42
JB
432 debug
433 ) {
434 logger.error(
435 `${chargingStation.logPrefix()} MeterValues measurand ${
436 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
437 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
438 }: phase ${
439 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
5edd8ba0 440 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
78085c42 441 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
5edd8ba0 442 }/${connectorMaximumPowerPerPhaseRounded}`,
78085c42
JB
443 );
444 }
445 }
446 }
447 // Current.Import measurand
ed3d2808 448 const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 449 chargingStation,
78085c42 450 connectorId,
5edd8ba0 451 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
78085c42 452 );
abe9e9dd 453 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
78085c42
JB
454 if (chargingStation.getNumberOfPhases() === 3) {
455 currentPerPhaseSampledValueTemplates = {
ed3d2808 456 L1: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 457 chargingStation,
78085c42
JB
458 connectorId,
459 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 460 OCPP16MeterValuePhase.L1,
78085c42 461 ),
ed3d2808 462 L2: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 463 chargingStation,
78085c42
JB
464 connectorId,
465 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 466 OCPP16MeterValuePhase.L2,
78085c42 467 ),
ed3d2808 468 L3: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 469 chargingStation,
78085c42
JB
470 connectorId,
471 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 472 OCPP16MeterValuePhase.L3,
78085c42
JB
473 ),
474 };
475 }
476 if (currentSampledValueTemplate) {
477 OCPP16ServiceUtils.checkMeasurandPowerDivider(
478 chargingStation,
e1d9a0f4 479 currentSampledValueTemplate.measurand!,
78085c42 480 );
fc040c43 481 const errMsg = `MeterValues measurand ${
78085c42
JB
482 currentSampledValueTemplate.measurand ??
483 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
484 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
2484ac1e 485 chargingStation.templateFile
78085c42
JB
486 }, cannot calculate ${
487 currentSampledValueTemplate.measurand ??
488 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
489 } measurand value`;
abe9e9dd 490 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
1b6498ba
JB
491 const connectorMaximumAvailablePower =
492 chargingStation.getConnectorMaximumAvailablePower(connectorId);
860ef183 493 const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
ad8537a7 494 let connectorMaximumAmperage: number;
78085c42
JB
495 switch (chargingStation.getCurrentOutType()) {
496 case CurrentType.AC:
ad8537a7 497 connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
78085c42 498 chargingStation.getNumberOfPhases(),
1b6498ba 499 connectorMaximumAvailablePower,
5edd8ba0 500 chargingStation.getVoltageOut(),
78085c42
JB
501 );
502 if (chargingStation.getNumberOfPhases() === 3) {
503 const defaultFluctuatedAmperagePerPhase =
504 currentSampledValueTemplate.value &&
9bf0ef23 505 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
506 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
507 currentSampledValueTemplate.value,
508 connectorMaximumAmperage,
5edd8ba0 509 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
7bc31f9c 510 ),
78085c42 511 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 512 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
513 );
514 const phase1FluctuatedValue =
e1d9a0f4 515 currentPerPhaseSampledValueTemplates.L1?.value &&
9bf0ef23 516 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
517 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
518 currentPerPhaseSampledValueTemplates.L1.value,
519 connectorMaximumAmperage,
5edd8ba0 520 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 521 ),
78085c42 522 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
5edd8ba0 523 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
524 );
525 const phase2FluctuatedValue =
e1d9a0f4 526 currentPerPhaseSampledValueTemplates.L2?.value &&
9bf0ef23 527 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
528 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
529 currentPerPhaseSampledValueTemplates.L2.value,
530 connectorMaximumAmperage,
5edd8ba0 531 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 532 ),
78085c42 533 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
5edd8ba0 534 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
535 );
536 const phase3FluctuatedValue =
e1d9a0f4 537 currentPerPhaseSampledValueTemplates.L3?.value &&
9bf0ef23 538 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
539 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
540 currentPerPhaseSampledValueTemplates.L3.value,
541 connectorMaximumAmperage,
5edd8ba0 542 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 543 ),
78085c42 544 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
5edd8ba0 545 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
546 );
547 currentMeasurandValues.L1 =
e1d9a0f4
JB
548 (phase1FluctuatedValue as number) ??
549 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 550 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42 551 currentMeasurandValues.L2 =
e1d9a0f4
JB
552 (phase2FluctuatedValue as number) ??
553 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 554 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42 555 currentMeasurandValues.L3 =
e1d9a0f4
JB
556 (phase3FluctuatedValue as number) ??
557 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 558 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42
JB
559 } else {
560 currentMeasurandValues.L1 = currentSampledValueTemplate.value
9bf0ef23 561 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
562 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
563 currentSampledValueTemplate.value,
564 connectorMaximumAmperage,
5edd8ba0 565 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
7bc31f9c 566 ),
78085c42 567 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 568 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 569 )
9bf0ef23 570 : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42
JB
571 currentMeasurandValues.L2 = 0;
572 currentMeasurandValues.L3 = 0;
573 }
9bf0ef23 574 currentMeasurandValues.allPhases = roundTo(
78085c42
JB
575 (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
576 chargingStation.getNumberOfPhases(),
5edd8ba0 577 2,
78085c42
JB
578 );
579 break;
580 case CurrentType.DC:
ad8537a7 581 connectorMaximumAmperage = DCElectricUtils.amperage(
1b6498ba 582 connectorMaximumAvailablePower,
5edd8ba0 583 chargingStation.getVoltageOut(),
78085c42
JB
584 );
585 currentMeasurandValues.allPhases = currentSampledValueTemplate.value
9bf0ef23 586 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
587 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
588 currentSampledValueTemplate.value,
589 connectorMaximumAmperage,
5edd8ba0 590 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
7bc31f9c 591 ),
78085c42 592 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 593 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 594 )
9bf0ef23 595 : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42
JB
596 break;
597 default:
fc040c43 598 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
78085c42
JB
599 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
600 }
601 meterValue.sampledValue.push(
602 OCPP16ServiceUtils.buildSampledValue(
603 currentSampledValueTemplate,
5edd8ba0
JB
604 currentMeasurandValues.allPhases,
605 ),
78085c42
JB
606 );
607 const sampledValuesIndex = meterValue.sampledValue.length - 1;
608 if (
9bf0ef23 609 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
ad8537a7 610 connectorMaximumAmperage ||
9bf0ef23 611 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
860ef183 612 connectorMinimumAmperage ||
78085c42
JB
613 debug
614 ) {
615 logger.error(
616 `${chargingStation.logPrefix()} MeterValues measurand ${
617 meterValue.sampledValue[sampledValuesIndex].measurand ??
618 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5edd8ba0 619 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
78085c42 620 meterValue.sampledValue[sampledValuesIndex].value
5edd8ba0 621 }/${connectorMaximumAmperage}`,
78085c42
JB
622 );
623 }
624 for (
625 let phase = 1;
626 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
627 phase++
628 ) {
629 const phaseValue = `L${phase}`;
630 meterValue.sampledValue.push(
631 OCPP16ServiceUtils.buildSampledValue(
a37fc6dc
JB
632 currentPerPhaseSampledValueTemplates[
633 phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
634 ]! ?? currentSampledValueTemplate,
635 currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
72092cfc 636 undefined,
5edd8ba0
JB
637 phaseValue as OCPP16MeterValuePhase,
638 ),
78085c42
JB
639 );
640 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
641 if (
9bf0ef23 642 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
ad8537a7 643 connectorMaximumAmperage ||
9bf0ef23 644 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
860ef183 645 connectorMinimumAmperage ||
78085c42
JB
646 debug
647 ) {
648 logger.error(
649 `${chargingStation.logPrefix()} MeterValues measurand ${
650 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
651 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
652 }: phase ${
653 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
5edd8ba0 654 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
78085c42 655 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
5edd8ba0 656 }/${connectorMaximumAmperage}`,
78085c42
JB
657 );
658 }
659 }
660 }
661 // Energy.Active.Import.Register measurand (default)
ed3d2808 662 const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 663 chargingStation,
5edd8ba0 664 connectorId,
492cf6ab 665 );
78085c42
JB
666 if (energySampledValueTemplate) {
667 OCPP16ServiceUtils.checkMeasurandPowerDivider(
668 chargingStation,
e1d9a0f4 669 energySampledValueTemplate.measurand!,
78085c42
JB
670 );
671 const unitDivider =
672 energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
1b6498ba
JB
673 const connectorMaximumAvailablePower =
674 chargingStation.getConnectorMaximumAvailablePower(connectorId);
9bf0ef23 675 const connectorMaximumEnergyRounded = roundTo(
1b6498ba 676 (connectorMaximumAvailablePower * interval) / (3600 * 1000),
5edd8ba0 677 2,
78085c42
JB
678 );
679 const energyValueRounded = energySampledValueTemplate.value
680 ? // Cumulate the fluctuated value around the static one
9bf0ef23 681 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
682 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
683 energySampledValueTemplate.value,
684 connectorMaximumEnergyRounded,
685 {
686 limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(),
687 unitMultiplier: unitDivider,
5edd8ba0 688 },
34464008 689 ),
5edd8ba0 690 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 691 )
9bf0ef23 692 : getRandomFloatRounded(connectorMaximumEnergyRounded);
78085c42 693 // Persist previous value on connector
e1d9a0f4
JB
694 if (connector) {
695 if (
696 isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
697 connector.energyActiveImportRegisterValue! >= 0 &&
698 isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
699 connector.transactionEnergyActiveImportRegisterValue! >= 0
700 ) {
701 connector.energyActiveImportRegisterValue! += energyValueRounded;
702 connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded;
703 } else {
704 connector.energyActiveImportRegisterValue = 0;
705 connector.transactionEnergyActiveImportRegisterValue = 0;
706 }
78085c42
JB
707 }
708 meterValue.sampledValue.push(
709 OCPP16ServiceUtils.buildSampledValue(
710 energySampledValueTemplate,
9bf0ef23 711 roundTo(
78085c42
JB
712 chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
713 unitDivider,
5edd8ba0
JB
714 2,
715 ),
716 ),
78085c42
JB
717 );
718 const sampledValuesIndex = meterValue.sampledValue.length - 1;
ad8537a7 719 if (energyValueRounded > connectorMaximumEnergyRounded || debug) {
78085c42
JB
720 logger.error(
721 `${chargingStation.logPrefix()} MeterValues measurand ${
722 meterValue.sampledValue[sampledValuesIndex].measurand ??
723 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
04c32a95 724 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`,
78085c42
JB
725 );
726 }
727 }
728 return meterValue;
729 }
730
e7aeea18
JB
731 public static buildTransactionBeginMeterValue(
732 chargingStation: ChargingStation,
733 connectorId: number,
5edd8ba0 734 meterStart: number,
e7aeea18 735 ): OCPP16MeterValue {
fd0c36fa 736 const meterValue: OCPP16MeterValue = {
c38f0ced 737 timestamp: new Date(),
fd0c36fa
JB
738 sampledValue: [],
739 };
9ccca265 740 // Energy.Active.Import.Register measurand (default)
ed3d2808 741 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 742 chargingStation,
5edd8ba0 743 connectorId,
492cf6ab 744 );
9ccca265 745 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
746 meterValue.sampledValue.push(
747 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 748 sampledValueTemplate!,
9bf0ef23 749 roundTo((meterStart ?? 0) / unitDivider, 4),
5edd8ba0
JB
750 MeterValueContext.TRANSACTION_BEGIN,
751 ),
e7aeea18 752 );
fd0c36fa
JB
753 return meterValue;
754 }
755
e7aeea18
JB
756 public static buildTransactionEndMeterValue(
757 chargingStation: ChargingStation,
758 connectorId: number,
5edd8ba0 759 meterStop: number,
e7aeea18 760 ): OCPP16MeterValue {
fd0c36fa 761 const meterValue: OCPP16MeterValue = {
c38f0ced 762 timestamp: new Date(),
fd0c36fa
JB
763 sampledValue: [],
764 };
9ccca265 765 // Energy.Active.Import.Register measurand (default)
ed3d2808 766 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 767 chargingStation,
5edd8ba0 768 connectorId,
492cf6ab 769 );
9ccca265 770 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
771 meterValue.sampledValue.push(
772 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 773 sampledValueTemplate!,
9bf0ef23 774 roundTo((meterStop ?? 0) / unitDivider, 4),
5edd8ba0
JB
775 MeterValueContext.TRANSACTION_END,
776 ),
e7aeea18 777 );
fd0c36fa
JB
778 return meterValue;
779 }
780
e7aeea18
JB
781 public static buildTransactionDataMeterValues(
782 transactionBeginMeterValue: OCPP16MeterValue,
5edd8ba0 783 transactionEndMeterValue: OCPP16MeterValue,
e7aeea18 784 ): OCPP16MeterValue[] {
fd0c36fa
JB
785 const meterValues: OCPP16MeterValue[] = [];
786 meterValues.push(transactionBeginMeterValue);
787 meterValues.push(transactionEndMeterValue);
788 return meterValues;
789 }
7bc31f9c 790
ed3d2808
JB
791 public static setChargingProfile(
792 chargingStation: ChargingStation,
793 connectorId: number,
5edd8ba0 794 cp: OCPP16ChargingProfile,
ed3d2808 795 ): void {
9bf0ef23 796 if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808 797 logger.error(
5edd8ba0 798 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
ed3d2808 799 );
e1d9a0f4 800 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808 801 }
72092cfc
JB
802 if (
803 Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
804 ) {
ed3d2808 805 logger.error(
5edd8ba0 806 `${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 initialization`,
ed3d2808 807 );
e1d9a0f4 808 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808
JB
809 }
810 let cpReplaced = false;
9bf0ef23 811 if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808
JB
812 chargingStation
813 .getConnectorStatus(connectorId)
72092cfc 814 ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
ed3d2808
JB
815 if (
816 chargingProfile.chargingProfileId === cp.chargingProfileId ||
817 (chargingProfile.stackLevel === cp.stackLevel &&
818 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
819 ) {
e1d9a0f4 820 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp;
ed3d2808
JB
821 cpReplaced = true;
822 }
823 });
824 }
72092cfc 825 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
ed3d2808
JB
826 }
827
73d87be1
JB
828 public static clearChargingProfiles = (
829 chargingStation: ChargingStation,
830 commandPayload: ClearChargingProfileRequest,
831 chargingProfiles: OCPP16ChargingProfile[] | undefined,
832 ): boolean => {
833 let clearedCP = false;
834 if (isNotEmptyArray(chargingProfiles)) {
835 chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
836 let clearCurrentCP = false;
837 if (chargingProfile.chargingProfileId === commandPayload.id) {
838 clearCurrentCP = true;
839 }
840 if (
841 !commandPayload.chargingProfilePurpose &&
842 chargingProfile.stackLevel === commandPayload.stackLevel
843 ) {
844 clearCurrentCP = true;
845 }
846 if (
847 !chargingProfile.stackLevel &&
848 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
849 ) {
850 clearCurrentCP = true;
851 }
852 if (
853 chargingProfile.stackLevel === commandPayload.stackLevel &&
854 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
855 ) {
856 clearCurrentCP = true;
857 }
858 if (clearCurrentCP) {
859 chargingProfiles.splice(index, 1);
860 logger.debug(
861 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
862 chargingProfile,
863 );
864 clearedCP = true;
865 }
866 });
867 }
868 return clearedCP;
869 };
870
1b271a54
JB
871 public static parseJsonSchemaFile<T extends JsonType>(
872 relativePath: string,
873 moduleName?: string,
5edd8ba0 874 methodName?: string,
1b271a54 875 ): JSONSchemaType<T> {
7164966d 876 return super.parseJsonSchemaFile<T>(
51022aa0 877 relativePath,
1b271a54
JB
878 OCPPVersion.VERSION_16,
879 moduleName,
5edd8ba0 880 methodName,
7164966d 881 );
130783a7
JB
882 }
883
7bc31f9c
JB
884 private static buildSampledValue(
885 sampledValueTemplate: SampledValueTemplate,
886 value: number,
887 context?: MeterValueContext,
5edd8ba0 888 phase?: OCPP16MeterValuePhase,
7bc31f9c
JB
889 ): OCPP16SampledValue {
890 const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
891 const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
892 const sampledValueLocation =
893 sampledValueTemplate?.location ??
e1d9a0f4 894 OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
7bc31f9c
JB
895 const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
896 return {
9bf0ef23 897 ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
7bc31f9c
JB
898 unit: sampledValueTemplate.unit,
899 }),
9bf0ef23
JB
900 ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
901 ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
7bc31f9c
JB
902 measurand: sampledValueTemplate.measurand,
903 }),
9bf0ef23
JB
904 ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
905 ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
906 ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
e1d9a0f4 907 } as OCPP16SampledValue;
7bc31f9c
JB
908 }
909
910 private static checkMeasurandPowerDivider(
911 chargingStation: ChargingStation,
5edd8ba0 912 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c 913 ): void {
9bf0ef23 914 if (isUndefined(chargingStation.powerDivider)) {
fc040c43 915 const errMsg = `MeterValues measurand ${
7bc31f9c
JB
916 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
917 }: powerDivider is undefined`;
fc040c43 918 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c 919 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
fa7bccf4 920 } else if (chargingStation?.powerDivider <= 0) {
fc040c43 921 const errMsg = `MeterValues measurand ${
7bc31f9c 922 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
fa7bccf4 923 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
fc040c43 924 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c
JB
925 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
926 }
927 }
928
929 private static getMeasurandDefaultLocation(
5edd8ba0 930 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
931 ): MeterValueLocation | undefined {
932 switch (measurandType) {
933 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
934 return MeterValueLocation.EV;
935 }
936 }
937
938 private static getMeasurandDefaultUnit(
5edd8ba0 939 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
940 ): MeterValueUnit | undefined {
941 switch (measurandType) {
942 case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
943 case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
944 case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
945 return MeterValueUnit.AMP;
946 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
947 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
948 return MeterValueUnit.WATT_HOUR;
949 case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
950 case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
951 case OCPP16MeterValueMeasurand.POWER_OFFERED:
952 return MeterValueUnit.WATT;
953 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
954 return MeterValueUnit.PERCENT;
955 case OCPP16MeterValueMeasurand.VOLTAGE:
956 return MeterValueUnit.VOLT;
957 }
958 }
6ed92bc1 959}