Better handling of missing serial number prefix in template
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
78085c42
JB
3import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
4import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
5import MeasurandPerPhaseSampledValueTemplates, {
6 SampledValueTemplate,
7} from '../../../types/MeasurandPerPhaseSampledValueTemplates';
e7aeea18
JB
8import {
9 MeterValueContext,
10 MeterValueLocation,
11 MeterValueUnit,
12 OCPP16MeterValue,
13 OCPP16MeterValueMeasurand,
14 OCPP16MeterValuePhase,
15 OCPP16SampledValue,
16} from '../../../types/ocpp/1.6/MeterValues';
6ed92bc1 17
73b9adec 18import type ChargingStation from '../../ChargingStation';
78085c42 19import Constants from '../../../utils/Constants';
14763b46 20import { ErrorType } from '../../../types/ocpp/ErrorType';
78085c42
JB
21import MeasurandValues from '../../../types/MeasurandValues';
22import { OCPP16RequestCommand } from '../../../types/ocpp/1.6/Requests';
e58068fd 23import OCPPError from '../../../exception/OCPPError';
6ed92bc1 24import Utils from '../../../utils/Utils';
9f2e3130 25import logger from '../../../utils/Logger';
6ed92bc1
JB
26
27export class OCPP16ServiceUtils {
e7aeea18
JB
28 public static checkMeasurandPowerDivider(
29 chargingStation: ChargingStation,
30 measurandType: OCPP16MeterValueMeasurand
31 ): void {
6ed92bc1 32 if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) {
e7aeea18
JB
33 const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
34 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
35 }: powerDivider is undefined`;
9f2e3130 36 logger.error(errMsg);
3d12fce4 37 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
fd0c36fa 38 } else if (chargingStation.stationInfo?.powerDivider <= 0) {
e7aeea18
JB
39 const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
40 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
41 }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
9f2e3130 42 logger.error(errMsg);
3d12fce4 43 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
6ed92bc1
JB
44 }
45 }
46
e7aeea18
JB
47 public static buildSampledValue(
48 sampledValueTemplate: SampledValueTemplate,
49 value: number,
50 context?: MeterValueContext,
51 phase?: OCPP16MeterValuePhase
52 ): OCPP16SampledValue {
53 const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
54 const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
55 const sampledValueLocation =
56 sampledValueTemplate?.location ??
57 OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null);
58 const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
6ed92bc1 59 return {
e7aeea18
JB
60 ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && {
61 unit: sampledValueTemplate.unit,
62 }),
63 ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
64 ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && {
65 measurand: sampledValueTemplate.measurand,
66 }),
67 ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
68 ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
69 ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
6ed92bc1
JB
70 };
71 }
72
e7aeea18
JB
73 public static getMeasurandDefaultUnit(
74 measurandType: OCPP16MeterValueMeasurand
75 ): MeterValueUnit | undefined {
6ed92bc1
JB
76 switch (measurandType) {
77 case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
78 case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
79 case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
80 return MeterValueUnit.AMP;
81 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
82 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
83 return MeterValueUnit.WATT_HOUR;
84 case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
85 case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
86 case OCPP16MeterValueMeasurand.POWER_OFFERED:
87 return MeterValueUnit.WATT;
88 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
89 return MeterValueUnit.PERCENT;
90 case OCPP16MeterValueMeasurand.VOLTAGE:
91 return MeterValueUnit.VOLT;
92 }
93 }
94
e7aeea18
JB
95 public static getMeasurandDefaultLocation(
96 measurandType: OCPP16MeterValueMeasurand
97 ): MeterValueLocation | undefined {
6ed92bc1
JB
98 switch (measurandType) {
99 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
100 return MeterValueLocation.EV;
101 }
102 }
fd0c36fa 103
78085c42
JB
104 public static buildMeterValue(
105 chargingStation: ChargingStation,
106 connectorId: number,
107 transactionId: number,
108 interval: number,
109 debug = false
110 ): OCPP16MeterValue {
111 const meterValue: OCPP16MeterValue = {
112 timestamp: new Date().toISOString(),
113 sampledValue: [],
114 };
115 const connector = chargingStation.getConnectorStatus(connectorId);
116 // SoC measurand
117 const socSampledValueTemplate = chargingStation.getSampledValueTemplate(
118 connectorId,
119 OCPP16MeterValueMeasurand.STATE_OF_CHARGE
120 );
121 if (socSampledValueTemplate) {
122 const socSampledValueTemplateValue = socSampledValueTemplate.value
123 ? Utils.getRandomFloatFluctuatedRounded(
124 parseInt(socSampledValueTemplate.value),
125 socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
126 )
127 : Utils.getRandomInteger(100);
128 meterValue.sampledValue.push(
129 OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue)
130 );
131 const sampledValuesIndex = meterValue.sampledValue.length - 1;
132 if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
133 logger.error(
134 `${chargingStation.logPrefix()} MeterValues measurand ${
135 meterValue.sampledValue[sampledValuesIndex].measurand ??
136 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
137 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
138 meterValue.sampledValue[sampledValuesIndex].value
139 }/100`
140 );
141 }
142 }
143 // Voltage measurand
144 const voltageSampledValueTemplate = chargingStation.getSampledValueTemplate(
145 connectorId,
146 OCPP16MeterValueMeasurand.VOLTAGE
147 );
148 if (voltageSampledValueTemplate) {
149 const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value
150 ? parseInt(voltageSampledValueTemplate.value)
151 : chargingStation.getVoltageOut();
152 const fluctuationPercent =
153 voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT;
154 const voltageMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
155 voltageSampledValueTemplateValue,
156 fluctuationPercent
157 );
158 if (
159 chargingStation.getNumberOfPhases() !== 3 ||
160 (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues())
161 ) {
162 meterValue.sampledValue.push(
163 OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue)
164 );
165 }
166 for (
167 let phase = 1;
168 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
169 phase++
170 ) {
171 const phaseLineToNeutralValue = `L${phase}-N`;
172 const voltagePhaseLineToNeutralSampledValueTemplate =
173 chargingStation.getSampledValueTemplate(
174 connectorId,
175 OCPP16MeterValueMeasurand.VOLTAGE,
176 phaseLineToNeutralValue as OCPP16MeterValuePhase
177 );
178 let voltagePhaseLineToNeutralMeasurandValue: number;
179 if (voltagePhaseLineToNeutralSampledValueTemplate) {
180 const voltagePhaseLineToNeutralSampledValueTemplateValue =
181 voltagePhaseLineToNeutralSampledValueTemplate.value
182 ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
183 : chargingStation.getVoltageOut();
184 const fluctuationPhaseToNeutralPercent =
185 voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ??
186 Constants.DEFAULT_FLUCTUATION_PERCENT;
187 voltagePhaseLineToNeutralMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
188 voltagePhaseLineToNeutralSampledValueTemplateValue,
189 fluctuationPhaseToNeutralPercent
190 );
191 }
192 meterValue.sampledValue.push(
193 OCPP16ServiceUtils.buildSampledValue(
194 voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate,
195 voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue,
196 null,
197 phaseLineToNeutralValue as OCPP16MeterValuePhase
198 )
199 );
200 if (chargingStation.getPhaseLineToLineVoltageMeterValues()) {
201 const phaseLineToLineValue = `L${phase}-L${
202 (phase + 1) % chargingStation.getNumberOfPhases() !== 0
203 ? (phase + 1) % chargingStation.getNumberOfPhases()
204 : chargingStation.getNumberOfPhases()
205 }`;
206 const voltagePhaseLineToLineSampledValueTemplate =
207 chargingStation.getSampledValueTemplate(
208 connectorId,
209 OCPP16MeterValueMeasurand.VOLTAGE,
210 phaseLineToLineValue as OCPP16MeterValuePhase
211 );
212 let voltagePhaseLineToLineMeasurandValue: number;
213 if (voltagePhaseLineToLineSampledValueTemplate) {
214 const voltagePhaseLineToLineSampledValueTemplateValue =
215 voltagePhaseLineToLineSampledValueTemplate.value
216 ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
217 : Voltage.VOLTAGE_400;
218 const fluctuationPhaseLineToLinePercent =
219 voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
220 Constants.DEFAULT_FLUCTUATION_PERCENT;
221 voltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
222 voltagePhaseLineToLineSampledValueTemplateValue,
223 fluctuationPhaseLineToLinePercent
224 );
225 }
226 const defaultVoltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded(
227 Voltage.VOLTAGE_400,
228 fluctuationPercent
229 );
230 meterValue.sampledValue.push(
231 OCPP16ServiceUtils.buildSampledValue(
232 voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate,
233 voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue,
234 null,
235 phaseLineToLineValue as OCPP16MeterValuePhase
236 )
237 );
238 }
239 }
240 }
241 // Power.Active.Import measurand
242 const powerSampledValueTemplate = chargingStation.getSampledValueTemplate(
243 connectorId,
244 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
245 );
246 let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
247 if (chargingStation.getNumberOfPhases() === 3) {
248 powerPerPhaseSampledValueTemplates = {
249 L1: chargingStation.getSampledValueTemplate(
250 connectorId,
251 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
252 OCPP16MeterValuePhase.L1_N
253 ),
254 L2: chargingStation.getSampledValueTemplate(
255 connectorId,
256 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
257 OCPP16MeterValuePhase.L2_N
258 ),
259 L3: chargingStation.getSampledValueTemplate(
260 connectorId,
261 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
262 OCPP16MeterValuePhase.L3_N
263 ),
264 };
265 }
266 if (powerSampledValueTemplate) {
267 OCPP16ServiceUtils.checkMeasurandPowerDivider(
268 chargingStation,
269 powerSampledValueTemplate.measurand
270 );
271 const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
272 powerSampledValueTemplate.measurand ??
273 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
274 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
275 chargingStation.stationTemplateFile
276 }, cannot calculate ${
277 powerSampledValueTemplate.measurand ??
278 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
279 } measurand value`;
280 const powerMeasurandValues = {} as MeasurandValues;
281 const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
282 const maxPower = Math.round(
283 chargingStation.stationInfo.maxPower / chargingStation.stationInfo.powerDivider
284 );
285 const maxPowerPerPhase = Math.round(
286 chargingStation.stationInfo.maxPower /
287 chargingStation.stationInfo.powerDivider /
288 chargingStation.getNumberOfPhases()
289 );
290 switch (chargingStation.getCurrentOutType()) {
291 case CurrentType.AC:
292 if (chargingStation.getNumberOfPhases() === 3) {
293 const defaultFluctuatedPowerPerPhase =
294 powerSampledValueTemplate.value &&
295 Utils.getRandomFloatFluctuatedRounded(
296 parseInt(powerSampledValueTemplate.value) / chargingStation.getNumberOfPhases(),
297 powerSampledValueTemplate.fluctuationPercent ??
298 Constants.DEFAULT_FLUCTUATION_PERCENT
299 );
300 const phase1FluctuatedValue =
301 powerPerPhaseSampledValueTemplates?.L1?.value &&
302 Utils.getRandomFloatFluctuatedRounded(
303 parseInt(powerPerPhaseSampledValueTemplates.L1.value),
304 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
305 Constants.DEFAULT_FLUCTUATION_PERCENT
306 );
307 const phase2FluctuatedValue =
308 powerPerPhaseSampledValueTemplates?.L2?.value &&
309 Utils.getRandomFloatFluctuatedRounded(
310 parseInt(powerPerPhaseSampledValueTemplates.L2.value),
311 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
312 Constants.DEFAULT_FLUCTUATION_PERCENT
313 );
314 const phase3FluctuatedValue =
315 powerPerPhaseSampledValueTemplates?.L3?.value &&
316 Utils.getRandomFloatFluctuatedRounded(
317 parseInt(powerPerPhaseSampledValueTemplates.L3.value),
318 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
319 Constants.DEFAULT_FLUCTUATION_PERCENT
320 );
321 powerMeasurandValues.L1 =
322 phase1FluctuatedValue ??
323 defaultFluctuatedPowerPerPhase ??
324 Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
325 powerMeasurandValues.L2 =
326 phase2FluctuatedValue ??
327 defaultFluctuatedPowerPerPhase ??
328 Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
329 powerMeasurandValues.L3 =
330 phase3FluctuatedValue ??
331 defaultFluctuatedPowerPerPhase ??
332 Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
333 } else {
334 powerMeasurandValues.L1 = powerSampledValueTemplate.value
335 ? Utils.getRandomFloatFluctuatedRounded(
336 parseInt(powerSampledValueTemplate.value),
337 powerSampledValueTemplate.fluctuationPercent ??
338 Constants.DEFAULT_FLUCTUATION_PERCENT
339 )
340 : Utils.getRandomFloatRounded(maxPower / unitDivider);
341 powerMeasurandValues.L2 = 0;
342 powerMeasurandValues.L3 = 0;
343 }
344 powerMeasurandValues.allPhases = Utils.roundTo(
345 powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
346 2
347 );
348 break;
349 case CurrentType.DC:
350 powerMeasurandValues.allPhases = powerSampledValueTemplate.value
351 ? Utils.getRandomFloatFluctuatedRounded(
352 parseInt(powerSampledValueTemplate.value),
353 powerSampledValueTemplate.fluctuationPercent ??
354 Constants.DEFAULT_FLUCTUATION_PERCENT
355 )
356 : Utils.getRandomFloatRounded(maxPower / unitDivider);
357 break;
358 default:
359 logger.error(errMsg);
360 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
361 }
362 meterValue.sampledValue.push(
363 OCPP16ServiceUtils.buildSampledValue(
364 powerSampledValueTemplate,
365 powerMeasurandValues.allPhases
366 )
367 );
368 const sampledValuesIndex = meterValue.sampledValue.length - 1;
369 const maxPowerRounded = Utils.roundTo(maxPower / unitDivider, 2);
370 if (
371 Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxPowerRounded ||
372 debug
373 ) {
374 logger.error(
375 `${chargingStation.logPrefix()} MeterValues measurand ${
376 meterValue.sampledValue[sampledValuesIndex].measurand ??
377 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
378 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
379 meterValue.sampledValue[sampledValuesIndex].value
380 }/${maxPowerRounded}`
381 );
382 }
383 for (
384 let phase = 1;
385 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
386 phase++
387 ) {
388 const phaseValue = `L${phase}-N`;
389 meterValue.sampledValue.push(
390 OCPP16ServiceUtils.buildSampledValue(
391 (powerPerPhaseSampledValueTemplates[`L${phase}`] as SampledValueTemplate) ??
392 powerSampledValueTemplate,
393 powerMeasurandValues[`L${phase}`] as number,
394 null,
395 phaseValue as OCPP16MeterValuePhase
396 )
397 );
398 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
399 const maxPowerPerPhaseRounded = Utils.roundTo(maxPowerPerPhase / unitDivider, 2);
400 if (
401 Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
402 maxPowerPerPhaseRounded ||
403 debug
404 ) {
405 logger.error(
406 `${chargingStation.logPrefix()} MeterValues measurand ${
407 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
408 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
409 }: phase ${
410 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
411 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
412 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
413 }/${maxPowerPerPhaseRounded}`
414 );
415 }
416 }
417 }
418 // Current.Import measurand
419 const currentSampledValueTemplate = chargingStation.getSampledValueTemplate(
420 connectorId,
421 OCPP16MeterValueMeasurand.CURRENT_IMPORT
422 );
423 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
424 if (chargingStation.getNumberOfPhases() === 3) {
425 currentPerPhaseSampledValueTemplates = {
426 L1: chargingStation.getSampledValueTemplate(
427 connectorId,
428 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
429 OCPP16MeterValuePhase.L1
430 ),
431 L2: chargingStation.getSampledValueTemplate(
432 connectorId,
433 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
434 OCPP16MeterValuePhase.L2
435 ),
436 L3: chargingStation.getSampledValueTemplate(
437 connectorId,
438 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
439 OCPP16MeterValuePhase.L3
440 ),
441 };
442 }
443 if (currentSampledValueTemplate) {
444 OCPP16ServiceUtils.checkMeasurandPowerDivider(
445 chargingStation,
446 currentSampledValueTemplate.measurand
447 );
448 const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
449 currentSampledValueTemplate.measurand ??
450 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
451 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
452 chargingStation.stationTemplateFile
453 }, cannot calculate ${
454 currentSampledValueTemplate.measurand ??
455 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
456 } measurand value`;
457 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
458 let maxAmperage: number;
459 switch (chargingStation.getCurrentOutType()) {
460 case CurrentType.AC:
461 maxAmperage = ACElectricUtils.amperagePerPhaseFromPower(
462 chargingStation.getNumberOfPhases(),
463 chargingStation.stationInfo.maxPower / chargingStation.stationInfo.powerDivider,
464 chargingStation.getVoltageOut()
465 );
466 if (chargingStation.getNumberOfPhases() === 3) {
467 const defaultFluctuatedAmperagePerPhase =
468 currentSampledValueTemplate.value &&
469 Utils.getRandomFloatFluctuatedRounded(
470 parseInt(currentSampledValueTemplate.value),
471 currentSampledValueTemplate.fluctuationPercent ??
472 Constants.DEFAULT_FLUCTUATION_PERCENT
473 );
474 const phase1FluctuatedValue =
475 currentPerPhaseSampledValueTemplates?.L1?.value &&
476 Utils.getRandomFloatFluctuatedRounded(
477 parseInt(currentPerPhaseSampledValueTemplates.L1.value),
478 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
479 Constants.DEFAULT_FLUCTUATION_PERCENT
480 );
481 const phase2FluctuatedValue =
482 currentPerPhaseSampledValueTemplates?.L2?.value &&
483 Utils.getRandomFloatFluctuatedRounded(
484 parseInt(currentPerPhaseSampledValueTemplates.L2.value),
485 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
486 Constants.DEFAULT_FLUCTUATION_PERCENT
487 );
488 const phase3FluctuatedValue =
489 currentPerPhaseSampledValueTemplates?.L3?.value &&
490 Utils.getRandomFloatFluctuatedRounded(
491 parseInt(currentPerPhaseSampledValueTemplates.L3.value),
492 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
493 Constants.DEFAULT_FLUCTUATION_PERCENT
494 );
495 currentMeasurandValues.L1 =
496 phase1FluctuatedValue ??
497 defaultFluctuatedAmperagePerPhase ??
498 Utils.getRandomFloatRounded(maxAmperage);
499 currentMeasurandValues.L2 =
500 phase2FluctuatedValue ??
501 defaultFluctuatedAmperagePerPhase ??
502 Utils.getRandomFloatRounded(maxAmperage);
503 currentMeasurandValues.L3 =
504 phase3FluctuatedValue ??
505 defaultFluctuatedAmperagePerPhase ??
506 Utils.getRandomFloatRounded(maxAmperage);
507 } else {
508 currentMeasurandValues.L1 = currentSampledValueTemplate.value
509 ? Utils.getRandomFloatFluctuatedRounded(
510 parseInt(currentSampledValueTemplate.value),
511 currentSampledValueTemplate.fluctuationPercent ??
512 Constants.DEFAULT_FLUCTUATION_PERCENT
513 )
514 : Utils.getRandomFloatRounded(maxAmperage);
515 currentMeasurandValues.L2 = 0;
516 currentMeasurandValues.L3 = 0;
517 }
518 currentMeasurandValues.allPhases = Utils.roundTo(
519 (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
520 chargingStation.getNumberOfPhases(),
521 2
522 );
523 break;
524 case CurrentType.DC:
525 maxAmperage = DCElectricUtils.amperage(
526 chargingStation.stationInfo.maxPower / chargingStation.stationInfo.powerDivider,
527 chargingStation.getVoltageOut()
528 );
529 currentMeasurandValues.allPhases = currentSampledValueTemplate.value
530 ? Utils.getRandomFloatFluctuatedRounded(
531 parseInt(currentSampledValueTemplate.value),
532 currentSampledValueTemplate.fluctuationPercent ??
533 Constants.DEFAULT_FLUCTUATION_PERCENT
534 )
535 : Utils.getRandomFloatRounded(maxAmperage);
536 break;
537 default:
538 logger.error(errMsg);
539 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
540 }
541 meterValue.sampledValue.push(
542 OCPP16ServiceUtils.buildSampledValue(
543 currentSampledValueTemplate,
544 currentMeasurandValues.allPhases
545 )
546 );
547 const sampledValuesIndex = meterValue.sampledValue.length - 1;
548 if (
549 Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxAmperage ||
550 debug
551 ) {
552 logger.error(
553 `${chargingStation.logPrefix()} MeterValues measurand ${
554 meterValue.sampledValue[sampledValuesIndex].measurand ??
555 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
556 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
557 meterValue.sampledValue[sampledValuesIndex].value
558 }/${maxAmperage}`
559 );
560 }
561 for (
562 let phase = 1;
563 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
564 phase++
565 ) {
566 const phaseValue = `L${phase}`;
567 meterValue.sampledValue.push(
568 OCPP16ServiceUtils.buildSampledValue(
569 (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
570 currentSampledValueTemplate,
571 currentMeasurandValues[phaseValue] as number,
572 null,
573 phaseValue as OCPP16MeterValuePhase
574 )
575 );
576 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
577 if (
578 Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
579 maxAmperage ||
580 debug
581 ) {
582 logger.error(
583 `${chargingStation.logPrefix()} MeterValues measurand ${
584 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
585 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
586 }: phase ${
587 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
588 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
589 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
590 }/${maxAmperage}`
591 );
592 }
593 }
594 }
595 // Energy.Active.Import.Register measurand (default)
596 const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
597 if (energySampledValueTemplate) {
598 OCPP16ServiceUtils.checkMeasurandPowerDivider(
599 chargingStation,
600 energySampledValueTemplate.measurand
601 );
602 const unitDivider =
603 energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
604 const maxEnergyRounded = Utils.roundTo(
605 ((chargingStation.stationInfo.maxPower / chargingStation.stationInfo.powerDivider) *
606 interval) /
607 (3600 * 1000),
608 2
609 );
610 const energyValueRounded = energySampledValueTemplate.value
611 ? // Cumulate the fluctuated value around the static one
612 Utils.getRandomFloatFluctuatedRounded(
613 parseInt(energySampledValueTemplate.value),
614 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
615 )
616 : Utils.getRandomFloatRounded(maxEnergyRounded);
617 // Persist previous value on connector
618 if (
619 connector &&
620 !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) &&
621 connector.energyActiveImportRegisterValue >= 0 &&
622 !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) &&
623 connector.transactionEnergyActiveImportRegisterValue >= 0
624 ) {
625 connector.energyActiveImportRegisterValue += energyValueRounded;
626 connector.transactionEnergyActiveImportRegisterValue += energyValueRounded;
627 } else {
628 connector.energyActiveImportRegisterValue = 0;
629 connector.transactionEnergyActiveImportRegisterValue = 0;
630 }
631 meterValue.sampledValue.push(
632 OCPP16ServiceUtils.buildSampledValue(
633 energySampledValueTemplate,
634 Utils.roundTo(
635 chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
636 unitDivider,
637 2
638 )
639 )
640 );
641 const sampledValuesIndex = meterValue.sampledValue.length - 1;
642 if (energyValueRounded > maxEnergyRounded || debug) {
643 logger.error(
644 `${chargingStation.logPrefix()} MeterValues measurand ${
645 meterValue.sampledValue[sampledValuesIndex].measurand ??
646 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
647 }: connectorId ${connectorId}, transaction ${
648 connector.transactionId
649 }, value: ${energyValueRounded}/${maxEnergyRounded}, duration: ${Utils.roundTo(
650 interval / (3600 * 1000),
651 4
652 )}h`
653 );
654 }
655 }
656 return meterValue;
657 }
658
e7aeea18
JB
659 public static buildTransactionBeginMeterValue(
660 chargingStation: ChargingStation,
661 connectorId: number,
78085c42 662 meterStart: number
e7aeea18 663 ): OCPP16MeterValue {
fd0c36fa
JB
664 const meterValue: OCPP16MeterValue = {
665 timestamp: new Date().toISOString(),
666 sampledValue: [],
667 };
9ccca265
JB
668 // Energy.Active.Import.Register measurand (default)
669 const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
670 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
671 meterValue.sampledValue.push(
672 OCPP16ServiceUtils.buildSampledValue(
673 sampledValueTemplate,
78085c42 674 Utils.roundTo(meterStart / unitDivider, 4),
e7aeea18
JB
675 MeterValueContext.TRANSACTION_BEGIN
676 )
677 );
fd0c36fa
JB
678 return meterValue;
679 }
680
e7aeea18
JB
681 public static buildTransactionEndMeterValue(
682 chargingStation: ChargingStation,
683 connectorId: number,
78085c42 684 meterStop: number
e7aeea18 685 ): OCPP16MeterValue {
fd0c36fa
JB
686 const meterValue: OCPP16MeterValue = {
687 timestamp: new Date().toISOString(),
688 sampledValue: [],
689 };
9ccca265
JB
690 // Energy.Active.Import.Register measurand (default)
691 const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
692 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
693 meterValue.sampledValue.push(
694 OCPP16ServiceUtils.buildSampledValue(
695 sampledValueTemplate,
78085c42 696 Utils.roundTo(meterStop / unitDivider, 4),
e7aeea18
JB
697 MeterValueContext.TRANSACTION_END
698 )
699 );
fd0c36fa
JB
700 return meterValue;
701 }
702
e7aeea18
JB
703 public static buildTransactionDataMeterValues(
704 transactionBeginMeterValue: OCPP16MeterValue,
705 transactionEndMeterValue: OCPP16MeterValue
706 ): OCPP16MeterValue[] {
fd0c36fa
JB
707 const meterValues: OCPP16MeterValue[] = [];
708 meterValues.push(transactionBeginMeterValue);
709 meterValues.push(transactionEndMeterValue);
710 return meterValues;
711 }
6ed92bc1 712}