2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.miele.internal;
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;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
25 * {@link TimeStabilizer} keeps a cache of historic timestamp values in order to
26 * provide moving average calculations to smooth out short-term fluctuations.
28 * @author Jacob Laursen - Initial contribution
31 public class TimeStabilizer {
33 private static final int SLIDING_SECONDS = 300;
34 private static final int MAX_FLUCTUATION_SECONDS = 180;
36 private final Deque<Item> cache = new ConcurrentLinkedDeque<Item>();
41 public Instant instant;
43 public Item(Instant instant, Instant start, Instant end) {
46 this.instant = instant;
50 public TimeStabilizer() {
57 public Instant apply(Instant value) {
58 return this.apply(value, Instant.now());
61 public Instant apply(Instant value, Instant now) {
62 if (cache.isEmpty()) {
63 cache.add(new Item(value, now, now));
68 Item first = cache.getFirst();
70 Item last = cache.getLast();
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();
81 if (current.end.isBefore(windowStart)) {
85 if (current.start.isBefore(windowStart) && current.end.isAfter(windowStart)) {
86 // Truncate last item before sliding window.
87 start = current.start = windowStart;
89 long secs = current.start.until(current.end, ChronoUnit.SECONDS);
90 weightedDiffFromBase += base.until(current.instant, ChronoUnit.SECONDS) * secs;
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))) {
100 cache.add(new Item(value, now, now));