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