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( | |
414 | (powerPerPhaseSampledValueTemplates[`L${phase}`] as SampledValueTemplate) ?? | |
415 | powerSampledValueTemplate, | |
416 | powerMeasurandValues[`L${phase}`] as number, | |
72092cfc | 417 | undefined, |
5edd8ba0 JB |
418 | phaseValue as OCPP16MeterValuePhase, |
419 | ), | |
78085c42 JB |
420 | ); |
421 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
9bf0ef23 | 422 | const connectorMaximumPowerPerPhaseRounded = roundTo( |
ad8537a7 | 423 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 424 | 2, |
ad8537a7 | 425 | ); |
9bf0ef23 | 426 | const connectorMinimumPowerPerPhaseRounded = roundTo( |
860ef183 | 427 | connectorMinimumPowerPerPhase / unitDivider, |
5edd8ba0 | 428 | 2, |
860ef183 | 429 | ); |
78085c42 | 430 | if ( |
9bf0ef23 | 431 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > |
ad8537a7 | 432 | connectorMaximumPowerPerPhaseRounded || |
9bf0ef23 | 433 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < |
860ef183 | 434 | connectorMinimumPowerPerPhaseRounded || |
78085c42 JB |
435 | debug |
436 | ) { | |
437 | logger.error( | |
438 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
439 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
440 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
441 | }: phase ${ | |
442 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
5edd8ba0 | 443 | }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${ |
78085c42 | 444 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value |
5edd8ba0 | 445 | }/${connectorMaximumPowerPerPhaseRounded}`, |
78085c42 JB |
446 | ); |
447 | } | |
448 | } | |
449 | } | |
450 | // Current.Import measurand | |
ed3d2808 | 451 | const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 452 | chargingStation, |
78085c42 | 453 | connectorId, |
5edd8ba0 | 454 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, |
78085c42 | 455 | ); |
abe9e9dd | 456 | let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; |
78085c42 JB |
457 | if (chargingStation.getNumberOfPhases() === 3) { |
458 | currentPerPhaseSampledValueTemplates = { | |
ed3d2808 | 459 | L1: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 460 | chargingStation, |
78085c42 JB |
461 | connectorId, |
462 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 463 | OCPP16MeterValuePhase.L1, |
78085c42 | 464 | ), |
ed3d2808 | 465 | L2: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 466 | chargingStation, |
78085c42 JB |
467 | connectorId, |
468 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 469 | OCPP16MeterValuePhase.L2, |
78085c42 | 470 | ), |
ed3d2808 | 471 | L3: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 472 | chargingStation, |
78085c42 JB |
473 | connectorId, |
474 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 475 | OCPP16MeterValuePhase.L3, |
78085c42 JB |
476 | ), |
477 | }; | |
478 | } | |
479 | if (currentSampledValueTemplate) { | |
480 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
481 | chargingStation, | |
e1d9a0f4 | 482 | currentSampledValueTemplate.measurand!, |
78085c42 | 483 | ); |
fc040c43 | 484 | const errMsg = `MeterValues measurand ${ |
78085c42 JB |
485 | currentSampledValueTemplate.measurand ?? |
486 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
487 | }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ | |
2484ac1e | 488 | chargingStation.templateFile |
78085c42 JB |
489 | }, cannot calculate ${ |
490 | currentSampledValueTemplate.measurand ?? | |
491 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
492 | } measurand value`; | |
abe9e9dd | 493 | const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; |
1b6498ba JB |
494 | const connectorMaximumAvailablePower = |
495 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
860ef183 | 496 | const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0; |
ad8537a7 | 497 | let connectorMaximumAmperage: number; |
78085c42 JB |
498 | switch (chargingStation.getCurrentOutType()) { |
499 | case CurrentType.AC: | |
ad8537a7 | 500 | connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( |
78085c42 | 501 | chargingStation.getNumberOfPhases(), |
1b6498ba | 502 | connectorMaximumAvailablePower, |
5edd8ba0 | 503 | chargingStation.getVoltageOut(), |
78085c42 JB |
504 | ); |
505 | if (chargingStation.getNumberOfPhases() === 3) { | |
506 | const defaultFluctuatedAmperagePerPhase = | |
507 | currentSampledValueTemplate.value && | |
9bf0ef23 | 508 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
509 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
510 | currentSampledValueTemplate.value, | |
511 | connectorMaximumAmperage, | |
5edd8ba0 | 512 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
7bc31f9c | 513 | ), |
78085c42 | 514 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 515 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
516 | ); |
517 | const phase1FluctuatedValue = | |
e1d9a0f4 | 518 | currentPerPhaseSampledValueTemplates.L1?.value && |
9bf0ef23 | 519 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
520 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
521 | currentPerPhaseSampledValueTemplates.L1.value, | |
522 | connectorMaximumAmperage, | |
5edd8ba0 | 523 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 524 | ), |
78085c42 | 525 | currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? |
5edd8ba0 | 526 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
527 | ); |
528 | const phase2FluctuatedValue = | |
e1d9a0f4 | 529 | currentPerPhaseSampledValueTemplates.L2?.value && |
9bf0ef23 | 530 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
531 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
532 | currentPerPhaseSampledValueTemplates.L2.value, | |
533 | connectorMaximumAmperage, | |
5edd8ba0 | 534 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 535 | ), |
78085c42 | 536 | currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? |
5edd8ba0 | 537 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
538 | ); |
539 | const phase3FluctuatedValue = | |
e1d9a0f4 | 540 | currentPerPhaseSampledValueTemplates.L3?.value && |
9bf0ef23 | 541 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
542 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
543 | currentPerPhaseSampledValueTemplates.L3.value, | |
544 | connectorMaximumAmperage, | |
5edd8ba0 | 545 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
34464008 | 546 | ), |
78085c42 | 547 | currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? |
5edd8ba0 | 548 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
549 | ); |
550 | currentMeasurandValues.L1 = | |
e1d9a0f4 JB |
551 | (phase1FluctuatedValue as number) ?? |
552 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 553 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 554 | currentMeasurandValues.L2 = |
e1d9a0f4 JB |
555 | (phase2FluctuatedValue as number) ?? |
556 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 557 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 558 | currentMeasurandValues.L3 = |
e1d9a0f4 JB |
559 | (phase3FluctuatedValue as number) ?? |
560 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 561 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
562 | } else { |
563 | currentMeasurandValues.L1 = currentSampledValueTemplate.value | |
9bf0ef23 | 564 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
565 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
566 | currentSampledValueTemplate.value, | |
567 | connectorMaximumAmperage, | |
5edd8ba0 | 568 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
7bc31f9c | 569 | ), |
78085c42 | 570 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 571 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 572 | ) |
9bf0ef23 | 573 | : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
574 | currentMeasurandValues.L2 = 0; |
575 | currentMeasurandValues.L3 = 0; | |
576 | } | |
9bf0ef23 | 577 | currentMeasurandValues.allPhases = roundTo( |
78085c42 JB |
578 | (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / |
579 | chargingStation.getNumberOfPhases(), | |
5edd8ba0 | 580 | 2, |
78085c42 JB |
581 | ); |
582 | break; | |
583 | case CurrentType.DC: | |
ad8537a7 | 584 | connectorMaximumAmperage = DCElectricUtils.amperage( |
1b6498ba | 585 | connectorMaximumAvailablePower, |
5edd8ba0 | 586 | chargingStation.getVoltageOut(), |
78085c42 JB |
587 | ); |
588 | currentMeasurandValues.allPhases = currentSampledValueTemplate.value | |
9bf0ef23 | 589 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
590 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
591 | currentSampledValueTemplate.value, | |
592 | connectorMaximumAmperage, | |
5edd8ba0 | 593 | { limitationEnabled: chargingStation.getCustomValueLimitationMeterValues() }, |
7bc31f9c | 594 | ), |
78085c42 | 595 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 596 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 597 | ) |
9bf0ef23 | 598 | : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
599 | break; |
600 | default: | |
fc040c43 | 601 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
78085c42 JB |
602 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
603 | } | |
604 | meterValue.sampledValue.push( | |
605 | OCPP16ServiceUtils.buildSampledValue( | |
606 | currentSampledValueTemplate, | |
5edd8ba0 JB |
607 | currentMeasurandValues.allPhases, |
608 | ), | |
78085c42 JB |
609 | ); |
610 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
611 | if ( | |
9bf0ef23 | 612 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 613 | connectorMaximumAmperage || |
9bf0ef23 | 614 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < |
860ef183 | 615 | connectorMinimumAmperage || |
78085c42 JB |
616 | debug |
617 | ) { | |
618 | logger.error( | |
619 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
620 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
621 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 622 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ |
78085c42 | 623 | meterValue.sampledValue[sampledValuesIndex].value |
5edd8ba0 | 624 | }/${connectorMaximumAmperage}`, |
78085c42 JB |
625 | ); |
626 | } | |
627 | for ( | |
628 | let phase = 1; | |
629 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
630 | phase++ | |
631 | ) { | |
632 | const phaseValue = `L${phase}`; | |
633 | meterValue.sampledValue.push( | |
634 | OCPP16ServiceUtils.buildSampledValue( | |
635 | (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ?? | |
636 | currentSampledValueTemplate, | |
637 | currentMeasurandValues[phaseValue] as number, | |
72092cfc | 638 | undefined, |
5edd8ba0 JB |
639 | phaseValue as OCPP16MeterValuePhase, |
640 | ), | |
78085c42 JB |
641 | ); |
642 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
643 | if ( | |
9bf0ef23 | 644 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > |
ad8537a7 | 645 | connectorMaximumAmperage || |
9bf0ef23 | 646 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < |
860ef183 | 647 | connectorMinimumAmperage || |
78085c42 JB |
648 | debug |
649 | ) { | |
650 | logger.error( | |
651 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
652 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
653 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
654 | }: phase ${ | |
655 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
5edd8ba0 | 656 | }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ |
78085c42 | 657 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value |
5edd8ba0 | 658 | }/${connectorMaximumAmperage}`, |
78085c42 JB |
659 | ); |
660 | } | |
661 | } | |
662 | } | |
663 | // Energy.Active.Import.Register measurand (default) | |
ed3d2808 | 664 | const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 665 | chargingStation, |
5edd8ba0 | 666 | connectorId, |
492cf6ab | 667 | ); |
78085c42 JB |
668 | if (energySampledValueTemplate) { |
669 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
670 | chargingStation, | |
e1d9a0f4 | 671 | energySampledValueTemplate.measurand!, |
78085c42 JB |
672 | ); |
673 | const unitDivider = | |
674 | energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
1b6498ba JB |
675 | const connectorMaximumAvailablePower = |
676 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
9bf0ef23 | 677 | const connectorMaximumEnergyRounded = roundTo( |
1b6498ba | 678 | (connectorMaximumAvailablePower * interval) / (3600 * 1000), |
5edd8ba0 | 679 | 2, |
78085c42 JB |
680 | ); |
681 | const energyValueRounded = energySampledValueTemplate.value | |
682 | ? // Cumulate the fluctuated value around the static one | |
9bf0ef23 | 683 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
684 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
685 | energySampledValueTemplate.value, | |
686 | connectorMaximumEnergyRounded, | |
687 | { | |
688 | limitationEnabled: chargingStation.getCustomValueLimitationMeterValues(), | |
689 | unitMultiplier: unitDivider, | |
5edd8ba0 | 690 | }, |
34464008 | 691 | ), |
5edd8ba0 | 692 | energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 693 | ) |
9bf0ef23 | 694 | : getRandomFloatRounded(connectorMaximumEnergyRounded); |
78085c42 | 695 | // Persist previous value on connector |
e1d9a0f4 JB |
696 | if (connector) { |
697 | if ( | |
698 | isNullOrUndefined(connector.energyActiveImportRegisterValue) === false && | |
699 | connector.energyActiveImportRegisterValue! >= 0 && | |
700 | isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false && | |
701 | connector.transactionEnergyActiveImportRegisterValue! >= 0 | |
702 | ) { | |
703 | connector.energyActiveImportRegisterValue! += energyValueRounded; | |
704 | connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded; | |
705 | } else { | |
706 | connector.energyActiveImportRegisterValue = 0; | |
707 | connector.transactionEnergyActiveImportRegisterValue = 0; | |
708 | } | |
78085c42 JB |
709 | } |
710 | meterValue.sampledValue.push( | |
711 | OCPP16ServiceUtils.buildSampledValue( | |
712 | energySampledValueTemplate, | |
9bf0ef23 | 713 | roundTo( |
78085c42 JB |
714 | chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / |
715 | unitDivider, | |
5edd8ba0 JB |
716 | 2, |
717 | ), | |
718 | ), | |
78085c42 JB |
719 | ); |
720 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
ad8537a7 | 721 | if (energyValueRounded > connectorMaximumEnergyRounded || debug) { |
78085c42 JB |
722 | logger.error( |
723 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
724 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
725 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 726 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${roundTo( |
78085c42 | 727 | interval / (3600 * 1000), |
5edd8ba0 JB |
728 | 4, |
729 | )}h`, | |
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 | ||
1b271a54 JB |
833 | public static parseJsonSchemaFile<T extends JsonType>( |
834 | relativePath: string, | |
835 | moduleName?: string, | |
5edd8ba0 | 836 | methodName?: string, |
1b271a54 | 837 | ): JSONSchemaType<T> { |
7164966d | 838 | return super.parseJsonSchemaFile<T>( |
51022aa0 | 839 | relativePath, |
1b271a54 JB |
840 | OCPPVersion.VERSION_16, |
841 | moduleName, | |
5edd8ba0 | 842 | methodName, |
7164966d | 843 | ); |
130783a7 JB |
844 | } |
845 | ||
66dd3447 JB |
846 | public static async isIdTagAuthorized( |
847 | chargingStation: ChargingStation, | |
848 | connectorId: number, | |
5edd8ba0 | 849 | idTag: string, |
66dd3447 JB |
850 | ): Promise<boolean> { |
851 | let authorized = false; | |
e1d9a0f4 | 852 | const connectorStatus: ConnectorStatus = chargingStation.getConnectorStatus(connectorId)!; |
66dd3447 JB |
853 | if (OCPP16ServiceUtils.isIdTagLocalAuthorized(chargingStation, idTag)) { |
854 | connectorStatus.localAuthorizeIdTag = idTag; | |
855 | connectorStatus.idTagLocalAuthorized = true; | |
856 | authorized = true; | |
857 | } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) { | |
858 | connectorStatus.authorizeIdTag = idTag; | |
859 | authorized = await OCPP16ServiceUtils.isIdTagRemoteAuthorized(chargingStation, idTag); | |
860 | } else { | |
861 | logger.warn( | |
862 | `${chargingStation.logPrefix()} The charging station configuration expects authorize at | |
5edd8ba0 | 863 | remote start transaction but local authorization or authorize isn't enabled`, |
66dd3447 JB |
864 | ); |
865 | } | |
866 | return authorized; | |
867 | } | |
868 | ||
7bc31f9c JB |
869 | private static buildSampledValue( |
870 | sampledValueTemplate: SampledValueTemplate, | |
871 | value: number, | |
872 | context?: MeterValueContext, | |
5edd8ba0 | 873 | phase?: OCPP16MeterValuePhase, |
7bc31f9c JB |
874 | ): OCPP16SampledValue { |
875 | const sampledValueValue = value ?? sampledValueTemplate?.value ?? null; | |
876 | const sampledValueContext = context ?? sampledValueTemplate?.context ?? null; | |
877 | const sampledValueLocation = | |
878 | sampledValueTemplate?.location ?? | |
e1d9a0f4 | 879 | OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!); |
7bc31f9c JB |
880 | const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null; |
881 | return { | |
9bf0ef23 | 882 | ...(!isNullOrUndefined(sampledValueTemplate.unit) && { |
7bc31f9c JB |
883 | unit: sampledValueTemplate.unit, |
884 | }), | |
9bf0ef23 JB |
885 | ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), |
886 | ...(!isNullOrUndefined(sampledValueTemplate.measurand) && { | |
7bc31f9c JB |
887 | measurand: sampledValueTemplate.measurand, |
888 | }), | |
9bf0ef23 JB |
889 | ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), |
890 | ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }), | |
891 | ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), | |
e1d9a0f4 | 892 | } as OCPP16SampledValue; |
7bc31f9c JB |
893 | } |
894 | ||
895 | private static checkMeasurandPowerDivider( | |
896 | chargingStation: ChargingStation, | |
5edd8ba0 | 897 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c | 898 | ): void { |
9bf0ef23 | 899 | if (isUndefined(chargingStation.powerDivider)) { |
fc040c43 | 900 | const errMsg = `MeterValues measurand ${ |
7bc31f9c JB |
901 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER |
902 | }: powerDivider is undefined`; | |
fc040c43 | 903 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
7bc31f9c | 904 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
fa7bccf4 | 905 | } else if (chargingStation?.powerDivider <= 0) { |
fc040c43 | 906 | const errMsg = `MeterValues measurand ${ |
7bc31f9c | 907 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER |
fa7bccf4 | 908 | }: powerDivider have zero or below value ${chargingStation.powerDivider}`; |
fc040c43 | 909 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
7bc31f9c JB |
910 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
911 | } | |
912 | } | |
913 | ||
914 | private static getMeasurandDefaultLocation( | |
5edd8ba0 | 915 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c JB |
916 | ): MeterValueLocation | undefined { |
917 | switch (measurandType) { | |
918 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
919 | return MeterValueLocation.EV; | |
920 | } | |
921 | } | |
922 | ||
923 | private static getMeasurandDefaultUnit( | |
5edd8ba0 | 924 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c JB |
925 | ): MeterValueUnit | undefined { |
926 | switch (measurandType) { | |
927 | case OCPP16MeterValueMeasurand.CURRENT_EXPORT: | |
928 | case OCPP16MeterValueMeasurand.CURRENT_IMPORT: | |
929 | case OCPP16MeterValueMeasurand.CURRENT_OFFERED: | |
930 | return MeterValueUnit.AMP; | |
931 | case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: | |
932 | case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: | |
933 | return MeterValueUnit.WATT_HOUR; | |
934 | case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: | |
935 | case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: | |
936 | case OCPP16MeterValueMeasurand.POWER_OFFERED: | |
937 | return MeterValueUnit.WATT; | |
938 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
939 | return MeterValueUnit.PERCENT; | |
940 | case OCPP16MeterValueMeasurand.VOLTAGE: | |
941 | return MeterValueUnit.VOLT; | |
942 | } | |
943 | } | |
66dd3447 JB |
944 | |
945 | private static isIdTagLocalAuthorized(chargingStation: ChargingStation, idTag: string): boolean { | |
946 | return ( | |
947 | chargingStation.getLocalAuthListEnabled() === true && | |
948 | chargingStation.hasIdTags() === true && | |
9bf0ef23 | 949 | isNotEmptyString( |
66dd3447 | 950 | chargingStation.idTagsCache |
e1d9a0f4 | 951 | .getIdTags(getIdTagsFile(chargingStation.stationInfo)!) |
5edd8ba0 | 952 | ?.find((tag) => tag === idTag), |
66dd3447 JB |
953 | ) |
954 | ); | |
955 | } | |
956 | ||
957 | private static async isIdTagRemoteAuthorized( | |
958 | chargingStation: ChargingStation, | |
5edd8ba0 | 959 | idTag: string, |
66dd3447 JB |
960 | ): Promise<boolean> { |
961 | const authorizeResponse: OCPP16AuthorizeResponse = | |
962 | await chargingStation.ocppRequestService.requestHandler< | |
963 | OCPP16AuthorizeRequest, | |
964 | OCPP16AuthorizeResponse | |
965 | >(chargingStation, OCPP16RequestCommand.AUTHORIZE, { | |
966 | idTag: idTag, | |
967 | }); | |
968 | return authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED; | |
969 | } | |
6ed92bc1 | 970 | } |