Skip to content

Commit 48e4725

Browse files
committed
copilot findings
Signed-off-by: Bernd Weymann <[email protected]>
1 parent 99dff42 commit 48e4725

File tree

2 files changed

+56
-25
lines changed

2 files changed

+56
-25
lines changed

bundles/org.openhab.transform.basicprofiles/src/main/java/org/openhab/transform/basicprofiles/internal/profiles/TimeweightedAverageStateProfile.java

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ public TimeweightedAverageStateProfile(ProfileCallback callback, ProfileContext
7070
scheduleDuration = DurationUtils.parse(config.duration);
7171
} catch (IllegalArgumentException e) {
7272
scheduleDuration = DEFAULT_TIMEOUT;
73-
logger.warn("Invalid duration configuration {} for item {}", config.duration, itemName);
73+
logger.warn("Invalid duration configuration {} for item {}. Fallback to {}", config.duration, itemName,
74+
DEFAULT_TIMEOUT);
7475
}
7576
}
7677

@@ -131,15 +132,24 @@ private boolean deltaExceeded(State state) {
131132
}
132133

133134
private void deliver() {
134-
TreeMap<Instant, State> delivery = new TreeMap<>();
135+
TreeMap<Instant, State> delivery = prepareDelivery();
136+
if (delivery.size() <= 1) {
137+
logger.debug("Cannot calculate time-weighted average for item {} with {} elements", itemName,
138+
delivery.size());
139+
} else {
140+
callback.sendUpdate(getState(average(delivery)));
141+
}
142+
}
143+
144+
private TreeMap<Instant, State> prepareDelivery() {
145+
TreeMap<Instant, State> delivery;
135146
synchronized (timeframe) {
136147
resetJob();
137-
delivery = (TreeMap<Instant, State>) timeframe.clone();
148+
delivery = new TreeMap<>(timeframe);
138149
// add termination element
139150
delivery.put(Instant.now(), DecimalType.ZERO);
140151
// clear time frame and put latest reported state as start point of the next calculation
141152
timeframe.clear();
142-
143153
State localState = latestState;
144154
if (localState != null) {
145155
if (streamingInTimeframe) {
@@ -149,17 +159,11 @@ private void deliver() {
149159
startJob();
150160
} else {
151161
// no new states received during this time frame
152-
logger.debug("no new states received, wait for next state update");
162+
logger.debug("No new states for {} received, wait for next state update", itemName);
153163
}
154164
}
155165
}
156-
157-
if (delivery.size() <= 1) {
158-
logger.debug("Cannot calculate time-weighted average for item {} with {} elements", itemName,
159-
delivery.size());
160-
} else {
161-
callback.sendUpdate(getState(average(delivery)));
162-
}
166+
return delivery;
163167
}
164168

165169
private void startJob() {
@@ -173,37 +177,34 @@ private void resetJob() {
173177
ScheduledFuture<?> localTwaJob = twaJob;
174178
if (localTwaJob != null) {
175179
localTwaJob.cancel(false);
180+
twaJob = null;
176181
}
177-
twaJob = null;
178182
}
179183

180184
private double state2Double(State state) {
181185
DecimalType as = state.as(DecimalType.class);
182186
if (as == null) {
183-
logger.error("Cannot convert state {} of item {} to DecimalType for average calculation", state, itemName);
187+
// may happen if state delivery contains NULL or UNDEF states
188+
logger.warn("Cannot convert state {} of item {} to DecimalType for average calculation", state, itemName);
184189
return 0;
185190
}
186191
return as.doubleValue();
187192
}
188193

189194
public double average(TreeMap<Instant, State> values) {
190-
Instant iterationTimestamp = null;
191-
State iterationValue = DecimalType.ZERO;
195+
Instant previousTimestamp = null;
196+
State previousValue = DecimalType.ZERO;
192197
double totalWeightedValue = 0;
193198
long totalDurationMs = 0;
194199

195200
for (Map.Entry<Instant, State> entry : values.entrySet()) {
196-
if (iterationTimestamp == null) {
197-
iterationTimestamp = entry.getKey();
198-
iterationValue = entry.getValue();
199-
} else {
200-
Instant end = entry.getKey();
201-
long durationMs = Duration.between(iterationTimestamp, end).toMillis();
202-
totalWeightedValue += state2Double(iterationValue) * durationMs;
201+
if (previousTimestamp != null) {
202+
long durationMs = Duration.between(previousTimestamp, entry.getKey()).toMillis();
203+
totalWeightedValue += state2Double(previousValue) * durationMs;
203204
totalDurationMs += durationMs;
204-
iterationTimestamp = end;
205-
iterationValue = entry.getValue();
206205
}
206+
previousTimestamp = entry.getKey();
207+
previousValue = entry.getValue();
207208
}
208209
double average = (totalDurationMs > 0) ? totalWeightedValue / totalDurationMs : 0;
209210
logger.debug("Average {} is {} for {} updates", itemName, average, values.size());

bundles/org.openhab.transform.basicprofiles/src/test/java/org/openhab/transform/basicprofiles/internal/profiles/TimeweightedAverageProfileTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.openhab.core.thing.profiles.ProfileCallback;
3939
import org.openhab.core.thing.profiles.ProfileContext;
4040
import org.openhab.core.types.State;
41+
import org.openhab.core.types.UnDefType;
4142

4243
/**
4344
* Unit test for {@link TimeweightedAverageStateProfile}.
@@ -139,4 +140,33 @@ public void testTWADelta(List<String> stateStrings, int expectedCallbacks) {
139140
verify(mockCallback, times(expectedCallbacks)).sendUpdate(any());
140141
reset(mockCallback);
141142
}
143+
144+
public static Stream<Arguments> testTWAInvalidStates() {
145+
List<State> statesWithNull = List.of(QuantityType.valueOf("700 W"), UnDefType.NULL,
146+
QuantityType.valueOf("900 W"), QuantityType.valueOf("900 W"));
147+
List<State> statesWithUndef = List.of(QuantityType.valueOf("700 W"), UnDefType.UNDEF,
148+
QuantityType.valueOf("900 W"), QuantityType.valueOf("900 W"));
149+
List<State> statesWithBoth = List.of(QuantityType.valueOf("700 W"), UnDefType.UNDEF, UnDefType.NULL,
150+
UnDefType.UNDEF, QuantityType.valueOf("900 W"), QuantityType.valueOf("900 W"));
151+
return Stream.of(Arguments.of(statesWithNull, 4), //
152+
Arguments.of(statesWithUndef, 4), //
153+
Arguments.of(statesWithBoth, 4) //
154+
);
155+
}
156+
157+
@ParameterizedTest
158+
@MethodSource
159+
public void testTWAInvalidStates(List<State> states, int expectedCallbacks) {
160+
TimeweightedAverageStateProfile profile = initTWAProfile("1h", 500);
161+
for (State state : states) {
162+
profile.onStateUpdateFromHandler(state);
163+
try {
164+
Thread.sleep(25); // ensure different time stamps
165+
} catch (InterruptedException e) {
166+
fail(e.getMessage());
167+
}
168+
}
169+
verify(mockCallback, times(expectedCallbacks)).sendUpdate(any());
170+
reset(mockCallback);
171+
}
142172
}

0 commit comments

Comments
 (0)