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