안드로이드

안드로이드 BLE(Bluetooth Low Energy) Scanner 구현 => BluetoothAdapter, BluetoothLeScanner

알통몬_ 2017. 10. 20. 10:34
반응형


공감 및 댓글은 포스팅 하는데

 아주아주 큰 힘이 됩니다!!

포스팅 내용이 찾아주신 분들께 

도움이 되길 바라며

더 깔끔하고 좋은 포스팅을 

만들어 나가겠습니다^^

 


이번 포스팅에서는 BLE Beacon Scanner 를 구현하는 방법에 대해 알아보겠습니다.


기존 안드로이드에서 제공하는 BluetoothAdapter 만으로는 iBeacon 같은 비콘들을 스캔할 수가 없는데요.


안드로이드에서 API 21버전 이상의 기기에서 사용가능한 BluetoothLeScanner 를 사용하면


스캔할 수 있습니다.


1. Manifest.xml 설정 ( FINE_LOCATION 과 COARSE_LOCATION 중 하나만 선언해도 됩니다 )


<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>


2. API 23버전 이상을 위한 동적 권한 요청 코드.

ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSIONS);

PERMISSIONS 상수의 값은 정수형 값으로 1이상의 아무 숫자나 사용하면 됩니다. 저는 100을 사용했어요.


저는 원하는 특정 비콘만 스캔해서 ListView에 추가시키는 기능을 만들어 봤어요.

아래에서 ScanFilter, ScanFilter.Builder, ScanSettings, ScanSetting.Builder 만 제거하면

모든 BLE Beacon들을 검색할 수 있습니다.


3. MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import java.text.SimpleDateFormat;
        import java.util.Date;
        import java.util.List;
        import java.util.Locale;
        import java.util.Vector;
 
public class MainActivity extends AppCompatActivity {
 
    BluetoothAdapter mBluetoothAdapter;
 
    BluetoothLeScanner mBluetoothLeScanner;
 
    BluetoothLeAdvertiser mBluetoothLeAdvertiser;
 
    private static final int PERMISSIONS = 100;
 
    Vector<Beacon> beacon;
 
    BeaconAdapter beaconAdapter;
 
    ListView beaconListView;
 
    ScanSettings.Builder mScanSettings;
 
    List<ScanFilter> scanFilters;
 
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss", Locale.KOREAN);
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSIONS);
        beaconListView = (ListView) findViewById(R.id.beaconListView);
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
        mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
        beacon = new Vector<>();
        mScanSettings = new ScanSettings.Builder();
        mScanSettings.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
// 얘는 스캔 주기를 2초로 줄여주는 Setting입니다.
// 공식문서에는 위 설정을 사용할 때는 다른 설정을 하지 말고
// 위 설정만 단독으로 사용하라고 되어 있네요 ^^
// 위 설정이 없으면 테스트해 본 결과 약 10초 주기로 스캔을 합니다.
        ScanSettings scanSettings = mScanSettings.build();
 
        scanFilters = new Vector<>();
        ScanFilter.Builder scanFilter = new ScanFilter.Builder();
        scanFilter.setDeviceAddress("특정 기기의 MAC 주소"); //ex) 00:00:00:00:00:00
        ScanFilter scan = scanFilter.build();
        scanFilters.add(scan);
        mBluetoothLeScanner.startScan(scanFilters, scanSettings, mScanCallback);
// filter와 settings 기능을 사용하지 않을 때는
mBluetoothLeScanner.startScan(mScanCallback); 처럼 사용하시면 돼요.
    }
 
    ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            try {
                ScanRecord scanRecord = result.getScanRecord();
                Log.d("getTxPowerLevel()",scanRecord.getTxPowerLevel()+"");
                Log.d("onScanResult()", result.getDevice().getAddress() + "\n" + result.getRssi() + "\n" + result.getDevice().getName()
                        + "\n" + result.getDevice().getBondState() + "\n" + result.getDevice().getType());
 
                    final ScanResult scanResult = result;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    beacon.add(0,new Beacon(scanResult.getDevice().getAddress(), scanResult.getRssi(), simpleDateFormat.format(new Date())));
                                    beaconAdapter = new BeaconAdapter(beacon, getLayoutInflater());
                                    beaconListView.setAdapter(beaconAdapter);
                                    beaconAdapter.notifyDataSetChanged();
                                }
                            });
                        }
                    }).start();
                
            } catch (Exception e) {
                e.printStackTrace();
            }
 
        }
 
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
            Log.d("onBatchScanResults", results.size() + "");
        }
 
        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
            Log.d("onScanFailed()", errorCode+"");
        }
    };
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBluetoothLeScanner.stopScan(mScanCallback);
    }
}

cs


4. Beacon.java

public class Beacon {
private String address;
private int rssi;
private String now;

public Beacon(String address, int rssi, String now) {
this.address = address;
this.rssi = rssi;
this.now = now;
}

public String getAddress() {
return address;
}

public int getRssi() {
return rssi;
}

public String getNow() {
return now;
}
}

5. BeaconAdapter.java

public class BeaconAdapter extends BaseAdapter {


private Vector<Beacon> beacons;
private LayoutInflater layoutInflater;

public BeaconAdapter(Vector<Beacon> beacons, LayoutInflater layoutInflater) {
this.beacons = beacons;
this.layoutInflater = layoutInflater;
}

@Override
public int getCount() {
return beacons.size();
}

@Override
public Object getItem(int position) {
return beacons.get(position);
}

@Override
public long getItemId(int position) {
return 0;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
BeaconHolder beaconHolder;
if (convertView == null) {
beaconHolder = new BeaconHolder();
convertView = layoutInflater.inflate(R.layout.item_beacon, parent, false);
beaconHolder.address = convertView.findViewById(R.id.address);
beaconHolder.rssi = convertView.findViewById(R.id.rssi);
beaconHolder.time = convertView.findViewById(R.id.time);
convertView.setTag(beaconHolder);
} else {
beaconHolder = (BeaconHolder)convertView.getTag();
}

beaconHolder.time.setText("시간 :" + beacons.get(position).getNow());
beaconHolder.address.setText("MAC Addr :"+beacons.get(position).getAddress());
beaconHolder.rssi.setText("RSSI :"+beacons.get(position).getRssi() + "dBm");
return convertView;
}

private class BeaconHolder {
TextView address;
TextView rssi;
TextView time;

}
}

6. item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="22sp"/>
<TextView
android:id="@+id/rssi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="20sp"/>
<TextView
android:id="@+id/time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="18sp"/>
</LinearLayout>

7. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="altong.mon.mybeaconscanner.MainActivity">

<ListView
android:id="@+id/beaconListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

</android.support.constraint.ConstraintLayout>


이상입니다.

궁금하신 점이 있으시다면 댓글 달아주세요.

프로젝트는 깃허브에 공유해 놓았습니다~^^

https://github.com/Parksunggyun/MyBeaconScanner/tree/master

반응형