Ứng dụng beacon trong việc xác định vị trí(Phần 2)

Tiếp theo phần trước phần này mình sẽ đi chi tiết hơn về cách scan các thiết bị beacon

3. Scanning Beacon

3.1 Ranging

3.1.1.Ranging là gì

Khi thiết bị điều khiển tạo 1 hàng rào ảo để phát hiện di chuyển của bạn , ranging sẽ bắt đầu scan các beacon ở gần và chuyển thông tin về mỗi giây . Khi bước vào hoặc ra khỏi vùng phủ sóng thì app của chúng ta sẽ thông báo đến cho người dùng . Còn khi ở trong vùng , ta sẽ lấy được tất cả các thông tin của beacon trong phạm vi đó , bao gồm UUID, major ,minor value và nhiều giá trị khác nữa .

3.1.2.Proximity estimation

Beacon phát ra tín hiệu Bluetooth với một cường độ nhất định và suy giảm dần trong không khí .Điều này cho phép nguồn nhận xác định khoảng cách chính xác đến vị trí của beacon (Tín hiệu mạnh-> đang ở gần , tín hiệu yếu -> đang ở xa )
Để bắt đầu ranging và kết thúc :

@Override
protected void onResume() {
    super.onResume();

    SystemRequirementsChecker.checkWithDefaultDialogs(this);

    beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
        @Override
        public void onServiceReady() {
            beaconManager.startRanging(region);
        }
    });
}

@Override
protected void onPause() {
    beaconManager.stopRanging(region);

    super.onPause();
}

Trong hàm setRangingListener sẽ trả về list các beacon ở gần , đây chính là cái chúng ta cần :


    mBeaconManager.setRangingListener(new BeaconManager.BeaconRangingListener() {
            @Override
            public void onBeaconsDiscovered(BeaconRegion beaconRegion, List<Beacon> list) {
                for(int i=0;i<list.size();i++){
                    Log.e("address : ",""+list.get(i).getMacAddress());
                }                  
            }
});

Beacon có rất nhiều thuộc tính ở trong , bạn có thể xem bên hình dưới :

Để tính khoảng cách thì bạn chỉ cần Rssi , MeasuredPower, còn công thức đã có sẵn trong sample , như hình dưới :

   public static double computeAccuracy(Beacon beacon) {
        if (beacon.getRssi() == 0) {
            return -1.0;
        } else {
            double ratio = (double) beacon.getRssi() / (double) beacon.getMeasuredPower();
            double rssiCorrection = 0.96D + Math.pow((double) Math.abs(beacon.getRssi()), 3.0D) % 10.0D / 150.0D;
            return ratio <= 1.0D ? Math.pow(ratio, 9.98D) * rssiCorrection : (0.103D + 0.89978D * Math.pow(ratio, 7.71D)) * rssiCorrection;
        }
}

Dưới đây là hinh ảnh sau khi chúng ta đã scan được beacon : Và cuối cùng , khi đã xác định được khoảng cách từ điện thoại đến beacon , ta có thể dựa vào đó để vẽ ra vị trí tương ứng , cụ thể như hình dưới (chấm đen đại diện cho vị trí của chúng ta , nó sẽ di chuyển khi ta di chuyển):

Trước hết tạo một layout :

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <vulan.com.trackingstore.util.customview.DistanceBackgroundView
        android:id="@+id/sonar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="56dp" />

    <TextView
        android:id="@+id/tv_shop_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Shop"
        android:textColor="@color/black"
        android:textSize="@dimen/common_text_size_20" />

    <TextView
        android:visibility="gone"
        android:layout_marginTop="@dimen/common_size_30"
        android:id="@+id/tv_meter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Shop"
        android:textColor="@color/black"
        android:textSize="@dimen/common_text_size_15" />

    <ImageView
        android:id="@+id/dot"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|top"
        android:src="@drawable/dot"
        android:visibility="gone" />
</FrameLayout>

Trong này bạn có thể thấy mình sử dụng một cái custom view , vậy trong này định nghĩa gì ? , hãy xem tiếp phần dưới :

package vulan.com.trackingstore.util.customview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

import vulan.com.trackingstore.R;

/**
 * Draws distance background that is stretched to parent's height, keeps aspect ration
 * and centers the image.
 *
 * @author [email protected] (Wiktor Gworek)
 */
public class DistanceBackgroundView extends View {

  private final Drawable drawable;

  public DistanceBackgroundView(Context context, AttributeSet attrs) {
    super(context, attrs);
    drawable = context.getResources().getDrawable(R.drawable.bg_distance);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int width = drawable.getIntrinsicWidth() * canvas.getHeight() / drawable.getIntrinsicHeight();
    int deltaX = (width - canvas.getWidth()) / 2;
    drawable.setBounds(-deltaX, 0, width - deltaX, canvas.getHeight());
    drawable.draw(canvas);
  }
}

Tất nhiên là class này mình cũng đi mượn thôi(cho nhanh) , bạn có thể thấy tác dụng của nó đã được chú thích rất rõ ràng :

* Draws distance background that is stretched to parent's height, keeps aspect ration
 * and centers the image.

Nôm na là :

vẽ ra 1 cái background kéo dãn theo toàn bộ chiều dài của lớp cha chứa nó trong khi vẫn giữ tỉ lệ và giữ cho thằng image ở vị trí trung tâm

Tiếp đến là tạo ra 1 Activity gọi là DetailBeacon để set vị trí thôi , ở đây mình làm theo sample , sẽ chỉ set vị trí thay đổi theo trục 0y (theo chiều dọc) : Bên dưới là hàm update vị trí

 private void updateDistanceView(Beacon foundBeacon) {
        if (segmentLength == -1) {
            return;
        }
        dotView.animate().translationY(computeDotPosY(foundBeacon)).start();
}

Còn dưới đây là toàn bộ code của DetailBeaconActivity :

package vulan.com.trackingstore.ui.activity;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import android.widget.Toast;

import com.estimote.coresdk.observation.region.beacon.BeaconRegion;
import com.estimote.coresdk.recognition.packets.Beacon;
import com.estimote.coresdk.service.BeaconManager;

import java.util.List;

import vulan.com.trackingstore.R;
import vulan.com.trackingstore.util.FakeContainer;

import static vulan.com.trackingstore.ui.activity.MainActivity.ALL_ESTIMOTE_BEACONS_REGION;

public class DetailBeaconActivity extends Activity {
    private static final double RELATIVE_START_POS = 320.0 / 1110.0;
    private static final double RELATIVE_STOP_POS = 885.0 / 1110.0;

    private BeaconManager beaconManager;
    private Beacon beacon;
    private BeaconRegion region;
    private String mShopName;
    private String mMeter;
    private View dotView;
    private int startY = -1;
    private int segmentLength = -1;
    private TextView mShopNameText;
    private TextView mMeterText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail_beacon);
        init();
    }

    private void init() {
        beaconManager = new BeaconManager(this);
        dotView = findViewById(R.id.dot);
        mShopNameText = (TextView) findViewById(R.id.tv_shop_name);
        mMeterText = (TextView) findViewById(R.id.tv_meter);
        beacon = getIntent().getParcelableExtra(MainActivity.EXTRAS_BEACON);
        mShopName = getIntent().getStringExtra(MainActivity.SHOP_NAME);
        mMeter = getIntent().getStringExtra(MainActivity.METER);
        mShopNameText.setText(mShopName);
        //mMeterText.setText(mMeter);
        region = new BeaconRegion("regionid", beacon.getProximityUUID(), beacon.getMajor(), beacon.getMinor());
        if (beacon == null) {
            Toast.makeText(this, "Beacon not found in intent extras", Toast.LENGTH_LONG).show();
            finish();
        }
        final View view = findViewById(R.id.sonar);
        view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

                startY = (int) (RELATIVE_START_POS * view.getMeasuredHeight());
                int stopY = (int) (RELATIVE_STOP_POS * view.getMeasuredHeight());
                segmentLength = stopY - startY;

                dotView.setVisibility(View.VISIBLE);
                dotView.setTranslationY(computeDotPosY(beacon));
            }
        });
    }

    private int computeDotPosY(Beacon beacon) {
        // Let's put dot at the end of the scale when it's further than 6m.
        double distance = Math.min(FakeContainer.computeAccuracy(beacon), 6.0);
        return startY + (int) (segmentLength * (distance / 6.0));
    }

    @Override
    protected void onStart() {
        super.onStart();

        beaconManager.setRangingListener(new BeaconManager.BeaconRangingListener() {
            @Override
            public void onBeaconsDiscovered(BeaconRegion region, final List<Beacon> rangedBeacons) {
                // Note that results are not delivered on UI thread.
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // Just in case if there are multiple beacons with the same uuid, major, minor.
                        Beacon foundBeacon = null;
                        for (Beacon rangedBeacon : rangedBeacons) {
                            if (rangedBeacon.getMacAddress().equals(beacon.getMacAddress())) {
                                foundBeacon = rangedBeacon;
                            }
                        }
                        if (foundBeacon != null) {
                            updateDistanceView(foundBeacon);
                            mMeterText.setText(
                                    String.format("%f  m",FakeContainer.computeAccuracy(foundBeacon)));
                        }
                    }
                });
            }
        });

        beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
            @Override
            public void onServiceReady() {
                beaconManager.startRanging(region);
            }
        });


    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onStop() {
        beaconManager.stopRanging(region);
        beaconManager.disconnect();

        super.onStop();
    }

    private void updateDistanceView(Beacon foundBeacon) {
        if (segmentLength == -1) {
            return;
        }
        dotView.animate().translationY(computeDotPosY(foundBeacon)).start();
    }
}

Mình sẽ kết thúc bài viết về beacon ở đây , nếu có gì thắc mắc các bạn hãy comment ở bên dưới. Ngoài ra các bạn có thể tham khảo sample app của estimote : https://github.com/Estimote/Android-SDK