Skip to content

Instantly share code, notes, and snippets.

@SoulAuctioneer
Created October 4, 2014 06:00
Show Gist options
  • Save SoulAuctioneer/29bc2bcec3d9fb046f90 to your computer and use it in GitHub Desktop.
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.
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