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