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