fix: ensure charging schedule periods are sorted by startPeriod
[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 {
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
JB
57 ): boolean {
58 if (!chargingStation.hasFeatureProfile(featureProfile)) {
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
5edd8ba0 729 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${roundTo(
78085c42 730 interval / (3600 * 1000),
5edd8ba0
JB
731 4,
732 )}h`,
78085c42
JB
733 );
734 }
735 }
736 return meterValue;
737 }
738
e7aeea18
JB
739 public static buildTransactionBeginMeterValue(
740 chargingStation: ChargingStation,
741 connectorId: number,
5edd8ba0 742 meterStart: number,
e7aeea18 743 ): OCPP16MeterValue {
fd0c36fa 744 const meterValue: OCPP16MeterValue = {
c38f0ced 745 timestamp: new Date(),
fd0c36fa
JB
746 sampledValue: [],
747 };
9ccca265 748 // Energy.Active.Import.Register measurand (default)
ed3d2808 749 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 750 chargingStation,
5edd8ba0 751 connectorId,
492cf6ab 752 );
9ccca265 753 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
754 meterValue.sampledValue.push(
755 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 756 sampledValueTemplate!,
9bf0ef23 757 roundTo((meterStart ?? 0) / unitDivider, 4),
5edd8ba0
JB
758 MeterValueContext.TRANSACTION_BEGIN,
759 ),
e7aeea18 760 );
fd0c36fa
JB
761 return meterValue;
762 }
763
e7aeea18
JB
764 public static buildTransactionEndMeterValue(
765 chargingStation: ChargingStation,
766 connectorId: number,
5edd8ba0 767 meterStop: number,
e7aeea18 768 ): OCPP16MeterValue {
fd0c36fa 769 const meterValue: OCPP16MeterValue = {
c38f0ced 770 timestamp: new Date(),
fd0c36fa
JB
771 sampledValue: [],
772 };
9ccca265 773 // Energy.Active.Import.Register measurand (default)
ed3d2808 774 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 775 chargingStation,
5edd8ba0 776 connectorId,
492cf6ab 777 );
9ccca265 778 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
779 meterValue.sampledValue.push(
780 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 781 sampledValueTemplate!,
9bf0ef23 782 roundTo((meterStop ?? 0) / unitDivider, 4),
5edd8ba0
JB
783 MeterValueContext.TRANSACTION_END,
784 ),
e7aeea18 785 );
fd0c36fa
JB
786 return meterValue;
787 }
788
e7aeea18
JB
789 public static buildTransactionDataMeterValues(
790 transactionBeginMeterValue: OCPP16MeterValue,
5edd8ba0 791 transactionEndMeterValue: OCPP16MeterValue,
e7aeea18 792 ): OCPP16MeterValue[] {
fd0c36fa
JB
793 const meterValues: OCPP16MeterValue[] = [];
794 meterValues.push(transactionBeginMeterValue);
795 meterValues.push(transactionEndMeterValue);
796 return meterValues;
797 }
7bc31f9c 798
ed3d2808
JB
799 public static setChargingProfile(
800 chargingStation: ChargingStation,
801 connectorId: number,
5edd8ba0 802 cp: OCPP16ChargingProfile,
ed3d2808 803 ): void {
9bf0ef23 804 if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808 805 logger.error(
5edd8ba0 806 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
ed3d2808 807 );
e1d9a0f4 808 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808 809 }
72092cfc
JB
810 if (
811 Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
812 ) {
ed3d2808 813 logger.error(
5edd8ba0 814 `${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 815 );
e1d9a0f4 816 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808
JB
817 }
818 let cpReplaced = false;
9bf0ef23 819 if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808
JB
820 chargingStation
821 .getConnectorStatus(connectorId)
72092cfc 822 ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
ed3d2808
JB
823 if (
824 chargingProfile.chargingProfileId === cp.chargingProfileId ||
825 (chargingProfile.stackLevel === cp.stackLevel &&
826 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
827 ) {
e1d9a0f4 828 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp;
ed3d2808
JB
829 cpReplaced = true;
830 }
831 });
832 }
72092cfc 833 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
ed3d2808
JB
834 }
835
73d87be1
JB
836 public static clearChargingProfiles = (
837 chargingStation: ChargingStation,
838 commandPayload: ClearChargingProfileRequest,
839 chargingProfiles: OCPP16ChargingProfile[] | undefined,
840 ): boolean => {
841 let clearedCP = false;
842 if (isNotEmptyArray(chargingProfiles)) {
843 chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
844 let clearCurrentCP = false;
845 if (chargingProfile.chargingProfileId === commandPayload.id) {
846 clearCurrentCP = true;
847 }
848 if (
849 !commandPayload.chargingProfilePurpose &&
850 chargingProfile.stackLevel === commandPayload.stackLevel
851 ) {
852 clearCurrentCP = true;
853 }
854 if (
855 !chargingProfile.stackLevel &&
856 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
857 ) {
858 clearCurrentCP = true;
859 }
860 if (
861 chargingProfile.stackLevel === commandPayload.stackLevel &&
862 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
863 ) {
864 clearCurrentCP = true;
865 }
866 if (clearCurrentCP) {
867 chargingProfiles.splice(index, 1);
868 logger.debug(
869 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
870 chargingProfile,
871 );
872 clearedCP = true;
873 }
874 });
875 }
876 return clearedCP;
877 };
878
1b271a54
JB
879 public static parseJsonSchemaFile<T extends JsonType>(
880 relativePath: string,
881 moduleName?: string,
5edd8ba0 882 methodName?: string,
1b271a54 883 ): JSONSchemaType<T> {
7164966d 884 return super.parseJsonSchemaFile<T>(
51022aa0 885 relativePath,
1b271a54
JB
886 OCPPVersion.VERSION_16,
887 moduleName,
5edd8ba0 888 methodName,
7164966d 889 );
130783a7
JB
890 }
891
66dd3447
JB
892 public static async isIdTagAuthorized(
893 chargingStation: ChargingStation,
894 connectorId: number,
5edd8ba0 895 idTag: string,
66dd3447
JB
896 ): Promise<boolean> {
897 let authorized = false;
e1d9a0f4 898 const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!;
66dd3447
JB
899 if (OCPP16ServiceUtils.isIdTagLocalAuthorized(chargingStation, idTag)) {
900 connectorStatus.localAuthorizeIdTag = idTag;
901 connectorStatus.idTagLocalAuthorized = true;
902 authorized = true;
903 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
904 connectorStatus.authorizeIdTag = idTag;
905 authorized = await OCPP16ServiceUtils.isIdTagRemoteAuthorized(chargingStation, idTag);
906 } else {
907 logger.warn(
908 `${chargingStation.logPrefix()} The charging station configuration expects authorize at
5edd8ba0 909 remote start transaction but local authorization or authorize isn't enabled`,
66dd3447
JB
910 );
911 }
912 return authorized;
913 }
914
7bc31f9c
JB
915 private static buildSampledValue(
916 sampledValueTemplate: SampledValueTemplate,
917 value: number,
918 context?: MeterValueContext,
5edd8ba0 919 phase?: OCPP16MeterValuePhase,
7bc31f9c
JB
920 ): OCPP16SampledValue {
921 const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
922 const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
923 const sampledValueLocation =
924 sampledValueTemplate?.location ??
e1d9a0f4 925 OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
7bc31f9c
JB
926 const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
927 return {
9bf0ef23 928 ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
7bc31f9c
JB
929 unit: sampledValueTemplate.unit,
930 }),
9bf0ef23
JB
931 ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
932 ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
7bc31f9c
JB
933 measurand: sampledValueTemplate.measurand,
934 }),
9bf0ef23
JB
935 ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
936 ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
937 ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
e1d9a0f4 938 } as OCPP16SampledValue;
7bc31f9c
JB
939 }
940
941 private static checkMeasurandPowerDivider(
942 chargingStation: ChargingStation,
5edd8ba0 943 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c 944 ): void {
9bf0ef23 945 if (isUndefined(chargingStation.powerDivider)) {
fc040c43 946 const errMsg = `MeterValues measurand ${
7bc31f9c
JB
947 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
948 }: powerDivider is undefined`;
fc040c43 949 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c 950 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
fa7bccf4 951 } else if (chargingStation?.powerDivider <= 0) {
fc040c43 952 const errMsg = `MeterValues measurand ${
7bc31f9c 953 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
fa7bccf4 954 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
fc040c43 955 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c
JB
956 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
957 }
958 }
959
960 private static getMeasurandDefaultLocation(
5edd8ba0 961 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
962 ): MeterValueLocation | undefined {
963 switch (measurandType) {
964 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
965 return MeterValueLocation.EV;
966 }
967 }
968
969 private static getMeasurandDefaultUnit(
5edd8ba0 970 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
971 ): MeterValueUnit | undefined {
972 switch (measurandType) {
973 case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
974 case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
975 case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
976 return MeterValueUnit.AMP;
977 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
978 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
979 return MeterValueUnit.WATT_HOUR;
980 case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
981 case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
982 case OCPP16MeterValueMeasurand.POWER_OFFERED:
983 return MeterValueUnit.WATT;
984 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
985 return MeterValueUnit.PERCENT;
986 case OCPP16MeterValueMeasurand.VOLTAGE:
987 return MeterValueUnit.VOLT;
988 }
989 }
66dd3447
JB
990
991 private static isIdTagLocalAuthorized(chargingStation: ChargingStation, idTag: string): boolean {
992 return (
993 chargingStation.getLocalAuthListEnabled() === true &&
994 chargingStation.hasIdTags() === true &&
9bf0ef23 995 isNotEmptyString(
66dd3447 996 chargingStation.idTagsCache
e1d9a0f4 997 .getIdTags(getIdTagsFile(chargingStation.stationInfo)!)
5edd8ba0 998 ?.find((tag) => tag === idTag),
66dd3447
JB
999 )
1000 );
1001 }
1002
1003 private static async isIdTagRemoteAuthorized(
1004 chargingStation: ChargingStation,
5edd8ba0 1005 idTag: string,
66dd3447
JB
1006 ): Promise<boolean> {
1007 const authorizeResponse: OCPP16AuthorizeResponse =
1008 await chargingStation.ocppRequestService.requestHandler<
1009 OCPP16AuthorizeRequest,
1010 OCPP16AuthorizeResponse
1011 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
1012 idTag: idTag,
1013 });
1014 return authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED;
1015 }
6ed92bc1 1016}