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