15b914c39e36db78bb0c5915c850febe929e49b9
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import OCPPError from '../../../exception/OCPPError';
4 import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate';
5 import type {
6 MeasurandPerPhaseSampledValueTemplates,
7 SampledValueTemplate,
8 } from '../../../types/MeasurandPerPhaseSampledValueTemplates';
9 import type { MeasurandValues } from '../../../types/MeasurandValues';
10 import type { OCPP16ChargingProfile } from '../../../types/ocpp/1.6/ChargingProfile';
11 import {
12 OCPP16StandardParametersKey,
13 OCPP16SupportedFeatureProfiles,
14 } from '../../../types/ocpp/1.6/Configuration';
15 import {
16 MeterValueContext,
17 MeterValueLocation,
18 MeterValueUnit,
19 type OCPP16MeterValue,
20 OCPP16MeterValueMeasurand,
21 OCPP16MeterValuePhase,
22 type OCPP16SampledValue,
23 } from '../../../types/ocpp/1.6/MeterValues';
24 import {
25 type OCPP16IncomingRequestCommand,
26 OCPP16RequestCommand,
27 } from '../../../types/ocpp/1.6/Requests';
28 import { ErrorType } from '../../../types/ocpp/ErrorType';
29 import Constants from '../../../utils/Constants';
30 import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
31 import logger from '../../../utils/Logger';
32 import Utils from '../../../utils/Utils';
33 import type ChargingStation from '../../ChargingStation';
34 import { OCPPServiceUtils } from '../OCPPServiceUtils';
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(),
62 sampledValue: [],
63 };
64 const connector = chargingStation.getConnectorStatus(connectorId);
65 // SoC measurand
66 const socSampledValueTemplate = OCPP16ServiceUtils.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 = OCPP16ServiceUtils.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 OCPP16ServiceUtils.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 OCPP16ServiceUtils.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 = OCPP16ServiceUtils.getSampledValueTemplate(
196 chargingStation,
197 connectorId,
198 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
199 );
200 let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
201 if (chargingStation.getNumberOfPhases() === 3) {
202 powerPerPhaseSampledValueTemplates = {
203 L1: OCPP16ServiceUtils.getSampledValueTemplate(
204 chargingStation,
205 connectorId,
206 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
207 OCPP16MeterValuePhase.L1_N
208 ),
209 L2: OCPP16ServiceUtils.getSampledValueTemplate(
210 chargingStation,
211 connectorId,
212 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
213 OCPP16MeterValuePhase.L2_N
214 ),
215 L3: OCPP16ServiceUtils.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 = `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(`${chargingStation.logPrefix()} ${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 = OCPP16ServiceUtils.getSampledValueTemplate(
403 chargingStation,
404 connectorId,
405 OCPP16MeterValueMeasurand.CURRENT_IMPORT
406 );
407 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
408 if (chargingStation.getNumberOfPhases() === 3) {
409 currentPerPhaseSampledValueTemplates = {
410 L1: OCPP16ServiceUtils.getSampledValueTemplate(
411 chargingStation,
412 connectorId,
413 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
414 OCPP16MeterValuePhase.L1
415 ),
416 L2: OCPP16ServiceUtils.getSampledValueTemplate(
417 chargingStation,
418 connectorId,
419 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
420 OCPP16MeterValuePhase.L2
421 ),
422 L3: OCPP16ServiceUtils.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 = `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(`${chargingStation.logPrefix()} ${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 = OCPP16ServiceUtils.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) === false &&
645 connector.energyActiveImportRegisterValue >= 0 &&
646 Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
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(),
690 sampledValue: [],
691 };
692 // Energy.Active.Import.Register measurand (default)
693 const sampledValueTemplate = OCPP16ServiceUtils.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 ?? 0) / 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(),
715 sampledValue: [],
716 };
717 // Energy.Active.Import.Register measurand (default)
718 const sampledValueTemplate = OCPP16ServiceUtils.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 ?? 0) / 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 public static setChargingProfile(
744 chargingStation: ChargingStation,
745 connectorId: number,
746 cp: OCPP16ChargingProfile
747 ): void {
748 if (Utils.isNullOrUndefined(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
749 logger.error(
750 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
751 );
752 chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
753 }
754 if (Array.isArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles) === false) {
755 logger.error(
756 `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
757 );
758 chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
759 }
760 let cpReplaced = false;
761 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
762 chargingStation
763 .getConnectorStatus(connectorId)
764 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
765 if (
766 chargingProfile.chargingProfileId === cp.chargingProfileId ||
767 (chargingProfile.stackLevel === cp.stackLevel &&
768 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
769 ) {
770 chargingStation.getConnectorStatus(connectorId).chargingProfiles[index] = cp;
771 cpReplaced = true;
772 }
773 });
774 }
775 !cpReplaced && chargingStation.getConnectorStatus(connectorId).chargingProfiles?.push(cp);
776 }
777
778 private static buildSampledValue(
779 sampledValueTemplate: SampledValueTemplate,
780 value: number,
781 context?: MeterValueContext,
782 phase?: OCPP16MeterValuePhase
783 ): OCPP16SampledValue {
784 const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
785 const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
786 const sampledValueLocation =
787 sampledValueTemplate?.location ??
788 OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null);
789 const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
790 return {
791 ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && {
792 unit: sampledValueTemplate.unit,
793 }),
794 ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
795 ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && {
796 measurand: sampledValueTemplate.measurand,
797 }),
798 ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
799 ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
800 ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
801 };
802 }
803
804 private static checkMeasurandPowerDivider(
805 chargingStation: ChargingStation,
806 measurandType: OCPP16MeterValueMeasurand
807 ): void {
808 if (Utils.isUndefined(chargingStation.powerDivider)) {
809 const errMsg = `MeterValues measurand ${
810 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
811 }: powerDivider is undefined`;
812 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
813 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
814 } else if (chargingStation?.powerDivider <= 0) {
815 const errMsg = `MeterValues measurand ${
816 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
817 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
818 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
819 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
820 }
821 }
822
823 private static getMeasurandDefaultLocation(
824 measurandType: OCPP16MeterValueMeasurand
825 ): MeterValueLocation | undefined {
826 switch (measurandType) {
827 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
828 return MeterValueLocation.EV;
829 }
830 }
831
832 private static getMeasurandDefaultUnit(
833 measurandType: OCPP16MeterValueMeasurand
834 ): MeterValueUnit | undefined {
835 switch (measurandType) {
836 case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
837 case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
838 case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
839 return MeterValueUnit.AMP;
840 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
841 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
842 return MeterValueUnit.WATT_HOUR;
843 case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
844 case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
845 case OCPP16MeterValueMeasurand.POWER_OFFERED:
846 return MeterValueUnit.WATT;
847 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
848 return MeterValueUnit.PERCENT;
849 case OCPP16MeterValueMeasurand.VOLTAGE:
850 return MeterValueUnit.VOLT;
851 }
852 }
853 }