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