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