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