Commit | Line | Data |
---|---|---|
edd13439 | 1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
c8eeb62b | 2 | |
130783a7 JB |
3 | import type { JSONSchemaType } from 'ajv'; |
4 | ||
366f75f6 | 5 | import { OCPP16Constants } from './OCPP16Constants'; |
ae725be3 | 6 | import { type ChargingStation, hasFeatureProfile } from '../../../charging-station'; |
268a74bb | 7 | import { OCPPError } from '../../../exception'; |
e7aeea18 | 8 | import { |
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 |
37 | import { |
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 | 52 | import { OCPPServiceUtils } from '../OCPPServiceUtils'; |
6ed92bc1 | 53 | |
7bc31f9c | 54 | export 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 | } |