본문 바로가기

Android/통신

[안드로이드-아두이노] bluetooth classic 자동 페어링&연결 / 데이터 송,수신

728x90
반응형

이 예제는, 특정 이름을 가진 블루투스를 검색 후 자동 페어링, 연결 후

데이터 송/수신을 하는 예제입니다.

아두이노 우노와 데이터 송/수신 하였고, HC-06을 사용 하였으며, SPP통신입니다.

이전에 안드로이드와 아두이노 블루투스 코드를 포스팅 했었습니다. (Java)

[Android/통신] - [안드로이드] 아두이노와 안드로이드 Bluetooth 통신하기

위의 포스팅과 비교하여 추가된 점과 달라진 점은 아래와 같습니다.

  • 페어링된 기기뿐 아니라 페어링 되지 않은 기기의 페어링 진행
  • 특정 디바이스를 필터하여 페어링&연결
  • 블루투스 connect 상태 체크
  • 안드로이드, 아두이노 송/수신 전부 구현
  • Kotlin

데이터 바인딩과 라이브데이터를 사용한 MVVM 구조로 짜여진 코드를 리뷰하는 것이므로,

메서드 구현위주로 봐주세요~

순서대로 진행해 보겠습니다.

 

코드 미리보기

 

 

권한


AndroidManifest.xml에 bluetooth를 위해 아래 permission을 추가합니다.

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

 

Activity에서 권한 추가 코드를 작성합니다.

    const val REQUEST_ALL_PERMISSION = 1
 ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.viewModel = viewModel

        //Permission
        if (!hasPermissions(this, PERMISSIONS)) {
            requestPermissions(PERMISSIONS, REQUEST_ALL_PERMISSION)
        }

        initObserving()

    }

...

    private fun hasPermissions(context: Context?, permissions: Array<String>): Boolean {
        for (permission in permissions) {
            if (context?.let { ActivityCompat.checkSelfPermission(it, permission) }
                    != PackageManager.PERMISSION_GRANTED
            ) {
                return false
            }
        }
        return true
    }
    // Permission check
    override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<String?>,
            grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            REQUEST_ALL_PERMISSION -> {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "Permissions granted!", Toast.LENGTH_SHORT).show()
                } else {
                    requestPermissions(permissions, REQUEST_ALL_PERMISSION)
                    Toast.makeText(this, "Permissions must be granted", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

 

블루투스 지원, 활성 체크


Connect 버튼을 누르면 블루투스에 페어링&연결을 시작합니다.

먼저, 블루투스 지원을 체크하고, 블루투스 기능이 꺼져있다면 켜도록 합니다.

전부 활성화되어 있다면, 스캔을 시작합니다.

if (isBluetoothSupport()) {   // 블루투스 지원 체크
      if(repository.isBluetoothEnabled()){ // 블루투스 활성화 체크
          //Progress Bar
          setInProgress(true)
          //디바이스 스캔 시작
          scanDevice()
       }else{
          // 블루투스를 지원하지만 비활성 상태인 경우
          // 블루투스를 활성 상태로 바꾸기 위해 사용자 동의 요청
          _requestBleOn.value = Event(true)
       }
}else{ //블루투스 지원 불가
      //Toast Massage
      Util.showNotification("Bluetooth is not supported.")
}

 

  • 블루투스 지원 확인
var mBluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
...
fun isBluetoothSupport():Boolean{
   return if(mBluetoothAdapter==null) {
         //Toast massage
         Util.showNotification("Bluetooth 지원을 하지 않는 기기입니다.")
         false
    }else{
         true
    }
}

 

  • 블루투스 활성 요청

블루투스 기능을 키는것을 activity에서 하기 위해, LiveData를 사용하여 보내준 Event를 Observe하였습니다.

//Bluetooth On 요청
viewModel.requestBleOn.observe(this, {
   val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
   startForResult.launch(enableBtIntent)
})

...

 private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
     if (result.resultCode == Activity.RESULT_OK) {
         val intent = result.data
         //Bluetooth를 활성화 할 경우 Connect 다시 수행
         viewModel.onClickConnect()
     }
}

StartActivityForResult가 deperacted되어, 위처럼 구현하기위해 gradle에 아래 코드를 추가해주세요.

// for new API replaced startActivityForResult
implementation 'androidx.fragment:fragment-ktx:1.3.0-beta02'

 

Bluetooth Scan


var foundDevice:Boolean = false
...
fun scanDevice(){
     //Progress State Text
     progressState.postValue("device 스캔 중...")

     //리시버 등록
     registerBluetoothReceiver()

     //블루투스 기기 검색 시작
     val bluetoothAdapter = mBluetoothAdapter
     foundDevice = false
     bluetoothAdapter?.startDiscovery() 
}

 

  • 블루투스 리시버 등록
private var mBluetoothStateReceiver: BroadcastReceiver? = null
...
fun registerBluetoothReceiver(){
        //intentfilter
        val stateFilter = IntentFilter()
        stateFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED) //BluetoothAdapter.ACTION_STATE_CHANGED : 블루투스 상태변화 액션
        stateFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
        stateFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED) //연결 확인
        stateFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) //연결 끊김 확인
        stateFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
        stateFilter.addAction(BluetoothDevice.ACTION_FOUND) //기기 검색됨
        stateFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED) //기기 검색 시작
        stateFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) //기기 검색 종료
        stateFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
        mBluetoothStateReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent) {
                val action = intent.action //입력된 action
                if (action != null) {
                    Log.d("Bluetooth action", action)
                }
                val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
                var name: String? = null
                if (device != null) {
                    name = device.name //broadcast를 보낸 기기의 이름을 가져온다.
                }
                when (action) {
                    BluetoothAdapter.ACTION_STATE_CHANGED -> {
                        val state = intent.getIntExtra(
                            BluetoothAdapter.EXTRA_STATE,
                            BluetoothAdapter.ERROR
                        )
                        when (state) {
                            BluetoothAdapter.STATE_OFF -> {
                            }
                            BluetoothAdapter.STATE_TURNING_OFF -> {
                            }
                            BluetoothAdapter.STATE_ON -> {
                            }
                            BluetoothAdapter.STATE_TURNING_ON -> {
                            }
                        }
                    }
                    BluetoothDevice.ACTION_ACL_CONNECTED -> {

                    }
                    BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
                    }
                    BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
                        //디바이스가 연결 해제될 경우 
                        connected.postValue(false)
                    }
                    BluetoothAdapter.ACTION_DISCOVERY_STARTED -> {
                    }
                    BluetoothDevice.ACTION_FOUND -> {
                        if (!foundDevice) {
                            val device_name = device!!.name
                            val device_Address = device.address
                            //블루투스 기기 이름의 앞글자가 "RNM"으로 시작하는 기기만을 검색한다
                            if (device_name != null && device_name.length > 4) {
                                if (device_name.substring(0, 3) == "RNM") {
                                    targetDevice = device
                                    foundDevice = true
                                    //찾은 디바이스에 연결한다.
                                    connectToTargetedDevice(targetDevice)
                                    
                                }
                            }
                        }
                    }
                    BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
                        if (!foundDevice) {
                            //Toast massage
                            Util.showNotification("디바이스를 찾을 수 없습니다. 다시 시도해 주세요.")
                            //Progress 해제
                            inProgress.postValue(Event(false))
                        }
                    }

                }
            }
        }
        //리시버 등록
        MyApplication.applicationContext().registerReceiver(
            mBluetoothStateReceiver,
            stateFilter
        )

    }

블루투스 리시버를 등록하여, 디바이스를 찾았을때, 디바이스가 연결, 연결 해제 되었을때의 action을 받아 이벤트 처리를 할 수 있습니다.

디바이스를 찾았을 때, 특정 기기의 이름인지 확인하여 원하는 기기의 이름일 경우 연결을 시작합니다.

또한 리시버는 생명주기의 onStop()같은 부분에서, unregister해줍니다.

fun unregisterReceiver(){
   if(mBluetoothStateReceiver!=null) {
       MyApplication.applicationContext().unregisterReceiver(mBluetoothStateReceiver)
       mBluetoothStateReceiver = null
   }
}

 

Connect Device


원하는 BluetoothDevice를 찾았다면, 해당 디바이스에 연결할 수 있습니다.

만약 페어링 되어 있지 않다면, 자동으로 페어링 후 연결합니다.

var targetDevice: BluetoothDevice? = null
var socket: BluetoothSocket? = null
var mOutputStream: OutputStream? = null
var mInputStream: InputStream? = null
...
private fun connectToTargetedDevice(targetedDevice: BluetoothDevice?) {
    //Progress state text
    progressState.postValue("${targetDevice?.name}에 연결중..")

    val thread = Thread {
        //선택된 기기의 이름을 갖는 bluetooth device의 object
        //SPP_UUID
        val uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
        try {
           // 소켓 생성
           socket = targetedDevice?.createRfcommSocketToServiceRecord(uuid)
           //Connect
           socket?.connect()

           /**
            * After Connect Device
            */
           //연결 상태
           connected.postValue(true)
           //output, input stream을 열어 송/수신 
           mOutputStream = bleSocket?.outputStream
           mInputStream = bleSocket?.inputStream
           // 데이터 수신 시작
           beginListenForData()

        } catch (e: java.lang.Exception) {
           // 블루투스 연결 중 오류 발생
            e.printStackTrace()
            connectError.postValue(Event(true))
            try {
                socket?.close()
            catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    //연결 thread를 수행한다
    thread.start()
}

 

연결 후 input, output stream을 열고 데이터 송/수신을 할 수 있습니다.

 

데이터 송/수신


  • 안드로이드 데이터 송신
/**
 * 블루투스 데이터 송신
 * String sendTxt를 byte array로 바꾸어 전송할 수 있다.
 * val byteArr = sendTxt.toByteArray(Charset.defaultCharset())
 * sendByteData(byteArr)
 */
fun sendByteData(data: ByteArray) {
   Thread {
       try {
           mOutputStream?.write(data) // 프로토콜 전송
       } catch (e: Exception) {
           // 문자열 전송 도중 오류가 발생한 경우.
            e.printStackTrace()
       }
    }.run()
}

 

  • 안드로이드 데이터 수신
/**
 * 블루투스 데이터 수신 Listener
 */
fun beginListenForData() {

    val mWorkerThread = Thread {
        while (!Thread.currentThread().isInterrupted) {
            try {
                val bytesAvailable = mInputStream?.available()
                if (bytesAvailable != null) {
                    if (bytesAvailable > 0) { //데이터가 수신된 경우
                        val packetBytes = ByteArray(bytesAvailable)
                        mInputStream?.read(packetBytes)
                        /**
                         * 한 버퍼 처리
                         */
                        // Byte -> String
                        val s = String(packetBytes,Charsets.UTF_8)
                        //수신 String 출력
                        putTxt.postValue(s)
                        
                        /**
                         * 한 바이트씩 처리
                         */
                        for (i in 0 until bytesAvailable) {
                            val b = packetBytes[i]
                             Log.d("inputData", String.format("%02x", b))
                        }
                    }
                }
            } catch (e: UnsupportedEncodingException) {
                 e.printStackTrace()
            } catch (e: IOException) {
                 e.printStackTrace()
            }
       }
    }
    //데이터 수신 thread 시작
    mWorkerThread.start()
}

 

  • 아두이노 송/수신 코드

#include <SoftwareSerial.h>  

const int pinTx = 5;  // 블루투스 TX 연결 핀 번호
const int pinRx = 4;  // 블루투스 RX 연결 핀 번호

SoftwareSerial   bluetooth( pinTx, pinRx );  

void  setup()
{
  bluetooth.begin(9600);  // 블루투스 통신 초기화 (속도= 9600 bps)
  Serial.begin(115200);
}


void  loop()
{
  
  // 블루투스 수신 
  if ( bluetooth.available() ) 
  {
    Serial.print((char)bluetooth.read());
  }
  else
  {
    delay( 10 );
  }
  // 블루투스 송신
  if (Serial.available()) { 
    //시리얼 모니터에서 입력된 값을 송신
    char toSend = (char)Serial.read();
    bluetooth.print(toSend);
  }
}

 

 

전체 소스를 github에서 확인하세요.

728x90
반응형