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