블루투스 4.0에 적용된 데이터 통신만을 위해 탄생된 새로운 기술, 음성지원이 되지 않으므로 주로 데이터 통신으로 사용된다.
GAP(Generic Access Profile)
GATT
BLE 디바이스간의 데이터를 교환할 때 데이터의 구조를 정의해 놓은 profile
Profile
Central (GATT client, master)
Peripheral (GATT Server, slave)
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
runtime 퍼미션 구현
private val runtimePermissions = arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.BLUETOOTH_CONNECT
)
.. 후략(테드퍼미션 쓰자)
bluetoothManager
를 통해 adapter 불러서 bluetooth adapter
에 연결
startScan 함수를 만들어 이를 호출 시 스캔을 시작하도록 하였다.
bluetoothscanner
객체를 통하여 주위의 블루투스 장비들을 스캔할 수 있다.
private fun startScan() {
decideListAdapter.clear()
decideListAdapter.notifyDataSetChanged()
Log.d(TAG, "startScan")
// BLE Sensor Scan
//전체 scan.
scanner.startScan(scanCallback)
//10초 후 scan 중지
handler.postDelayed({
stopScan()
}, 5_000)
}
이 스캔을 필터를 통해 조건을 줄 수도 있다.
전체코드
class MainActivity : AppCompatActivity() {
companion object {
private const val PERMISSION_REQUEST_CODE = 8
}
private lateinit var bluetoothManager: BluetoothManager
private lateinit var scanner: BluetoothLeScanner
private lateinit var decideListAdapter: LeDeviceListAdapter
private lateinit var checkPermission: CheckPermission
private var blueAdapter: BluetoothAdapter? = null
private val runtimePermissions = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.BLUETOOTH_CONNECT
)
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
checkPermission = CheckPermission(this)
blueAdapter = bluetoothManager.adapter
if (blueAdapter == null || !blueAdapter!!.isEnabled) {
Toast.makeText(this, "블루투스 기능을 확인해 주세요.", Toast.LENGTH_SHORT).show()
val bleIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(bleIntent, 1)
}
scanner = blueAdapter!!.getBluetoothLeScanner()
if (!checkPermission.runtimeCheckPermission(this, *runtimePermissions)) {
ActivityCompat.requestPermissions(this, runtimePermissions, PERMISSION_REQUEST_CODE)
} else { //이미 전체 권한이 있는 경우
initView()
}
}
override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PERMISSION_REQUEST_CODE -> if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 권한을 모두 획득했다면.
initView()
} else {
checkPermission.requestPermission()
}
}
}
private fun initView() {
decideListAdapter = LeDeviceListAdapter(this)
binding.devicelist.adapter = decideListAdapter
binding.devicelist.onItemClickListener =
OnItemClickListener { parent, view, position, id ->
val device = decideListAdapter.getItem(position) as BluetoothDevice
Log.d(TAG, "++++++++++++ Selected Device ++++++++++++++++++")
Toast.makeText(this@MainActivity, "selected..$device", Toast.LENGTH_SHORT).show()
Log.d(TAG, "onItemClick: $device") //62:A6:2D:32:69:F2
// onScanResult: result:ScanResult{device=62:A6:2D:32:69:F2, scanRecord=ScanRecord [mAdvertiseFlags=4, mServiceUuids=[0000fd5a-0000-1000-8000-00805f9b34fb], mServiceSolicitationUuids=[], mManufacturerSpecificData={}, mServiceData={0000fd5a-0000-1000-8000-00805f9b34fb=[21, -22, 3, 1, 70, -20, -122, -22, 126, 44, 105, 77, -61, 0, 0, 0, -97, 25, -34, -7]}, mTxPowerLevel=-2147483648, mDeviceName=Smart Tag, mTransportBlocks=[]], rssi=-85, timestampNanos=2925531664814018, eventType=27, primaryPhy=1, secondaryPhy=0, advertisingSid=255, txPower=127, periodicAdvertisingInterval=0}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_scan ->
// Toast.makeText(this, "Start Scan", Toast.LENGTH_SHORT).show();
startScan()
R.id.action_stop ->
// Toast.makeText(this, "Stop Scan", Toast.LENGTH_SHORT).show();
stopScan()
}
return super.onOptionsItemSelected(item)
}
private fun startScan() {
decideListAdapter.clear()
decideListAdapter.notifyDataSetChanged()
Log.d(TAG, "startScan")
// BLE Sensor Scan
//전체 scan.
// scanner.startScan(scanCallback)
val setting = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build()
val filter = ScanFilter.Builder()
.setDeviceAddress("54:6C:0E:B7:D1:04")
.build()
//특정 device 만 scan
scanner.startScan(mutableListOf(filter) , setting, scanCallback)
//10초 후 scan 중지
handler.postDelayed({
stopScan()
}, 10_000)
}
private fun stopScan() {
Log.d(TAG, "stopSCan")
// BLE Sensor Scan Stop
scanner.stopScan(scanCallback)
}
private var scanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
processResult(result)
}
override fun onScanFailed(errorCode: Int) {
Log.d(TAG, "errorCode: errorCode:$errorCode")
}
private fun processResult(result: ScanResult) {
Log.d(TAG, "processResult: ")
Log.d(TAG, "++++++++++++scan Result++++++++++++++++++")
Log.d(TAG, result.toString())
//블루투스는 UI가 아닌 다른 스레드에서 실행되므로 ui 변경은 ui스레드에서 하도록
runOnUiThread {
decideListAdapter.addDevice(result.device)
decideListAdapter.notifyDataSetChanged()
}
}
}
var handler = Handler(Looper.getMainLooper())
override fun onPause() {
super.onPause()
stopScan()
}
}
BLE 센서를 직접적으로 다루어 온도, 습도 등의 값을 가져오는 작업은 내용이 방대하여 소스코드로 대체한다.
BLE 통신규약에 근거하여 BLE 신호와 연동하여 다양한 정보를 송수신하는 무선통신기술
beacon 역시 major, minor, 거리(tx)를 가져와 사용할 수 있는데, 거리를 변환해주는 라이브러리를 제공한다.
https://github.com/AltBeacon/android-beacon-library
implementation 'org.altbeacon:android-beacon-library:2:19'
private fun startScan() {
Log.d(TAG, "startScan")
leDeviceListAdapter.clear()
leDeviceListAdapter.notifyDataSetChanged()
// IBeacon Sensor Scan
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val filter = ScanFilter.Builder()
// .setDeviceAddress("54:6C:0E:B7:40:05")
.setDeviceAddress("24:71:89:0B:9C:2D")
.build()
bluetoothLeScanner.startScan(mutableListOf(filter), settings, mLeScanCallback)
}
beacon의 major, minor, 및 uuid 가져오기