안드로이드 - 아두이노 블루투스 통신 기본에 대해서 질문드립니다!

조회수 2534회

package com.example.ehdrb.test; // 1.JAVA I/O중 바이트 스트림에 관련된 최상위 클래스인 InputStream, OutputStream (영문1,한글 2바이트)

import java.io.InputStream;import java.io.OutputStream; /** 2. JAVA 에서는 배열보다는 Util 패키지의 List,Set,Map 인터페이스를 주요 사용한다. 배열은 같은 타입만 저장 가능하지만, 위의 인터페이스는 서로 다른 타입을 같은 List 안에 저장할 수 있다 */

import java.util.ArrayList; import java.util.List;import java.util.Set; import java.util.UUID; // 3. UUID : Universally Unique IDentifier, 범용 고유 실별자.import java.util.UUID;

import android.app.Activity; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast;

public class MainActivity extends Activity { // 사용자 정의 함수로 블루투스 활성 상태의 변경 결과를 App으로 알려줄때 식별자로 사용됨 (0보다 커야함) static final int REQUEST_ENABLE_BT = 10; int mPariedDeviceCount = 0; Set mDevices; // 폰의 블루투스 모듈을 사용하기 위한 오브젝트. BluetoothAdapter mBluetoothAdapter; /** BluetoothDevice 로 기기의 장치정보를 알아낼 수 있는 자세한 메소드 및 상태값을 알아낼 수 있다. 연결하고자 하는 다른 블루투스 기기의 이름, 주소, 연결 상태 등의 정보를 조회할 수 있는 클래스. 현재 기기가 아닌 다른 블루투스 기기와의 연결 및 정보를 알아낼 때 사용. */ BluetoothDevice mRemoteDevice; // 스마트폰과 페어링 된 디바이스간 통신 채널에 대응 하는 BluetoothSocket BluetoothSocket mSocket = null; OutputStream mOutputStream = null; InputStream mInputStream = null; String mStrDelimiter = "\n"; char mCharDelimiter = '\n';

Thread mWorkerThread = null;
byte[] readBuffer;
int readBufferPosition;


EditText mEditReceive, mEditSend;
Button mButtonSend;



@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mEditReceive = (EditText)findViewById(R.id.receiveString);
    mEditSend = (EditText)findViewById(R.id.sendString);
    mButtonSend = (Button)findViewById(R.id.sendButton);

    mButtonSend.setOnClickListener(new OnClickListener(){

        @Override
        public void onClick(View v) {
            // 문자열 전송하는 함수(쓰레드 사용 x)
            sendData(mEditSend.getText().toString());
            mEditSend.setText("");
        }
    });

    // 블루투스 활성화 시키는 메소드
    checkBluetooth();
}


// 블루투스 장치의 이름이 주어졌을때 해당 블루투스 장치 객체를 페어링 된 장치 목록에서 찾아내는 코드.
BluetoothDevice getDeviceFromBondedList(String name) {
    // BluetoothDevice : 페어링 된 기기 목록을 얻어옴.
    BluetoothDevice selectedDevice = null;
    // getBondedDevices 함수가 반환하는 페어링 된 기기 목록은 Set 형식이며,
    // Set 형식에서는 n 번째 원소를 얻어오는 방법이 없으므로 주어진 이름과 비교해서 찾는다.
    for(BluetoothDevice deivce : mDevices) {
        // getName() : 단말기의 Bluetooth Adapter 이름을 반환
        Class device;
        if(name.equals(device.getName())) {
            selectedDevice = deivce;
            break;
        }
    }
    return selectedDevice;
}

// 문자열 전송하는 함수(쓰레드 사용 x)
void sendData(String msg) {
    msg += mStrDelimiter;  // 문자열 종료표시 (\n)
    try{
        // getBytes() : String을 byte로 변환
        // OutputStream.write : 데이터를 쓸때는 write(byte[]) 메소드를 사용함. byte[] 안에 있는 데이터를 한번에 기록해 준다.
        mOutputStream.write(msg.getBytes());  // 문자열 전송.
    }catch(Exception e) {  // 문자열 전송 도중 오류가 발생한 경우
        Toast.makeText(getApplicationContext(), "데이터 전송중 오류가 발생", Toast.LENGTH_LONG).show();
        finish();  // App 종료
    }
}

//  connectToSelectedDevice() : 원격 장치와 연결하는 과정을 나타냄.
//        실제 데이터 송수신을 위해서는 소켓으로부터 입출력 스트림을 얻고 입출력 스트림을 이용하여 이루어 진다.
void connectToSelectedDevice(String selectedDeviceName) {
    // BluetoothDevice 원격 블루투스 기기를 나타냄.
    mRemoteDevice = getDeviceFromBondedList(selectedDeviceName);
    // java.util.UUID.fromString : 자바에서 중복되지 않는 Unique 키 생성.
    UUID uuid = java.util.UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");

    try {
        // 소켓 생성, RFCOMM 채널을 통한 연결.
        // createRfcommSocketToServiceRecord(uuid) : 이 함수를 사용하여 원격 블루투스 장치와 통신할 수 있는 소켓을 생성함.
        // 이 메소드가 성공하면 스마트폰과 페어링 된 디바이스간 통신 채널에 대응하는 BluetoothSocket 오브젝트를 리턴함.
        mSocket = mRemoteDevice.createRfcommSocketToServiceRecord(uuid);
        mSocket.connect(); // 소켓이 생성 되면 connect() 함수를 호출함으로써 두기기의 연결은 완료된다.

        // 데이터 송수신을 위한 스트림 얻기.
        // BluetoothSocket 오브젝트는 두개의 Stream을 제공한다.
        // 1. 데이터를 보내기 위한 OutputStrem
        // 2. 데이터를 받기 위한 InputStream
        mOutputStream = mSocket.getOutputStream();
        mInputStream = mSocket.getInputStream();

        // 데이터 수신 준비.
        beginListenForData();

    }catch(Exception e) { // 블루투스 연결 중 오류 발생
        Toast.makeText(getApplicationContext(), "블루투스 연결 중 오류가 발생했습니다.", Toast.LENGTH_LONG).show();
        finish();  // App 종료
    }
}

// 데이터 수신(쓰레드 사용 수신된 메시지를 계속 검사함)
void beginListenForData() {
    final Handler handler = new Handler();

    readBufferPosition = 0;                 // 버퍼 내 수신 문자 저장 위치.
    readBuffer = new byte[1024];            // 수신 버퍼.

    // 문자열 수신 쓰레드.
    mWorkerThread = new Thread(new Runnable()
    {
        @Override
        public void run() {
            // interrupt() 메소드를 이용 스레드를 종료시키는 예제이다.
            // interrupt() 메소드는 하던 일을 멈추는 메소드이다.
            // isInterrupted() 메소드를 사용하여 멈추었을 경우 반복문을 나가서 스레드가 종료하게 된다.
            while(!Thread.currentThread().isInterrupted()) {
                try {
                    // InputStream.available() : 다른 스레드에서 blocking 하기 전까지 읽은 수 있는 문자열 개수를 반환함.
                    int byteAvailable = mInputStream.available();   // 수신 데이터 확인
                    if(byteAvailable > 0) {                        // 데이터가 수신된 경우.
                        byte[] packetBytes = new byte[byteAvailable];
                        // read(buf[]) : 입력스트림에서 buf[] 크기만큼 읽어서 저장 없을 경우에 -1 리턴.
                        mInputStream.read(packetBytes);
                        for(int i=0; i<byteAvailable; i++) {
                            byte b = packetBytes[i];
                            if(b == mCharDelimiter) {
                                byte[] encodedBytes = new byte[readBufferPosition];
                                //  System.arraycopy(복사할 배열, 복사시작점, 복사된 배열, 붙이기 시작점, 복사할 개수)
                                //  readBuffer 배열을 처음 부터 끝까지 encodedBytes 배열로 복사.
                                System.arraycopy(readBuffer, 0, encodedBytes, 0, encodedBytes.length);

                                final String data = new String(encodedBytes, "US-ASCII");
                                readBufferPosition = 0;

                                handler.post(new Runnable(){
                                    // 수신된 문자열 데이터에 대한 처리.
                                    @Override
                                    public void run() {
                                        // mStrDelimiter = '\n';
                                        mEditReceive.setText(mEditReceive.getText().toString() + data+ mStrDelimiter);
                                    }

                                });
                            }
                            else {
                                readBuffer[readBufferPosition++] = b;
                            }
                        }
                    }

                } catch (Exception e) {    // 데이터 수신 중 오류 발생.
                    Toast.makeText(getApplicationContext(), "데이터 수신 중 오류가 발생 했습니다.", Toast.LENGTH_LONG).show();
                    finish();            // App 종료.
                }
            }
        }

    });

}

// 블루투스 지원하며 활성 상태인 경우.
void selectDevice() {
    // 블루투스 디바이스는 연결해서 사용하기 전에 먼저 페어링 되어야만 한다
    // getBondedDevices() : 페어링된 장치 목록 얻어오는 함수.
    mDevices = mBluetoothAdapter.getBondedDevices();
    mPariedDeviceCount = mDevices.size();

    if(mPariedDeviceCount == 0 ) { // 페어링된 장치가 없는 경우.
        Toast.makeText(getApplicationContext(), "페어링된 장치가 없습니다.", Toast.LENGTH_LONG).show();
        finish(); // App 종료.
    }
    // 페어링된 장치가 있는 경우.
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle("블루투스 장치 선택");

    // 각 디바이스는 이름과(서로 다른) 주소를 가진다. 페어링 된 디바이스들을 표시한다.
    List<String> listItems = new ArrayList<String>();
    for(BluetoothDevice device : mDevices) {
        // device.getName() : 단말기의 Bluetooth Adapter 이름을 반환.
        listItems.add(device.getName());
    }
    listItems.add("취소");  // 취소 항목 추가.


    // CharSequence : 변경 가능한 문자열.
    // toArray : List형태로 넘어온것 배열로 바꿔서 처리하기 위한 toArray() 함수.
    final CharSequence[] items = listItems.toArray(new CharSequence[listItems.size()]);
    // toArray 함수를 이용해서 size만큼 배열이 생성 되었다.
    listItems.toArray(new CharSequence[listItems.size()]);

    builder.setItems(items, new DialogInterface.OnClickListener() {

        @Override
        public void onClick(DialogInterface dialog, int item) {
            // TODO Auto-generated method stub
            if(item == mPariedDeviceCount) { // 연결할 장치를 선택하지 않고 '취소' 를 누른 경우.
                Toast.makeText(getApplicationContext(), "연결할 장치를 선택하지 않았습니다.", Toast.LENGTH_LONG).show();
                finish();
            }
            else { // 연결할 장치를 선택한 경우, 선택한 장치와 연결을 시도함.
                connectToSelectedDevice(items[item].toString());
            }
        }

    });

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


void checkBluetooth() {
    /**
     * getDefaultAdapter() : 만일 폰에 블루투스 모듈이 없으면 null 을 리턴한다.
     이경우 Toast를 사용해 에러메시지를 표시하고 앱을 종료한다.
     */
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if(mBluetoothAdapter == null ) {  // 블루투스 미지원
        Toast.makeText(getApplicationContext(), "기기가 블루투스를 지원하지 않습니다.", Toast.LENGTH_LONG).show();
        finish();  // 앱종료
    }
    else { // 블루투스 지원
        /** isEnable() : 블루투스 모듈이 활성화 되었는지 확인.
         *               true : 지원 ,  false : 미지원
         */
        if(!mBluetoothAdapter.isEnabled()) { // 블루투스 지원하며 비활성 상태인 경우.
            Toast.makeText(getApplicationContext(), "현재 블루투스가 비활성 상태입니다.", Toast.LENGTH_LONG).show();
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            // REQUEST_ENABLE_BT : 블루투스 활성 상태의 변경 결과를 App 으로 알려줄 때 식별자로 사용(0이상)
            /**
             startActivityForResult 함수 호출후 다이얼로그가 나타남
             "예" 를 선택하면 시스템의 블루투스 장치를 활성화 시키고
             "아니오" 를 선택하면 비활성화 상태를 유지 한다.
             선택 결과는 onActivityResult 콜백 함수에서 확인할 수 있다.
             */
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
        else // 블루투스 지원하며 활성 상태인 경우.
            selectDevice();
    }
}



// onDestroy() : 어플이 종료될때 호출 되는 함수.
//               블루투스 연결이 필요하지 않는 경우 입출력 스트림 소켓을 닫아줌.
@Override
protected void onDestroy() {
    try{
        mWorkerThread.interrupt(); // 데이터 수신 쓰레드 종료
        mInputStream.close();
        mSocket.close();
    }catch(Exception e){}
    super.onDestroy();
}


// onActivityResult : 사용자의 선택결과 확인 (아니오, 예)
// RESULT_OK: 블루투스가 활성화 상태로 변경된 경우. "예"
// RESULT_CANCELED : 오류나 사용자의 "아니오" 선택으로 비활성 상태로 남아 있는 경우  RESULT_CANCELED

/**
 사용자가 request를 허가(또는 거부)하면 안드로이드 앱의 onActivityResult 메소도를 호출해서 request의 허가/거부를 확인할수 있다.
 첫번째 requestCode : startActivityForResult 에서 사용했던 요청 코드. REQUEST_ENABLE_BT 값
 두번째 resultCode  : 종료된 액티비티가 setReuslt로 지정한 결과 코드. RESULT_OK, RESULT_CANCELED 값중 하나가 들어감.
 세번째 data        : 종료된 액티비티가 인테트를 첨부했을 경우, 그 인텐트가 들어있고 첨부하지 않으면 null
 */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // startActivityForResult 를 여러번 사용할 땐 이런 식으로 switch 문을 사용하여 어떤 요청인지 구분하여 사용함.
    switch(requestCode) {
        case REQUEST_ENABLE_BT:
            if(resultCode == RESULT_OK) { // 블루투스 활성화 상태
                selectDevice();
            }
            else if(resultCode == RESULT_CANCELED) { // 블루투스 비활성화 상태 (종료)
                Toast.makeText(getApplicationContext(), "블루투수를 사용할 수 없어 프로그램을 종료합니다", Toast.LENGTH_LONG).show();
                finish();
            }
            break;
    }
    super.onActivityResult(requestCode, resultCode, data);

이러한 코드로 작성했는데 중간에 edit text와 button 부분에 오류가 발생합니다.. 이거 해결 방법을 좀 알고싶습니다!

  • (•́ ✖ •̀)
    알 수 없는 사용자

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)