]> git.basschouten.com Git - openhab-addons.git/blob
81fcb188b70c0984825b0955f010a02cd05086c4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.miele.internal;
14
15 import java.time.Instant;
16 import java.time.temporal.ChronoUnit;
17 import java.util.Deque;
18 import java.util.Iterator;
19 import java.util.concurrent.ConcurrentLinkedDeque;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23
24 /**
25  * {@link TimeStabilizer} keeps a cache of historic timestamp values in order to
26  * provide moving average calculations to smooth out short-term fluctuations.
27  *
28  * @author Jacob Laursen - Initial contribution
29  */
30 @NonNullByDefault
31 public class TimeStabilizer {
32
33     private static final int SLIDING_SECONDS = 300;
34     private static final int MAX_FLUCTUATION_SECONDS = 180;
35
36     private final Deque<Item> cache = new ConcurrentLinkedDeque<Item>();
37
38     private class Item {
39         public Instant start;
40         public Instant end;
41         public Instant instant;
42
43         public Item(Instant instant, Instant start, Instant end) {
44             this.start = start;
45             this.end = end;
46             this.instant = instant;
47         }
48     }
49
50     public TimeStabilizer() {
51     }
52
53     public void clear() {
54         cache.clear();
55     }
56
57     public Instant apply(Instant value) {
58         return this.apply(value, Instant.now());
59     }
60
61     public Instant apply(Instant value, Instant now) {
62         if (cache.isEmpty()) {
63             cache.add(new Item(value, now, now));
64             return value;
65         }
66
67         @Nullable
68         Item first = cache.getFirst();
69         @Nullable
70         Item last = cache.getLast();
71         last.end = now;
72
73         Instant windowStart = now.minusSeconds(SLIDING_SECONDS);
74         Instant start = first.start;
75         Instant base = first.instant;
76         long weightedDiffFromBase = 0;
77         final Iterator<Item> it = cache.iterator();
78         while (it.hasNext()) {
79             Item current = it.next();
80
81             if (current.end.isBefore(windowStart)) {
82                 it.remove();
83                 continue;
84             }
85             if (current.start.isBefore(windowStart) && current.end.isAfter(windowStart)) {
86                 // Truncate last item before sliding window.
87                 start = current.start = windowStart;
88             }
89             long secs = current.start.until(current.end, ChronoUnit.SECONDS);
90             weightedDiffFromBase += base.until(current.instant, ChronoUnit.SECONDS) * secs;
91         }
92
93         Instant average = base.plusSeconds(weightedDiffFromBase / start.until(now, ChronoUnit.SECONDS));
94         if (value.isBefore(average.minusSeconds(MAX_FLUCTUATION_SECONDS))
95                 || value.isAfter(average.plusSeconds(MAX_FLUCTUATION_SECONDS))) {
96             cache.clear();
97             average = value;
98         }
99
100         cache.add(new Item(value, now, now));
101
102         return average;
103     }
104 }