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