refactor(simulator): switch to named exports
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ServiceUtils.ts
CommitLineData
edd13439 1// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
c8eeb62b 2
130783a7
JB
3import path from 'node:path';
4import { fileURLToPath } from 'node:url';
5
6import type { JSONSchemaType } from 'ajv';
7
268a74bb 8import { OCPPError } from '../../../exception';
e7aeea18 9import {
268a74bb
JB
10 CurrentType,
11 ErrorType,
12 type JsonType,
13 type MeasurandPerPhaseSampledValueTemplates,
14 type MeasurandValues,
e7aeea18
JB
15 MeterValueContext,
16 MeterValueLocation,
17 MeterValueUnit,
268a74bb
JB
18 type OCPP16ChargingProfile,
19 type OCPP16IncomingRequestCommand,
27782dbc 20 type OCPP16MeterValue,
e7aeea18
JB
21 OCPP16MeterValueMeasurand,
22 OCPP16MeterValuePhase,
370ae4ee 23 OCPP16RequestCommand,
268a74bb
JB
24 type OCPP16SampledValue,
25 OCPP16StandardParametersKey,
26 type OCPP16SupportedFeatureProfiles,
27 OCPPVersion,
28 type SampledValueTemplate,
29 Voltage,
30} from '../../../types';
31import { Constants } from '../../../utils/Constants';
8114d10e 32import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
268a74bb
JB
33import { logger } from '../../../utils/Logger';
34import { Utils } from '../../../utils/Utils';
35import type { ChargingStation } from '../../ChargingStation';
7bc31f9c 36import { OCPPServiceUtils } from '../OCPPServiceUtils';
6ed92bc1 37
7bc31f9c 38export class OCPP16ServiceUtils extends OCPPServiceUtils {
370ae4ee
JB
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
78085c42
JB
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 = {
c38f0ced 63 timestamp: new Date(),
78085c42
JB
64 sampledValue: [],
65 };
66 const connector = chargingStation.getConnectorStatus(connectorId);
67 // SoC measurand
ed3d2808 68 const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 69 chargingStation,
78085c42
JB
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
72092cfc 89 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
78085c42
JB
90 meterValue.sampledValue[sampledValuesIndex].value
91 }/100`
92 );
93 }
94 }
95 // Voltage measurand
ed3d2808 96 const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 97 chargingStation,
78085c42
JB
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 =
ed3d2808 126 OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 127 chargingStation,
78085c42
JB
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,
72092cfc 150 undefined,
78085c42
JB
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 =
ed3d2808 161 OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 162 chargingStation,
78085c42
JB
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,
72092cfc 189 undefined,
78085c42
JB
190 phaseLineToLineValue as OCPP16MeterValuePhase
191 )
192 );
193 }
194 }
195 }
196 // Power.Active.Import measurand
ed3d2808 197 const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 198 chargingStation,
78085c42
JB
199 connectorId,
200 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
201 );
202 let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
203 if (chargingStation.getNumberOfPhases() === 3) {
204 powerPerPhaseSampledValueTemplates = {
ed3d2808 205 L1: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 206 chargingStation,
78085c42
JB
207 connectorId,
208 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
209 OCPP16MeterValuePhase.L1_N
210 ),
ed3d2808 211 L2: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 212 chargingStation,
78085c42
JB
213 connectorId,
214 OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT,
215 OCPP16MeterValuePhase.L2_N
216 ),
ed3d2808 217 L3: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 218 chargingStation,
78085c42
JB
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 );
fc040c43 230 const errMsg = `MeterValues measurand ${
78085c42
JB
231 powerSampledValueTemplate.measurand ??
232 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
233 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
2484ac1e 234 chargingStation.templateFile
78085c42
JB
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;
1b6498ba
JB
241 const connectorMaximumAvailablePower =
242 chargingStation.getConnectorMaximumAvailablePower(connectorId);
243 const connectorMaximumPower = Math.round(connectorMaximumAvailablePower);
ad8537a7 244 const connectorMaximumPowerPerPhase = Math.round(
1b6498ba 245 connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
78085c42
JB
246 );
247 switch (chargingStation.getCurrentOutType()) {
248 case CurrentType.AC:
249 if (chargingStation.getNumberOfPhases() === 3) {
250 const defaultFluctuatedPowerPerPhase =
251 powerSampledValueTemplate.value &&
252 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
253 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
254 powerSampledValueTemplate.value,
255 connectorMaximumPower / unitDivider,
256 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 257 ) / chargingStation.getNumberOfPhases(),
78085c42
JB
258 powerSampledValueTemplate.fluctuationPercent ??
259 Constants.DEFAULT_FLUCTUATION_PERCENT
260 );
261 const phase1FluctuatedValue =
262 powerPerPhaseSampledValueTemplates?.L1?.value &&
263 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
264 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
265 powerPerPhaseSampledValueTemplates.L1.value,
266 connectorMaximumPowerPerPhase / unitDivider,
267 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 268 ),
78085c42
JB
269 powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
270 Constants.DEFAULT_FLUCTUATION_PERCENT
271 );
272 const phase2FluctuatedValue =
273 powerPerPhaseSampledValueTemplates?.L2?.value &&
274 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
275 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
276 powerPerPhaseSampledValueTemplates.L2.value,
277 connectorMaximumPowerPerPhase / unitDivider,
278 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 279 ),
78085c42
JB
280 powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
281 Constants.DEFAULT_FLUCTUATION_PERCENT
282 );
283 const phase3FluctuatedValue =
284 powerPerPhaseSampledValueTemplates?.L3?.value &&
285 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
286 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
287 powerPerPhaseSampledValueTemplates.L3.value,
288 connectorMaximumPowerPerPhase / unitDivider,
289 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 290 ),
78085c42
JB
291 powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
292 Constants.DEFAULT_FLUCTUATION_PERCENT
293 );
294 powerMeasurandValues.L1 =
295 phase1FluctuatedValue ??
296 defaultFluctuatedPowerPerPhase ??
ad8537a7 297 Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
78085c42
JB
298 powerMeasurandValues.L2 =
299 phase2FluctuatedValue ??
300 defaultFluctuatedPowerPerPhase ??
ad8537a7 301 Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
78085c42
JB
302 powerMeasurandValues.L3 =
303 phase3FluctuatedValue ??
304 defaultFluctuatedPowerPerPhase ??
ad8537a7 305 Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
78085c42
JB
306 } else {
307 powerMeasurandValues.L1 = powerSampledValueTemplate.value
308 ? Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
309 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
310 powerSampledValueTemplate.value,
311 connectorMaximumPower / unitDivider,
312 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 313 ),
78085c42
JB
314 powerSampledValueTemplate.fluctuationPercent ??
315 Constants.DEFAULT_FLUCTUATION_PERCENT
316 )
ad8537a7 317 : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
78085c42
JB
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(
7bc31f9c
JB
329 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
330 powerSampledValueTemplate.value,
331 connectorMaximumPower / unitDivider,
332 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 333 ),
78085c42
JB
334 powerSampledValueTemplate.fluctuationPercent ??
335 Constants.DEFAULT_FLUCTUATION_PERCENT
336 )
ad8537a7 337 : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
78085c42
JB
338 break;
339 default:
fc040c43 340 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
78085c42
JB
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;
ad8537a7 350 const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2);
78085c42 351 if (
71a77ac2 352 Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
ad8537a7 353 connectorMaximumPowerRounded ||
78085c42
JB
354 debug
355 ) {
356 logger.error(
357 `${chargingStation.logPrefix()} MeterValues measurand ${
358 meterValue.sampledValue[sampledValuesIndex].measurand ??
359 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
72092cfc 360 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
78085c42 361 meterValue.sampledValue[sampledValuesIndex].value
ad8537a7 362 }/${connectorMaximumPowerRounded}`
78085c42
JB
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,
72092cfc 376 undefined,
78085c42
JB
377 phaseValue as OCPP16MeterValuePhase
378 )
379 );
380 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
ad8537a7
JB
381 const connectorMaximumPowerPerPhaseRounded = Utils.roundTo(
382 connectorMaximumPowerPerPhase / unitDivider,
383 2
384 );
78085c42
JB
385 if (
386 Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
ad8537a7 387 connectorMaximumPowerPerPhaseRounded ||
78085c42
JB
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
72092cfc 396 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
78085c42 397 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
ad8537a7 398 }/${connectorMaximumPowerPerPhaseRounded}`
78085c42
JB
399 );
400 }
401 }
402 }
403 // Current.Import measurand
ed3d2808 404 const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 405 chargingStation,
78085c42
JB
406 connectorId,
407 OCPP16MeterValueMeasurand.CURRENT_IMPORT
408 );
409 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
410 if (chargingStation.getNumberOfPhases() === 3) {
411 currentPerPhaseSampledValueTemplates = {
ed3d2808 412 L1: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 413 chargingStation,
78085c42
JB
414 connectorId,
415 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
416 OCPP16MeterValuePhase.L1
417 ),
ed3d2808 418 L2: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 419 chargingStation,
78085c42
JB
420 connectorId,
421 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
422 OCPP16MeterValuePhase.L2
423 ),
ed3d2808 424 L3: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 425 chargingStation,
78085c42
JB
426 connectorId,
427 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
428 OCPP16MeterValuePhase.L3
429 ),
430 };
431 }
432 if (currentSampledValueTemplate) {
433 OCPP16ServiceUtils.checkMeasurandPowerDivider(
434 chargingStation,
435 currentSampledValueTemplate.measurand
436 );
fc040c43 437 const errMsg = `MeterValues measurand ${
78085c42
JB
438 currentSampledValueTemplate.measurand ??
439 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
440 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
2484ac1e 441 chargingStation.templateFile
78085c42
JB
442 }, cannot calculate ${
443 currentSampledValueTemplate.measurand ??
444 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
445 } measurand value`;
446 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
1b6498ba
JB
447 const connectorMaximumAvailablePower =
448 chargingStation.getConnectorMaximumAvailablePower(connectorId);
ad8537a7 449 let connectorMaximumAmperage: number;
78085c42
JB
450 switch (chargingStation.getCurrentOutType()) {
451 case CurrentType.AC:
ad8537a7 452 connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
78085c42 453 chargingStation.getNumberOfPhases(),
1b6498ba 454 connectorMaximumAvailablePower,
78085c42
JB
455 chargingStation.getVoltageOut()
456 );
457 if (chargingStation.getNumberOfPhases() === 3) {
458 const defaultFluctuatedAmperagePerPhase =
459 currentSampledValueTemplate.value &&
460 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
461 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
462 currentSampledValueTemplate.value,
463 connectorMaximumAmperage,
464 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
465 ),
78085c42
JB
466 currentSampledValueTemplate.fluctuationPercent ??
467 Constants.DEFAULT_FLUCTUATION_PERCENT
468 );
469 const phase1FluctuatedValue =
470 currentPerPhaseSampledValueTemplates?.L1?.value &&
471 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
472 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
473 currentPerPhaseSampledValueTemplates.L1.value,
474 connectorMaximumAmperage,
475 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 476 ),
78085c42
JB
477 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
478 Constants.DEFAULT_FLUCTUATION_PERCENT
479 );
480 const phase2FluctuatedValue =
481 currentPerPhaseSampledValueTemplates?.L2?.value &&
482 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
483 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
484 currentPerPhaseSampledValueTemplates.L2.value,
485 connectorMaximumAmperage,
486 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 487 ),
78085c42
JB
488 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
489 Constants.DEFAULT_FLUCTUATION_PERCENT
490 );
491 const phase3FluctuatedValue =
492 currentPerPhaseSampledValueTemplates?.L3?.value &&
493 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
494 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
495 currentPerPhaseSampledValueTemplates.L3.value,
496 connectorMaximumAmperage,
497 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
34464008 498 ),
78085c42
JB
499 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
500 Constants.DEFAULT_FLUCTUATION_PERCENT
501 );
502 currentMeasurandValues.L1 =
503 phase1FluctuatedValue ??
504 defaultFluctuatedAmperagePerPhase ??
ad8537a7 505 Utils.getRandomFloatRounded(connectorMaximumAmperage);
78085c42
JB
506 currentMeasurandValues.L2 =
507 phase2FluctuatedValue ??
508 defaultFluctuatedAmperagePerPhase ??
ad8537a7 509 Utils.getRandomFloatRounded(connectorMaximumAmperage);
78085c42
JB
510 currentMeasurandValues.L3 =
511 phase3FluctuatedValue ??
512 defaultFluctuatedAmperagePerPhase ??
ad8537a7 513 Utils.getRandomFloatRounded(connectorMaximumAmperage);
78085c42
JB
514 } else {
515 currentMeasurandValues.L1 = currentSampledValueTemplate.value
516 ? Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
517 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
518 currentSampledValueTemplate.value,
519 connectorMaximumAmperage,
520 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
521 ),
78085c42
JB
522 currentSampledValueTemplate.fluctuationPercent ??
523 Constants.DEFAULT_FLUCTUATION_PERCENT
524 )
ad8537a7 525 : Utils.getRandomFloatRounded(connectorMaximumAmperage);
78085c42
JB
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:
ad8537a7 536 connectorMaximumAmperage = DCElectricUtils.amperage(
1b6498ba 537 connectorMaximumAvailablePower,
78085c42
JB
538 chargingStation.getVoltageOut()
539 );
540 currentMeasurandValues.allPhases = currentSampledValueTemplate.value
541 ? Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
542 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
543 currentSampledValueTemplate.value,
544 connectorMaximumAmperage,
545 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }
546 ),
78085c42
JB
547 currentSampledValueTemplate.fluctuationPercent ??
548 Constants.DEFAULT_FLUCTUATION_PERCENT
549 )
ad8537a7 550 : Utils.getRandomFloatRounded(connectorMaximumAmperage);
78085c42
JB
551 break;
552 default:
fc040c43 553 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
78085c42
JB
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 (
ad8537a7
JB
564 Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
565 connectorMaximumAmperage ||
78085c42
JB
566 debug
567 ) {
568 logger.error(
569 `${chargingStation.logPrefix()} MeterValues measurand ${
570 meterValue.sampledValue[sampledValuesIndex].measurand ??
571 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
72092cfc 572 }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
78085c42 573 meterValue.sampledValue[sampledValuesIndex].value
ad8537a7 574 }/${connectorMaximumAmperage}`
78085c42
JB
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,
72092cfc 588 undefined,
78085c42
JB
589 phaseValue as OCPP16MeterValuePhase
590 )
591 );
592 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
593 if (
594 Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
ad8537a7 595 connectorMaximumAmperage ||
78085c42
JB
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
72092cfc 604 }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
78085c42 605 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
ad8537a7 606 }/${connectorMaximumAmperage}`
78085c42
JB
607 );
608 }
609 }
610 }
611 // Energy.Active.Import.Register measurand (default)
ed3d2808 612 const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab
JB
613 chargingStation,
614 connectorId
615 );
78085c42
JB
616 if (energySampledValueTemplate) {
617 OCPP16ServiceUtils.checkMeasurandPowerDivider(
618 chargingStation,
619 energySampledValueTemplate.measurand
620 );
621 const unitDivider =
622 energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
1b6498ba
JB
623 const connectorMaximumAvailablePower =
624 chargingStation.getConnectorMaximumAvailablePower(connectorId);
ad8537a7 625 const connectorMaximumEnergyRounded = Utils.roundTo(
1b6498ba 626 (connectorMaximumAvailablePower * interval) / (3600 * 1000),
78085c42
JB
627 2
628 );
629 const energyValueRounded = energySampledValueTemplate.value
630 ? // Cumulate the fluctuated value around the static one
631 Utils.getRandomFloatFluctuatedRounded(
7bc31f9c
JB
632 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
633 energySampledValueTemplate.value,
634 connectorMaximumEnergyRounded,
635 {
636 limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(),
637 unitMultiplier: unitDivider,
638 }
34464008 639 ),
78085c42
JB
640 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
641 )
ad8537a7 642 : Utils.getRandomFloatRounded(connectorMaximumEnergyRounded);
78085c42
JB
643 // Persist previous value on connector
644 if (
645 connector &&
3a13fc92 646 Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
78085c42 647 connector.energyActiveImportRegisterValue >= 0 &&
3a13fc92 648 Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
78085c42
JB
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;
ad8537a7 668 if (energyValueRounded > connectorMaximumEnergyRounded || debug) {
78085c42
JB
669 logger.error(
670 `${chargingStation.logPrefix()} MeterValues measurand ${
671 meterValue.sampledValue[sampledValuesIndex].measurand ??
672 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
673 }: connectorId ${connectorId}, transaction ${
72092cfc 674 connector?.transactionId
ad8537a7 675 }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
78085c42
JB
676 interval / (3600 * 1000),
677 4
678 )}h`
679 );
680 }
681 }
682 return meterValue;
683 }
684
e7aeea18
JB
685 public static buildTransactionBeginMeterValue(
686 chargingStation: ChargingStation,
687 connectorId: number,
78085c42 688 meterStart: number
e7aeea18 689 ): OCPP16MeterValue {
fd0c36fa 690 const meterValue: OCPP16MeterValue = {
c38f0ced 691 timestamp: new Date(),
fd0c36fa
JB
692 sampledValue: [],
693 };
9ccca265 694 // Energy.Active.Import.Register measurand (default)
ed3d2808 695 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab
JB
696 chargingStation,
697 connectorId
698 );
9ccca265 699 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
700 meterValue.sampledValue.push(
701 OCPP16ServiceUtils.buildSampledValue(
702 sampledValueTemplate,
2040a613 703 Utils.roundTo((meterStart ?? 0) / unitDivider, 4),
e7aeea18
JB
704 MeterValueContext.TRANSACTION_BEGIN
705 )
706 );
fd0c36fa
JB
707 return meterValue;
708 }
709
e7aeea18
JB
710 public static buildTransactionEndMeterValue(
711 chargingStation: ChargingStation,
712 connectorId: number,
78085c42 713 meterStop: number
e7aeea18 714 ): OCPP16MeterValue {
fd0c36fa 715 const meterValue: OCPP16MeterValue = {
c38f0ced 716 timestamp: new Date(),
fd0c36fa
JB
717 sampledValue: [],
718 };
9ccca265 719 // Energy.Active.Import.Register measurand (default)
ed3d2808 720 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab
JB
721 chargingStation,
722 connectorId
723 );
9ccca265 724 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
725 meterValue.sampledValue.push(
726 OCPP16ServiceUtils.buildSampledValue(
727 sampledValueTemplate,
2040a613 728 Utils.roundTo((meterStop ?? 0) / unitDivider, 4),
e7aeea18
JB
729 MeterValueContext.TRANSACTION_END
730 )
731 );
fd0c36fa
JB
732 return meterValue;
733 }
734
e7aeea18
JB
735 public static buildTransactionDataMeterValues(
736 transactionBeginMeterValue: OCPP16MeterValue,
737 transactionEndMeterValue: OCPP16MeterValue
738 ): OCPP16MeterValue[] {
fd0c36fa
JB
739 const meterValues: OCPP16MeterValue[] = [];
740 meterValues.push(transactionBeginMeterValue);
741 meterValues.push(transactionEndMeterValue);
742 return meterValues;
743 }
7bc31f9c 744
ed3d2808
JB
745 public static setChargingProfile(
746 chargingStation: ChargingStation,
747 connectorId: number,
884a6fdf 748 cp: OCPP16ChargingProfile
ed3d2808 749 ): void {
72092cfc
JB
750 if (
751 Utils.isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)
752 ) {
ed3d2808
JB
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 }
72092cfc
JB
758 if (
759 Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
760 ) {
ed3d2808
JB
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;
53ac516c 767 if (Utils.isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808
JB
768 chargingStation
769 .getConnectorStatus(connectorId)
72092cfc 770 ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
ed3d2808
JB
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 }
72092cfc 781 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
ed3d2808
JB
782 }
783
1b271a54
JB
784 public static parseJsonSchemaFile<T extends JsonType>(
785 relativePath: string,
786 moduleName?: string,
787 methodName?: string
788 ): JSONSchemaType<T> {
7164966d
JB
789 return super.parseJsonSchemaFile<T>(
790 path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
1b271a54
JB
791 OCPPVersion.VERSION_16,
792 moduleName,
793 methodName
7164966d 794 );
130783a7
JB
795 }
796
7bc31f9c
JB
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 {
fa7bccf4 827 if (Utils.isUndefined(chargingStation.powerDivider)) {
fc040c43 828 const errMsg = `MeterValues measurand ${
7bc31f9c
JB
829 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
830 }: powerDivider is undefined`;
fc040c43 831 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c 832 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
fa7bccf4 833 } else if (chargingStation?.powerDivider <= 0) {
fc040c43 834 const errMsg = `MeterValues measurand ${
7bc31f9c 835 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
fa7bccf4 836 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
fc040c43 837 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c
JB
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 }
6ed92bc1 872}