Package org.openhab.binding.onewire.internal

Source Code of org.openhab.binding.onewire.internal.Filter

/**
* Copyright (c) 2010-2014, openHAB.org and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.onewire.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;

import org.openhab.core.library.types.DecimalType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Class to filter sensor data. For now directly implements a specific filter,
* but can serve as base class later on.
*
* @author Bernd Pfrommer
* @since 1.5.0
*/
public class Filter {
  /**
   * Implements a Tukey style outlier filter
   */
  private static final Logger logger = LoggerFactory.getLogger(Filter.class);
  /** number of data points to use for outlier estimation. */
  private final static int WINDOW_SIZE = 40;
  /** what n-tile to use for boundaries */
  private final static int NTILE = 6;
  /** drop criterion for filter */
  private final static double ALPHA = 2.5;
  /** list to keep track of age of data points */
  private LinkedList<DecimalType> dataList = new LinkedList<DecimalType>();
  /** ordered set of data points, needed to compute n-tiles */
  private SortedArrayList<DecimalType> sortedList = new SortedArrayList<DecimalType>();
  /**
   * Restricts the data point value to be between
   *
   *   lowerbound = qbottom - alpha * r  and
   *   upperbound = qtop + alpha * r
   *  
   * where qtop = top n-tile, qbottom = bottom ntile, and the range r = qtop - qbottom.
   * The original Tukey filter drops points if they are outside of 1.5 * range, i.e. alpha = 1.5,
   * and takes quartiles.
   *
   * Another implementation wrinkle: for slow changing data such as e.g. temperature,
   * the binding may pick up the same data point over and over again. This compresses the
   * range artificially, and will lead to spurious filtering.
   *
   * For that reason a point is added to the sample set only if it is not present there
   *
   * @param v the new raw data point as received
   * @return the filtered data point vf, i.e. v is boxed into:  lowerbound <= vf <= upperbound 
   */
  public DecimalType filter(DecimalType v) {
    DecimalType y = v;
    if (dataList.size() > WINDOW_SIZE) {
      removeOldestPoint(v);
      // calculate filter parameters
      double qbottom = sortedList.get(sortedList.getNtile(1, NTILE)).doubleValue();
      double qtop = sortedList.get(sortedList.getNtile(NTILE - 1, NTILE)).doubleValue();
      double r = qtop - qbottom; // range
      double lowerbound = qbottom - ALPHA * r;
      double upperbound = qtop + ALPHA * r;
      // box it
      double yd = y.doubleValue();
      yd = Math.max(yd,  lowerbound);
      yd = Math.min(yd,  upperbound);
      if (yd != y.doubleValue()) {
        logger.debug("boxed outlier: range: {}-{}={} orig data: {} filt data: {}",
            qbottom, qtop, qtop - qbottom, v.doubleValue(), yd);
      }
      y = new DecimalType(yd);
    }
    addNewPoint(v);
    return y;  // filtered value
  }
 
  /**
   * Removes the oldest data point only if the new point
   * is not known yet
   * @param v the value to use for test if unknown
   */
  private void removeOldestPoint(DecimalType v) {
    if (isNewPoint(v)) {
      // removes the oldest element in the set
      DecimalType oldest = dataList.remove();
      sortedList.removeSorted(oldest);
    }
  }
  /**
   * Adds data point only if it's really new
   * @param v the point to add
   */
  private void addNewPoint(DecimalType v) {
    if (isNewPoint(v)) {
      dataList.add(v);
      sortedList.addSorted(v);
    }
  }
  /**
   * Checks if the data point is contained in the data set
   * @param v the value to check for
   * @return true if the value is already in the data set
   */
  private boolean isNewPoint(DecimalType v) {
    return (!sortedList.hasElement(v));
  }
 
  /**
   * Maintains a O(log(N)) sorted ArrayList (so long as you don't directly access the add()
   * method, but just use addSorted() and removeSorted().
   *
   * @author Bernd Pfrommer
   *
   * @param <T> type to store in sorted ArrayList
   */
  @SuppressWarnings("serial")
  public class SortedArrayList<T extends Comparable<T>> extends ArrayList<T> {
    /**
     * Add element to list while preserving the order. Runtime O(log(N))
     * @param a the element to add
     */
    public void addSorted(T a) {
      int idx = Collections.binarySearch(this, a);
      add(idx >= 0 ? idx : Math.abs(idx + 1), a);
    }
    /**
     * Remove element from list (performs O(log(N)) search to find it
     * @param a the element to remove
     */
    public void removeSorted(T a) {
      int idx = Collections.binarySearch(this, a);
      if (idx >= 0) {
        remove(idx);
      }
    }
    /**
     * O(log(N)) test for presence of element
     * @param a the element value to search for
     * @return true if element is in list, false if otherwise
     */
    public boolean hasElement(T a) {
      return (Collections.binarySearch(this, a) >= 0);
    }
    /**
     * Finds array index for specified n-tile
     * @param q which n-tile to find
     * @return array index for given n-tile
     */
    public int getNtile(int q, int n) {
      if (q > 0 && q < n) {
        return (size() * q) / n;
      }
      if (q >= n) {
        return (size() - 1);
      }
      return 0;
    }
  }
}
TOP

Related Classes of org.openhab.binding.onewire.internal.Filter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.