iBeacon - Estimote Beacons Introduction & Scan Beacons to get Item's Informations

小弟我去年底回以前念研究所的LAB走一遭,發現學弟們在玩的iBeacons還蠻酷的說,主要是

能夠透過藍芽4.0的技術來收到該Beacon上發出的訊號 (一組UUID及Major version、Minor

 version等辨識碼),藉此該Beacon就能夠當做一個識別器。因此之後也自行至網路上購買了

一組 Estimote Beacons,這是一間在波蘭的公司,三個一組是美金$100元左右,再加上運費

的話大概台幣快$4000元(老實說也蠻不便宜的說orz...)。


                                    
Estimote Beacons

Estimote Beacons官網有提供開發者們利用iOS或Android等語言可自行開發一個App來識

別Beacons,如基本的即為如何得知目前的範圍內有哪些Beacons或App可偵測是否有進入

或離開特定Beacon的範圍,並且還有可能做到室內定位等等。

除了基本的範例外,也有相關的API可以參考


由於小弟只會寫基本的Android,因此在這邊就不會提到iOS的部分(因為我不會XD,也沒有

設備)

另外,當您購買了產品後,在登入官網可以看到相關您購買的Beacon資訊及設定,如下


基本的產品介紹完之後,接下來就是進入如何撰寫程式來建立與Beacon的連結應用了!

而在這邊小弟我不會提到BLE(Bluetooth low energy)的相關技術介紹(藍芽4.0的一部分),會

針對的是開發一個小小應用的分享。

官網上面的論壇也找到有人提供相關的Android版本開發的基本範例


在此,小弟就利用了裡面的範例來進行改寫(遜掉了....)

一、具備開發環境

由於您要跑得設備必須具備BLE的支援,因此一般的模擬器是沒辦法執行該應用的(聽說連

一般的藍芽都不支援),因此我是在Nexus 7 one平板上直接跑應用的,所以您的平板必須開啟

USB debug模式。

在這邊實在是花了很多心力,因為我的平板是one,也就是當時還沒有支援藍芽4.0,因此就

利用一APP開啟root權限,再安裝Bluetooth Low Energy Enabler APP並使用後,才有支援

到BLE(因為小弟我沒有預算再換第二版的Nexus)。但好消息的是,現今很多手機或平板已

經有支援囉!!

二、應用程式說明

在這邊的APP應用,如題只是能夠去掃出範圍內的Beacons,在這邊我也為這三個Beacons綁

定了三個身份XD。好比依據官網對於Beacons的介紹,假設您走到一間商店,您可以利用

商場開發的APP來提醒您該商店有哪些優惠商品。而小弟在這邊做的也只是幫我的Beacons

賦予他意義囉!

另外,在Android開發環境下,記得載入相關的library - estimote-sdk-preview.jar

主頁面

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    getActionBar().setDisplayHomeAsUpEnabled(true);
  
    //Configure device list.
    adapter = new ListADInforsAdapter(this);
    ListView list = (ListView) findViewById(R.id.device_list);
    list.setAdapter(adapter);
    list.setOnItemClickListener(createOnItemClickListener());
     
    // Configure verbose debug logging.
    L.enableDebugLogging(true);
     
    // Configure BeaconManager.
    beaconManager = new BeaconManager(this);
    beaconManager.setRangingListener(new BeaconManager.RangingListener() {
    //傾聽目前有哪些beacons在所定義的範圍內
    @Override
    public void onBeaconsDiscovered(Region region, final List<beacon> beacons) {
       // Note that results are not delivered on UI thread.
       runOnUiThread(new Runnable() {
           @Override
           public void run() {
             // Note that beacons reported here are already sorted by estimated
             // distance between device and beacon.
             getActionBar().setSubtitle("找到" + beacons.size()+"樣優惠.");
             adapter.replaceWith(beacons);
             adapter.setItemInitials(beacons);
           }
         });
       }
     });
 }

在這邊,主要是建立一個ListView的頁面呈現方式,而這ListView上面所建立的adapter會

賦予Beacons所代表的定義!!

而我們要搜尋這個區域內有哪些Beacons必須利用BeaconManager來操作,而BeaconManager

需要載入Region所定義的實體
private static final Region ALL_ESTIMOTE_BEACONS_REGION = new Region("rid", UUID, null, null);

參數1 => 該區域的名稱設定
參數2 => UUID代碼
參數3 => Major Version
參數4 => Minor Version

當參數2 ~ 參數4都設定為null時,表示會偵測當下所有的Beacons

因此在這邊也有定義BeaconManager會執行事件,即時偵測是否有哪些特定搜尋的Beacons

在範圍內,並且不定時的更新距離位置(據說可以偵測10m內的)

adapter.replaceWith(beacons); //定時更換為當下被偵測到的beacons實體物件

adapter.setItemInitials(beacons); //針對beacons賦予意義的初始化設定

而該頁面的Android生命週期的其它部分,如onStart()主要是會偵測該硬體是否有支援BLE

或者該設備的藍芽是否有開啟等等,這個部分都還是沿用範例原本設計的。

另外,點選item後所觸發的method,在這邊還沒有撰寫接續動作,因此就沒有另外說明。

private void connectToService() {
    getActionBar().setSubtitle("Scanning...");
    adapter.replaceWith(Collections.<beacon>emptyList()); //初始化丟入空的beacons list
    
    beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
      @Override
      public void onServiceReady() {
        try {
          beaconManager.startRanging(ALL_ESTIMOTE_BEACONS_REGION);
        } catch (RemoteException e) {
          Toast.makeText(MainActivity.this, "Cannot start ranging, something terrible happened",
              Toast.LENGTH_LONG).show();
          Log.e(TAG, "Cannot start ranging", e);
        }
      }
    });
  }

在這邊要提到這個上面的method,即當onStart內確認該設備有支援BLE及藍芽有開啟之後,

才會呼叫該method進行BeaconManager的工作!!

接下來說明一下ListADInforsAdapter改寫的地方,即增加定義Beacons的資訊改寫
static class ItemsView {
  final TextView iNameTextView;
  //final TextView iDescripeTextView;
  final TextView iPriceTextView;
  final TextView iDateTextView;
  final ImageView iPicImageView;

  public ItemsView(View view) {
       iNameTextView = (TextView) view.findViewWithTag("item_name");
       //iDescripeTextView = (TextView) view.findViewWithTag("item_description");
       iPriceTextView = (TextView) view.findViewWithTag("item_price");
       iDateTextView = (TextView) view.findViewWithTag("item_date");
       iPicImageView = (ImageView) view.findViewWithTag("item_image");
     }
}

private void bind(Beacon beacon, View view) {
  ItemsView holder = (ItemsView) view.getTag();
  ItemADInfos items = beacon2item.get(beacon);
  holder.iNameTextView.setText(String.format("商品: %s (距離:%.2fm)", items.getAdname(), Utils.computeAccuracy(beacon)));
  //holder.iDescripeTextView.setText(items.getAddescription());
  holder.iPriceTextView.setText("特價: $"+items.getAdprice());
  holder.iDateTextView.setText("活動日期:"+items.getDate());
  holder.iPicImageView.setImageDrawable(rsys.getDrawable(items.getAdimageresource()));
}

public void setItemInitials(List<beacon> beacons){
  for(Beacon b : beacons){
 //Log.i(ListADInforsAdapter.class.getSimpleName(), ">>>>>>>>>>>>>>>>>");
 ItemADInfos itemObj = null;
 if(!beacon2item.containsKey(b)){
  if(b.getMajor() == 37254 && b.getMinor() == 43541){ //ice
   itemObj = new ItemADInfos("ASUS 14吋 X450JN (750G) 筆電", "X450...", 22900, "04/06 ~ 04/13", R.drawable.asus);

  }else if(b.getMajor() == 15160 && b.getMinor() == 40811){ //mint
   itemObj = new ItemADInfos("ACER Iconia B1-750 7吋四核平板16G/wifi-黑/白", "搭載Intel...", 3490, "04/06 ~ 04/13", R.drawable.acer);

  }else if(b.getMajor() == 298 && b.getMinor() == 24861){ //blueberry
   itemObj = new ItemADInfos("EPSON L360高速三合一原廠連續供墨印表", "機器壽命...", 4990, "04/06 ~ 04/13", R.drawable.epson);
  }
  beacon2item.put(b, itemObj);
 }
  }
}

bind method由getView所呼叫,在這邊可以動態的改變ListView下的item資訊

setItemInitials method是在discover event時執行的,此時會由Map beacon2item來建立beacon所

對應的ItemADInfos物件(自行定義),而怎麼針對哪個beacon給予不同的意義,則由major及

minor來區隔(因為這一組的UUID是相同的),在這邊目前是以寫死的資訊來定義。

最後,APP demo的畫面如下:

以下的商品圖示僅供demo使用,來自yahoo奇摩購物中心

在這邊的item會即時的更換距離的數字部分,而當您離開了某個beacon的範圍之後當下的

特定項目也會消失,直到進入可偵測的範圍內。

總結

這個範例很簡單的將原本的範例進行小小改寫,怎麼將該beacon賦予不同的意義,如利用

某個beacon安置在賣場的筆電館,則當您持著裝有APP的裝置接近筆電館時會偵測到該

beacon的訊號而得知該beacon的識別碼,此時在APP內可以以此識別碼做資料的查詢(如查

資料庫或寫死在APP的程式內)來得到訊息,也就是由beacon的識別碼配合來觸發!!

PS. 以上只是舉範例內重點或新加的method進行說明,相關完整list beacons的範例可以自行

下載github所提供的囉!!

留言

  1. 很棒的分享
    但還是有許多不理解的地方
    請問能提供example檔或是有詳細的解說嗎
    感謝

    回覆刪除
    回覆
    1. 你好~ 是否請你提供email,我再mail給你

      刪除
  2. 你好,很感謝你的分享,但可以提供範例或者更詳細的解說嗎?因為目前還是有很多地方想要了解更多,謝謝你。

    回覆刪除
    回覆
    1. 已提供範例下載於文末囉

      刪除
    2. 真是非常感謝!!
      今後也會繼續關注您的分享的
      : )

      刪除
    3. 非常感謝你的分享!

      刪除
  3. 你好,想請問一下區域名稱是否會影響到接收Beacon數量?因為我設定了UUID、Major 、Minor但我無法抓到資料,想請問一下是否是這個問題。

    回覆刪除
    回覆
    1. 自己自訂的區域名稱要影響到應該不至於!我想問題出在UUID、Major、Minor的正確性!
      印象中,API那邊是說自訂區域名稱需為唯一,而UUID可在官網login後設定頁面取得,Major、Minor我都設為null。這樣子的初始化,能夠偵測到的將是我自己的三個Beacons。而程式之中你再各別以Beacon的物件get Major、Minor來做區隔!

      刪除
  4. 請問我買的是無錫谷雨開發套件組但我設定了自己的UUID,Major、Minor都是設定null可是沒辦法抓到耶?

    回覆刪除
    回覆
    1. 是否從官方提供的範例下手?! 小弟我也是從example、官方API得到一點資訊

      刪除
  5. 不好意思我想請問在搜尋部分的問題

    目前我的專題研究是使用apple裝置

    在搜尋時 必須指定UUID才能搜尋裝置

    但在您的文章中似乎表示安卓可以不指定UUID去搜尋Beacon

    是指在UUID設定為null的情況下

    可以同時搜尋到兩個不同UUID的裝置嗎?

    回覆刪除
    回覆
    1. 參考官網API
      new Region(java.lang.String identifier, java.util.UUID proximityUUID, java.lang.Integer major, java.lang.Integer minor)
      針對new Region的後三個參數設定為null,則在同一個identifier之下的距離範圍內可以搜尋出所有的Beacons;依序若再設第二個參數的UUID,則會找出相同UUID的裝置(印象中,我三個裝置UUID都一樣,差別在Major version、Minor version各不同)

      刪除

張貼留言