Distance calculation from RSSI BLE android
Asked Answered
C

2

17

I know there is lot of question on stackoverflow related to my question, but I would like to know if there is any way to get the exact distance from RSSI.

I have followed this link and some other git library methods for distance calculation as well as this tutorial. But I can't get the correct solution.

This is what I'm using to measure the distance:

protected double calculateDistance(float txPower, double rssi) {

    if (rssi == 0) {
        return -1.0; // if we cannot determine distance, return -1.
    }

    double ratio = rssi * 1.0 / txPower;

    if (ratio < 1.0) {
        return Math.pow(ratio, 10);
    } else {
        double accuracy = (0.89976) * Math.pow(ratio, 7.7095) + 0.111;
        return accuracy;
    }
}

When I call this method I pass the standard and rssi what i'm get from my mLeScanCallBack()

private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {

            @Override
            public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
                runOnUiThread(new Runnable() {

                    @Override
                    public void run() {

                        BluetoothDeviceModel bluetoothDeviceModel = new BluetoothDeviceModel(device, rssi);

                        mLeDeviceListAdapter.addDevice(bluetoothDeviceModel, rssi, bluetoothDeviceModel.device.getAddress());
                        mLeDeviceListAdapter.notifyDataSetChanged();

                        if (mapBluetooth != null) {
                            mapBluetooth.put(bluetoothDeviceModel.getDevice().getAddress(), bluetoothDeviceModel);
                            mLeDeviceListAdapter.refresh(mapBluetooth);
                        }
                    }
                });
            }
        };

What Problem i'm facing?

There is nothing wrong with the code above. It gives me the distance but i'm not satisfied with that because it's not the correct distance. So can anyone tell me if it is possible to get the exact distance with the above method or if there is any other way?

Cringe answered 4/4, 2016 at 10:14 Comment(9)
I'm in the same situation as you, the RSSI signals are not perfect, because on the environment there are many factors that distort the signals as magnetism and other things that make the signals are affected. There are several filters that remove noise signals as "Kalman Filter" or "Particle Filter" but I have not found how to apply it when you have an array of RSSI signal, if you find a way tell me please!Pacer
did you get any solutions?Esch
No man sorry till i did't get accurate solution for this calculationCringe
THen What you did?Esch
We change our BLE device, client make custom device for this accuracyCringe
@AjayPandya, Can you tell me, what is that 0.89976 and 7.7095 and 0.111?Antibes
Does the method calculateDistance return the distance in meter or what?Ventilator
Look to my answer below if you need. @Mahamutha M these are constants devices specific. You should calculate these values for every Bluetooth Beacon Emitter you're using and for every Bluetooth Receiver you are using. There is another formula (the one I normally use): ( 10 ^ ((txPower - rssi) / x) ) or Math.pow(10, ((mTxPower - rssi) * 1.0) / x) With x = 20 for default. In this case "x" also is Beacon Emitter / Bluetooth Receiver specific.Aer
@Rosario Vivas you can't apply Kalman Filter to an array of rssi. You can pass only 1 rssi to a kalman filter at once, if you have an array of rssi you should apply the kalman filter to every rssi you get and then save the state of the Kalman Filter. When you get another rssi you will pass it to the kalman filter to get a filtered measurement. The kalman filter will change is states with every rssi you pass to it. With an array of rssi you can do an average, but this isn't good if your tag moves.. The best way I found is with Kalman Filter.Aer
A
9

You can't get the "Exact Distance" because the RSSI you get is really bad and if the BLE Tag is > 1 meter of distance the RSSI oscillation become really high and more you will increase the distance and more the calculated distance will be wrong.

The RSSI you measure depends on a lot of factors, for example the environment (inside or outside), the kind of tag you are using, the kind of receiver you are using, if there are things between your tag and your receiver, if there are tags near your tag ecc ecc ecc

YOU CAN'T GET A PRECISE MEASURE OF THE DISTANCE USING BLUETOOTH

But you can get better measurement of the distance by implementing some operation to smooth the Gaussian you get (the oscillation of your measurements values).

For example if you know that the tag will be always at the same distance you can do an average of the Rssi values you get. For example you do the average of the Rssi you get in the last 1 minute.

In this way you will get a better distance value, but the tag must stay in same position.

Remember that in the way you are using to calculate the distance from the rssi and txPower: (0.89976) * Math.pow(ratio, 7.7095) + 0.111;

In this formula you have 3 constants, these constants are tag & bluetooth receiver specific. So to increase the precision you should calculate these 3 constants for every kind of tag you are using and for every type of bluetooth module of the smartphones you're using.

If your tag moves you can implements others functions to smooth your values, for example a Kalman Filter.

(Credit: wouterbulten - kalmanjs - GitHub)

Kalman Filter in Java (used on Android):

private class KalmanFilter implements Serializable {

    private double R;   //  Process Noise
    private double Q;   //  Measurement Noise
    private double A;   //  State Vector
    private double B;   //  Control Vector
    private double C;   //  Measurement Vector

    private Double x;   //  Filtered Measurement Value (No Noise)
    private double cov; //  Covariance

    public KalmanFilter(double r, double q, double a, double b, double c) {
        R = r;
        Q = q;
        A = a;
        B = b;
        C = c;
    }

    public KalmanFilter(double r, double q){
        R = r;
        Q = q;
        A = 1;
        B = 0;
        C = 1;
    }

    /** Public Methods **/
    public double applyFilter(double rssi){
        return applyFilter(rssi, 0.0d);
    }

    /**
     * Filters a measurement
     *
     * @param measurement The measurement value to be filtered
     * @param u The controlled input value
     * @return The filtered value
     */
    public double applyFilter(double measurement, double u) {
        double predX;           //  Predicted Measurement Value
        double K;               //  Kalman Gain
        double predCov;         //  Predicted Covariance
        if (x == null) {
            x = (1 / C) * measurement;
            cov = (1 / C) * Q * (1 / C);
        } else {
            predX = predictValue(u);
            predCov = getUncertainty();
            K = predCov * C * (1 / ((C * predCov * C) + Q));
            x = predX + K * (measurement - (C * predX));
            cov = predCov - (K * C * predCov);
        }
        return x;
    }

    /** Private Methods **/
    private double predictValue(double control){
        return (A * x) + (B * control);
    }

    private double getUncertainty(){
        return ((A * cov) * A) + R;
    }

    @Override
    public String toString() {
        return "KalmanFilter{" +
                "R=" + R +
                ", Q=" + Q +
                ", A=" + A +
                ", B=" + B +
                ", C=" + C +
                ", x=" + x +
                ", cov=" + cov +
                '}';
    }
}

Usage:

private KalmanFilter mKalmanFilter; // Property of your class to store kalman filter values
    
mKalmanFilter = new KalmanFilter(KALMAN_R, KALMAN_Q); // init Kalman Filter
    
// Method Apply Filter
private void applyKalmanFilterToRssi(){
     mFilteredRSSI = mKalmanFilter.applyFilter(mRSSI);
}

Constants Values:

// Kalman R & Q
    private static final double KALMAN_R = 0.125d;
    private static final double KALMAN_Q = 0.5d;

+ KALMAN_R is the Process Noise

+ KALMAN_Q is the Measurement Noise

You Should Change These 2 Values looking to your measurements and you case of use. Changing these 2 values you will change how fast the filtered measurement value will change from a value to another. So if you have values with a lot of noise and you want to slow down how fast the measurement value changes (to smooth the Gaussian) you should try to increment KALMAN_R & KALMAN_Q values. The values here for KALMAN_R & KALMAN_Q, if I remember right, were the one I was using when I was programming for BLE Devices so these 2 values for KALMAN_R & KALMAN_Q are already "big" because the RSSI of BLE Devices changes a lot.

I Suggest you to use a Kalman Filter to smooth your values.

Hope this is helpful, have a nice day and nice coding!

Upload, these are my classes for TagBLE:

Base class for a Tag Bluetooth Low Energy:

public class TagBLE extends RealmObject implements Parcelable {

    // Field Names
    public static final String FIELD_ID = "id";
    public static final String FIELD_MAC = FIELD_ID;

    @Expose
    @PrimaryKey
    @SerializedName("tag_mac")
    private String id;
    @Expose
    @SerializedName("tag_nome")
    private String mName;
    @Expose
    @SerializedName("tx_power")
    private int mTxPower;

    public TagBLE(){}

    public TagBLE(String mac, String name, int txPower){
        id = mac;
        mName = name;
        mTxPower = txPower;
    }

    public TagBLE(TagBLE tag){
        id = tag.getMAC();
        mName = tag.getName();
        mTxPower = tag.getTxPower();
    }

    /** Private Constructors **/
    private TagBLE(Parcel in){
        id = in.readString();
        mName = in.readString();
        mTxPower = in.readInt();
    }

    /** Public Static Factory Methods **/
    public static TagBLE initInstanceFromScanResult(ScanResult result, int txPower){
        BluetoothDevice bDevice = result.getDevice();
        return new TagBLE(bDevice.getAddress(), bDevice.getName(), txPower);
    }

    /** Parcelling Methods **/
    public static Parcelable.Creator<TagBLE> CREATOR = new TagBLECreator();

    /** Override Parcelable Methods **/
    @Override
    public int describeContents(){
        return 0x0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags){
        out.writeString(id);
        out.writeString(mName);
        out.writeInt(mTxPower);
    }

    /** Getter Methods **/
    public String getId(){
        return id;
    }

    public String getMAC() {
        return id;
    }

    public String getName() {
        return mName;
    }

    public int getTxPower() {
        return mTxPower;
    }

    /** Setter Methods **/
    public void setId(String id) {
        this.id = id;
    }

    public void setName(String name) {
        mName = name;
    }

    public void setTxPower(int txPower) {
        mTxPower = txPower;
    }

    /** Public Methods **/
    public double getDistance(int rssi){
        return getDistance((double) rssi);
    }

    public double getDistance(double rssi){
        return Math.pow(10, ((mTxPower - rssi) * 1.0) / 20);
    }

    @Override
    public String toString() {
        return "TagBLE{" +
                "id='" + id + '\'' +
                ", mName='" + mName + '\'' +
                ", mTxPower=" + mTxPower +
                '}';
    }

    /** Private Static Class - Parcelable Creator **/
    private static class TagBLECreator implements Parcelable.Creator<TagBLE> {
        @Override
        public TagBLE createFromParcel(Parcel in) {
            return new TagBLE(in);
        }

        @Override
        public TagBLE[] newArray(int size) {
            return new TagBLE[size];
        }
    }

}

Specific class to manage data (in my case I need to manage the distance, if it is near to a device or it is far, but I removed these parts from the class code) of a Tag BLE found (with KalmanFilter):

public class DataTagBLE extends RealmObject {

    // Field Names
    public static final String FIELD_ID = "id";

    // Kalman R & Q
    private static final double KALMAN_R = 0.125d;
    private static final double KALMAN_Q = 0.5d;

    @PrimaryKey
    private String id;
    @Expose
    @SerializedName("tag")
    private TagBLE mTag;
    @Expose
    @SerializedName("acquired")
    private Date mAcquired;
    @Expose
    @SerializedName("rssi")
    private int mRSSI;
    @Expose
    @SerializedName("filtered_rssi")
    private double mFilteredRSSI;
    @Ignore
    private KalmanFilter mKalmanFilter;

    private double mDistance;

    public DataTagBLE(){}

    public DataTagBLE(TagBLE tag){
        id = UUID.randomUUID().toString();
        mTag = tag;
        mAcquired = new Date();
        mRSSI = 0x0;
        mFilteredRSSI = 0x0;
        mKalmanFilter = new KalmanFilter(KALMAN_R, KALMAN_Q);
    }

    /** Private Constructors **/
    private DataTagBLE(TagBLE tag, int rssi){
        id = UUID.randomUUID().toString();
        mTag = tag;
        mAcquired = new Date();
        mRSSI = rssi;
    }

    /** Public Static Factory Methods **/
    public static DataTagBLE initInstanceDataTagFound(@NonNull ScanResult scanResult, int txPower){
        return new DataTagBLE(TagBLE.initInstanceFromScanResult(scanResult, txPower));
    }

    /** Getter Methods **/
    public TagBLE getTag(){
        return mTag;
    }

    public Date getAcquired() {
        return mAcquired;
    }

    public int getRSSI(){
        return mRSSI;
    }

    public double getFilteredRSSI(){
        return this.mFilteredRSSI;
    }

    public KalmanFilter getKalmanFilter() {
        return mKalmanFilter;
    }

    /** Setter Methods **/
    public void setTag(TagBLE tag){
        mTag = tag;
    }

    public void setAcquired(Date acquired) {
        this.mAcquired = acquired;
    }

    public void setRSSI(int rssi){
        mRSSI = rssi;
    }

    public void setFilteredRSSI(int rssi){
        this.mFilteredRSSI = rssi;
    }

    public void setKalmanFilter(KalmanFilter kalmanFilter) {
        this.mKalmanFilter = kalmanFilter;
    }

    /** TagBLE Getter Methods **/
    public String getTagMac() {
        if (mTag != null) {
            return mTag.getMAC();
        } else {
            return null;
        }
    }

    /** TagBLE Setter Methods **/
    public void setTagNameAndTxPower(String tagName, int txPower){
        if(mTag != null){
            mTag.setName(tagName);
            mTag.setTxPower(txPower);
        }
    }

    /** Public Methods **/
    public void generateNewID(){
        id = UUID.randomUUID().toString();
    }

    public void onNewDataTagAcquired(DataTagBLE dataTagFound){
        setRSSI(dataTagFound.getRSSI());
        applyKalmanFilterToRssi();
        TagBLE tagFound = dataTagFound.getTag();
        if(tagFound != null) {
            setTagNameAndTxPower(tagFound.getName(), tagFound.getTxPower());
        }
        setAcquired(new Date());
    }

    public void store(){
        generateNewID();
        RealmHelper rHelper = new RealmHelper();
        rHelper.saveUpdateRealmObject(this);
        rHelper.close();
    }

    /** Distance & RSSI Filtering Methods **/
    public double getDistanceFiltered(){
        return mTag.getDistance(mFilteredRSSI);
    }

    public double getDistance(){
        return mTag.getDistance(mRSSI);
    }

    /** Private Methods **/
    private void applyKalmanFilterToRssi(){
        mFilteredRSSI = mKalmanFilter.applyFilter(mRSSI);
    }

    @Override
    public String toString() {
        return "DataTagBLE{" +
                "id='" + id + '\'' +
                ", mTag=" + mTag +
                ", mAcquired=" + mAcquired +
                ", mRSSI=" + mRSSI +
                ", mFilteredRSSI=" + mFilteredRSSI +
                ", mKalmanFilter=" + mKalmanFilter +
                ", mDistance=" + mDistance +
                '}';
    }

    /** Private Classes **/
    /*
        SOURCE: https://github.com/wouterbulten/kalmanjs/blob/master/dist/kalman.js
    */
    private class KalmanFilter implements Serializable {

        private double R;   //  Process Noise
        private double Q;   //  Measurement Noise
        private double A;   //  State Vector
        private double B;   //  Control Vector
        private double C;   //  Measurement Vector

        private Double x;   //  Filtered Measurement Value (No Noise)
        private double cov; //  Covariance

        public KalmanFilter(double r, double q, double a, double b, double c) {
            R = r;
            Q = q;
            A = a;
            B = b;
            C = c;
        }

        public KalmanFilter(double r, double q){
            R = r;
            Q = q;
            A = 1;
            B = 0;
            C = 1;
        }

        /** Public Methods **/
        public double applyFilter(double rssi){
            return applyFilter(rssi, 0.0d);
        }

        /**
         * Filters a measurement
         *
         * @param measurement The measurement value to be filtered
         * @param u The controlled input value
         * @return The filtered value
         */
        public double applyFilter(double measurement, double u) {
            double predX;           //  Predicted Measurement Value
            double K;               //  Kalman Gain
            double predCov;         //  Predicted Covariance
            if (x == null) {
                x = (0x1 / C) * measurement;
                cov = (0x1 / C) * Q * (0x1 / C);
            } else {
                predX = predictValue(u);
                predCov = getUncertainty();
                K = predCov * C * (0x1 / (C * predCov * C + Q));
                x = predX + K * (measurement - (C * predX));
                cov = predCov - (K * C * predCov);
            }
            return x;
        }

        /** Private Methods **/
        private double predictValue(double control){
            return (A * x) + (B * control);
        }

        private double getUncertainty(){
            return (A * cov * A) + R;
        }

        @Override
        public String toString() {
            return "KalmanFilter{" +
                    "R=" + R +
                    ", Q=" + Q +
                    ", A=" + A +
                    ", B=" + B +
                    ", C=" + C +
                    ", x=" + x +
                    ", cov=" + cov +
                    '}';
        }
    }

}

In my case when I found a unique MAC for a Tag BLE I instantiate a "DataTagBLE" (the class isn't complete, I removed all distance checks that I'm using). I init the instance the first time by:

DataTagBLE initInstanceDataTagFound(@NonNull ScanResult scanResult, int txPower)

Then every time I found the same tag (which will have a different RSSI) I will get back the DataTagBLE for this tag by watching the MAC Address (I'm using an HashMap<MAC_STRING, DataTagBLE> on my service.). Then when I have the instance I'll use:

myDataTagBLEInstance.onNewDataTagAcquired(DataTagBLE newDataTagBLEInstance)

(I have base services that always return to me a made DataTagBLE instance, so I will have the old instance updated with the new instance data using the method above) That's just to answer to question below, the KalmanFilter must be the same instance for the same tag!

Get txPower from beacon:

It uses: 'com.neovisionaries:nv-bluetooth:1.8'

public static int getBeaconTxPower(ScanResult result){
        if(result != null) {
            // This part uses the library above
            if(result.getScanRecord() != null) {
                List<ADStructure> structures = ADPayloadParser.getInstance().parse(result.getScanRecord().getBytes());
                if (structures != null && structures.size() > 0x0) {
                    for (ADStructure st : structures) {
                        if (st instanceof IBeacon) {
                            IBeacon beacon = (IBeacon) st;
                            if(beacon.getPower() != 0x0) {
                                return beacon.getPower();
                            }
                        }
                    }
                }
            }
            // Add case here if the Tag doesn't have the txPower setted. For example: if(MAC ADDRESS contains "XX:XX:XX..." then txPower = Y"
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
                return result.getTxPower();
            }
        }
        return 0x0;
    }

Pseudo Code KalmanFilter using an HashMap:

// Store here KalmanFilters associated to every MAC Address
HashMap<String, KalmanFilter> mKalmanFilters;

// When you find a tag:
if mKalmanFilters.keySet().contains(tagFound.mac){
   KalmanFilter mKalman = mKalmanFilters.get(tagFound.mac());

   // This will give you a smoothed RSSI value because 'x == lastRssi'
   double smoothed = mKalman.applyFilter(tagFound.rssi);

   // Do what you want with this rssi
} else {
   KalmanFilter mKalman = new KalmanFilter(valR, valQ);
   
   /* This will set the first measurement, so the 'x', of the KalmanFilter. Next time you find the tag and you use the 'if part' you will get a smoothed rssi value.
This rssi value will be smoothed depending on the 'C' value (so Measurement Vector) setted in your KalmanFilter instance. If C == 1 => smoothed == rssi. */
   double smoothed = mKalman.applyFilter(tagFound.rssi);
   mKalmanFilters.put(tagFound.mac, mKalmanFilter);
}
Aer answered 7/5, 2020 at 8:42 Comment(10)
Hello, thanks for the explanations. So I have a question. I've applied the class and make some changes for R and Q values. But what is the x value? It is RSSI itself? Cause if it's null the function always returns the RSSI itself.Quadrinomial
Hello, if x == null it will get set on next time you call the KalmanFilter and it will return "x == input_rssi" because it hasn't any value before. Every time you get an rssi from a tag you need to pass it to your kalman filter instance, the smoothing works by applying some weights to every measurements on this way your tag will slowly pass from, for example, -56 rssi to -70 rssi.Aer
That's because the rssi changes a lot, more if it is far than a 1 mt. For example, if you have your tag at 2 mt. you will get a range of rssi from "txPower * 0x2" to, for example, "txPower * 0x3". By using a kalman filter the measurement will slowly pass from "txPower * 0x2" to "txPower * 0x3" this way you will have a more accurate values, but to have this behaviour you need to: - set a Kalman Filter instance - pass every RSSI from 1 specific tag to your kalman filter instance to get the filtered measurement. I upload the answer with my classes and an exampleAer
The 'x' is the last filtered measurement, the KalmanFilter will use it to smooth the value from last measurement to new measurement using the weights you set. Ask if you need anything else, have a nice coding & dayAer
Hello, thanks for the clarification. So I have to set x to RSSI value that I got from my BLE device. I was calculating the distance with a formula. And it seems that correct for me. So I need to really apply the Kalman filter. If you see my code, here it is: github.com/yhznd/indoorpositioning/blob/master/app/src/main/… (see the calculateDistance function)Quadrinomial
I'm gonna write this function like this? return mKalmanFilter.applyFilter(rssi); Also I'm using BluetoohDevice class for my application.Quadrinomial
You don't need to specifically set x to rssi, it will set the first time you use 'applyFilter' of your KalmanFilter instance. Next time you find the same BluetoothDevice with same MAC Address you'll pass the new rssi to his KalmanFilter instance and by using 'applyFilter' again you will get a smoothed rssi measurement. So basically you need a KalmanFilter instance for every different BluetoothDevice you find, so for every BluetoothDevice with different MAC AddressAer
I saw the function, I suggest you to get the txPower from the BluetoothDevice instance if they have it, otherwise set the txPower hardcoded as you are doing but you need to know which are your devices.Later I'll try to add all my code with the BaseBleScanService I made, I hope I have enough characters last on the answerAer
@YunusHaznedar I couldn't add all my classes code, too many characters. So I added the function I'm using to get the txPower value from a tag found and also a really bad looking pseudo-code to manage KalmanFilters and get smoothed rssi. Hope this help, have a nice coding and day!Aer
Thanks for the reply again. I will follow your instructions. Happy coding!Quadrinomial
F
4

I am also working on the same thing. With this method you are able to calculate the distance but that distance is changing frequently. This is because RSSI value is also changing frequently.

What you need to do is to smooth out your result and for that you have to apply Kalman Filter or (linear quadratic estimation). If you want to stick with kalman filter then start from here. Beacon Tracking

Still I am looking for better implementation of Kalman Filter for my project. Alternatively, you can smooth up your RSSI value

𝑅𝑆𝑆𝐼𝑠𝑚𝑜𝑜𝑡h = 𝛼 ∗ 𝑅𝑆𝑆𝐼𝑛 + (1 − 𝛼) ∗ 𝑅𝑆𝑆𝐼𝑛−1

𝑅𝑆𝑆𝐼𝑛 is the recent 𝑅𝑆𝑆𝐼 value and 𝑅𝑆𝑆𝐼𝑛−1 is the mean 𝑅𝑆𝑆𝐼 value till previous 𝑅𝑆𝑆𝐼.

𝛼 varies from 0 to 1 [consider 𝛼=0.75]

Source :- RSSI Smoothing

Floria answered 9/6, 2016 at 6:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.