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