Commit | Line | Data |
---|---|---|
c8eeb62b JB |
1 | // Partial Copyright Jerome Benoit. 2021. All Rights Reserved. |
2 | ||
78085c42 JB |
3 | import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils'; |
4 | import { CurrentType, Voltage } from '../../../types/ChargingStationTemplate'; | |
5 | import MeasurandPerPhaseSampledValueTemplates, { | |
6 | SampledValueTemplate, | |
7 | } from '../../../types/MeasurandPerPhaseSampledValueTemplates'; | |
e7aeea18 JB |
8 | import { |
9 | MeterValueContext, | |
10 | MeterValueLocation, | |
11 | MeterValueUnit, | |
12 | OCPP16MeterValue, | |
13 | OCPP16MeterValueMeasurand, | |
14 | OCPP16MeterValuePhase, | |
15 | OCPP16SampledValue, | |
16 | } from '../../../types/ocpp/1.6/MeterValues'; | |
370ae4ee JB |
17 | import { |
18 | OCPP16IncomingRequestCommand, | |
19 | OCPP16RequestCommand, | |
20 | } from '../../../types/ocpp/1.6/Requests'; | |
21 | import { | |
22 | OCPP16StandardParametersKey, | |
23 | OCPP16SupportedFeatureProfiles, | |
24 | } from '../../../types/ocpp/1.6/Configuration'; | |
6ed92bc1 | 25 | |
73b9adec | 26 | import type ChargingStation from '../../ChargingStation'; |
78085c42 | 27 | import Constants from '../../../utils/Constants'; |
14763b46 | 28 | import { ErrorType } from '../../../types/ocpp/ErrorType'; |
78085c42 | 29 | import MeasurandValues from '../../../types/MeasurandValues'; |
e58068fd | 30 | import OCPPError from '../../../exception/OCPPError'; |
6ed92bc1 | 31 | import Utils from '../../../utils/Utils'; |
9f2e3130 | 32 | import logger from '../../../utils/Logger'; |
6ed92bc1 JB |
33 | |
34 | export class OCPP16ServiceUtils { | |
e7aeea18 JB |
35 | public static checkMeasurandPowerDivider( |
36 | chargingStation: ChargingStation, | |
37 | measurandType: OCPP16MeterValueMeasurand | |
38 | ): void { | |
6ed92bc1 | 39 | if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) { |
e7aeea18 JB |
40 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ |
41 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
42 | }: powerDivider is undefined`; | |
9f2e3130 | 43 | logger.error(errMsg); |
3d12fce4 | 44 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
fd0c36fa | 45 | } else if (chargingStation.stationInfo?.powerDivider <= 0) { |
e7aeea18 JB |
46 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ |
47 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
48 | }: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`; | |
9f2e3130 | 49 | logger.error(errMsg); |
3d12fce4 | 50 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
6ed92bc1 JB |
51 | } |
52 | } | |
53 | ||
370ae4ee JB |
54 | public static checkFeatureProfile( |
55 | chargingStation: ChargingStation, | |
56 | featureProfile: OCPP16SupportedFeatureProfiles, | |
57 | command: OCPP16RequestCommand | OCPP16IncomingRequestCommand | |
58 | ): boolean { | |
59 | if (!chargingStation.hasFeatureProfile(featureProfile)) { | |
60 | logger.warn( | |
61 | `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${ | |
62 | OCPP16StandardParametersKey.SupportedFeatureProfiles | |
63 | } in configuration` | |
64 | ); | |
65 | return false; | |
66 | } | |
67 | return true; | |
68 | } | |
69 | ||
e7aeea18 JB |
70 | public static buildSampledValue( |
71 | sampledValueTemplate: SampledValueTemplate, | |
72 | value: number, | |
73 | context?: MeterValueContext, | |
74 | phase?: OCPP16MeterValuePhase | |
75 | ): OCPP16SampledValue { | |
76 | const sampledValueValue = value ?? sampledValueTemplate?.value ?? null; | |
77 | const sampledValueContext = context ?? sampledValueTemplate?.context ?? null; | |
78 | const sampledValueLocation = | |
79 | sampledValueTemplate?.location ?? | |
80 | OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null); | |
81 | const sampledValuePhase = phase ?? sampledValueTemplate?.phase ?? null; | |
6ed92bc1 | 82 | return { |
e7aeea18 JB |
83 | ...(!Utils.isNullOrUndefined(sampledValueTemplate.unit) && { |
84 | unit: sampledValueTemplate.unit, | |
85 | }), | |
86 | ...(!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), | |
87 | ...(!Utils.isNullOrUndefined(sampledValueTemplate.measurand) && { | |
88 | measurand: sampledValueTemplate.measurand, | |
89 | }), | |
90 | ...(!Utils.isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), | |
91 | ...(!Utils.isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }), | |
92 | ...(!Utils.isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), | |
6ed92bc1 JB |
93 | }; |
94 | } | |
95 | ||
e7aeea18 JB |
96 | public static getMeasurandDefaultUnit( |
97 | measurandType: OCPP16MeterValueMeasurand | |
98 | ): MeterValueUnit | undefined { | |
6ed92bc1 JB |
99 | switch (measurandType) { |
100 | case OCPP16MeterValueMeasurand.CURRENT_EXPORT: | |
101 | case OCPP16MeterValueMeasurand.CURRENT_IMPORT: | |
102 | case OCPP16MeterValueMeasurand.CURRENT_OFFERED: | |
103 | return MeterValueUnit.AMP; | |
104 | case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: | |
105 | case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: | |
106 | return MeterValueUnit.WATT_HOUR; | |
107 | case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: | |
108 | case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: | |
109 | case OCPP16MeterValueMeasurand.POWER_OFFERED: | |
110 | return MeterValueUnit.WATT; | |
111 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
112 | return MeterValueUnit.PERCENT; | |
113 | case OCPP16MeterValueMeasurand.VOLTAGE: | |
114 | return MeterValueUnit.VOLT; | |
115 | } | |
116 | } | |
117 | ||
e7aeea18 JB |
118 | public static getMeasurandDefaultLocation( |
119 | measurandType: OCPP16MeterValueMeasurand | |
120 | ): MeterValueLocation | undefined { | |
6ed92bc1 JB |
121 | switch (measurandType) { |
122 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
123 | return MeterValueLocation.EV; | |
124 | } | |
125 | } | |
fd0c36fa | 126 | |
78085c42 JB |
127 | public static buildMeterValue( |
128 | chargingStation: ChargingStation, | |
129 | connectorId: number, | |
130 | transactionId: number, | |
131 | interval: number, | |
132 | debug = false | |
133 | ): OCPP16MeterValue { | |
134 | const meterValue: OCPP16MeterValue = { | |
135 | timestamp: new Date().toISOString(), | |
136 | sampledValue: [], | |
137 | }; | |
138 | const connector = chargingStation.getConnectorStatus(connectorId); | |
139 | // SoC measurand | |
140 | const socSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
141 | connectorId, | |
142 | OCPP16MeterValueMeasurand.STATE_OF_CHARGE | |
143 | ); | |
144 | if (socSampledValueTemplate) { | |
145 | const socSampledValueTemplateValue = socSampledValueTemplate.value | |
146 | ? Utils.getRandomFloatFluctuatedRounded( | |
147 | parseInt(socSampledValueTemplate.value), | |
148 | socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT | |
149 | ) | |
150 | : Utils.getRandomInteger(100); | |
151 | meterValue.sampledValue.push( | |
152 | OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue) | |
153 | ); | |
154 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
155 | if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) { | |
156 | logger.error( | |
157 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
158 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
159 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
160 | }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
161 | meterValue.sampledValue[sampledValuesIndex].value | |
162 | }/100` | |
163 | ); | |
164 | } | |
165 | } | |
166 | // Voltage measurand | |
167 | const voltageSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
168 | connectorId, | |
169 | OCPP16MeterValueMeasurand.VOLTAGE | |
170 | ); | |
171 | if (voltageSampledValueTemplate) { | |
172 | const voltageSampledValueTemplateValue = voltageSampledValueTemplate.value | |
173 | ? parseInt(voltageSampledValueTemplate.value) | |
174 | : chargingStation.getVoltageOut(); | |
175 | const fluctuationPercent = | |
176 | voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; | |
177 | const voltageMeasurandValue = Utils.getRandomFloatFluctuatedRounded( | |
178 | voltageSampledValueTemplateValue, | |
179 | fluctuationPercent | |
180 | ); | |
181 | if ( | |
182 | chargingStation.getNumberOfPhases() !== 3 || | |
183 | (chargingStation.getNumberOfPhases() === 3 && chargingStation.getMainVoltageMeterValues()) | |
184 | ) { | |
185 | meterValue.sampledValue.push( | |
186 | OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue) | |
187 | ); | |
188 | } | |
189 | for ( | |
190 | let phase = 1; | |
191 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
192 | phase++ | |
193 | ) { | |
194 | const phaseLineToNeutralValue = `L${phase}-N`; | |
195 | const voltagePhaseLineToNeutralSampledValueTemplate = | |
196 | chargingStation.getSampledValueTemplate( | |
197 | connectorId, | |
198 | OCPP16MeterValueMeasurand.VOLTAGE, | |
199 | phaseLineToNeutralValue as OCPP16MeterValuePhase | |
200 | ); | |
201 | let voltagePhaseLineToNeutralMeasurandValue: number; | |
202 | if (voltagePhaseLineToNeutralSampledValueTemplate) { | |
203 | const voltagePhaseLineToNeutralSampledValueTemplateValue = | |
204 | voltagePhaseLineToNeutralSampledValueTemplate.value | |
205 | ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) | |
206 | : chargingStation.getVoltageOut(); | |
207 | const fluctuationPhaseToNeutralPercent = | |
208 | voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? | |
209 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
210 | voltagePhaseLineToNeutralMeasurandValue = Utils.getRandomFloatFluctuatedRounded( | |
211 | voltagePhaseLineToNeutralSampledValueTemplateValue, | |
212 | fluctuationPhaseToNeutralPercent | |
213 | ); | |
214 | } | |
215 | meterValue.sampledValue.push( | |
216 | OCPP16ServiceUtils.buildSampledValue( | |
217 | voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate, | |
218 | voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue, | |
219 | null, | |
220 | phaseLineToNeutralValue as OCPP16MeterValuePhase | |
221 | ) | |
222 | ); | |
223 | if (chargingStation.getPhaseLineToLineVoltageMeterValues()) { | |
224 | const phaseLineToLineValue = `L${phase}-L${ | |
225 | (phase + 1) % chargingStation.getNumberOfPhases() !== 0 | |
226 | ? (phase + 1) % chargingStation.getNumberOfPhases() | |
227 | : chargingStation.getNumberOfPhases() | |
228 | }`; | |
229 | const voltagePhaseLineToLineSampledValueTemplate = | |
230 | chargingStation.getSampledValueTemplate( | |
231 | connectorId, | |
232 | OCPP16MeterValueMeasurand.VOLTAGE, | |
233 | phaseLineToLineValue as OCPP16MeterValuePhase | |
234 | ); | |
235 | let voltagePhaseLineToLineMeasurandValue: number; | |
236 | if (voltagePhaseLineToLineSampledValueTemplate) { | |
237 | const voltagePhaseLineToLineSampledValueTemplateValue = | |
238 | voltagePhaseLineToLineSampledValueTemplate.value | |
239 | ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) | |
240 | : Voltage.VOLTAGE_400; | |
241 | const fluctuationPhaseLineToLinePercent = | |
242 | voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? | |
243 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
244 | voltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded( | |
245 | voltagePhaseLineToLineSampledValueTemplateValue, | |
246 | fluctuationPhaseLineToLinePercent | |
247 | ); | |
248 | } | |
249 | const defaultVoltagePhaseLineToLineMeasurandValue = Utils.getRandomFloatFluctuatedRounded( | |
250 | Voltage.VOLTAGE_400, | |
251 | fluctuationPercent | |
252 | ); | |
253 | meterValue.sampledValue.push( | |
254 | OCPP16ServiceUtils.buildSampledValue( | |
255 | voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate, | |
256 | voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue, | |
257 | null, | |
258 | phaseLineToLineValue as OCPP16MeterValuePhase | |
259 | ) | |
260 | ); | |
261 | } | |
262 | } | |
263 | } | |
264 | // Power.Active.Import measurand | |
265 | const powerSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
266 | connectorId, | |
267 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT | |
268 | ); | |
269 | let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; | |
270 | if (chargingStation.getNumberOfPhases() === 3) { | |
271 | powerPerPhaseSampledValueTemplates = { | |
272 | L1: chargingStation.getSampledValueTemplate( | |
273 | connectorId, | |
274 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
275 | OCPP16MeterValuePhase.L1_N | |
276 | ), | |
277 | L2: chargingStation.getSampledValueTemplate( | |
278 | connectorId, | |
279 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
280 | OCPP16MeterValuePhase.L2_N | |
281 | ), | |
282 | L3: chargingStation.getSampledValueTemplate( | |
283 | connectorId, | |
284 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
285 | OCPP16MeterValuePhase.L3_N | |
286 | ), | |
287 | }; | |
288 | } | |
289 | if (powerSampledValueTemplate) { | |
290 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
291 | chargingStation, | |
292 | powerSampledValueTemplate.measurand | |
293 | ); | |
294 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ | |
295 | powerSampledValueTemplate.measurand ?? | |
296 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
297 | }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ | |
2484ac1e | 298 | chargingStation.templateFile |
78085c42 JB |
299 | }, cannot calculate ${ |
300 | powerSampledValueTemplate.measurand ?? | |
301 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
302 | } measurand value`; | |
303 | const powerMeasurandValues = {} as MeasurandValues; | |
304 | const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; | |
ad8537a7 JB |
305 | const connectorMaximumPower = Math.round( |
306 | chargingStation.getConnectorMaximumAvailablePower(connectorId) | |
78085c42 | 307 | ); |
ad8537a7 JB |
308 | const connectorMaximumPowerPerPhase = Math.round( |
309 | chargingStation.getConnectorMaximumAvailablePower(connectorId) / | |
78085c42 JB |
310 | chargingStation.getNumberOfPhases() |
311 | ); | |
312 | switch (chargingStation.getCurrentOutType()) { | |
313 | case CurrentType.AC: | |
314 | if (chargingStation.getNumberOfPhases() === 3) { | |
315 | const defaultFluctuatedPowerPerPhase = | |
316 | powerSampledValueTemplate.value && | |
317 | Utils.getRandomFloatFluctuatedRounded( | |
318 | parseInt(powerSampledValueTemplate.value) / chargingStation.getNumberOfPhases(), | |
319 | powerSampledValueTemplate.fluctuationPercent ?? | |
320 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
321 | ); | |
322 | const phase1FluctuatedValue = | |
323 | powerPerPhaseSampledValueTemplates?.L1?.value && | |
324 | Utils.getRandomFloatFluctuatedRounded( | |
325 | parseInt(powerPerPhaseSampledValueTemplates.L1.value), | |
326 | powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? | |
327 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
328 | ); | |
329 | const phase2FluctuatedValue = | |
330 | powerPerPhaseSampledValueTemplates?.L2?.value && | |
331 | Utils.getRandomFloatFluctuatedRounded( | |
332 | parseInt(powerPerPhaseSampledValueTemplates.L2.value), | |
333 | powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? | |
334 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
335 | ); | |
336 | const phase3FluctuatedValue = | |
337 | powerPerPhaseSampledValueTemplates?.L3?.value && | |
338 | Utils.getRandomFloatFluctuatedRounded( | |
339 | parseInt(powerPerPhaseSampledValueTemplates.L3.value), | |
340 | powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? | |
341 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
342 | ); | |
343 | powerMeasurandValues.L1 = | |
344 | phase1FluctuatedValue ?? | |
345 | defaultFluctuatedPowerPerPhase ?? | |
ad8537a7 | 346 | Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); |
78085c42 JB |
347 | powerMeasurandValues.L2 = |
348 | phase2FluctuatedValue ?? | |
349 | defaultFluctuatedPowerPerPhase ?? | |
ad8537a7 | 350 | Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); |
78085c42 JB |
351 | powerMeasurandValues.L3 = |
352 | phase3FluctuatedValue ?? | |
353 | defaultFluctuatedPowerPerPhase ?? | |
ad8537a7 | 354 | Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider); |
78085c42 JB |
355 | } else { |
356 | powerMeasurandValues.L1 = powerSampledValueTemplate.value | |
357 | ? Utils.getRandomFloatFluctuatedRounded( | |
358 | parseInt(powerSampledValueTemplate.value), | |
359 | powerSampledValueTemplate.fluctuationPercent ?? | |
360 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
361 | ) | |
ad8537a7 | 362 | : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider); |
78085c42 JB |
363 | powerMeasurandValues.L2 = 0; |
364 | powerMeasurandValues.L3 = 0; | |
365 | } | |
366 | powerMeasurandValues.allPhases = Utils.roundTo( | |
367 | powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, | |
368 | 2 | |
369 | ); | |
370 | break; | |
371 | case CurrentType.DC: | |
372 | powerMeasurandValues.allPhases = powerSampledValueTemplate.value | |
373 | ? Utils.getRandomFloatFluctuatedRounded( | |
374 | parseInt(powerSampledValueTemplate.value), | |
375 | powerSampledValueTemplate.fluctuationPercent ?? | |
376 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
377 | ) | |
ad8537a7 | 378 | : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider); |
78085c42 JB |
379 | break; |
380 | default: | |
381 | logger.error(errMsg); | |
382 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); | |
383 | } | |
384 | meterValue.sampledValue.push( | |
385 | OCPP16ServiceUtils.buildSampledValue( | |
386 | powerSampledValueTemplate, | |
387 | powerMeasurandValues.allPhases | |
388 | ) | |
389 | ); | |
390 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
ad8537a7 | 391 | const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2); |
78085c42 | 392 | if ( |
71a77ac2 | 393 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 394 | connectorMaximumPowerRounded || |
78085c42 JB |
395 | debug |
396 | ) { | |
397 | logger.error( | |
398 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
399 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
400 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
401 | }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
402 | meterValue.sampledValue[sampledValuesIndex].value | |
ad8537a7 | 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, | |
417 | null, | |
418 | phaseValue as OCPP16MeterValuePhase | |
419 | ) | |
420 | ); | |
421 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
ad8537a7 JB |
422 | const connectorMaximumPowerPerPhaseRounded = Utils.roundTo( |
423 | connectorMaximumPowerPerPhase / unitDivider, | |
424 | 2 | |
425 | ); | |
78085c42 JB |
426 | if ( |
427 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > | |
ad8537a7 | 428 | connectorMaximumPowerPerPhaseRounded || |
78085c42 JB |
429 | debug |
430 | ) { | |
431 | logger.error( | |
432 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
433 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
434 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
435 | }: phase ${ | |
436 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
437 | }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
438 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value | |
ad8537a7 | 439 | }/${connectorMaximumPowerPerPhaseRounded}` |
78085c42 JB |
440 | ); |
441 | } | |
442 | } | |
443 | } | |
444 | // Current.Import measurand | |
445 | const currentSampledValueTemplate = chargingStation.getSampledValueTemplate( | |
446 | connectorId, | |
447 | OCPP16MeterValueMeasurand.CURRENT_IMPORT | |
448 | ); | |
449 | let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; | |
450 | if (chargingStation.getNumberOfPhases() === 3) { | |
451 | currentPerPhaseSampledValueTemplates = { | |
452 | L1: chargingStation.getSampledValueTemplate( | |
453 | connectorId, | |
454 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
455 | OCPP16MeterValuePhase.L1 | |
456 | ), | |
457 | L2: chargingStation.getSampledValueTemplate( | |
458 | connectorId, | |
459 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
460 | OCPP16MeterValuePhase.L2 | |
461 | ), | |
462 | L3: chargingStation.getSampledValueTemplate( | |
463 | connectorId, | |
464 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
465 | OCPP16MeterValuePhase.L3 | |
466 | ), | |
467 | }; | |
468 | } | |
469 | if (currentSampledValueTemplate) { | |
470 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
471 | chargingStation, | |
472 | currentSampledValueTemplate.measurand | |
473 | ); | |
474 | const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${ | |
475 | currentSampledValueTemplate.measurand ?? | |
476 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
477 | }: Unknown ${chargingStation.getCurrentOutType()} currentOutType in template file ${ | |
2484ac1e | 478 | chargingStation.templateFile |
78085c42 JB |
479 | }, cannot calculate ${ |
480 | currentSampledValueTemplate.measurand ?? | |
481 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
482 | } measurand value`; | |
483 | const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; | |
ad8537a7 | 484 | let connectorMaximumAmperage: number; |
78085c42 JB |
485 | switch (chargingStation.getCurrentOutType()) { |
486 | case CurrentType.AC: | |
ad8537a7 | 487 | connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( |
78085c42 | 488 | chargingStation.getNumberOfPhases(), |
ad8537a7 | 489 | chargingStation.getConnectorMaximumAvailablePower(connectorId), |
78085c42 JB |
490 | chargingStation.getVoltageOut() |
491 | ); | |
492 | if (chargingStation.getNumberOfPhases() === 3) { | |
493 | const defaultFluctuatedAmperagePerPhase = | |
494 | currentSampledValueTemplate.value && | |
495 | Utils.getRandomFloatFluctuatedRounded( | |
496 | parseInt(currentSampledValueTemplate.value), | |
497 | currentSampledValueTemplate.fluctuationPercent ?? | |
498 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
499 | ); | |
500 | const phase1FluctuatedValue = | |
501 | currentPerPhaseSampledValueTemplates?.L1?.value && | |
502 | Utils.getRandomFloatFluctuatedRounded( | |
503 | parseInt(currentPerPhaseSampledValueTemplates.L1.value), | |
504 | currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? | |
505 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
506 | ); | |
507 | const phase2FluctuatedValue = | |
508 | currentPerPhaseSampledValueTemplates?.L2?.value && | |
509 | Utils.getRandomFloatFluctuatedRounded( | |
510 | parseInt(currentPerPhaseSampledValueTemplates.L2.value), | |
511 | currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? | |
512 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
513 | ); | |
514 | const phase3FluctuatedValue = | |
515 | currentPerPhaseSampledValueTemplates?.L3?.value && | |
516 | Utils.getRandomFloatFluctuatedRounded( | |
517 | parseInt(currentPerPhaseSampledValueTemplates.L3.value), | |
518 | currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? | |
519 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
520 | ); | |
521 | currentMeasurandValues.L1 = | |
522 | phase1FluctuatedValue ?? | |
523 | defaultFluctuatedAmperagePerPhase ?? | |
ad8537a7 | 524 | Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
525 | currentMeasurandValues.L2 = |
526 | phase2FluctuatedValue ?? | |
527 | defaultFluctuatedAmperagePerPhase ?? | |
ad8537a7 | 528 | Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
529 | currentMeasurandValues.L3 = |
530 | phase3FluctuatedValue ?? | |
531 | defaultFluctuatedAmperagePerPhase ?? | |
ad8537a7 | 532 | Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
533 | } else { |
534 | currentMeasurandValues.L1 = currentSampledValueTemplate.value | |
535 | ? Utils.getRandomFloatFluctuatedRounded( | |
536 | parseInt(currentSampledValueTemplate.value), | |
537 | currentSampledValueTemplate.fluctuationPercent ?? | |
538 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
539 | ) | |
ad8537a7 | 540 | : Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
541 | currentMeasurandValues.L2 = 0; |
542 | currentMeasurandValues.L3 = 0; | |
543 | } | |
544 | currentMeasurandValues.allPhases = Utils.roundTo( | |
545 | (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / | |
546 | chargingStation.getNumberOfPhases(), | |
547 | 2 | |
548 | ); | |
549 | break; | |
550 | case CurrentType.DC: | |
ad8537a7 JB |
551 | connectorMaximumAmperage = DCElectricUtils.amperage( |
552 | chargingStation.getConnectorMaximumAvailablePower(connectorId), | |
78085c42 JB |
553 | chargingStation.getVoltageOut() |
554 | ); | |
555 | currentMeasurandValues.allPhases = currentSampledValueTemplate.value | |
556 | ? Utils.getRandomFloatFluctuatedRounded( | |
557 | parseInt(currentSampledValueTemplate.value), | |
558 | currentSampledValueTemplate.fluctuationPercent ?? | |
559 | Constants.DEFAULT_FLUCTUATION_PERCENT | |
560 | ) | |
ad8537a7 | 561 | : Utils.getRandomFloatRounded(connectorMaximumAmperage); |
78085c42 JB |
562 | break; |
563 | default: | |
564 | logger.error(errMsg); | |
565 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); | |
566 | } | |
567 | meterValue.sampledValue.push( | |
568 | OCPP16ServiceUtils.buildSampledValue( | |
569 | currentSampledValueTemplate, | |
570 | currentMeasurandValues.allPhases | |
571 | ) | |
572 | ); | |
573 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
574 | if ( | |
ad8537a7 JB |
575 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
576 | connectorMaximumAmperage || | |
78085c42 JB |
577 | debug |
578 | ) { | |
579 | logger.error( | |
580 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
581 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
582 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
583 | }: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
584 | meterValue.sampledValue[sampledValuesIndex].value | |
ad8537a7 | 585 | }/${connectorMaximumAmperage}` |
78085c42 JB |
586 | ); |
587 | } | |
588 | for ( | |
589 | let phase = 1; | |
590 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
591 | phase++ | |
592 | ) { | |
593 | const phaseValue = `L${phase}`; | |
594 | meterValue.sampledValue.push( | |
595 | OCPP16ServiceUtils.buildSampledValue( | |
596 | (currentPerPhaseSampledValueTemplates[phaseValue] as SampledValueTemplate) ?? | |
597 | currentSampledValueTemplate, | |
598 | currentMeasurandValues[phaseValue] as number, | |
599 | null, | |
600 | phaseValue as OCPP16MeterValuePhase | |
601 | ) | |
602 | ); | |
603 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
604 | if ( | |
605 | Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > | |
ad8537a7 | 606 | connectorMaximumAmperage || |
78085c42 JB |
607 | debug |
608 | ) { | |
609 | logger.error( | |
610 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
611 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
612 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
613 | }: phase ${ | |
614 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
615 | }, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${ | |
616 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value | |
ad8537a7 | 617 | }/${connectorMaximumAmperage}` |
78085c42 JB |
618 | ); |
619 | } | |
620 | } | |
621 | } | |
622 | // Energy.Active.Import.Register measurand (default) | |
623 | const energySampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
624 | if (energySampledValueTemplate) { | |
625 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
626 | chargingStation, | |
627 | energySampledValueTemplate.measurand | |
628 | ); | |
629 | const unitDivider = | |
630 | energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
ad8537a7 JB |
631 | const connectorMaximumEnergyRounded = Utils.roundTo( |
632 | (chargingStation.getConnectorMaximumAvailablePower(connectorId) * interval) / (3600 * 1000), | |
78085c42 JB |
633 | 2 |
634 | ); | |
635 | const energyValueRounded = energySampledValueTemplate.value | |
636 | ? // Cumulate the fluctuated value around the static one | |
637 | Utils.getRandomFloatFluctuatedRounded( | |
638 | parseInt(energySampledValueTemplate.value), | |
639 | energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT | |
640 | ) | |
ad8537a7 | 641 | : Utils.getRandomFloatRounded(connectorMaximumEnergyRounded); |
78085c42 JB |
642 | // Persist previous value on connector |
643 | if ( | |
644 | connector && | |
645 | !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) && | |
646 | connector.energyActiveImportRegisterValue >= 0 && | |
647 | !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) && | |
648 | connector.transactionEnergyActiveImportRegisterValue >= 0 | |
649 | ) { | |
650 | connector.energyActiveImportRegisterValue += energyValueRounded; | |
651 | connector.transactionEnergyActiveImportRegisterValue += energyValueRounded; | |
652 | } else { | |
653 | connector.energyActiveImportRegisterValue = 0; | |
654 | connector.transactionEnergyActiveImportRegisterValue = 0; | |
655 | } | |
656 | meterValue.sampledValue.push( | |
657 | OCPP16ServiceUtils.buildSampledValue( | |
658 | energySampledValueTemplate, | |
659 | Utils.roundTo( | |
660 | chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / | |
661 | unitDivider, | |
662 | 2 | |
663 | ) | |
664 | ) | |
665 | ); | |
666 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
ad8537a7 | 667 | if (energyValueRounded > connectorMaximumEnergyRounded || debug) { |
78085c42 JB |
668 | logger.error( |
669 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
670 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
671 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
672 | }: connectorId ${connectorId}, transaction ${ | |
673 | connector.transactionId | |
ad8537a7 | 674 | }, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo( |
78085c42 JB |
675 | interval / (3600 * 1000), |
676 | 4 | |
677 | )}h` | |
678 | ); | |
679 | } | |
680 | } | |
681 | return meterValue; | |
682 | } | |
683 | ||
e7aeea18 JB |
684 | public static buildTransactionBeginMeterValue( |
685 | chargingStation: ChargingStation, | |
686 | connectorId: number, | |
78085c42 | 687 | meterStart: number |
e7aeea18 | 688 | ): OCPP16MeterValue { |
fd0c36fa JB |
689 | const meterValue: OCPP16MeterValue = { |
690 | timestamp: new Date().toISOString(), | |
691 | sampledValue: [], | |
692 | }; | |
9ccca265 JB |
693 | // Energy.Active.Import.Register measurand (default) |
694 | const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
695 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
e7aeea18 JB |
696 | meterValue.sampledValue.push( |
697 | OCPP16ServiceUtils.buildSampledValue( | |
698 | sampledValueTemplate, | |
78085c42 | 699 | Utils.roundTo(meterStart / unitDivider, 4), |
e7aeea18 JB |
700 | MeterValueContext.TRANSACTION_BEGIN |
701 | ) | |
702 | ); | |
fd0c36fa JB |
703 | return meterValue; |
704 | } | |
705 | ||
e7aeea18 JB |
706 | public static buildTransactionEndMeterValue( |
707 | chargingStation: ChargingStation, | |
708 | connectorId: number, | |
78085c42 | 709 | meterStop: number |
e7aeea18 | 710 | ): OCPP16MeterValue { |
fd0c36fa JB |
711 | const meterValue: OCPP16MeterValue = { |
712 | timestamp: new Date().toISOString(), | |
713 | sampledValue: [], | |
714 | }; | |
9ccca265 JB |
715 | // Energy.Active.Import.Register measurand (default) |
716 | const sampledValueTemplate = chargingStation.getSampledValueTemplate(connectorId); | |
717 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
e7aeea18 JB |
718 | meterValue.sampledValue.push( |
719 | OCPP16ServiceUtils.buildSampledValue( | |
720 | sampledValueTemplate, | |
78085c42 | 721 | Utils.roundTo(meterStop / unitDivider, 4), |
e7aeea18 JB |
722 | MeterValueContext.TRANSACTION_END |
723 | ) | |
724 | ); | |
fd0c36fa JB |
725 | return meterValue; |
726 | } | |
727 | ||
e7aeea18 JB |
728 | public static buildTransactionDataMeterValues( |
729 | transactionBeginMeterValue: OCPP16MeterValue, | |
730 | transactionEndMeterValue: OCPP16MeterValue | |
731 | ): OCPP16MeterValue[] { | |
fd0c36fa JB |
732 | const meterValues: OCPP16MeterValue[] = []; |
733 | meterValues.push(transactionBeginMeterValue); | |
734 | meterValues.push(transactionEndMeterValue); | |
735 | return meterValues; | |
736 | } | |
6ed92bc1 | 737 | } |