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