본문 바로가기

major/Android

[Android] 블루투스 통신 예제 (with 아두이노)

이전 포스팅에서 아두이노와 안드로이드가 블루투스로 통신이 되는 걸 확인했습니다.

 

블루투스 통신 예제는 GitHub에 많아서 금방 개발할 수 있을 거라고 생각했는데, 예상외로 permission과 connectedthread에서 삽질을 많이 했습니다. Google Developers에서 블루투스 개요라는 문서를 주고 있지만, 초보 개발자인 저는 공식문서를 봐도 어려웠습니다. 그래서 Github에서 쉬운 코드를 제공해준 Android-Simple-Bluetooth-Example와 공식문서를 번갈아가면서 참고했습니다.

 

블루투스 개요에서 제공하는 코드를 기반으로, 통신 예제 코드를 구현하는 방법을 정리했습니다.

project는 empty activity로 만들고 따라 하시면 됩니다.

전체 코드는 src 파일만 포스팅 제일 아래에 첨부했습니다.

1. 권한

저는 권한 때문에 쓸데없는 삽질을 많이 해서 빨간색으로 강조했습니다.

AndroidManifest.xml에서 다음 4개의 권한을 추가합니다.

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

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

MainActivity.java에서 아래와 같이 위치 권한을 허용하는 코드를 추가합니다.

// Get permission
String[] permission_list = {
	Manifest.permission.ACCESS_FINE_LOCATION,
	Manifest.permission.ACCESS_COARSE_LOCATION
};

ActivityCompat.requestPermissions(MainActivity.this, permission_list,  1);

위치 권한이 없다면, 나중에 블루투스로 주변 장치 검색이 안됩니다.

앱 아이콘을 꾹 누르고 앱 정보> 권한에서 다음처럼 위치 권한이 on 상태인지 다시 한번 확인해주세요.

 

 

앱 권한 확인하기

 

2. Bluetooth 활성화

블루투스를 활성화해야 합니다.

블루투스 개요에 나와있는 코드를 그대로 MainActivity.java의 onCreate안에 복붙하면 됩니다.

BluetoothAdapter btAdapter;
private final static int REQUEST_ENABLE_BT = 1;

protected void onCreate(Bundle savedInstanceState) {
	...
    
    // Enable bluetooth
    btAdapter = BluetoothAdapter.getDefaultAdapter();
	if (!btAdapter.isEnabled()) {
	Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
	}
}

3. 레이아웃

저는

1. 페어링 된 디바이스 목록 불러오기

2. 주변 기기 검색하기

3. 선택한 기기와 블루투스 연결하기

4. 연결한 블루투스 기기에 데이터 전송하기

기능 4가지를 구현했습니다.

 

각 기능을 구현하는 방법을 설명하기 전에, 레이아웃부터 확실히 해주겠습니다.

activity_main.xml 파일 안에 다음 코드를 붙여넣어주세요.

<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">

    <TextView
        android:id="@+id/text_status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="status"
        android:textSize="20dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_paired"
        android:text="Paired Devices"
        android:onClick="onClickButtonPaired"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text_status"/>

    <Button
        android:id="@+id/btn_search"
        android:text="Search"
        android:onClick="onClickButtonSearch"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_paired"/>

    <Button
        android:id="@+id/btn_send"
        android:text="Send a"
        android:onClick="onClickButtonSend"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_search"/>

    <ListView
        android:id="@+id/listview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_send"/>

</androidx.constraintlayout.widget.ConstraintLayout>

앱 레이아웃은 다음과 같습니다.

레이아웃

paired devices 버튼을 누르면 기존에 페어링 된 기기 목록을 listview에 보여주고,

search 버튼을 누르면 주변 블루투스 기기를 검색해서 listview로 보여 줄 예정입니다.

listview에서 기기를 누르면 그 기기와 블루투스 연결을 시도하고,

send a를 누르면 연결된 블루투스 기기에 "a"문자열을 보내도록 구현했습니다.

textview에는 블루투스 연결 상태를 출력 해 눈으로 확인할 예정입니다.

 

각 기능을 구현하기 위해 MainActivity.java에 변수들을 선언해주세요.

TextView textStatus;
Button btnParied, btnSearch, btnSend;
ListView listView;

protected void onCreate(Bundle savedInstanceState) {
	...
    
	// variables
	textStatus = (TextView) findViewById(R.id.text_status);
	btnParied = (Button) findViewById(R.id.btn_paired);
	btnSearch = (Button) findViewById(R.id.btn_search);
	btnSend = (Button) findViewById(R.id.btn_send);
	listView = (ListView) findViewById(R.id.listview);
}

4. 기존에 페어링 된 기기 목록 가져오기

이제 페어링 된 기기의 목록을 listview에 뿌려주는 기능을 구현하겠습니다.

 

먼저 다음 변수들을 선언해주세요.

Set<BluetoothDevice> pairedDevices;
ArrayAdapter<String> btArrayAdapter;
ArrayList<String> deviceAddressArray;

protected void onCreate(Bundle savedInstanceState) {
	...
	
    // show paired devices
	btArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
    deviceAddressArray = new ArrayList<>();
    listView.setAdapter(btArrayAdapter);
}

paired devices 버튼의 클릭 이벤트는 다음과 같이 구현했습니다.

public void onClickButtonPaired(View view){
        btArrayAdapter.clear();
        if(deviceAddressArray!=null && !deviceAddressArray.isEmpty()){ deviceAddressArray.clear(); }
        pairedDevices = btAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
            // There are paired devices. Get the name and address of each paired device.
            for (BluetoothDevice device : pairedDevices) {
                String deviceName = device.getName();
                String deviceHardwareAddress = device.getAddress(); // MAC address
                btArrayAdapter.add(deviceName);
                deviceAddressArray.add(deviceHardwareAddress);
            }
        }
    }

getBondedDevices()를 통해 기존에 페어링 된 기기의 목록을 가져옵니다.

그리고 deviceName은 btArrayAdapter에, devicesHardwareAddress는 deviceAddressArray에 저장합니다.

 

기기를 검색했을 때와 페어링 된 기기 목록을 불러올 때 btArrayAdapter, devicesAddressArray를 같이 쓸 예정이기 때문에, 버튼을 누를 때 마다 기존 Array들에 있던 값들은 모두 clear()해줍니다.

 

여기까지 성공했다면 아래와 같이 paired device 목록이 뜹니다.

 

페어링 된 디바이스 목록 가져오기

4. 기기 검색하기

이제 주변의 블루투스 기기를 검색하고 목록을 가져올 차례입니다.

 

bluetoothAdapter.startDiscovery()를 통해서 주변 기기를 검색합니다.

그리고 receiver를 통해서 검색된 기기의 정보를 가져옵니다.

public void onClickButtonSearch(View view){
	// Check if the device is already discovering
    if(btAdapter.isDiscovering()){
		btAdapter.cancelDiscovery();
    } else {
        if (btAdapter.isEnabled()) {
        	btAdapter.startDiscovery();
            btArrayAdapter.clear();
            if (deviceAddressArray != null && !deviceAddressArray.isEmpty()) {
            	deviceAddressArray.clear();
            }
            IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
            registerReceiver(receiver, filter);
		} else {
			Toast.makeText(getApplicationContext(), "bluetooth not on", Toast.LENGTH_SHORT).show();
        }
    }
}

 // Create a BroadcastReceiver for ACTION_FOUND.
private final BroadcastReceiver receiver = new BroadcastReceiver() {
	public void onReceive(Context context, Intent intent) {
		String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
			// Discovery has found a device. Get the BluetoothDevice
            // object and its info from the Intent.
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            String deviceName = device.getName();
            String deviceHardwareAddress = device.getAddress(); // MAC address
            btArrayAdapter.add(deviceName);
            deviceAddressArray.add(deviceHardwareAddress);
            btArrayAdapter.notifyDataSetChanged();
        }
    }
};

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

	// Don't forget to unregister the ACTION_FOUND receiver.
    unregisterReceiver(receiver);
}

search 버튼을 누르면, 주변 블루투스 기기 목록이 뜹니다.

 

5. 통신

이제 블루투스 기기와 통신을 할 차례입니다. 이 부분부터는 구글에서 준 공식문서를 이해하지 못해서 Android-Simple-Bluetooth-Example 코드를 참고해서 작성했습니다. 말이 참고지 거의 그대로 가져왔네요.

 

listview에서 클릭한 기기와 블루투스 연결을 시도하도록 만들었습니다.

그러려면 먼저 listview에 클릭이벤트가 있어야합니다.

onCreate()내에 다음 코드를 넣어주세요.

listView.setOnItemClickListener(new myOnItemClickListener());

그리고 myOnItemClickListener()는 아래와 같이 선언해줬습니다.

 public class myOnItemClickListener implements AdapterView.OnItemClickListener {

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Toast.makeText(getApplicationContext(), btArrayAdapter.getItem(position), Toast.LENGTH_SHORT).show();

            textStatus.setText("try...");

            final String name = btArrayAdapter.getItem(position); // get name
            final String address = deviceAddressArray.get(position); // get address
            boolean flag = true;

            BluetoothDevice device = btAdapter.getRemoteDevice(address);

            // create & connect socket
            try {
                btSocket = createBluetoothSocket(device);
                btSocket.connect();
            } catch (IOException e) {
                flag = false;
                textStatus.setText("connection failed!");
                e.printStackTrace();
            }

            if(flag){
                textStatus.setText("connected to "+name);
                connectedThread = new ConnectedThread(btSocket);
                connectedThread.start();
            }

        }
    }

 

간단하게 풀이하자면, 먼저 선택된 기기의 이름과 address를 가져옵니다.

address로 BluetoothDevice를 만들고, 그 기기와의 소켓을 만들고 연결을 시도합니다.

socket이 정상적으로 만들어지고 연결이 되었다면 flag가 true입니다. 그러면 thread를 통해서 블루투스 통신을 시작할겁니다. 자세한건connectedThread 코드를 열심히 읽어보시면 될 것 같습니다. 이 부분은 저도 코드를 가져온거라서 더 설명드리기 어렵네요.

 

connectedThread 선언을 위해서 ConnectedThread.java 파일 새로 만드시고 아래 코드 복붙해주세요.

public class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;

        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
        }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    @Override
    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()
        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.available();
                if (bytes != 0) {
                    buffer = new byte[1024];
                    SystemClock.sleep(100); //pause and wait for rest of data. Adjust this depending on your sending speed.
                    bytes = mmInStream.available(); // how many bytes are ready to be read?
                    bytes = mmInStream.read(buffer, 0, bytes); // record how many bytes we actually read
                }
            } catch (IOException e) {
                e.printStackTrace();

                break;
            }
        }
    }

    /* Call this from the main activity to send data to the remote device */
    public void write(String input) {
        byte[] bytes = input.getBytes();           //converts entered String into bytes
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) {
        }
    }

    /* Call this from the main activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) {
        }
    }
}

 

기기를 클릭했을 때 textStatus에 try...라는 문구로 변경됩니다.

연결이 성공했다면 connected to 디바이스 이름으로 변경될거고, 연결에 실패했다면 connection failed로 변경됩니다.

 

6. 블루투스 통신으로 문자열 보내기

이제 send a버튼을 눌렀을 때 기기에 a를 송신해보겠습니다.

ConnectedThread가 잘 구현되어있어서 한 줄로 구현이 가능합니다.

public void onClickButtonSend(View view){
	if(connectedThread!=null){ connectedThread.write("a"); }
}

 

참 쉽죠?

 

제가 가지고 있는 아두이노&HC-06모듈과 테스트해봤습니다.

아두이노에 올라간 코드는 이전 포스팅에 사용한 코드와 똑같습니다.

 

Arduino 기기를 클릭하고 연결이 잘 되어서 Connected to Arduino 문구가 떴습니다.

 

실행 화면

그 때 Send A 버튼을 누르니 아두이노 시리얼모니터에 a라는 문구가 잘 찍혔습니다.

 

 

프로젝트 src만 압축한 파일입니다.

src.zip
0.09MB

GitHub에 올릴까 했지만, 현재 원하는 기능 구현을 위해 Custom을 하고 있는 상태라서 통신 구현을 위한 예제로 보여드리기에는 적합하지 않다고 생각해 압축파일만 올리게 되었습니다. 

 

@안드로이드 스튜디오 4.0.1

@targetSdk 30

@아두이노 HC-06

@갤럭시 S8

에서 직접 테스트하고 동작하는 코드를 올렸습니다.

문제가 발생하거나, 코드가 동작하지 않는다면 댓글/메일로 알려주세요.

 

더 쉽게 포스팅을 하기위해 코드를 많이 갈아엎었습니다. 이번 포스팅 하나에 삼일정도 걸렸습니다ㅠ_ㅜ 도움이 되었다면 공감과 댓글부탁드립니다 :)

+ 포스팅의 코드 들여쓰기가 이상합니다. 불편하신 분들도 src파일 보면서 참고해주세요.

+ 티스토리 코드블럭 들여쓰기 문제 관련해서 아시는 분도 댓글 부탁드립니다😭


잘못된 내용이 있다면 언제든지 댓글이나 메일로 알려주시면 감사하겠습니다.

이 포스팅이 도움이 되었다면 공감 부탁드립니다.

궁금한 점은 언제든지 댓글 남겨주시면 답변해드리겠습니다:D