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