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