fix: check for the connector status definition in ATG
[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 }
3b0ed034 841 if (responses.includes(OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED)) {
225e32b0 842 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
366f75f6 843 }
225e32b0 844 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
366f75f6
JB
845 };
846
ed3d2808
JB
847 public static setChargingProfile(
848 chargingStation: ChargingStation,
849 connectorId: number,
5edd8ba0 850 cp: OCPP16ChargingProfile,
ed3d2808 851 ): void {
9bf0ef23 852 if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808 853 logger.error(
5edd8ba0 854 `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`,
ed3d2808 855 );
e1d9a0f4 856 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808 857 }
72092cfc
JB
858 if (
859 Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
860 ) {
ed3d2808 861 logger.error(
5edd8ba0 862 `${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 863 );
e1d9a0f4 864 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = [];
ed3d2808
JB
865 }
866 let cpReplaced = false;
9bf0ef23 867 if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
ed3d2808
JB
868 chargingStation
869 .getConnectorStatus(connectorId)
72092cfc 870 ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
ed3d2808
JB
871 if (
872 chargingProfile.chargingProfileId === cp.chargingProfileId ||
873 (chargingProfile.stackLevel === cp.stackLevel &&
874 chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
875 ) {
e1d9a0f4 876 chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp;
ed3d2808
JB
877 cpReplaced = true;
878 }
879 });
880 }
72092cfc 881 !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp);
ed3d2808
JB
882 }
883
73d87be1
JB
884 public static clearChargingProfiles = (
885 chargingStation: ChargingStation,
886 commandPayload: ClearChargingProfileRequest,
887 chargingProfiles: OCPP16ChargingProfile[] | undefined,
888 ): boolean => {
889 let clearedCP = false;
890 if (isNotEmptyArray(chargingProfiles)) {
891 chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
892 let clearCurrentCP = false;
893 if (chargingProfile.chargingProfileId === commandPayload.id) {
894 clearCurrentCP = true;
895 }
896 if (
897 !commandPayload.chargingProfilePurpose &&
898 chargingProfile.stackLevel === commandPayload.stackLevel
899 ) {
900 clearCurrentCP = true;
901 }
902 if (
903 !chargingProfile.stackLevel &&
904 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
905 ) {
906 clearCurrentCP = true;
907 }
908 if (
909 chargingProfile.stackLevel === commandPayload.stackLevel &&
910 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
911 ) {
912 clearCurrentCP = true;
913 }
914 if (clearCurrentCP) {
915 chargingProfiles.splice(index, 1);
916 logger.debug(
917 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
918 chargingProfile,
919 );
920 clearedCP = true;
921 }
922 });
923 }
924 return clearedCP;
925 };
926
1b271a54
JB
927 public static parseJsonSchemaFile<T extends JsonType>(
928 relativePath: string,
929 moduleName?: string,
5edd8ba0 930 methodName?: string,
1b271a54 931 ): JSONSchemaType<T> {
7164966d 932 return super.parseJsonSchemaFile<T>(
51022aa0 933 relativePath,
1b271a54
JB
934 OCPPVersion.VERSION_16,
935 moduleName,
5edd8ba0 936 methodName,
7164966d 937 );
130783a7
JB
938 }
939
7bc31f9c
JB
940 private static buildSampledValue(
941 sampledValueTemplate: SampledValueTemplate,
942 value: number,
943 context?: MeterValueContext,
5edd8ba0 944 phase?: OCPP16MeterValuePhase,
7bc31f9c
JB
945 ): OCPP16SampledValue {
946 const sampledValueValue = value ?? sampledValueTemplate?.value ?? null;
947 const sampledValueContext = context ?? sampledValueTemplate?.context ?? null;
948 const sampledValueLocation =
949 sampledValueTemplate?.location ??
e1d9a0f4 950 OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!);
7bc31f9c
JB
951 const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null;
952 return {
9bf0ef23 953 ...(!isNullOrUndefined(sampledValueTemplate.unit) && {
7bc31f9c
JB
954 unit: sampledValueTemplate.unit,
955 }),
9bf0ef23
JB
956 ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }),
957 ...(!isNullOrUndefined(sampledValueTemplate.measurand) && {
7bc31f9c
JB
958 measurand: sampledValueTemplate.measurand,
959 }),
9bf0ef23
JB
960 ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }),
961 ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }),
962 ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }),
e1d9a0f4 963 } as OCPP16SampledValue;
7bc31f9c
JB
964 }
965
966 private static checkMeasurandPowerDivider(
967 chargingStation: ChargingStation,
5edd8ba0 968 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c 969 ): void {
9bf0ef23 970 if (isUndefined(chargingStation.powerDivider)) {
fc040c43 971 const errMsg = `MeterValues measurand ${
7bc31f9c
JB
972 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
973 }: powerDivider is undefined`;
fc040c43 974 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c 975 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
fa7bccf4 976 } else if (chargingStation?.powerDivider <= 0) {
fc040c43 977 const errMsg = `MeterValues measurand ${
7bc31f9c 978 measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
fa7bccf4 979 }: powerDivider have zero or below value ${chargingStation.powerDivider}`;
fc040c43 980 logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
7bc31f9c
JB
981 throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES);
982 }
983 }
984
985 private static getMeasurandDefaultLocation(
5edd8ba0 986 measurandType: OCPP16MeterValueMeasurand,
7bc31f9c
JB
987 ): MeterValueLocation | undefined {
988 switch (measurandType) {
989 case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
990 return MeterValueLocation.EV;
991 }
992 }
993
3b0ed034
JB
994 // private static getMeasurandDefaultUnit(
995 // measurandType: OCPP16MeterValueMeasurand,
996 // ): MeterValueUnit | undefined {
997 // switch (measurandType) {
998 // case OCPP16MeterValueMeasurand.CURRENT_EXPORT:
999 // case OCPP16MeterValueMeasurand.CURRENT_IMPORT:
1000 // case OCPP16MeterValueMeasurand.CURRENT_OFFERED:
1001 // return MeterValueUnit.AMP;
1002 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER:
1003 // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER:
1004 // return MeterValueUnit.WATT_HOUR;
1005 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT:
1006 // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT:
1007 // case OCPP16MeterValueMeasurand.POWER_OFFERED:
1008 // return MeterValueUnit.WATT;
1009 // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE:
1010 // return MeterValueUnit.PERCENT;
1011 // case OCPP16MeterValueMeasurand.VOLTAGE:
1012 // return MeterValueUnit.VOLT;
1013 // }
1014 // }
6ed92bc1 1015}