43b291be0680777088d9bef662cc1c16ab239999
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
3 import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
4 import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
5 import MeasurandPerPhaseSampledValueTemplates, {
6 SampledValueTemplate,
7 } from '../../../types/MeasurandPerPhaseSampledValueTemplates';
8 import {
9 MeterValueContext,
10 MeterValueLocation,
11 MeterValueUnit,
12 OCPP16MeterValue,
13 OCPP16MeterValueMeasurand,
14 OCPP16MeterValuePhase,
15 OCPP16SampledValue,
16 } from '../../../types/ocpp/1.6/MeterValues';
17
18 import type ChargingStation from '../../ChargingStation';
19 import Constants from '../../../utils/Constants';
20 import { ErrorType } from '../../../types/ocpp/ErrorType';
21 import MeasurandValues from '../../../types/MeasurandValues';
22 import { OCPP16RequestCommand } from '../../../types/ocpp/1.6/Requests';
23 import OCPPError from '../../../exception/OCPPError';
24 import Utils from '../../../utils/Utils';
25 import logger from '../../../utils/Logger';
26
27 export class OCPP16ServiceUtils {
28 public static checkMeasurandPowerDivider(
29 chargingStation: ChargingStation,
30 measurandType: OCPP16MeterValueMeasurand
31 ): void {
32 if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) {
33 const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
34 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
35 }: powerDivider is undefined`;
36 logger.error(errMsg);
37 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
38 } else if (chargingStation.stationInfo?.powerDivider <= 0) {
39 const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
40 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
41 }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
42 logger.error(errMsg);
43 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
44 }
45 }
46
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;
59 return {
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 }),
70 };
71 }
72
73 public static getMeasurandDefaultUnit(
74 measurandType: OCPP16MeterValueMeasurand
75 ): MeterValueUnit | undefined {
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
95 public static getMeasurandDefaultLocation(
96 measurandType: OCPP16MeterValueMeasurand
97 ): MeterValueLocation | undefined {
98 switch (measurandType) {
99 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
100 return MeterValueLocation.EV;
101 }
102 }
103
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.templateFile
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 connectorMaximumPower = Math.round(
283 chargingStation.getConnectorMaximumAvailablePower(connectorId)
284 );
285 const connectorMaximumPowerPerPhase = Math.round(
286 chargingStation.getConnectorMaximumAvailablePower(connectorId) /
287 chargingStation.getNumberOfPhases()
288 );
289 switch (chargingStation.getCurrentOutType()) {
290 case CurrentType.AC:
291 if (chargingStation.getNumberOfPhases() === 3) {
292 const defaultFluctuatedPowerPerPhase =
293 powerSampledValueTemplate.value &&
294 Utils.getRandomFloatFluctuatedRounded(
295 parseInt(powerSampledValueTemplate.value) / chargingStation.getNumberOfPhases(),
296 powerSampledValueTemplate.fluctuationPercent ??
297 Constants.DEFAULT_FLUCTUATION_PERCENT
298 );
299 const phase1FluctuatedValue =
300 powerPerPhaseSampledValueTemplates?.L1?.value &&
301 Utils.getRandomFloatFluctuatedRounded(
302 parseInt(powerPerPhaseSampledValueTemplates.L1.value),
303 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
304 Constants.DEFAULT_FLUCTUATION_PERCENT
305 );
306 const phase2FluctuatedValue =
307 powerPerPhaseSampledValueTemplates?.L2?.value &&
308 Utils.getRandomFloatFluctuatedRounded(
309 parseInt(powerPerPhaseSampledValueTemplates.L2.value),
310 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
311 Constants.DEFAULT_FLUCTUATION_PERCENT
312 );
313 const phase3FluctuatedValue =
314 powerPerPhaseSampledValueTemplates?.L3?.value &&
315 Utils.getRandomFloatFluctuatedRounded(
316 parseInt(powerPerPhaseSampledValueTemplates.L3.value),
317 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
318 Constants.DEFAULT_FLUCTUATION_PERCENT
319 );
320 powerMeasurandValues.L1 =
321 phase1FluctuatedValue ??
322 defaultFluctuatedPowerPerPhase ??
323 Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
324 powerMeasurandValues.L2 =
325 phase2FluctuatedValue ??
326 defaultFluctuatedPowerPerPhase ??
327 Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
328 powerMeasurandValues.L3 =
329 phase3FluctuatedValue ??
330 defaultFluctuatedPowerPerPhase ??
331 Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
332 } else {
333 powerMeasurandValues.L1 = powerSampledValueTemplate.value
334 ? Utils.getRandomFloatFluctuatedRounded(
335 parseInt(powerSampledValueTemplate.value),
336 powerSampledValueTemplate.fluctuationPercent ??
337 Constants.DEFAULT_FLUCTUATION_PERCENT
338 )
339 : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
340 powerMeasurandValues.L2 = 0;
341 powerMeasurandValues.L3 = 0;
342 }
343 powerMeasurandValues.allPhases = Utils.roundTo(
344 powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3,
345 2
346 );
347 break;
348 case CurrentType.DC:
349 powerMeasurandValues.allPhases = powerSampledValueTemplate.value
350 ? Utils.getRandomFloatFluctuatedRounded(
351 parseInt(powerSampledValueTemplate.value),
352 powerSampledValueTemplate.fluctuationPercent ??
353 Constants.DEFAULT_FLUCTUATION_PERCENT
354 )
355 : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
356 break;
357 default:
358 logger.error(errMsg);
359 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
360 }
361 meterValue.sampledValue.push(
362 OCPP16ServiceUtils.buildSampledValue(
363 powerSampledValueTemplate,
364 powerMeasurandValues.allPhases
365 )
366 );
367 const sampledValuesIndex = meterValue.sampledValue.length - 1;
368 const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2);
369 if (
370 Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
371 connectorMaximumPowerRounded ||
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 }/${connectorMaximumPowerRounded}`
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 connectorMaximumPowerPerPhaseRounded = Utils.roundTo(
400 connectorMaximumPowerPerPhase / unitDivider,
401 2
402 );
403 if (
404 Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
405 connectorMaximumPowerPerPhaseRounded ||
406 debug
407 ) {
408 logger.error(
409 `${chargingStation.logPrefix()} MeterValues measurand ${
410 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
411 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
412 }: phase ${
413 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
414 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
415 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
416 }/${connectorMaximumPowerPerPhaseRounded}`
417 );
418 }
419 }
420 }
421 // Current.Import measurand
422 const currentSampledValueTemplate = chargingStation.getSampledValueTemplate(
423 connectorId,
424 OCPP16MeterValueMeasurand.CURRENT_IMPORT
425 );
426 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
427 if (chargingStation.getNumberOfPhases() === 3) {
428 currentPerPhaseSampledValueTemplates = {
429 L1: chargingStation.getSampledValueTemplate(
430 connectorId,
431 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
432 OCPP16MeterValuePhase.L1
433 ),
434 L2: chargingStation.getSampledValueTemplate(
435 connectorId,
436 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
437 OCPP16MeterValuePhase.L2
438 ),
439 L3: chargingStation.getSampledValueTemplate(
440 connectorId,
441 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
442 OCPP16MeterValuePhase.L3
443 ),
444 };
445 }
446 if (currentSampledValueTemplate) {
447 OCPP16ServiceUtils.checkMeasurandPowerDivider(
448 chargingStation,
449 currentSampledValueTemplate.measurand
450 );
451 const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${
452 currentSampledValueTemplate.measurand ??
453 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
454 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
455 chargingStation.templateFile
456 }, cannot calculate ${
457 currentSampledValueTemplate.measurand ??
458 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
459 } measurand value`;
460 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
461 let connectorMaximumAmperage: number;
462 switch (chargingStation.getCurrentOutType()) {
463 case CurrentType.AC:
464 connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
465 chargingStation.getNumberOfPhases(),
466 chargingStation.getConnectorMaximumAvailablePower(connectorId),
467 chargingStation.getVoltageOut()
468 );
469 if (chargingStation.getNumberOfPhases() === 3) {
470 const defaultFluctuatedAmperagePerPhase =
471 currentSampledValueTemplate.value &&
472 Utils.getRandomFloatFluctuatedRounded(
473 parseInt(currentSampledValueTemplate.value),
474 currentSampledValueTemplate.fluctuationPercent ??
475 Constants.DEFAULT_FLUCTUATION_PERCENT
476 );
477 const phase1FluctuatedValue =
478 currentPerPhaseSampledValueTemplates?.L1?.value &&
479 Utils.getRandomFloatFluctuatedRounded(
480 parseInt(currentPerPhaseSampledValueTemplates.L1.value),
481 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
482 Constants.DEFAULT_FLUCTUATION_PERCENT
483 );
484 const phase2FluctuatedValue =
485 currentPerPhaseSampledValueTemplates?.L2?.value &&
486 Utils.getRandomFloatFluctuatedRounded(
487 parseInt(currentPerPhaseSampledValueTemplates.L2.value),
488 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
489 Constants.DEFAULT_FLUCTUATION_PERCENT
490 );
491 const phase3FluctuatedValue =
492 currentPerPhaseSampledValueTemplates?.L3?.value &&
493 Utils.getRandomFloatFluctuatedRounded(
494 parseInt(currentPerPhaseSampledValueTemplates.L3.value),
495 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
496 Constants.DEFAULT_FLUCTUATION_PERCENT
497 );
498 currentMeasurandValues.L1 =
499 phase1FluctuatedValue ??
500 defaultFluctuatedAmperagePerPhase ??
501 Utils.getRandomFloatRounded(connectorMaximumAmperage);
502 currentMeasurandValues.L2 =
503 phase2FluctuatedValue ??
504 defaultFluctuatedAmperagePerPhase ??
505 Utils.getRandomFloatRounded(connectorMaximumAmperage);
506 currentMeasurandValues.L3 =
507 phase3FluctuatedValue ??
508 defaultFluctuatedAmperagePerPhase ??
509 Utils.getRandomFloatRounded(connectorMaximumAmperage);
510 } else {
511 currentMeasurandValues.L1 = currentSampledValueTemplate.value
512 ? Utils.getRandomFloatFluctuatedRounded(
513 parseInt(currentSampledValueTemplate.value),
514 currentSampledValueTemplate.fluctuationPercent ??
515 Constants.DEFAULT_FLUCTUATION_PERCENT
516 )
517 : Utils.getRandomFloatRounded(connectorMaximumAmperage);
518 currentMeasurandValues.L2 = 0;
519 currentMeasurandValues.L3 = 0;
520 }
521 currentMeasurandValues.allPhases = Utils.roundTo(
522 (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
523 chargingStation.getNumberOfPhases(),
524 2
525 );
526 break;
527 case CurrentType.DC:
528 connectorMaximumAmperage = DCElectricUtils.amperage(
529 chargingStation.getConnectorMaximumAvailablePower(connectorId),
530 chargingStation.getVoltageOut()
531 );
532 currentMeasurandValues.allPhases = currentSampledValueTemplate.value
533 ? Utils.getRandomFloatFluctuatedRounded(
534 parseInt(currentSampledValueTemplate.value),
535 currentSampledValueTemplate.fluctuationPercent ??
536 Constants.DEFAULT_FLUCTUATION_PERCENT
537 )
538 : Utils.getRandomFloatRounded(connectorMaximumAmperage);
539 break;
540 default:
541 logger.error(errMsg);
542 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
543 }
544 meterValue.sampledValue.push(
545 OCPP16ServiceUtils.buildSampledValue(
546 currentSampledValueTemplate,
547 currentMeasurandValues.allPhases
548 )
549 );
550 const sampledValuesIndex = meterValue.sampledValue.length - 1;
551 if (
552 Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
553 connectorMaximumAmperage ||
554 debug
555 ) {
556 logger.error(
557 `${chargingStation.logPrefix()} MeterValues measurand ${
558 meterValue.sampledValue[sampledValuesIndex].measurand ??
559 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
560 }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
561 meterValue.sampledValue[sampledValuesIndex].value
562 }/${connectorMaximumAmperage}`
563 );
564 }
565 for (
566 let phase = 1;
567 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
568 phase++
569 ) {
570 const phaseValue = `L${phase}`;
571 meterValue.sampledValue.push(
572 OCPP16ServiceUtils.buildSampledValue(
573 (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ??
574 currentSampledValueTemplate,
575 currentMeasurandValues[phaseValue] as number,
576 null,
577 phaseValue as OCPP16MeterValuePhase
578 )
579 );
580 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
581 if (
582 Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
583 connectorMaximumAmperage ||
584 debug
585 ) {
586 logger.error(
587 `${chargingStation.logPrefix()} MeterValues measurand ${
588 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
589 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
590 }: phase ${
591 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
592 }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${
593 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
594 }/${connectorMaximumAmperage}`
595 );
596 }
597 }
598 }
599 // Energy.Active.Import.Register measurand (default)
600 const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
601 if (energySampledValueTemplate) {
602 OCPP16ServiceUtils.checkMeasurandPowerDivider(
603 chargingStation,
604 energySampledValueTemplate.measurand
605 );
606 const unitDivider =
607 energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
608 const connectorMaximumEnergyRounded = Utils.roundTo(
609 (chargingStation.getConnectorMaximumAvailablePower(connectorId) * interval) / (3600 * 1000),
610 2
611 );
612 const energyValueRounded = energySampledValueTemplate.value
613 ? // Cumulate the fluctuated value around the static one
614 Utils.getRandomFloatFluctuatedRounded(
615 parseInt(energySampledValueTemplate.value),
616 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
617 )
618 : Utils.getRandomFloatRounded(connectorMaximumEnergyRounded);
619 // Persist previous value on connector
620 if (
621 connector &&
622 !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) &&
623 connector.energyActiveImportRegisterValue >= 0 &&
624 !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) &&
625 connector.transactionEnergyActiveImportRegisterValue >= 0
626 ) {
627 connector.energyActiveImportRegisterValue += energyValueRounded;
628 connector.transactionEnergyActiveImportRegisterValue += energyValueRounded;
629 } else {
630 connector.energyActiveImportRegisterValue = 0;
631 connector.transactionEnergyActiveImportRegisterValue = 0;
632 }
633 meterValue.sampledValue.push(
634 OCPP16ServiceUtils.buildSampledValue(
635 energySampledValueTemplate,
636 Utils.roundTo(
637 chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
638 unitDivider,
639 2
640 )
641 )
642 );
643 const sampledValuesIndex = meterValue.sampledValue.length - 1;
644 if (energyValueRounded > connectorMaximumEnergyRounded || debug) {
645 logger.error(
646 `${chargingStation.logPrefix()} MeterValues measurand ${
647 meterValue.sampledValue[sampledValuesIndex].measurand ??
648 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
649 }: connectorId ${connectorId}, transaction ${
650 connector.transactionId
651 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
652 interval / (3600 * 1000),
653 4
654 )}h`
655 );
656 }
657 }
658 return meterValue;
659 }
660
661 public static buildTransactionBeginMeterValue(
662 chargingStation: ChargingStation,
663 connectorId: number,
664 meterStart: number
665 ): OCPP16MeterValue {
666 const meterValue: OCPP16MeterValue = {
667 timestamp: new Date().toISOString(),
668 sampledValue: [],
669 };
670 // Energy.Active.Import.Register measurand (default)
671 const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
672 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
673 meterValue.sampledValue.push(
674 OCPP16ServiceUtils.buildSampledValue(
675 sampledValueTemplate,
676 Utils.roundTo(meterStart / unitDivider, 4),
677 MeterValueContext.TRANSACTION_BEGIN
678 )
679 );
680 return meterValue;
681 }
682
683 public static buildTransactionEndMeterValue(
684 chargingStation: ChargingStation,
685 connectorId: number,
686 meterStop: number
687 ): OCPP16MeterValue {
688 const meterValue: OCPP16MeterValue = {
689 timestamp: new Date().toISOString(),
690 sampledValue: [],
691 };
692 // Energy.Active.Import.Register measurand (default)
693 const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId);
694 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
695 meterValue.sampledValue.push(
696 OCPP16ServiceUtils.buildSampledValue(
697 sampledValueTemplate,
698 Utils.roundTo(meterStop / unitDivider, 4),
699 MeterValueContext.TRANSACTION_END
700 )
701 );
702 return meterValue;
703 }
704
705 public static buildTransactionDataMeterValues(
706 transactionBeginMeterValue: OCPP16MeterValue,
707 transactionEndMeterValue: OCPP16MeterValue
708 ): OCPP16MeterValue[] {
709 const meterValues: OCPP16MeterValue[] = [];
710 meterValues.push(transactionBeginMeterValue);
711 meterValues.push(transactionEndMeterValue);
712 return meterValues;
713 }
714 }