안드로이드와 아두이노 블루투스 모듈 HC-06간의 소켓 통신 코드 리뷰.

해커톤 프로젝트를 간단히 요약하면 안드로이드 통신 기능을 포함하는 모듈화 스마트 화분이었다. 사용자의 입력에 따라 화분의 조도, 습도량을 실시간으로 측정하여 모바일 디바이스로 출력해야 하기 때문에 측정값을 보내는 아두이노와 측정값을 받아 출력하는 안드로이드 디바이스 간의 소켓 통신의 구현이 필요했다.

실제 개발 과정

  • 안드로이드 UI 구현 (생략)
  • 블루투스 소켓 통신 구현.
  • 수신된 데이터를 화면을 통해 출력한다.

블루투스 소켓 통신 구현.

대부분의 시간을 할애한 과정이다. 중간에 모듈이 고장나 제대로 된 코드를 작성하였음에도 불구하고 삽질아닌 삽질을한 과정이기도 하다. 크게 과정을 다시 나눠보면

  • 연결가능한 장치를 나열하고 해당 장치와의 연결 시도.(selectDevice())
  • 소켓을 생성하고 측정값 수신을 기다린다.(connectToSelectdDevice())

1. 연결가능한 장치를 나열하고 해당 장치와의 연결 시도

if (mBluetoothAdapter == null) {
          //장치가 블루투스를 지원하지 않는 경우.
        } else {
            // 장치가 블루투스를 지원하는 경우.
            Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
            if (pairedDevices.size() > 0) {
                // 페어링 된 장치가 있는 경우.
                selectDevice();
            } else {
                // 페어링 된 장치가 없는 경우.
            }
        }
void selectDevice() {
        mDevices = mBluetoothAdapter.getBondedDevices();
        mPairedDeviceCount = mDevices.size();

        if (mPairedDeviceCount == 0) {
            //  페어링 된 장치가 없는 경우
            finish();    // 어플리케이션 종료
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("블루투스 장치 선택");


        // 페어링 된 블루투스 장치의 이름 목록 작성
        List<String> listItems = new ArrayList<String>();
        for (BluetoothDevice device : mDevices)
            listItems.add(device.getName());

        listItems.add("취소");    // 취소 항목 추가

        final CharSequence[] items = listItems.toArray(new CharSequence[listItems.size()]);

        builder.setItems(items, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                if (item == mPairedDeviceCount) {
                    // 연결할 장치를 선택하지 않고 '취소'를 누른 경우
                    finish();
                } else {
                    // 연결할 장치를 선택한 경우
                    // 선택한 장치와 연결을 시도함
                    connectToSelectdDevice(items[item].toString());
                }
            }
        });

        builder.setCancelable(false);    // 뒤로 가기 버튼 사용 금지
        AlertDialog alert = builder.create();
        alert.show();
    }

Application을 켜자 마자 페어링된 모든 블루투스를 검색하여 선택하도록 출력하게 하는 코드이다. HC-06 모듈과의 연결이 성공하면 선택한 장치와의 연결을 시도하고, 취소를 누르면 Application을 종료한다.


2. 소켓을 생성하고 측정값 수신을 기다린다.

void connectToSelectdDevice(String selectedDeviceName) {
        mRemoteDevice = getDeviceFromBondedList(selectedDeviceName);
        // HC-06 블루투스 통신을 위한 UUID
        UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

        try {
            // 소켓 생성
            mSocket = mRemoteDevice.createRfcommSocketToServiceRecord(uuid);
            // RFCOMM 채널을 통한 연결
            mSocket.connect();

            // 데이터 송수신을 위한 스트림 열기
            mOutputStream = mSocket.getOutputStream();
            mInputStream = mSocket.getInputStream();
            // 데이터 수신 준비
            beginListenForData();
        } catch (Exception e) {
            // 블루투스 연결 중 오류 발생
            Log.e("err", String.valueOf(e));
            finish();   // 어플 종료
        }
    }

BluetoothDevice getDeviceFromBondedList(String name) {
        BluetoothDevice selectedDevice = null;

        for (BluetoothDevice device : mDevices) {
            if (name.equals(device.getName())) {
                selectedDevice = device;
                break;
            }
        }

        return selectedDevice;
    }

소켓을 생성한 뒤 통신에 필요한 스트림을 생성한다.


수신된 데이터를 화면을 통해 출력한다.

void beginListenForData() {
        final Handler handler = new Handler();
        readBuffer = new byte[1024];  //  수신 버퍼
        readBufferPosition = 0;        //   버퍼 내 수신 문자 저장 위치

        // 문자열 수신 쓰레드
        mWorkerThread = new Thread(new Runnable() {
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    try {
                        int bytesAvailable = mInputStream.available();    // 수신 데이터 확인
                        if (bytesAvailable > 0) {                     // 데이터가 수신된 경우
                            byte[] packetBytes = new byte[bytesAvailable];
                            mInputStream.read(packetBytes);
                            for (int i = 0; i < bytesAvailable; i++) {
                                byte b = packetBytes[i];
                                if (b == mDelimiter) {
                                    byte[] encodedBytes = new byte[readBufferPosition];
                                    System.arraycopy(readBuffer, 0, encodedBytes, 0, encodedBytes.length);
                                    final String data = new String(encodedBytes, "US-ASCII");
                                    readBufferPosition = 0;

                                    handler.post(new Runnable() {
                                        public void run() {
                                            // 수신된 문자열 데이터에 대한 처리 작업
                                            Log.e("data", data);
                                            result += data;
                                        }
                                    });
                                } else {
                                    readBuffer[readBufferPosition++] = b;
                                }
                            }
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    numbertext.setText(result);
                                    result = "";
                                }
                            });
                        }
                    } catch (IOException ex) {
                        // 데이터 수신 중 오류 발생.
                        finish();
                    }
                }
            }
        });
        mWorkerThread.start();
    }

소켓을 통해 저장된 데이터를 안드로이드 화면으로 출력한다. result의 값은 아두이노 측정 delay값에 따라 실시간으로 변하기 때문에 출력한 뒤 초기화한다.