Commit | Line | Data |
---|---|---|
edd13439 | 1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
c8eeb62b | 2 | |
130783a7 | 3 | import type { JSONSchemaType } from 'ajv'; |
ef9e3b33 JB |
4 | import { |
5 | addSeconds, | |
6 | areIntervalsOverlapping, | |
7 | differenceInSeconds, | |
8 | isAfter, | |
9 | isBefore, | |
10 | isWithinInterval, | |
11 | } from 'date-fns'; | |
130783a7 | 12 | |
366f75f6 | 13 | import { OCPP16Constants } from './OCPP16Constants'; |
90aceaf6 JB |
14 | import { |
15 | type ChargingStation, | |
16 | hasFeatureProfile, | |
17 | hasReservationExpired, | |
18 | } from '../../../charging-station'; | |
268a74bb | 19 | import { OCPPError } from '../../../exception'; |
e7aeea18 | 20 | import { |
73d87be1 | 21 | type ClearChargingProfileRequest, |
268a74bb JB |
22 | CurrentType, |
23 | ErrorType, | |
d19b10a8 | 24 | type GenericResponse, |
268a74bb JB |
25 | type JsonType, |
26 | type MeasurandPerPhaseSampledValueTemplates, | |
27 | type MeasurandValues, | |
e7aeea18 JB |
28 | MeterValueContext, |
29 | MeterValueLocation, | |
30 | MeterValueUnit, | |
d19b10a8 | 31 | OCPP16AuthorizationStatus, |
366f75f6 JB |
32 | OCPP16AvailabilityType, |
33 | type OCPP16ChangeAvailabilityResponse, | |
34 | OCPP16ChargePointStatus, | |
268a74bb | 35 | type OCPP16ChargingProfile, |
ef9e3b33 | 36 | type OCPP16ChargingSchedule, |
268a74bb | 37 | type OCPP16IncomingRequestCommand, |
27782dbc | 38 | type OCPP16MeterValue, |
e7aeea18 JB |
39 | OCPP16MeterValueMeasurand, |
40 | OCPP16MeterValuePhase, | |
370ae4ee | 41 | OCPP16RequestCommand, |
268a74bb JB |
42 | type OCPP16SampledValue, |
43 | OCPP16StandardParametersKey, | |
d19b10a8 | 44 | OCPP16StopTransactionReason, |
268a74bb JB |
45 | type OCPP16SupportedFeatureProfiles, |
46 | OCPPVersion, | |
47 | type SampledValueTemplate, | |
268a74bb | 48 | } from '../../../types'; |
9bf0ef23 JB |
49 | import { |
50 | ACElectricUtils, | |
51 | Constants, | |
52 | DCElectricUtils, | |
53 | convertToFloat, | |
54 | convertToInt, | |
55 | getRandomFloatFluctuatedRounded, | |
56 | getRandomFloatRounded, | |
57 | getRandomInteger, | |
58 | isNotEmptyArray, | |
5a47f72c | 59 | isNotEmptyString, |
9bf0ef23 JB |
60 | isNullOrUndefined, |
61 | isUndefined, | |
62 | logger, | |
63 | roundTo, | |
64 | } from '../../../utils'; | |
4c3c0d59 | 65 | import { OCPPServiceUtils } from '../OCPPServiceUtils'; |
6ed92bc1 | 66 | |
7bc31f9c | 67 | export class OCPP16ServiceUtils extends OCPPServiceUtils { |
370ae4ee JB |
68 | public static checkFeatureProfile( |
69 | chargingStation: ChargingStation, | |
70 | featureProfile: OCPP16SupportedFeatureProfiles, | |
5edd8ba0 | 71 | command: OCPP16RequestCommand | OCPP16IncomingRequestCommand, |
370ae4ee | 72 | ): boolean { |
d8093be1 | 73 | if (!hasFeatureProfile(chargingStation, featureProfile)) { |
370ae4ee JB |
74 | logger.warn( |
75 | `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${ | |
76 | OCPP16StandardParametersKey.SupportedFeatureProfiles | |
5edd8ba0 | 77 | } in configuration`, |
370ae4ee JB |
78 | ); |
79 | return false; | |
80 | } | |
81 | return true; | |
82 | } | |
83 | ||
78085c42 JB |
84 | public static buildMeterValue( |
85 | chargingStation: ChargingStation, | |
86 | connectorId: number, | |
87 | transactionId: number, | |
88 | interval: number, | |
5edd8ba0 | 89 | debug = false, |
78085c42 JB |
90 | ): OCPP16MeterValue { |
91 | const meterValue: OCPP16MeterValue = { | |
c38f0ced | 92 | timestamp: new Date(), |
78085c42 JB |
93 | sampledValue: [], |
94 | }; | |
95 | const connector = chargingStation.getConnectorStatus(connectorId); | |
96 | // SoC measurand | |
ed3d2808 | 97 | const socSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 98 | chargingStation, |
78085c42 | 99 | connectorId, |
5edd8ba0 | 100 | OCPP16MeterValueMeasurand.STATE_OF_CHARGE, |
78085c42 JB |
101 | ); |
102 | if (socSampledValueTemplate) { | |
860ef183 JB |
103 | const socMaximumValue = 100; |
104 | const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0; | |
5a47f72c | 105 | const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value) |
9bf0ef23 | 106 | ? getRandomFloatFluctuatedRounded( |
78085c42 | 107 | parseInt(socSampledValueTemplate.value), |
5edd8ba0 | 108 | socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 109 | ) |
9bf0ef23 | 110 | : getRandomInteger(socMaximumValue, socMinimumValue); |
78085c42 | 111 | meterValue.sampledValue.push( |
5edd8ba0 | 112 | OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue), |
78085c42 JB |
113 | ); |
114 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
860ef183 | 115 | if ( |
9bf0ef23 JB |
116 | convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue || |
117 | convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue || | |
860ef183 JB |
118 | debug |
119 | ) { | |
78085c42 JB |
120 | logger.error( |
121 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
122 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
123 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 124 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${ |
78085c42 | 125 | meterValue.sampledValue[sampledValuesIndex].value |
9ff486f4 | 126 | }/${socMaximumValue}`, |
78085c42 JB |
127 | ); |
128 | } | |
129 | } | |
130 | // Voltage measurand | |
ed3d2808 | 131 | const voltageSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 132 | chargingStation, |
78085c42 | 133 | connectorId, |
5edd8ba0 | 134 | OCPP16MeterValueMeasurand.VOLTAGE, |
78085c42 JB |
135 | ); |
136 | if (voltageSampledValueTemplate) { | |
5a47f72c | 137 | const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value) |
78085c42 | 138 | ? parseInt(voltageSampledValueTemplate.value) |
5398cecf | 139 | : chargingStation.stationInfo.voltageOut!; |
78085c42 JB |
140 | const fluctuationPercent = |
141 | voltageSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 142 | const voltageMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 143 | voltageSampledValueTemplateValue, |
5edd8ba0 | 144 | fluctuationPercent, |
78085c42 JB |
145 | ); |
146 | if ( | |
147 | chargingStation.getNumberOfPhases() !== 3 || | |
5398cecf JB |
148 | (chargingStation.getNumberOfPhases() === 3 && |
149 | chargingStation.stationInfo?.mainVoltageMeterValues) | |
78085c42 JB |
150 | ) { |
151 | meterValue.sampledValue.push( | |
5edd8ba0 | 152 | OCPP16ServiceUtils.buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue), |
78085c42 JB |
153 | ); |
154 | } | |
155 | for ( | |
156 | let phase = 1; | |
157 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
158 | phase++ | |
159 | ) { | |
160 | const phaseLineToNeutralValue = `L${phase}-N`; | |
161 | const voltagePhaseLineToNeutralSampledValueTemplate = | |
ed3d2808 | 162 | OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 163 | chargingStation, |
78085c42 JB |
164 | connectorId, |
165 | OCPP16MeterValueMeasurand.VOLTAGE, | |
5edd8ba0 | 166 | phaseLineToNeutralValue as OCPP16MeterValuePhase, |
78085c42 | 167 | ); |
e1d9a0f4 | 168 | let voltagePhaseLineToNeutralMeasurandValue: number | undefined; |
78085c42 | 169 | if (voltagePhaseLineToNeutralSampledValueTemplate) { |
5a47f72c JB |
170 | const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString( |
171 | voltagePhaseLineToNeutralSampledValueTemplate.value, | |
172 | ) | |
173 | ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value) | |
174 | : chargingStation.stationInfo.voltageOut!; | |
78085c42 JB |
175 | const fluctuationPhaseToNeutralPercent = |
176 | voltagePhaseLineToNeutralSampledValueTemplate.fluctuationPercent ?? | |
177 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 178 | voltagePhaseLineToNeutralMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 179 | voltagePhaseLineToNeutralSampledValueTemplateValue, |
5edd8ba0 | 180 | fluctuationPhaseToNeutralPercent, |
78085c42 JB |
181 | ); |
182 | } | |
183 | meterValue.sampledValue.push( | |
184 | OCPP16ServiceUtils.buildSampledValue( | |
185 | voltagePhaseLineToNeutralSampledValueTemplate ?? voltageSampledValueTemplate, | |
186 | voltagePhaseLineToNeutralMeasurandValue ?? voltageMeasurandValue, | |
72092cfc | 187 | undefined, |
5edd8ba0 JB |
188 | phaseLineToNeutralValue as OCPP16MeterValuePhase, |
189 | ), | |
78085c42 | 190 | ); |
5398cecf | 191 | if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues) { |
78085c42 JB |
192 | const phaseLineToLineValue = `L${phase}-L${ |
193 | (phase + 1) % chargingStation.getNumberOfPhases() !== 0 | |
194 | ? (phase + 1) % chargingStation.getNumberOfPhases() | |
195 | : chargingStation.getNumberOfPhases() | |
196 | }`; | |
4c149643 JB |
197 | const voltagePhaseLineToLineValueRounded = roundTo( |
198 | Math.sqrt(chargingStation.getNumberOfPhases()) * | |
199 | chargingStation.stationInfo.voltageOut!, | |
200 | 2, | |
201 | ); | |
78085c42 | 202 | const voltagePhaseLineToLineSampledValueTemplate = |
ed3d2808 | 203 | OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 204 | chargingStation, |
78085c42 JB |
205 | connectorId, |
206 | OCPP16MeterValueMeasurand.VOLTAGE, | |
5edd8ba0 | 207 | phaseLineToLineValue as OCPP16MeterValuePhase, |
78085c42 | 208 | ); |
e1d9a0f4 | 209 | let voltagePhaseLineToLineMeasurandValue: number | undefined; |
78085c42 | 210 | if (voltagePhaseLineToLineSampledValueTemplate) { |
5a47f72c JB |
211 | const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString( |
212 | voltagePhaseLineToLineSampledValueTemplate.value, | |
213 | ) | |
214 | ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value) | |
4c149643 | 215 | : voltagePhaseLineToLineValueRounded; |
78085c42 JB |
216 | const fluctuationPhaseLineToLinePercent = |
217 | voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ?? | |
218 | Constants.DEFAULT_FLUCTUATION_PERCENT; | |
9bf0ef23 | 219 | voltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( |
78085c42 | 220 | voltagePhaseLineToLineSampledValueTemplateValue, |
5edd8ba0 | 221 | fluctuationPhaseLineToLinePercent, |
78085c42 JB |
222 | ); |
223 | } | |
9bf0ef23 | 224 | const defaultVoltagePhaseLineToLineMeasurandValue = getRandomFloatFluctuatedRounded( |
4c149643 | 225 | voltagePhaseLineToLineValueRounded, |
5edd8ba0 | 226 | fluctuationPercent, |
78085c42 JB |
227 | ); |
228 | meterValue.sampledValue.push( | |
229 | OCPP16ServiceUtils.buildSampledValue( | |
230 | voltagePhaseLineToLineSampledValueTemplate ?? voltageSampledValueTemplate, | |
231 | voltagePhaseLineToLineMeasurandValue ?? defaultVoltagePhaseLineToLineMeasurandValue, | |
72092cfc | 232 | undefined, |
5edd8ba0 JB |
233 | phaseLineToLineValue as OCPP16MeterValuePhase, |
234 | ), | |
78085c42 JB |
235 | ); |
236 | } | |
237 | } | |
238 | } | |
239 | // Power.Active.Import measurand | |
ed3d2808 | 240 | const powerSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 241 | chargingStation, |
78085c42 | 242 | connectorId, |
5edd8ba0 | 243 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, |
78085c42 | 244 | ); |
abe9e9dd | 245 | let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; |
78085c42 JB |
246 | if (chargingStation.getNumberOfPhases() === 3) { |
247 | powerPerPhaseSampledValueTemplates = { | |
ed3d2808 | 248 | L1: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 249 | chargingStation, |
78085c42 JB |
250 | connectorId, |
251 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 252 | OCPP16MeterValuePhase.L1_N, |
78085c42 | 253 | ), |
ed3d2808 | 254 | L2: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 255 | chargingStation, |
78085c42 JB |
256 | connectorId, |
257 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 258 | OCPP16MeterValuePhase.L2_N, |
78085c42 | 259 | ), |
ed3d2808 | 260 | L3: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 261 | chargingStation, |
78085c42 JB |
262 | connectorId, |
263 | OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT, | |
5edd8ba0 | 264 | OCPP16MeterValuePhase.L3_N, |
78085c42 JB |
265 | ), |
266 | }; | |
267 | } | |
268 | if (powerSampledValueTemplate) { | |
269 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
270 | chargingStation, | |
e1d9a0f4 | 271 | powerSampledValueTemplate.measurand!, |
78085c42 | 272 | ); |
fc040c43 | 273 | const errMsg = `MeterValues measurand ${ |
78085c42 JB |
274 | powerSampledValueTemplate.measurand ?? |
275 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5398cecf | 276 | }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ |
2484ac1e | 277 | chargingStation.templateFile |
78085c42 JB |
278 | }, cannot calculate ${ |
279 | powerSampledValueTemplate.measurand ?? | |
280 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
281 | } measurand value`; | |
e1d9a0f4 | 282 | const powerMeasurandValues: MeasurandValues = {} as MeasurandValues; |
78085c42 | 283 | const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1; |
1b6498ba JB |
284 | const connectorMaximumAvailablePower = |
285 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
286 | const connectorMaximumPower = Math.round(connectorMaximumAvailablePower); | |
ad8537a7 | 287 | const connectorMaximumPowerPerPhase = Math.round( |
5edd8ba0 | 288 | connectorMaximumAvailablePower / chargingStation.getNumberOfPhases(), |
78085c42 | 289 | ); |
d71ce3fa | 290 | const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue ?? 0); |
860ef183 | 291 | const connectorMinimumPowerPerPhase = Math.round( |
5edd8ba0 | 292 | connectorMinimumPower / chargingStation.getNumberOfPhases(), |
860ef183 | 293 | ); |
5398cecf | 294 | switch (chargingStation.stationInfo?.currentOutType) { |
78085c42 JB |
295 | case CurrentType.AC: |
296 | if (chargingStation.getNumberOfPhases() === 3) { | |
297 | const defaultFluctuatedPowerPerPhase = | |
298 | powerSampledValueTemplate.value && | |
9bf0ef23 | 299 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
300 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
301 | powerSampledValueTemplate.value, | |
302 | connectorMaximumPower / unitDivider, | |
d71ce3fa | 303 | connectorMinimumPower / unitDivider, |
5398cecf JB |
304 | { |
305 | limitationEnabled: | |
306 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 307 | fallbackValue: connectorMinimumPower / unitDivider, |
5398cecf | 308 | }, |
34464008 | 309 | ) / chargingStation.getNumberOfPhases(), |
78085c42 | 310 | powerSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 311 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
312 | ); |
313 | const phase1FluctuatedValue = | |
e1d9a0f4 | 314 | powerPerPhaseSampledValueTemplates.L1?.value && |
9bf0ef23 | 315 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
316 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
317 | powerPerPhaseSampledValueTemplates.L1.value, | |
318 | connectorMaximumPowerPerPhase / unitDivider, | |
d71ce3fa | 319 | connectorMinimumPowerPerPhase / unitDivider, |
5398cecf JB |
320 | { |
321 | limitationEnabled: | |
322 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 323 | fallbackValue: connectorMinimumPowerPerPhase / unitDivider, |
5398cecf | 324 | }, |
34464008 | 325 | ), |
78085c42 | 326 | powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? |
5edd8ba0 | 327 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
328 | ); |
329 | const phase2FluctuatedValue = | |
e1d9a0f4 | 330 | powerPerPhaseSampledValueTemplates.L2?.value && |
9bf0ef23 | 331 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
332 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
333 | powerPerPhaseSampledValueTemplates.L2.value, | |
334 | connectorMaximumPowerPerPhase / unitDivider, | |
d71ce3fa | 335 | connectorMinimumPowerPerPhase / unitDivider, |
5398cecf JB |
336 | { |
337 | limitationEnabled: | |
338 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 339 | fallbackValue: connectorMinimumPowerPerPhase / unitDivider, |
5398cecf | 340 | }, |
34464008 | 341 | ), |
78085c42 | 342 | powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? |
5edd8ba0 | 343 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
344 | ); |
345 | const phase3FluctuatedValue = | |
e1d9a0f4 | 346 | powerPerPhaseSampledValueTemplates.L3?.value && |
9bf0ef23 | 347 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
348 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
349 | powerPerPhaseSampledValueTemplates.L3.value, | |
350 | connectorMaximumPowerPerPhase / unitDivider, | |
d71ce3fa | 351 | connectorMinimumPowerPerPhase / unitDivider, |
5398cecf JB |
352 | { |
353 | limitationEnabled: | |
354 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 355 | fallbackValue: connectorMinimumPowerPerPhase / unitDivider, |
5398cecf | 356 | }, |
34464008 | 357 | ), |
78085c42 | 358 | powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? |
5edd8ba0 | 359 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
360 | ); |
361 | powerMeasurandValues.L1 = | |
e1d9a0f4 JB |
362 | (phase1FluctuatedValue as number) ?? |
363 | (defaultFluctuatedPowerPerPhase as number) ?? | |
9bf0ef23 | 364 | getRandomFloatRounded( |
860ef183 | 365 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 366 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 367 | ); |
78085c42 | 368 | powerMeasurandValues.L2 = |
e1d9a0f4 JB |
369 | (phase2FluctuatedValue as number) ?? |
370 | (defaultFluctuatedPowerPerPhase as number) ?? | |
9bf0ef23 | 371 | getRandomFloatRounded( |
860ef183 | 372 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 373 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 374 | ); |
78085c42 | 375 | powerMeasurandValues.L3 = |
e1d9a0f4 JB |
376 | (phase3FluctuatedValue as number) ?? |
377 | (defaultFluctuatedPowerPerPhase as number) ?? | |
9bf0ef23 | 378 | getRandomFloatRounded( |
860ef183 | 379 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 380 | connectorMinimumPowerPerPhase / unitDivider, |
860ef183 | 381 | ); |
78085c42 | 382 | } else { |
856e8f67 | 383 | powerMeasurandValues.L1 = isNotEmptyString(powerSampledValueTemplate.value) |
9bf0ef23 | 384 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
385 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
386 | powerSampledValueTemplate.value, | |
387 | connectorMaximumPower / unitDivider, | |
d71ce3fa | 388 | connectorMinimumPower / unitDivider, |
5398cecf JB |
389 | { |
390 | limitationEnabled: | |
391 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 392 | fallbackValue: connectorMinimumPower / unitDivider, |
5398cecf | 393 | }, |
34464008 | 394 | ), |
78085c42 | 395 | powerSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 396 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 397 | ) |
9bf0ef23 | 398 | : getRandomFloatRounded( |
860ef183 | 399 | connectorMaximumPower / unitDivider, |
5edd8ba0 | 400 | connectorMinimumPower / unitDivider, |
860ef183 | 401 | ); |
78085c42 JB |
402 | powerMeasurandValues.L2 = 0; |
403 | powerMeasurandValues.L3 = 0; | |
404 | } | |
9bf0ef23 | 405 | powerMeasurandValues.allPhases = roundTo( |
78085c42 | 406 | powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, |
5edd8ba0 | 407 | 2, |
78085c42 JB |
408 | ); |
409 | break; | |
410 | case CurrentType.DC: | |
5a47f72c | 411 | powerMeasurandValues.allPhases = isNotEmptyString(powerSampledValueTemplate.value) |
9bf0ef23 | 412 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
413 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
414 | powerSampledValueTemplate.value, | |
415 | connectorMaximumPower / unitDivider, | |
d71ce3fa | 416 | connectorMinimumPower / unitDivider, |
5398cecf JB |
417 | { |
418 | limitationEnabled: | |
419 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 420 | fallbackValue: connectorMinimumPower / unitDivider, |
5398cecf | 421 | }, |
34464008 | 422 | ), |
78085c42 | 423 | powerSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 424 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 425 | ) |
9bf0ef23 | 426 | : getRandomFloatRounded( |
860ef183 | 427 | connectorMaximumPower / unitDivider, |
5edd8ba0 | 428 | connectorMinimumPower / unitDivider, |
860ef183 | 429 | ); |
78085c42 JB |
430 | break; |
431 | default: | |
fc040c43 | 432 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
78085c42 JB |
433 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
434 | } | |
435 | meterValue.sampledValue.push( | |
436 | OCPP16ServiceUtils.buildSampledValue( | |
437 | powerSampledValueTemplate, | |
5edd8ba0 JB |
438 | powerMeasurandValues.allPhases, |
439 | ), | |
78085c42 JB |
440 | ); |
441 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
9bf0ef23 JB |
442 | const connectorMaximumPowerRounded = roundTo(connectorMaximumPower / unitDivider, 2); |
443 | const connectorMinimumPowerRounded = roundTo(connectorMinimumPower / unitDivider, 2); | |
78085c42 | 444 | if ( |
9bf0ef23 | 445 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 446 | connectorMaximumPowerRounded || |
9bf0ef23 | 447 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < |
860ef183 | 448 | connectorMinimumPowerRounded || |
78085c42 JB |
449 | debug |
450 | ) { | |
451 | logger.error( | |
452 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
453 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
454 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 455 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${ |
78085c42 | 456 | meterValue.sampledValue[sampledValuesIndex].value |
5edd8ba0 | 457 | }/${connectorMaximumPowerRounded}`, |
78085c42 JB |
458 | ); |
459 | } | |
460 | for ( | |
461 | let phase = 1; | |
462 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
463 | phase++ | |
464 | ) { | |
465 | const phaseValue = `L${phase}-N`; | |
466 | meterValue.sampledValue.push( | |
467 | OCPP16ServiceUtils.buildSampledValue( | |
a37fc6dc JB |
468 | powerPerPhaseSampledValueTemplates[ |
469 | `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates | |
470 | ]! ?? powerSampledValueTemplate, | |
471 | powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates], | |
72092cfc | 472 | undefined, |
5edd8ba0 JB |
473 | phaseValue as OCPP16MeterValuePhase, |
474 | ), | |
78085c42 JB |
475 | ); |
476 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
9bf0ef23 | 477 | const connectorMaximumPowerPerPhaseRounded = roundTo( |
ad8537a7 | 478 | connectorMaximumPowerPerPhase / unitDivider, |
5edd8ba0 | 479 | 2, |
ad8537a7 | 480 | ); |
9bf0ef23 | 481 | const connectorMinimumPowerPerPhaseRounded = roundTo( |
860ef183 | 482 | connectorMinimumPowerPerPhase / unitDivider, |
5edd8ba0 | 483 | 2, |
860ef183 | 484 | ); |
78085c42 | 485 | if ( |
9bf0ef23 | 486 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > |
ad8537a7 | 487 | connectorMaximumPowerPerPhaseRounded || |
9bf0ef23 | 488 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < |
860ef183 | 489 | connectorMinimumPowerPerPhaseRounded || |
78085c42 JB |
490 | debug |
491 | ) { | |
492 | logger.error( | |
493 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
494 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
495 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
496 | }: phase ${ | |
497 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
5edd8ba0 | 498 | }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${ |
78085c42 | 499 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value |
5edd8ba0 | 500 | }/${connectorMaximumPowerPerPhaseRounded}`, |
78085c42 JB |
501 | ); |
502 | } | |
503 | } | |
504 | } | |
505 | // Current.Import measurand | |
ed3d2808 | 506 | const currentSampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 507 | chargingStation, |
78085c42 | 508 | connectorId, |
5edd8ba0 | 509 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, |
78085c42 | 510 | ); |
abe9e9dd | 511 | let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {}; |
78085c42 JB |
512 | if (chargingStation.getNumberOfPhases() === 3) { |
513 | currentPerPhaseSampledValueTemplates = { | |
ed3d2808 | 514 | L1: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 515 | chargingStation, |
78085c42 JB |
516 | connectorId, |
517 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 518 | OCPP16MeterValuePhase.L1, |
78085c42 | 519 | ), |
ed3d2808 | 520 | L2: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 521 | chargingStation, |
78085c42 JB |
522 | connectorId, |
523 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 524 | OCPP16MeterValuePhase.L2, |
78085c42 | 525 | ), |
ed3d2808 | 526 | L3: OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 527 | chargingStation, |
78085c42 JB |
528 | connectorId, |
529 | OCPP16MeterValueMeasurand.CURRENT_IMPORT, | |
5edd8ba0 | 530 | OCPP16MeterValuePhase.L3, |
78085c42 JB |
531 | ), |
532 | }; | |
533 | } | |
534 | if (currentSampledValueTemplate) { | |
535 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
536 | chargingStation, | |
e1d9a0f4 | 537 | currentSampledValueTemplate.measurand!, |
78085c42 | 538 | ); |
fc040c43 | 539 | const errMsg = `MeterValues measurand ${ |
78085c42 JB |
540 | currentSampledValueTemplate.measurand ?? |
541 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5398cecf | 542 | }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${ |
2484ac1e | 543 | chargingStation.templateFile |
78085c42 JB |
544 | }, cannot calculate ${ |
545 | currentSampledValueTemplate.measurand ?? | |
546 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
547 | } measurand value`; | |
abe9e9dd | 548 | const currentMeasurandValues: MeasurandValues = {} as MeasurandValues; |
1b6498ba JB |
549 | const connectorMaximumAvailablePower = |
550 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
860ef183 | 551 | const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0; |
ad8537a7 | 552 | let connectorMaximumAmperage: number; |
5398cecf | 553 | switch (chargingStation.stationInfo?.currentOutType) { |
78085c42 | 554 | case CurrentType.AC: |
ad8537a7 | 555 | connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower( |
78085c42 | 556 | chargingStation.getNumberOfPhases(), |
1b6498ba | 557 | connectorMaximumAvailablePower, |
5398cecf | 558 | chargingStation.stationInfo.voltageOut!, |
78085c42 JB |
559 | ); |
560 | if (chargingStation.getNumberOfPhases() === 3) { | |
561 | const defaultFluctuatedAmperagePerPhase = | |
562 | currentSampledValueTemplate.value && | |
9bf0ef23 | 563 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
564 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
565 | currentSampledValueTemplate.value, | |
566 | connectorMaximumAmperage, | |
d71ce3fa | 567 | connectorMinimumAmperage, |
5398cecf JB |
568 | { |
569 | limitationEnabled: | |
570 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 571 | fallbackValue: connectorMinimumAmperage, |
5398cecf | 572 | }, |
7bc31f9c | 573 | ), |
78085c42 | 574 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 575 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
576 | ); |
577 | const phase1FluctuatedValue = | |
e1d9a0f4 | 578 | currentPerPhaseSampledValueTemplates.L1?.value && |
9bf0ef23 | 579 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
580 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
581 | currentPerPhaseSampledValueTemplates.L1.value, | |
582 | connectorMaximumAmperage, | |
d71ce3fa | 583 | connectorMinimumAmperage, |
5398cecf JB |
584 | { |
585 | limitationEnabled: | |
586 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 587 | fallbackValue: connectorMinimumAmperage, |
5398cecf | 588 | }, |
34464008 | 589 | ), |
78085c42 | 590 | currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ?? |
5edd8ba0 | 591 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
592 | ); |
593 | const phase2FluctuatedValue = | |
e1d9a0f4 | 594 | currentPerPhaseSampledValueTemplates.L2?.value && |
9bf0ef23 | 595 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
596 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
597 | currentPerPhaseSampledValueTemplates.L2.value, | |
598 | connectorMaximumAmperage, | |
d71ce3fa | 599 | connectorMinimumAmperage, |
5398cecf JB |
600 | { |
601 | limitationEnabled: | |
602 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 603 | fallbackValue: connectorMinimumAmperage, |
5398cecf | 604 | }, |
34464008 | 605 | ), |
78085c42 | 606 | currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ?? |
5edd8ba0 | 607 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
608 | ); |
609 | const phase3FluctuatedValue = | |
e1d9a0f4 | 610 | currentPerPhaseSampledValueTemplates.L3?.value && |
9bf0ef23 | 611 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
612 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
613 | currentPerPhaseSampledValueTemplates.L3.value, | |
614 | connectorMaximumAmperage, | |
d71ce3fa | 615 | connectorMinimumAmperage, |
5398cecf JB |
616 | { |
617 | limitationEnabled: | |
618 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 619 | fallbackValue: connectorMinimumAmperage, |
5398cecf | 620 | }, |
34464008 | 621 | ), |
78085c42 | 622 | currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ?? |
5edd8ba0 | 623 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 JB |
624 | ); |
625 | currentMeasurandValues.L1 = | |
e1d9a0f4 JB |
626 | (phase1FluctuatedValue as number) ?? |
627 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 628 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 629 | currentMeasurandValues.L2 = |
e1d9a0f4 JB |
630 | (phase2FluctuatedValue as number) ?? |
631 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 632 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 633 | currentMeasurandValues.L3 = |
e1d9a0f4 JB |
634 | (phase3FluctuatedValue as number) ?? |
635 | (defaultFluctuatedAmperagePerPhase as number) ?? | |
9bf0ef23 | 636 | getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 | 637 | } else { |
5a47f72c | 638 | currentMeasurandValues.L1 = isNotEmptyString(currentSampledValueTemplate.value) |
9bf0ef23 | 639 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
640 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
641 | currentSampledValueTemplate.value, | |
642 | connectorMaximumAmperage, | |
d71ce3fa | 643 | connectorMinimumAmperage, |
5398cecf JB |
644 | { |
645 | limitationEnabled: | |
646 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 647 | fallbackValue: connectorMinimumAmperage, |
5398cecf | 648 | }, |
7bc31f9c | 649 | ), |
78085c42 | 650 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 651 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 652 | ) |
9bf0ef23 | 653 | : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
654 | currentMeasurandValues.L2 = 0; |
655 | currentMeasurandValues.L3 = 0; | |
656 | } | |
9bf0ef23 | 657 | currentMeasurandValues.allPhases = roundTo( |
78085c42 JB |
658 | (currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / |
659 | chargingStation.getNumberOfPhases(), | |
5edd8ba0 | 660 | 2, |
78085c42 JB |
661 | ); |
662 | break; | |
663 | case CurrentType.DC: | |
ad8537a7 | 664 | connectorMaximumAmperage = DCElectricUtils.amperage( |
1b6498ba | 665 | connectorMaximumAvailablePower, |
5398cecf | 666 | chargingStation.stationInfo.voltageOut!, |
78085c42 | 667 | ); |
5a47f72c | 668 | currentMeasurandValues.allPhases = isNotEmptyString(currentSampledValueTemplate.value) |
9bf0ef23 | 669 | ? getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
670 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
671 | currentSampledValueTemplate.value, | |
672 | connectorMaximumAmperage, | |
d71ce3fa | 673 | connectorMinimumAmperage, |
5398cecf JB |
674 | { |
675 | limitationEnabled: | |
676 | chargingStation.stationInfo?.customValueLimitationMeterValues, | |
d71ce3fa | 677 | fallbackValue: connectorMinimumAmperage, |
5398cecf | 678 | }, |
7bc31f9c | 679 | ), |
78085c42 | 680 | currentSampledValueTemplate.fluctuationPercent ?? |
5edd8ba0 | 681 | Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 682 | ) |
9bf0ef23 | 683 | : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage); |
78085c42 JB |
684 | break; |
685 | default: | |
fc040c43 | 686 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
78085c42 JB |
687 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
688 | } | |
689 | meterValue.sampledValue.push( | |
690 | OCPP16ServiceUtils.buildSampledValue( | |
691 | currentSampledValueTemplate, | |
5edd8ba0 JB |
692 | currentMeasurandValues.allPhases, |
693 | ), | |
78085c42 JB |
694 | ); |
695 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
696 | if ( | |
9bf0ef23 | 697 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > |
ad8537a7 | 698 | connectorMaximumAmperage || |
9bf0ef23 | 699 | convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) < |
860ef183 | 700 | connectorMinimumAmperage || |
78085c42 JB |
701 | debug |
702 | ) { | |
703 | logger.error( | |
704 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
705 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
706 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
5edd8ba0 | 707 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ |
78085c42 | 708 | meterValue.sampledValue[sampledValuesIndex].value |
5edd8ba0 | 709 | }/${connectorMaximumAmperage}`, |
78085c42 JB |
710 | ); |
711 | } | |
712 | for ( | |
713 | let phase = 1; | |
714 | chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); | |
715 | phase++ | |
716 | ) { | |
717 | const phaseValue = `L${phase}`; | |
718 | meterValue.sampledValue.push( | |
719 | OCPP16ServiceUtils.buildSampledValue( | |
a37fc6dc JB |
720 | currentPerPhaseSampledValueTemplates[ |
721 | phaseValue as keyof MeasurandPerPhaseSampledValueTemplates | |
722 | ]! ?? currentSampledValueTemplate, | |
723 | currentMeasurandValues[phaseValue as keyof MeasurandPerPhaseSampledValueTemplates], | |
72092cfc | 724 | undefined, |
5edd8ba0 JB |
725 | phaseValue as OCPP16MeterValuePhase, |
726 | ), | |
78085c42 JB |
727 | ); |
728 | const sampledValuesPerPhaseIndex = meterValue.sampledValue.length - 1; | |
729 | if ( | |
9bf0ef23 | 730 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) > |
ad8537a7 | 731 | connectorMaximumAmperage || |
9bf0ef23 | 732 | convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) < |
860ef183 | 733 | connectorMinimumAmperage || |
78085c42 JB |
734 | debug |
735 | ) { | |
736 | logger.error( | |
737 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
738 | meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? | |
739 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
740 | }: phase ${ | |
741 | meterValue.sampledValue[sampledValuesPerPhaseIndex].phase | |
5edd8ba0 | 742 | }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${ |
78085c42 | 743 | meterValue.sampledValue[sampledValuesPerPhaseIndex].value |
5edd8ba0 | 744 | }/${connectorMaximumAmperage}`, |
78085c42 JB |
745 | ); |
746 | } | |
747 | } | |
748 | } | |
749 | // Energy.Active.Import.Register measurand (default) | |
ed3d2808 | 750 | const energySampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 751 | chargingStation, |
5edd8ba0 | 752 | connectorId, |
492cf6ab | 753 | ); |
78085c42 JB |
754 | if (energySampledValueTemplate) { |
755 | OCPP16ServiceUtils.checkMeasurandPowerDivider( | |
756 | chargingStation, | |
e1d9a0f4 | 757 | energySampledValueTemplate.measurand!, |
78085c42 JB |
758 | ); |
759 | const unitDivider = | |
760 | energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; | |
1b6498ba JB |
761 | const connectorMaximumAvailablePower = |
762 | chargingStation.getConnectorMaximumAvailablePower(connectorId); | |
9bf0ef23 | 763 | const connectorMaximumEnergyRounded = roundTo( |
1b6498ba | 764 | (connectorMaximumAvailablePower * interval) / (3600 * 1000), |
5edd8ba0 | 765 | 2, |
78085c42 | 766 | ); |
d71ce3fa JB |
767 | const connectorMinimumEnergyRounded = roundTo( |
768 | energySampledValueTemplate.minimumValue ?? 0, | |
769 | 2, | |
770 | ); | |
77684af8 | 771 | const energyValueRounded = isNotEmptyString(energySampledValueTemplate.value) |
78085c42 | 772 | ? // Cumulate the fluctuated value around the static one |
9bf0ef23 | 773 | getRandomFloatFluctuatedRounded( |
7bc31f9c JB |
774 | OCPP16ServiceUtils.getLimitFromSampledValueTemplateCustomValue( |
775 | energySampledValueTemplate.value, | |
776 | connectorMaximumEnergyRounded, | |
d71ce3fa | 777 | connectorMinimumEnergyRounded, |
7bc31f9c | 778 | { |
5398cecf | 779 | limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues, |
7bc31f9c | 780 | unitMultiplier: unitDivider, |
d71ce3fa | 781 | fallbackValue: connectorMinimumEnergyRounded, |
5edd8ba0 | 782 | }, |
34464008 | 783 | ), |
5edd8ba0 | 784 | energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT, |
78085c42 | 785 | ) |
d71ce3fa | 786 | : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded); |
78085c42 | 787 | // Persist previous value on connector |
e1d9a0f4 JB |
788 | if (connector) { |
789 | if ( | |
790 | isNullOrUndefined(connector.energyActiveImportRegisterValue) === false && | |
791 | connector.energyActiveImportRegisterValue! >= 0 && | |
792 | isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) === false && | |
793 | connector.transactionEnergyActiveImportRegisterValue! >= 0 | |
794 | ) { | |
795 | connector.energyActiveImportRegisterValue! += energyValueRounded; | |
796 | connector.transactionEnergyActiveImportRegisterValue! += energyValueRounded; | |
797 | } else { | |
798 | connector.energyActiveImportRegisterValue = 0; | |
799 | connector.transactionEnergyActiveImportRegisterValue = 0; | |
800 | } | |
78085c42 JB |
801 | } |
802 | meterValue.sampledValue.push( | |
803 | OCPP16ServiceUtils.buildSampledValue( | |
804 | energySampledValueTemplate, | |
9bf0ef23 | 805 | roundTo( |
78085c42 JB |
806 | chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / |
807 | unitDivider, | |
5edd8ba0 JB |
808 | 2, |
809 | ), | |
810 | ), | |
78085c42 JB |
811 | ); |
812 | const sampledValuesIndex = meterValue.sampledValue.length - 1; | |
d71ce3fa JB |
813 | if ( |
814 | energyValueRounded > connectorMaximumEnergyRounded || | |
815 | energyValueRounded < connectorMinimumEnergyRounded || | |
816 | debug | |
817 | ) { | |
78085c42 JB |
818 | logger.error( |
819 | `${chargingStation.logPrefix()} MeterValues measurand ${ | |
820 | meterValue.sampledValue[sampledValuesIndex].measurand ?? | |
821 | OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER | |
d71ce3fa | 822 | }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`, |
78085c42 JB |
823 | ); |
824 | } | |
825 | } | |
826 | return meterValue; | |
827 | } | |
828 | ||
e7aeea18 JB |
829 | public static buildTransactionBeginMeterValue( |
830 | chargingStation: ChargingStation, | |
831 | connectorId: number, | |
5edd8ba0 | 832 | meterStart: number, |
e7aeea18 | 833 | ): OCPP16MeterValue { |
fd0c36fa | 834 | const meterValue: OCPP16MeterValue = { |
c38f0ced | 835 | timestamp: new Date(), |
fd0c36fa JB |
836 | sampledValue: [], |
837 | }; | |
9ccca265 | 838 | // Energy.Active.Import.Register measurand (default) |
ed3d2808 | 839 | const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 840 | chargingStation, |
5edd8ba0 | 841 | connectorId, |
492cf6ab | 842 | ); |
9ccca265 | 843 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; |
e7aeea18 JB |
844 | meterValue.sampledValue.push( |
845 | OCPP16ServiceUtils.buildSampledValue( | |
e1d9a0f4 | 846 | sampledValueTemplate!, |
9bf0ef23 | 847 | roundTo((meterStart ?? 0) / unitDivider, 4), |
5edd8ba0 JB |
848 | MeterValueContext.TRANSACTION_BEGIN, |
849 | ), | |
e7aeea18 | 850 | ); |
fd0c36fa JB |
851 | return meterValue; |
852 | } | |
853 | ||
e7aeea18 JB |
854 | public static buildTransactionEndMeterValue( |
855 | chargingStation: ChargingStation, | |
856 | connectorId: number, | |
5edd8ba0 | 857 | meterStop: number, |
e7aeea18 | 858 | ): OCPP16MeterValue { |
fd0c36fa | 859 | const meterValue: OCPP16MeterValue = { |
c38f0ced | 860 | timestamp: new Date(), |
fd0c36fa JB |
861 | sampledValue: [], |
862 | }; | |
9ccca265 | 863 | // Energy.Active.Import.Register measurand (default) |
ed3d2808 | 864 | const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate( |
492cf6ab | 865 | chargingStation, |
5edd8ba0 | 866 | connectorId, |
492cf6ab | 867 | ); |
9ccca265 | 868 | const unitDivider = sampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1; |
e7aeea18 JB |
869 | meterValue.sampledValue.push( |
870 | OCPP16ServiceUtils.buildSampledValue( | |
e1d9a0f4 | 871 | sampledValueTemplate!, |
9bf0ef23 | 872 | roundTo((meterStop ?? 0) / unitDivider, 4), |
5edd8ba0 JB |
873 | MeterValueContext.TRANSACTION_END, |
874 | ), | |
e7aeea18 | 875 | ); |
fd0c36fa JB |
876 | return meterValue; |
877 | } | |
878 | ||
e7aeea18 JB |
879 | public static buildTransactionDataMeterValues( |
880 | transactionBeginMeterValue: OCPP16MeterValue, | |
5edd8ba0 | 881 | transactionEndMeterValue: OCPP16MeterValue, |
e7aeea18 | 882 | ): OCPP16MeterValue[] { |
fd0c36fa JB |
883 | const meterValues: OCPP16MeterValue[] = []; |
884 | meterValues.push(transactionBeginMeterValue); | |
885 | meterValues.push(transactionEndMeterValue); | |
886 | return meterValues; | |
887 | } | |
7bc31f9c | 888 | |
d19b10a8 JB |
889 | public static remoteStopTransaction = async ( |
890 | chargingStation: ChargingStation, | |
891 | connectorId: number, | |
892 | ): Promise<GenericResponse> => { | |
893 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( | |
894 | chargingStation, | |
895 | connectorId, | |
896 | OCPP16ChargePointStatus.Finishing, | |
897 | ); | |
898 | const stopResponse = await chargingStation.stopTransactionOnConnector( | |
899 | connectorId, | |
900 | OCPP16StopTransactionReason.REMOTE, | |
901 | ); | |
902 | if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { | |
903 | return OCPP16Constants.OCPP_RESPONSE_ACCEPTED; | |
904 | } | |
905 | return OCPP16Constants.OCPP_RESPONSE_REJECTED; | |
906 | }; | |
907 | ||
366f75f6 JB |
908 | public static changeAvailability = async ( |
909 | chargingStation: ChargingStation, | |
225e32b0 | 910 | connectorIds: number[], |
366f75f6 JB |
911 | chargePointStatus: OCPP16ChargePointStatus, |
912 | availabilityType: OCPP16AvailabilityType, | |
913 | ): Promise<OCPP16ChangeAvailabilityResponse> => { | |
225e32b0 JB |
914 | const responses: OCPP16ChangeAvailabilityResponse[] = []; |
915 | for (const connectorId of connectorIds) { | |
916 | let response: OCPP16ChangeAvailabilityResponse = | |
917 | OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED; | |
918 | const connectorStatus = chargingStation.getConnectorStatus(connectorId)!; | |
919 | if (connectorStatus?.transactionStarted === true) { | |
920 | response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED; | |
921 | } | |
922 | connectorStatus.availability = availabilityType; | |
923 | if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) { | |
924 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( | |
925 | chargingStation, | |
926 | connectorId, | |
927 | chargePointStatus, | |
928 | ); | |
929 | } | |
930 | responses.push(response); | |
366f75f6 | 931 | } |
3b0ed034 | 932 | if (responses.includes(OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED)) { |
225e32b0 | 933 | return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED; |
366f75f6 | 934 | } |
225e32b0 | 935 | return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED; |
366f75f6 JB |
936 | }; |
937 | ||
ed3d2808 JB |
938 | public static setChargingProfile( |
939 | chargingStation: ChargingStation, | |
940 | connectorId: number, | |
5edd8ba0 | 941 | cp: OCPP16ChargingProfile, |
ed3d2808 | 942 | ): void { |
9bf0ef23 | 943 | if (isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) { |
ed3d2808 | 944 | logger.error( |
5edd8ba0 | 945 | `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`, |
ed3d2808 | 946 | ); |
e1d9a0f4 | 947 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []; |
ed3d2808 | 948 | } |
72092cfc JB |
949 | if ( |
950 | Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false | |
951 | ) { | |
ed3d2808 | 952 | logger.error( |
bbb55ee4 | 953 | `${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 | 954 | ); |
e1d9a0f4 | 955 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []; |
ed3d2808 JB |
956 | } |
957 | let cpReplaced = false; | |
9bf0ef23 | 958 | if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) { |
ed3d2808 JB |
959 | chargingStation |
960 | .getConnectorStatus(connectorId) | |
72092cfc | 961 | ?.chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { |
ed3d2808 JB |
962 | if ( |
963 | chargingProfile.chargingProfileId === cp.chargingProfileId || | |
964 | (chargingProfile.stackLevel === cp.stackLevel && | |
965 | chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose) | |
966 | ) { | |
e1d9a0f4 | 967 | chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp; |
ed3d2808 JB |
968 | cpReplaced = true; |
969 | } | |
970 | }); | |
971 | } | |
72092cfc | 972 | !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp); |
ed3d2808 JB |
973 | } |
974 | ||
73d87be1 JB |
975 | public static clearChargingProfiles = ( |
976 | chargingStation: ChargingStation, | |
977 | commandPayload: ClearChargingProfileRequest, | |
978 | chargingProfiles: OCPP16ChargingProfile[] | undefined, | |
979 | ): boolean => { | |
0d1f33ba | 980 | const { id, chargingProfilePurpose, stackLevel } = commandPayload; |
73d87be1 JB |
981 | let clearedCP = false; |
982 | if (isNotEmptyArray(chargingProfiles)) { | |
983 | chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => { | |
984 | let clearCurrentCP = false; | |
0d1f33ba | 985 | if (chargingProfile.chargingProfileId === id) { |
73d87be1 JB |
986 | clearCurrentCP = true; |
987 | } | |
0d1f33ba | 988 | if (!chargingProfilePurpose && chargingProfile.stackLevel === stackLevel) { |
73d87be1 JB |
989 | clearCurrentCP = true; |
990 | } | |
0d1f33ba | 991 | if (!stackLevel && chargingProfile.chargingProfilePurpose === chargingProfilePurpose) { |
73d87be1 JB |
992 | clearCurrentCP = true; |
993 | } | |
994 | if ( | |
0d1f33ba JB |
995 | chargingProfile.stackLevel === stackLevel && |
996 | chargingProfile.chargingProfilePurpose === chargingProfilePurpose | |
73d87be1 JB |
997 | ) { |
998 | clearCurrentCP = true; | |
999 | } | |
1000 | if (clearCurrentCP) { | |
1001 | chargingProfiles.splice(index, 1); | |
1002 | logger.debug( | |
1003 | `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`, | |
1004 | chargingProfile, | |
1005 | ); | |
1006 | clearedCP = true; | |
1007 | } | |
1008 | }); | |
1009 | } | |
1010 | return clearedCP; | |
1011 | }; | |
1012 | ||
ef9e3b33 | 1013 | public static composeChargingSchedules = ( |
4abf6441 JB |
1014 | chargingScheduleHigher: OCPP16ChargingSchedule | undefined, |
1015 | chargingScheduleLower: OCPP16ChargingSchedule | undefined, | |
d632062f | 1016 | compositeInterval: Interval, |
ef9e3b33 | 1017 | ): OCPP16ChargingSchedule | undefined => { |
4abf6441 | 1018 | if (!chargingScheduleHigher && !chargingScheduleLower) { |
ef9e3b33 JB |
1019 | return undefined; |
1020 | } | |
4abf6441 | 1021 | if (chargingScheduleHigher && !chargingScheduleLower) { |
d632062f | 1022 | return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher, compositeInterval); |
ef9e3b33 | 1023 | } |
4abf6441 | 1024 | if (!chargingScheduleHigher && chargingScheduleLower) { |
d632062f | 1025 | return OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower, compositeInterval); |
ef9e3b33 | 1026 | } |
4abf6441 | 1027 | const compositeChargingScheduleHigher: OCPP16ChargingSchedule | undefined = |
d632062f | 1028 | OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleHigher!, compositeInterval); |
4abf6441 | 1029 | const compositeChargingScheduleLower: OCPP16ChargingSchedule | undefined = |
d632062f | 1030 | OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval); |
4abf6441 JB |
1031 | const compositeChargingScheduleHigherInterval: Interval = { |
1032 | start: compositeChargingScheduleHigher!.startSchedule!, | |
ef9e3b33 | 1033 | end: addSeconds( |
4abf6441 JB |
1034 | compositeChargingScheduleHigher!.startSchedule!, |
1035 | compositeChargingScheduleHigher!.duration!, | |
ef9e3b33 JB |
1036 | ), |
1037 | }; | |
4abf6441 JB |
1038 | const compositeChargingScheduleLowerInterval: Interval = { |
1039 | start: compositeChargingScheduleLower!.startSchedule!, | |
ef9e3b33 | 1040 | end: addSeconds( |
4abf6441 JB |
1041 | compositeChargingScheduleLower!.startSchedule!, |
1042 | compositeChargingScheduleLower!.duration!, | |
ef9e3b33 JB |
1043 | ), |
1044 | }; | |
4abf6441 JB |
1045 | const higherFirst = isBefore( |
1046 | compositeChargingScheduleHigherInterval.start, | |
1047 | compositeChargingScheduleLowerInterval.start, | |
1048 | ); | |
ef9e3b33 JB |
1049 | if ( |
1050 | !areIntervalsOverlapping( | |
4abf6441 JB |
1051 | compositeChargingScheduleHigherInterval, |
1052 | compositeChargingScheduleLowerInterval, | |
ef9e3b33 JB |
1053 | ) |
1054 | ) { | |
1055 | return { | |
4abf6441 JB |
1056 | ...compositeChargingScheduleLower, |
1057 | ...compositeChargingScheduleHigher!, | |
1058 | startSchedule: higherFirst | |
1059 | ? (compositeChargingScheduleHigherInterval.start as Date) | |
1060 | : (compositeChargingScheduleLowerInterval.start as Date), | |
1061 | duration: higherFirst | |
1062 | ? differenceInSeconds( | |
1063 | compositeChargingScheduleLowerInterval.end, | |
1064 | compositeChargingScheduleHigherInterval.start, | |
1065 | ) | |
1066 | : differenceInSeconds( | |
1067 | compositeChargingScheduleHigherInterval.end, | |
1068 | compositeChargingScheduleLowerInterval.start, | |
1069 | ), | |
1070 | chargingSchedulePeriod: [ | |
1071 | ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { | |
1072 | return { | |
1073 | ...schedulePeriod, | |
1074 | startPeriod: higherFirst | |
1075 | ? 0 | |
1076 | : schedulePeriod.startPeriod + | |
1077 | differenceInSeconds( | |
1078 | compositeChargingScheduleHigherInterval.start, | |
1079 | compositeChargingScheduleLowerInterval.start, | |
1080 | ), | |
1081 | }; | |
1082 | }), | |
1083 | ...compositeChargingScheduleLower!.chargingSchedulePeriod.map((schedulePeriod) => { | |
1084 | return { | |
1085 | ...schedulePeriod, | |
1086 | startPeriod: higherFirst | |
1087 | ? schedulePeriod.startPeriod + | |
1088 | differenceInSeconds( | |
1089 | compositeChargingScheduleLowerInterval.start, | |
1090 | compositeChargingScheduleHigherInterval.start, | |
1091 | ) | |
1092 | : 0, | |
1093 | }; | |
1094 | }), | |
1095 | ].sort((a, b) => a.startPeriod - b.startPeriod), | |
ef9e3b33 JB |
1096 | }; |
1097 | } | |
4abf6441 JB |
1098 | return { |
1099 | ...compositeChargingScheduleLower, | |
1100 | ...compositeChargingScheduleHigher!, | |
1101 | startSchedule: higherFirst | |
1102 | ? (compositeChargingScheduleHigherInterval.start as Date) | |
1103 | : (compositeChargingScheduleLowerInterval.start as Date), | |
1104 | duration: higherFirst | |
1105 | ? differenceInSeconds( | |
1106 | compositeChargingScheduleLowerInterval.end, | |
1107 | compositeChargingScheduleHigherInterval.start, | |
1108 | ) | |
1109 | : differenceInSeconds( | |
1110 | compositeChargingScheduleHigherInterval.end, | |
1111 | compositeChargingScheduleLowerInterval.start, | |
1112 | ), | |
1113 | chargingSchedulePeriod: [ | |
1114 | ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map((schedulePeriod) => { | |
1115 | return { | |
1116 | ...schedulePeriod, | |
1117 | startPeriod: higherFirst | |
1118 | ? 0 | |
1119 | : schedulePeriod.startPeriod + | |
1120 | differenceInSeconds( | |
1121 | compositeChargingScheduleHigherInterval.start, | |
1122 | compositeChargingScheduleLowerInterval.start, | |
1123 | ), | |
1124 | }; | |
1125 | }), | |
1126 | ...compositeChargingScheduleLower!.chargingSchedulePeriod | |
c4ab56ba | 1127 | .filter((schedulePeriod, index) => { |
4abf6441 JB |
1128 | if ( |
1129 | higherFirst && | |
1130 | isWithinInterval( | |
1131 | addSeconds( | |
1132 | compositeChargingScheduleLowerInterval.start, | |
1133 | schedulePeriod.startPeriod, | |
1134 | ), | |
1135 | { | |
1136 | start: compositeChargingScheduleLowerInterval.start, | |
1137 | end: compositeChargingScheduleHigherInterval.end, | |
1138 | }, | |
1139 | ) | |
1140 | ) { | |
1141 | return false; | |
1142 | } | |
c4ab56ba JB |
1143 | if ( |
1144 | higherFirst && | |
1145 | index < compositeChargingScheduleLower!.chargingSchedulePeriod.length - 1 && | |
1146 | !isWithinInterval( | |
1147 | addSeconds( | |
1148 | compositeChargingScheduleLowerInterval.start, | |
1149 | schedulePeriod.startPeriod, | |
1150 | ), | |
1151 | { | |
1152 | start: compositeChargingScheduleLowerInterval.start, | |
1153 | end: compositeChargingScheduleHigherInterval.end, | |
1154 | }, | |
1155 | ) && | |
1156 | isWithinInterval( | |
1157 | addSeconds( | |
1158 | compositeChargingScheduleLowerInterval.start, | |
1159 | compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod, | |
1160 | ), | |
1161 | { | |
1162 | start: compositeChargingScheduleLowerInterval.start, | |
1163 | end: compositeChargingScheduleHigherInterval.end, | |
1164 | }, | |
1165 | ) | |
1166 | ) { | |
c4ab56ba JB |
1167 | return false; |
1168 | } | |
4abf6441 JB |
1169 | if ( |
1170 | !higherFirst && | |
1171 | isWithinInterval( | |
1172 | addSeconds( | |
1173 | compositeChargingScheduleLowerInterval.start, | |
1174 | schedulePeriod.startPeriod, | |
1175 | ), | |
1176 | { | |
1177 | start: compositeChargingScheduleHigherInterval.start, | |
1178 | end: compositeChargingScheduleLowerInterval.end, | |
1179 | }, | |
1180 | ) | |
1181 | ) { | |
1182 | return false; | |
1183 | } | |
1184 | return true; | |
1185 | }) | |
0e14e1d4 JB |
1186 | .map((schedulePeriod, index) => { |
1187 | if (index === 0 && schedulePeriod.startPeriod !== 0) { | |
1188 | schedulePeriod.startPeriod = 0; | |
1189 | } | |
4abf6441 JB |
1190 | return { |
1191 | ...schedulePeriod, | |
1192 | startPeriod: higherFirst | |
1193 | ? schedulePeriod.startPeriod + | |
1194 | differenceInSeconds( | |
1195 | compositeChargingScheduleLowerInterval.start, | |
1196 | compositeChargingScheduleHigherInterval.start, | |
1197 | ) | |
1198 | : 0, | |
1199 | }; | |
1200 | }), | |
1201 | ].sort((a, b) => a.startPeriod - b.startPeriod), | |
1202 | }; | |
ef9e3b33 JB |
1203 | }; |
1204 | ||
90aceaf6 JB |
1205 | public static hasReservation = ( |
1206 | chargingStation: ChargingStation, | |
1207 | connectorId: number, | |
1208 | idTag: string, | |
1209 | ): boolean => { | |
1210 | const connectorReservation = chargingStation.getReservationBy('connectorId', connectorId); | |
1211 | const chargingStationReservation = chargingStation.getReservationBy('connectorId', 0); | |
1212 | if ( | |
1213 | (chargingStation.getConnectorStatus(connectorId)?.status === | |
1214 | OCPP16ChargePointStatus.Reserved && | |
1215 | connectorReservation && | |
56563a3c | 1216 | !hasReservationExpired(connectorReservation) && |
90aceaf6 | 1217 | // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing |
56563a3c | 1218 | connectorReservation?.idTag === idTag) || |
90aceaf6 JB |
1219 | (chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved && |
1220 | chargingStationReservation && | |
56563a3c JB |
1221 | !hasReservationExpired(chargingStationReservation) && |
1222 | chargingStationReservation?.idTag === idTag) | |
90aceaf6 | 1223 | ) { |
88499f52 JB |
1224 | logger.debug( |
1225 | `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`, | |
1226 | connectorReservation ?? chargingStationReservation, | |
1227 | ); | |
56563a3c | 1228 | return true; |
90aceaf6 | 1229 | } |
56563a3c | 1230 | return false; |
90aceaf6 JB |
1231 | }; |
1232 | ||
1b271a54 JB |
1233 | public static parseJsonSchemaFile<T extends JsonType>( |
1234 | relativePath: string, | |
1235 | moduleName?: string, | |
5edd8ba0 | 1236 | methodName?: string, |
1b271a54 | 1237 | ): JSONSchemaType<T> { |
7164966d | 1238 | return super.parseJsonSchemaFile<T>( |
51022aa0 | 1239 | relativePath, |
1b271a54 JB |
1240 | OCPPVersion.VERSION_16, |
1241 | moduleName, | |
5edd8ba0 | 1242 | methodName, |
7164966d | 1243 | ); |
130783a7 JB |
1244 | } |
1245 | ||
ef9e3b33 JB |
1246 | private static composeChargingSchedule = ( |
1247 | chargingSchedule: OCPP16ChargingSchedule, | |
d632062f | 1248 | compositeInterval: Interval, |
ef9e3b33 JB |
1249 | ): OCPP16ChargingSchedule | undefined => { |
1250 | const chargingScheduleInterval: Interval = { | |
1251 | start: chargingSchedule.startSchedule!, | |
1252 | end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), | |
1253 | }; | |
d632062f | 1254 | if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) { |
ef9e3b33 | 1255 | chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod); |
d632062f | 1256 | if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) { |
ef9e3b33 JB |
1257 | return { |
1258 | ...chargingSchedule, | |
d632062f JB |
1259 | startSchedule: compositeInterval.start as Date, |
1260 | duration: differenceInSeconds( | |
1261 | chargingScheduleInterval.end, | |
1262 | compositeInterval.start as Date, | |
1263 | ), | |
0e14e1d4 JB |
1264 | chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod |
1265 | .filter((schedulePeriod, index) => { | |
ef9e3b33 JB |
1266 | if ( |
1267 | isWithinInterval( | |
1268 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!, | |
d632062f | 1269 | compositeInterval, |
ef9e3b33 JB |
1270 | ) |
1271 | ) { | |
1272 | return true; | |
1273 | } | |
1274 | if ( | |
1275 | index < chargingSchedule.chargingSchedulePeriod.length - 1 && | |
1276 | !isWithinInterval( | |
1277 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod), | |
d632062f | 1278 | compositeInterval, |
ef9e3b33 JB |
1279 | ) && |
1280 | isWithinInterval( | |
1281 | addSeconds( | |
1282 | chargingScheduleInterval.start, | |
1283 | chargingSchedule.chargingSchedulePeriod[index + 1].startPeriod, | |
1284 | ), | |
d632062f | 1285 | compositeInterval, |
ef9e3b33 JB |
1286 | ) |
1287 | ) { | |
ef9e3b33 JB |
1288 | return true; |
1289 | } | |
1290 | return false; | |
0e14e1d4 JB |
1291 | }) |
1292 | .map((schedulePeriod, index) => { | |
1293 | if (index === 0 && schedulePeriod.startPeriod !== 0) { | |
1294 | schedulePeriod.startPeriod = 0; | |
1295 | } | |
1296 | return schedulePeriod; | |
1297 | }), | |
ef9e3b33 JB |
1298 | }; |
1299 | } | |
d632062f | 1300 | if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) { |
ef9e3b33 JB |
1301 | return { |
1302 | ...chargingSchedule, | |
d632062f JB |
1303 | duration: differenceInSeconds( |
1304 | compositeInterval.end as Date, | |
1305 | chargingScheduleInterval.start, | |
1306 | ), | |
ef9e3b33 JB |
1307 | chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter((schedulePeriod) => |
1308 | isWithinInterval( | |
1309 | addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod)!, | |
d632062f | 1310 | compositeInterval, |
ef9e3b33 JB |
1311 | ), |
1312 | ), | |
1313 | }; | |
1314 | } | |
1315 | return chargingSchedule; | |
1316 | } | |
1317 | }; | |
1318 | ||
7bc31f9c JB |
1319 | private static buildSampledValue( |
1320 | sampledValueTemplate: SampledValueTemplate, | |
1321 | value: number, | |
1322 | context?: MeterValueContext, | |
5edd8ba0 | 1323 | phase?: OCPP16MeterValuePhase, |
7bc31f9c | 1324 | ): OCPP16SampledValue { |
4ed03b6e JB |
1325 | const sampledValueValue = value ?? sampledValueTemplate?.value; |
1326 | const sampledValueContext = context ?? sampledValueTemplate?.context; | |
7bc31f9c JB |
1327 | const sampledValueLocation = |
1328 | sampledValueTemplate?.location ?? | |
e1d9a0f4 | 1329 | OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand!); |
4ed03b6e | 1330 | const sampledValuePhase = phase ?? sampledValueTemplate?.phase; |
7bc31f9c | 1331 | return { |
9bf0ef23 | 1332 | ...(!isNullOrUndefined(sampledValueTemplate.unit) && { |
7bc31f9c JB |
1333 | unit: sampledValueTemplate.unit, |
1334 | }), | |
9bf0ef23 JB |
1335 | ...(!isNullOrUndefined(sampledValueContext) && { context: sampledValueContext }), |
1336 | ...(!isNullOrUndefined(sampledValueTemplate.measurand) && { | |
7bc31f9c JB |
1337 | measurand: sampledValueTemplate.measurand, |
1338 | }), | |
9bf0ef23 JB |
1339 | ...(!isNullOrUndefined(sampledValueLocation) && { location: sampledValueLocation }), |
1340 | ...(!isNullOrUndefined(sampledValueValue) && { value: sampledValueValue.toString() }), | |
1341 | ...(!isNullOrUndefined(sampledValuePhase) && { phase: sampledValuePhase }), | |
e1d9a0f4 | 1342 | } as OCPP16SampledValue; |
7bc31f9c JB |
1343 | } |
1344 | ||
1345 | private static checkMeasurandPowerDivider( | |
1346 | chargingStation: ChargingStation, | |
5edd8ba0 | 1347 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c | 1348 | ): void { |
9bf0ef23 | 1349 | if (isUndefined(chargingStation.powerDivider)) { |
fc040c43 | 1350 | const errMsg = `MeterValues measurand ${ |
7bc31f9c JB |
1351 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER |
1352 | }: powerDivider is undefined`; | |
fc040c43 | 1353 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
7bc31f9c | 1354 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
fa7bccf4 | 1355 | } else if (chargingStation?.powerDivider <= 0) { |
fc040c43 | 1356 | const errMsg = `MeterValues measurand ${ |
7bc31f9c | 1357 | measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER |
fa7bccf4 | 1358 | }: powerDivider have zero or below value ${chargingStation.powerDivider}`; |
fc040c43 | 1359 | logger.error(`${chargingStation.logPrefix()} ${errMsg}`); |
7bc31f9c JB |
1360 | throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, OCPP16RequestCommand.METER_VALUES); |
1361 | } | |
1362 | } | |
1363 | ||
1364 | private static getMeasurandDefaultLocation( | |
5edd8ba0 | 1365 | measurandType: OCPP16MeterValueMeasurand, |
7bc31f9c JB |
1366 | ): MeterValueLocation | undefined { |
1367 | switch (measurandType) { | |
1368 | case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
1369 | return MeterValueLocation.EV; | |
1370 | } | |
1371 | } | |
1372 | ||
3b0ed034 JB |
1373 | // private static getMeasurandDefaultUnit( |
1374 | // measurandType: OCPP16MeterValueMeasurand, | |
1375 | // ): MeterValueUnit | undefined { | |
1376 | // switch (measurandType) { | |
1377 | // case OCPP16MeterValueMeasurand.CURRENT_EXPORT: | |
1378 | // case OCPP16MeterValueMeasurand.CURRENT_IMPORT: | |
1379 | // case OCPP16MeterValueMeasurand.CURRENT_OFFERED: | |
1380 | // return MeterValueUnit.AMP; | |
1381 | // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_EXPORT_REGISTER: | |
1382 | // case OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER: | |
1383 | // return MeterValueUnit.WATT_HOUR; | |
1384 | // case OCPP16MeterValueMeasurand.POWER_ACTIVE_EXPORT: | |
1385 | // case OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT: | |
1386 | // case OCPP16MeterValueMeasurand.POWER_OFFERED: | |
1387 | // return MeterValueUnit.WATT; | |
1388 | // case OCPP16MeterValueMeasurand.STATE_OF_CHARGE: | |
1389 | // return MeterValueUnit.PERCENT; | |
1390 | // case OCPP16MeterValueMeasurand.VOLTAGE: | |
1391 | // return MeterValueUnit.VOLT; | |
1392 | // } | |
1393 | // } | |
6ed92bc1 | 1394 | } |