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