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