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