refactor: cleanup date handling
[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(
a37fc6dc
JB
414 powerPerPhaseSampledValueTemplates[
415 `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
416 ]! ?? powerSampledValueTemplate,
417 powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
72092cfc 418 undefined,
5edd8ba0
JB
419 phaseValue as OCPP16MeterValuePhase,
420 ),
78085c42
JB
421 );
422 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
9bf0ef23 423 const connectorMaximumPowerPerPhaseRounded = roundTo(
ad8537a7 424 connectorMaximumPowerPerPhase / unitDivider,
5edd8ba0 425 2,
ad8537a7 426 );
9bf0ef23 427 const connectorMinimumPowerPerPhaseRounded = roundTo(
860ef183 428 connectorMinimumPowerPerPhase / unitDivider,
5edd8ba0 429 2,
860ef183 430 );
78085c42 431 if (
9bf0ef23 432 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
ad8537a7 433 connectorMaximumPowerPerPhaseRounded ||
9bf0ef23 434 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
860ef183 435 connectorMinimumPowerPerPhaseRounded ||
78085c42
JB
436 debug
437 ) {
438 logger.error(
439 `${chargingStation.logPrefix()} MeterValues measurand ${
440 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
441 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
442 }: phase ${
443 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
5edd8ba0 444 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
78085c42 445 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
5edd8ba0 446 }/${connectorMaximumPowerPerPhaseRounded}`,
78085c42
JB
447 );
448 }
449 }
450 }
451 // Current.Import measurand
ed3d2808 452 const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 453 chargingStation,
78085c42 454 connectorId,
5edd8ba0 455 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
78085c42 456 );
abe9e9dd 457 let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
78085c42
JB
458 if (chargingStation.getNumberOfPhases() === 3) {
459 currentPerPhaseSampledValueTemplates = {
ed3d2808 460 L1: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 461 chargingStation,
78085c42
JB
462 connectorId,
463 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 464 OCPP16MeterValuePhase.L1,
78085c42 465 ),
ed3d2808 466 L2: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 467 chargingStation,
78085c42
JB
468 connectorId,
469 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 470 OCPP16MeterValuePhase.L2,
78085c42 471 ),
ed3d2808 472 L3: OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 473 chargingStation,
78085c42
JB
474 connectorId,
475 OCPP16MeterValueMeasurand.CURRENT_IMPORT,
5edd8ba0 476 OCPP16MeterValuePhase.L3,
78085c42
JB
477 ),
478 };
479 }
480 if (currentSampledValueTemplate) {
481 OCPP16ServiceUtils.checkMeasurandPowerDivider(
482 chargingStation,
e1d9a0f4 483 currentSampledValueTemplate.measurand!,
78085c42 484 );
fc040c43 485 const errMsg = `MeterValues measurand ${
78085c42
JB
486 currentSampledValueTemplate.measurand ??
487 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
488 }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${
2484ac1e 489 chargingStation.templateFile
78085c42
JB
490 }, cannot calculate ${
491 currentSampledValueTemplate.measurand ??
492 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
493 } measurand value`;
abe9e9dd 494 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
1b6498ba
JB
495 const connectorMaximumAvailablePower =
496 chargingStation.getConnectorMaximumAvailablePower(connectorId);
860ef183 497 const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
ad8537a7 498 let connectorMaximumAmperage: number;
78085c42
JB
499 switch (chargingStation.getCurrentOutType()) {
500 case CurrentType.AC:
ad8537a7 501 connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
78085c42 502 chargingStation.getNumberOfPhases(),
1b6498ba 503 connectorMaximumAvailablePower,
5edd8ba0 504 chargingStation.getVoltageOut(),
78085c42
JB
505 );
506 if (chargingStation.getNumberOfPhases() === 3) {
507 const defaultFluctuatedAmperagePerPhase =
508 currentSampledValueTemplate.value &&
9bf0ef23 509 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
510 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
511 currentSampledValueTemplate.value,
512 connectorMaximumAmperage,
5edd8ba0 513 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
7bc31f9c 514 ),
78085c42 515 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 516 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
517 );
518 const phase1FluctuatedValue =
e1d9a0f4 519 currentPerPhaseSampledValueTemplates.L1?.value &&
9bf0ef23 520 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
521 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
522 currentPerPhaseSampledValueTemplates.L1.value,
523 connectorMaximumAmperage,
5edd8ba0 524 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 525 ),
78085c42 526 currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
5edd8ba0 527 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
528 );
529 const phase2FluctuatedValue =
e1d9a0f4 530 currentPerPhaseSampledValueTemplates.L2?.value &&
9bf0ef23 531 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
532 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
533 currentPerPhaseSampledValueTemplates.L2.value,
534 connectorMaximumAmperage,
5edd8ba0 535 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 536 ),
78085c42 537 currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
5edd8ba0 538 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
539 );
540 const phase3FluctuatedValue =
e1d9a0f4 541 currentPerPhaseSampledValueTemplates.L3?.value &&
9bf0ef23 542 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
543 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
544 currentPerPhaseSampledValueTemplates.L3.value,
545 connectorMaximumAmperage,
5edd8ba0 546 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
34464008 547 ),
78085c42 548 currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
5edd8ba0 549 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42
JB
550 );
551 currentMeasurandValues.L1 =
e1d9a0f4
JB
552 (phase1FluctuatedValue as number) ??
553 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 554 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42 555 currentMeasurandValues.L2 =
e1d9a0f4
JB
556 (phase2FluctuatedValue as number) ??
557 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 558 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42 559 currentMeasurandValues.L3 =
e1d9a0f4
JB
560 (phase3FluctuatedValue as number) ??
561 (defaultFluctuatedAmperagePerPhase as number) ??
9bf0ef23 562 getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42
JB
563 } else {
564 currentMeasurandValues.L1 = currentSampledValueTemplate.value
9bf0ef23 565 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
566 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
567 currentSampledValueTemplate.value,
568 connectorMaximumAmperage,
5edd8ba0 569 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
7bc31f9c 570 ),
78085c42 571 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 572 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 573 )
9bf0ef23 574 : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42
JB
575 currentMeasurandValues.L2 = 0;
576 currentMeasurandValues.L3 = 0;
577 }
9bf0ef23 578 currentMeasurandValues.allPhases = roundTo(
78085c42
JB
579 (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) /
580 chargingStation.getNumberOfPhases(),
5edd8ba0 581 2,
78085c42
JB
582 );
583 break;
584 case CurrentType.DC:
ad8537a7 585 connectorMaximumAmperage = DCElectricUtils.amperage(
1b6498ba 586 connectorMaximumAvailablePower,
5edd8ba0 587 chargingStation.getVoltageOut(),
78085c42
JB
588 );
589 currentMeasurandValues.allPhases = currentSampledValueTemplate.value
9bf0ef23 590 ? getRandomFloatFluctuatedRounded(
7bc31f9c
JB
591 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
592 currentSampledValueTemplate.value,
593 connectorMaximumAmperage,
5edd8ba0 594 { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() },
7bc31f9c 595 ),
78085c42 596 currentSampledValueTemplate.fluctuationPercent ??
5edd8ba0 597 Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 598 )
9bf0ef23 599 : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
78085c42
JB
600 break;
601 default:
fc040c43 602 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
78085c42
JB
603 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
604 }
605 meterValue.sampledValue.push(
606 OCPP16ServiceUtils.buildSampledValue(
607 currentSampledValueTemplate,
5edd8ba0
JB
608 currentMeasurandValues.allPhases,
609 ),
78085c42
JB
610 );
611 const sampledValuesIndex = meterValue.sampledValue.length - 1;
612 if (
9bf0ef23 613 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
ad8537a7 614 connectorMaximumAmperage ||
9bf0ef23 615 convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
860ef183 616 connectorMinimumAmperage ||
78085c42
JB
617 debug
618 ) {
619 logger.error(
620 `${chargingStation.logPrefix()} MeterValues measurand ${
621 meterValue.sampledValue[sampledValuesIndex].measurand ??
622 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5edd8ba0 623 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
78085c42 624 meterValue.sampledValue[sampledValuesIndex].value
5edd8ba0 625 }/${connectorMaximumAmperage}`,
78085c42
JB
626 );
627 }
628 for (
629 let phase = 1;
630 chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
631 phase++
632 ) {
633 const phaseValue = `L${phase}`;
634 meterValue.sampledValue.push(
635 OCPP16ServiceUtils.buildSampledValue(
a37fc6dc
JB
636 currentPerPhaseSampledValueTemplates[
637 phaseValue as keyof MeasurandPerPhaseSampledValueTemplates
638 ]! ?? currentSampledValueTemplate,
639 currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates],
72092cfc 640 undefined,
5edd8ba0
JB
641 phaseValue as OCPP16MeterValuePhase,
642 ),
78085c42
JB
643 );
644 const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1;
645 if (
9bf0ef23 646 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
ad8537a7 647 connectorMaximumAmperage ||
9bf0ef23 648 convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
860ef183 649 connectorMinimumAmperage ||
78085c42
JB
650 debug
651 ) {
652 logger.error(
653 `${chargingStation.logPrefix()} MeterValues measurand ${
654 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
655 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
656 }: phase ${
657 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
5edd8ba0 658 }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
78085c42 659 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
5edd8ba0 660 }/${connectorMaximumAmperage}`,
78085c42
JB
661 );
662 }
663 }
664 }
665 // Energy.Active.Import.Register measurand (default)
ed3d2808 666 const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 667 chargingStation,
5edd8ba0 668 connectorId,
492cf6ab 669 );
78085c42
JB
670 if (energySampledValueTemplate) {
671 OCPP16ServiceUtils.checkMeasurandPowerDivider(
672 chargingStation,
e1d9a0f4 673 energySampledValueTemplate.measurand!,
78085c42
JB
674 );
675 const unitDivider =
676 energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
1b6498ba
JB
677 const connectorMaximumAvailablePower =
678 chargingStation.getConnectorMaximumAvailablePower(connectorId);
9bf0ef23 679 const connectorMaximumEnergyRounded = roundTo(
1b6498ba 680 (connectorMaximumAvailablePower * interval) / (3600 * 1000),
5edd8ba0 681 2,
78085c42
JB
682 );
683 const energyValueRounded = energySampledValueTemplate.value
684 ? // Cumulate the fluctuated value around the static one
9bf0ef23 685 getRandomFloatFluctuatedRounded(
7bc31f9c
JB
686 OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue(
687 energySampledValueTemplate.value,
688 connectorMaximumEnergyRounded,
689 {
690 limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(),
691 unitMultiplier: unitDivider,
5edd8ba0 692 },
34464008 693 ),
5edd8ba0 694 energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT,
78085c42 695 )
9bf0ef23 696 : getRandomFloatRounded(connectorMaximumEnergyRounded);
78085c42 697 // Persist previous value on connector
e1d9a0f4
JB
698 if (connector) {
699 if (
700 isNullOrUndefined(connector.energyActiveImportRegisterValue) === false &&
701 connector.energyActiveImportRegisterValue! >= 0 &&
702 isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false &&
703 connector.transactionEnergyActiveImportRegisterValue! >= 0
704 ) {
705 connector.energyActiveImportRegisterValue! += energyValueRounded;
706 connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded;
707 } else {
708 connector.energyActiveImportRegisterValue = 0;
709 connector.transactionEnergyActiveImportRegisterValue = 0;
710 }
78085c42
JB
711 }
712 meterValue.sampledValue.push(
713 OCPP16ServiceUtils.buildSampledValue(
714 energySampledValueTemplate,
9bf0ef23 715 roundTo(
78085c42
JB
716 chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) /
717 unitDivider,
5edd8ba0
JB
718 2,
719 ),
720 ),
78085c42
JB
721 );
722 const sampledValuesIndex = meterValue.sampledValue.length - 1;
ad8537a7 723 if (energyValueRounded > connectorMaximumEnergyRounded || debug) {
78085c42
JB
724 logger.error(
725 `${chargingStation.logPrefix()} MeterValues measurand ${
726 meterValue.sampledValue[sampledValuesIndex].measurand ??
727 OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
5edd8ba0 728 }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${roundTo(
78085c42 729 interval / (3600 * 1000),
5edd8ba0
JB
730 4,
731 )}h`,
78085c42
JB
732 );
733 }
734 }
735 return meterValue;
736 }
737
e7aeea18
JB
738 public static buildTransactionBeginMeterValue(
739 chargingStation: ChargingStation,
740 connectorId: number,
5edd8ba0 741 meterStart: number,
e7aeea18 742 ): OCPP16MeterValue {
fd0c36fa 743 const meterValue: OCPP16MeterValue = {
c38f0ced 744 timestamp: new Date(),
fd0c36fa
JB
745 sampledValue: [],
746 };
9ccca265 747 // Energy.Active.Import.Register measurand (default)
ed3d2808 748 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 749 chargingStation,
5edd8ba0 750 connectorId,
492cf6ab 751 );
9ccca265 752 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
753 meterValue.sampledValue.push(
754 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 755 sampledValueTemplate!,
9bf0ef23 756 roundTo((meterStart ?? 0) / unitDivider, 4),
5edd8ba0
JB
757 MeterValueContext.TRANSACTION_BEGIN,
758 ),
e7aeea18 759 );
fd0c36fa
JB
760 return meterValue;
761 }
762
e7aeea18
JB
763 public static buildTransactionEndMeterValue(
764 chargingStation: ChargingStation,
765 connectorId: number,
5edd8ba0 766 meterStop: number,
e7aeea18 767 ): OCPP16MeterValue {
fd0c36fa 768 const meterValue: OCPP16MeterValue = {
c38f0ced 769 timestamp: new Date(),
fd0c36fa
JB
770 sampledValue: [],
771 };
9ccca265 772 // Energy.Active.Import.Register measurand (default)
ed3d2808 773 const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
492cf6ab 774 chargingStation,
5edd8ba0 775 connectorId,
492cf6ab 776 );
9ccca265 777 const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
e7aeea18
JB
778 meterValue.sampledValue.push(
779 OCPP16ServiceUtils.buildSampledValue(
e1d9a0f4 780 sampledValueTemplate!,
9bf0ef23 781 roundTo((meterStop ?? 0) / unitDivider, 4),
5edd8ba0
JB
782 MeterValueContext.TRANSACTION_END,
783 ),
e7aeea18 784 );
fd0c36fa
JB
785 return meterValue;
786 }
787
e7aeea18
JB
788 public static buildTransactionDataMeterValues(
789 transactionBeginMeterValue: OCPP16MeterValue,
5edd8ba0 790 transactionEndMeterValue: OCPP16MeterValue,
e7aeea18 791 ): OCPP16MeterValue[] {
fd0c36fa
JB
792 const meterValues: OCPP16MeterValue[] = [];
793 meterValues.push(transactionBeginMeterValue);
794 meterValues.push(transactionEndMeterValue);
795 return meterValues;
796 }
7bc31f9c 797
ed3d2808
JB
798 public static setChargingProfile(
799 chargingStation: ChargingStation,
800 connectorId: number,
5edd8ba0 801 cp: OCPP16ChargingProfile,
ed3d2808 802 ): void {
9bf0ef23 803 if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808 804 logger.error(
5edd8ba0 805 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
ed3d2808 806 );
e1d9a0f4 807 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808 808 }
72092cfc
JB
809 if (
810 Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
811 ) {
ed3d2808 812 logger.error(
5edd8ba0 813 `${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 814 );
e1d9a0f4 815 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808
JB
816 }
817 let cpReplaced = false;
9bf0ef23 818 if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808
JB
819 chargingStation
820 .getConnectorStatus(connectorId)
72092cfc 821 ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
ed3d2808
JB
822 if (
823 chargingProfile.chargingProfileId === cp.chargingProfileId ||
824 (chargingProfile.stackLevel === cp.stackLevel &&
825 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
826 ) {
e1d9a0f4 827 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp;
ed3d2808
JB
828 cpReplaced = true;
829 }
830 });
831 }
72092cfc 832 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
ed3d2808
JB
833 }
834
1b271a54
JB
835 public static parseJsonSchemaFile<T extends JsonType>(
836 relativePath: string,
837 moduleName?: string,
5edd8ba0 838 methodName?: string,
1b271a54 839 ): JSONSchemaType<T> {
7164966d 840 return super.parseJsonSchemaFile<T>(
51022aa0 841 relativePath,
1b271a54
JB
842 OCPPVersion.VERSION_16,
843 moduleName,
5edd8ba0 844 methodName,
7164966d 845 );
130783a7
JB
846 }
847
66dd3447
JB
848 public static async isIdTagAuthorized(
849 chargingStation: ChargingStation,
850 connectorId: number,
5edd8ba0 851 idTag: string,
66dd3447
JB
852 ): Promise<boolean> {
853 let authorized = false;
e1d9a0f4 854 const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!;
66dd3447
JB
855 if (OCPP16ServiceUtils.isIdTagLocalAuthorized(chargingStation, idTag)) {
856 connectorStatus.localAuthorizeIdTag = idTag;
857 connectorStatus.idTagLocalAuthorized = true;
858 authorized = true;
859 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
860 connectorStatus.authorizeIdTag = idTag;
861 authorized = await OCPP16ServiceUtils.isIdTagRemoteAuthorized(chargingStation, idTag);
862 } else {
863 logger.warn(
864 `${chargingStation.logPrefix()} The charging station configuration expects authorize at
5edd8ba0 865 remote start transaction but local authorization or authorize isn't enabled`,
66dd3447
JB
866 );
867 }
868 return authorized;
869 }
870
7bc31f9c
JB
871 private static buildSampledValue(
872 sampledValueTemplate: SampledValueTemplate,
873 value: number,
874 context?: MeterValueContext,
5edd8ba0 875 phase?: OCPP16MeterValuePhase,
7bc31f9c
JB
876 ): OCPP16SampledValue {
877 const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
878 const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
879 const sampledValueLocation =
880 sampledValueTemplate?.location ??
e1d9a0f4 881 OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
7bc31f9c
JB
882 const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
883 return {
9bf0ef23 884 ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
7bc31f9c
JB
885 unit: sampledValueTemplate.unit,
886 }),
9bf0ef23
JB
887 ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
888 ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
7bc31f9c
JB
889 measurand: sampledValueTemplate.measurand,
890 }),
9bf0ef23
JB
891 ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
892 ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
893 ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
e1d9a0f4 894 } as OCPP16SampledValue;
7bc31f9c
JB
895 }
896
897 private static checkMeasurandPowerDivider(
898 chargingStation: ChargingStation,
5edd8ba0 899 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c 900 ): void {
9bf0ef23 901 if (isUndefined(chargingStation.powerDivider)) {
fc040c43 902 const errMsg = `MeterValues measurand ${
7bc31f9c
JB
903 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
904 }: powerDivider is undefined`;
fc040c43 905 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c 906 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
fa7bccf4 907 } else if (chargingStation?.powerDivider <= 0) {
fc040c43 908 const errMsg = `MeterValues measurand ${
7bc31f9c 909 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
fa7bccf4 910 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
fc040c43 911 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c
JB
912 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
913 }
914 }
915
916 private static getMeasurandDefaultLocation(
5edd8ba0 917 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
918 ): MeterValueLocation | undefined {
919 switch (measurandType) {
920 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
921 return MeterValueLocation.EV;
922 }
923 }
924
925 private static getMeasurandDefaultUnit(
5edd8ba0 926 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
927 ): MeterValueUnit | undefined {
928 switch (measurandType) {
929 case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
930 case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
931 case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
932 return MeterValueUnit.AMP;
933 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
934 case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
935 return MeterValueUnit.WATT_HOUR;
936 case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
937 case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
938 case OCPP16MeterValueMeasurand.POWER_OFFERED:
939 return MeterValueUnit.WATT;
940 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
941 return MeterValueUnit.PERCENT;
942 case OCPP16MeterValueMeasurand.VOLTAGE:
943 return MeterValueUnit.VOLT;
944 }
945 }
66dd3447
JB
946
947 private static isIdTagLocalAuthorized(chargingStation: ChargingStation, idTag: string): boolean {
948 return (
949 chargingStation.getLocalAuthListEnabled() === true &&
950 chargingStation.hasIdTags() === true &&
9bf0ef23 951 isNotEmptyString(
66dd3447 952 chargingStation.idTagsCache
e1d9a0f4 953 .getIdTags(getIdTagsFile(chargingStation.stationInfo)!)
5edd8ba0 954 ?.find((tag) => tag === idTag),
66dd3447
JB
955 )
956 );
957 }
958
959 private static async isIdTagRemoteAuthorized(
960 chargingStation: ChargingStation,
5edd8ba0 961 idTag: string,
66dd3447
JB
962 ): Promise<boolean> {
963 const authorizeResponse: OCPP16AuthorizeResponse =
964 await chargingStation.ocppRequestService.requestHandler<
965 OCPP16AuthorizeRequest,
966 OCPP16AuthorizeResponse
967 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
968 idTag: idTag,
969 });
970 return authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED;
971 }
6ed92bc1 972}