Created
October 4, 2014 06:00
-
-
Save SoulAuctioneer/29bc2bcec3d9fb046f90 to your computer and use it in GitHub Desktop.
Android class to reliably detect that a Bluetooth Low Energy device has been moving further away and crossed a specific threshold, by testing for a consistently declining moving average of RSSI. Needed because Bluetooth sequential RSSI readings can be highly variable. No actual Bluetooth code here so can easily be repurposed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.android; | |
/** | |
* Created by Ash Eldritch. | |
* | |
* Bluetooth RSSI is very volatile, and so in its raw form not a reliable method to detect change in proximity. | |
* Here, I use a moving average over a number of RSSI samples, plus a test for consistently declining RSSI over | |
* those samples. This much more accurately determines that the Bluetooth device is moving away. | |
* | |
* To use: | |
* - Implement RssiFilterListener's onRssiPathLoss() to re a callback when device has moved away. | |
* - Instantiate this class, passing in the listener implementation | |
* - Call add() for each new RSSI value that's received | |
*/ | |
import android.util.Log; | |
import java.util.LinkedList; | |
import java.util.Queue; | |
public class RssiFilter { | |
/** | |
* RSSI threshold below which we are considered to be distant from the alarm | |
*/ | |
private static final int rssiPathLossThreshold = -70; | |
/** | |
* Size of window for moving average filter of RSSI | |
*/ | |
private static final int period = 5; | |
/** | |
* Number of samples to check for consistently weakening RSSI | |
*/ | |
private static final int numFilteredRssiSamples = 5; | |
private final Queue<Integer> window = new LinkedList<Integer>(); | |
private final Queue<Integer> filteredRssiSamples = new LinkedList<Integer>(); | |
private final RssiFilterListener rssiFilterListener; | |
private Integer sum = 0; // Counteract initially very low values triggering pathLoss | |
public RssiFilter(RssiFilterListener rssiFilterListener) | |
{ | |
this.rssiFilterListener = rssiFilterListener; | |
} | |
/** | |
* Adds a sample to the set and checks path loss | |
* @param num The RSSI value to add | |
*/ | |
public void add(Integer num) { | |
sum = sum + num; | |
window.add(num); | |
if (window.size() > period) { | |
sum = sum - window.remove(); | |
} | |
Log.d("com.nojack.RssiFilter", "Filtered RSSI:" + String.valueOf(getAverage())); | |
// Store averaged rssi for testing trend | |
filteredRssiSamples.add(getAverage()); | |
if (filteredRssiSamples.size() > numFilteredRssiSamples) { | |
filteredRssiSamples.remove(); | |
} | |
// We have a new sample, so test for path loss | |
testRssiPathLoss(); | |
} | |
public Integer getAverage() { | |
if (window.isEmpty()) return null; // technically the average is undefined | |
return sum / window.size(); | |
} | |
/** | |
* Tests if path loss conditions are met | |
*/ | |
public void testRssiPathLoss() { | |
// Ensure we have enough samples for a proper evaluation | |
if (window.size() < period) { | |
return; | |
} | |
// Check if we're currently below the rssi threshold to consider possible path loss | |
// Then check for a downward trending signal consistent with increasing distance from alarm | |
if (getAverage() < rssiPathLossThreshold && isTrendingWeaker()) { | |
rssiFilterListener.onRssiPathLoss(); | |
} | |
} | |
/** | |
* Returns true if each sample in the filtered set is smaller than its previous sample | |
* @return boolean indicating whether the RSSI is trending weaker | |
*/ | |
public boolean isTrendingWeaker() { | |
Integer previousSample = filteredRssiSamples.peek(); | |
for (Integer sample : filteredRssiSamples) { | |
if (sample > previousSample) { | |
return false; | |
} | |
previousSample = sample; | |
} | |
return true; | |
} | |
public interface RssiFilterListener { | |
void onRssiPathLoss(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment