RSSI BLE android的距离计算

Aja*_*dya 9 android rssi bluetooth-lowenergy

我知道有关我的问题的stackoverflow有很多问题,但我想知道是否有任何方法可以获得与RSSI的确切距离.

我已经按照这个链接和一些其他git库方法进行距离计算以及本教程.但我无法得到正确的解决方案.

这就是我用来测量距离的方法:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

当我调用这个方法时,我通过标准和rssi从我的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);
                        }
                    }
                });
            }
        };
Run Code Online (Sandbox Code Playgroud)

我面临的问题是什么?

上面的代码没有任何问题.它给了我距离,但我不满意,因为它不是正确的距离.那么有人能告诉我是否可以通过上述方法获得确切的距离或者是否有其他方法?

Z3R*_*3R0 8

您无法获得“精确距离”,因为您获得的 RSSI 非常糟糕,如果 BLE 标签距离 > 1 米,RSSI 振荡会变得非常高,您将增加距离,计算出的距离将是错误的。

您测量的 RSSI 取决于很多因素,例如环境(内部或外部)、您使用的标签类型、您使用的接收器类型、标签和接收器之间是否有东西、如果有标签是否靠近您的标签 ecc ecc ecc

您无法使用蓝牙精确测量距离

但是,您可以通过执行一些操作来平滑您获得的高斯(测量值的振荡),从而更好地测量距离。

例如,如果您知道标签始终处于相同的距离,则可以对获得的 Rssi 值求平均值。例如,您计算最后 1 分钟内获得的 Rssi 的平均值。

通过这种方式,您将获得更好的距离值,但标签必须保持在同一位置。

请记住,您用来计算 rssi 和 txPower 距离的方式是: (0.89976) * Math.pow(ratio, 7.7095) + 0.111;

在此公式中,您有 3 个常量,这些常量是特定于标签和蓝牙接收器的。因此,为了提高精度,您应该为您正在使用的每种标签以及您正在使用的智能手机的每种蓝牙模块计算这 3 个常数。

如果您的标签移动,您可以实现其他函数来平滑您的值,例如卡尔曼滤波器。

(图片来源: wouterbulten - kalmanjs - GitHub

Java中的卡尔曼滤波器(在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 +
                '}';
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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);
}
Run Code Online (Sandbox Code Playgroud)

常量值:

// Kalman R & Q
    private static final double KALMAN_R = 0.125d;
    private static final double KALMAN_Q = 0.5d;
Run Code Online (Sandbox Code Playgroud)

+ KALMAN_R 是过程噪声

+ KALMAN_Q 是测量噪声

您应该根据您的测量结果和使用情况更改这两个值。更改这两个值,您将更改过滤后的测量值从一个值更改为另一个值的速度。因此,如果您的值含有大量噪声,并且您想要减慢测量值变化的速度(以平滑高斯),您应该尝试增加 KALMAN_R 和 KALMAN_Q 值。如果我没记错的话,此处 KALMAN_R 和 KALMAN_Q 的值是我在为 BLE 设备编程时使用的值,因此 KALMAN_R 和 KALMAN_Q 的这 2 个值已经“大”,因为 BLE 设备的 RSSI 变化很大。

我建议您使用卡尔曼滤波器来平滑您的值。

希望这对您有所帮助,祝您有美好的一天和良好的编码!

上传,这些是我的 TagBLE 类:

标签蓝牙低功耗的基类:

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];
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

管理找到的标签 BLE 数据的特定类(在我的例子中,我需要管理距离,如果它靠近设备或很远,但我从类代码中删除了这些部分)(使用 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 +
                    '}';
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

就我而言,当我找到标签 BLE 的唯一 MAC 时,我实例化了一个“DataTagBLE”(该类尚未完成,我删除了正在使用的所有距离检查)。我第一次通过以下方式初始化实例:

DataTagBLE initInstanceDataTagFound(@NonNull ScanResult scanResult, int txPower)
Run Code Online (Sandbox Code Playgroud)

然后,每次我找到相同的标签(将具有不同的 RSSI)时,我都会通过观察 MAC 地址来取回该标签的 DataTagBLE(我在我的服务上使用 HashMap<MAC_STRING, DataTagBLE>)。然后当我有实例时我将使用:

myDataTagBLEInstance.onNewDataTagAcquired(DataTagBLE newDataTagBLEInstance)
Run Code Online (Sandbox Code Playgroud)

(我的基础服务总是返回给我一个制作的 DataTagBLE 实例,因此我将使用上面的方法用新实例数据更新旧实例)这只是为了回答下面的问题,KalmanFilter 必须与相同的标签!

从信标获取 txPower:

它使用:'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;
    }
Run Code Online (Sandbox Code Playgroud)

使用 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);
}
Run Code Online (Sandbox Code Playgroud)


Nis*_*yal 4

我也在做同样的事情。使用这种方法,您可以计算距离,但该距离经常变化。这是因为 RSSI 值也在频繁变化。

\n\n

您需要做的是平滑结果,为此您必须应用卡尔曼滤波器或(线性二次估计)。如果您想坚持使用卡尔曼滤波器,请从这里开始。\n信标跟踪

\n\n

我仍然在为我的项目寻找更好的卡尔曼滤波器实现。或者,您可以平滑 RSSI 值

\n\n
\n

h = \xe2\x88\x97 + (1 \xe2\x88\x92 ) \xe2\x88\x97 \xe2\x88\x921

\n
\n\n

是最近的值, \xe2\x88\x921 是直到上一个的平均值。

\n\n

从 0 到 1 变化 [考虑 =0.75]

\n\n

来源:- RSSI 平滑

\n