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