2014년 10월 11일 토요일

mbed 시작하기

http://www.mbed.org 사이트에 접속


우측 상단의 Developer Site 를 클릭


 우측 상단의 Login or signup을 클릭


Signup 을 선택해서 어카운트를 생성



로그인 한 후 우측 상단의 Compiler를 클릭


자신이 가지고 있는 디바이스를 선택해야 함. 우측 상단의 'No device selected'를 클릭


'Add a device' 를 클릭


mbed 호환 플랫폼 목록이 나오고 이 중에 자신이 가지고 있는 보드를 선택


보드를 선택하면 브라우저에 해당 보드에 대한 자세한 설명이 나옴. 'Add to your mbed Compiler'를 클릭


플랫폼이 추가되었음. 추가한 플랫폼을 선택하고 'Select Platform'을 클릭



우측 상단에서 방금 선택한 플랫폼 이름을 볼 수 있음. 좌측 상단의 'New'를 클릭


새 프로젝트 생성을 위한 윈도우가 열림. 'OK'를 클릭


'main.cpp'를 클릭하면 우측에 소스코드가 나타남


'Compile' 버튼을 클릭해서 프로그램을 컴파일


에러 없이 컴파일이 정상적으로 끝나면 실행 가능한 바이너리 파일이 자동으로 다운로드 됨. 파일 이름은 [프로그램 이름]+'_'+[플랫폼 이름]+'.bin'이 됨. 여기서 프로그램 이름이 'mbed_blinky'이고 플랫폼이 'LPC1768'이므로 파일 이름이 'mbed_blinky_LPC1768.bin'임

보드를 PC에 연결하면 보드는 외장하드로 인식됨


다운로드 된 바이너리 파일을 드래그해서 외장하드로 복사함. 복사가 완료되면 보드의 RESET 버튼을 눌러주면 바이너리 파일이 보드의 플래쉬에 기록되고 나서 바로 실행됨













2014년 6월 4일 수요일

Bluetooth Programming in Android (2/2)

이전 포스트(Bluetooth Programming in Android (1/2))에 이어 이제부터는 실제로 블루투스 디바이스와 연결해 데이터를 보내는 방법에 대해 이야기하겠다.

Profile and UUID


블루투스 표준은 블루투스 디바이스들이 제공하는 기능들에 대한 몇가지 프로파일을 정의하고 있다.

간단하게 말하자면 블루투스 프로파일은 디바이스가 무엇을 할 수 있는가에 대응한다. 예를 들어 블루투스 헤드셋은 오디오 스트림을 어떻게 주고 받고, 페어링 된 휴대폰에 기본적인 명령(응답, 볼륨 조절 등)을 어떻게 보내는가를 정의한 HSP(Handset Service Profile)을 구현한다. 일부 고급 헤드셋은 A2DP(Advanced Audio Distribution Profile)도 구현해서 사용자가 휴대폰에서 스트리밍하는 음악도 고음질 스테레오로 들을 수 있게 해 준다.



연결을 설정할 때, 연결을 시작하는 디바이스는 SDP(Service Discovery Protocol) 프로토콜을 사용해서 상대방 디바이스가 어떤 서비스를 제공하는가, 즉 어떤 프로파일을 구현했는가를 알아낼 수도 있다.

각 서비스는 128비트 숫자 식별자(UUID)를 사용해 정의되어 있다. 일반적으로는 이 식별자의 짧은 형태가 사용된다.

  • 0x00000000-0000-1000-8000-00805f9b34fb같은 128비트 베이스 UUID가 정의되어 있다.
  • 서비스 UUID의 짧은 형태는 앞쪽의 8개 0을 대치한다.
  • 이것이 완전한 서비스 UUID이다.
예를 들어 HSP 서비스의 짧은 형태 UUID는 0x1108이므로 완전한 UUID는 0x00001108-0000-1000-8000-00805f9b34fb가 된다.

SPP


가장 간단하고 임베디드 디바이스와 통신하는데 가장 많이 사용되는 것이 Serial Port Profile(SPP)로 짧은 UUID는 0x1101이다.

이 프로파일은 두 디바이스간 시리얼 링크를 에뮬레이션 한다.

Android


이전 예제에서 스마트폰에 페어링 된 디바이스들을 어떻게 나열하는가를 배웠다. 각 디바이스는 BluetoothDevice 오브젝트의 인스턴스에 대응한다. 이 오브젝트는 통신채널을 열기 위해 두 가지 메소드를 제공한다.

  • createRfcommSocketToServiceRecord(UUID)
  • createInsecureRfcommSocketToServiceRecord(UUID)
두 메소드는 프로파일의 UUID를 요구하고, 첫번째 메소드는 암호화 된 커넥션을 만드는것만 다르다.
메소드가 성공하면 스마트폰과 페어링 된 디바이스간 통신 채널에 대응하는 BluetoothSocket 오브젝트를 리턴한다.

SPP 프로파일을 구현한 디바이스에 어떻게 데이터를 보내는 지 보도록 하자.

먼저 프로파일의 UUID를 정의한다.

UUID SPP_UUID = java.util.UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");

그리고 나서 디바이스와의 통신 채널을 가져온다.

BluetoothSocket btSocket = null;
try {
  btSocket = targetDevice.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
} catch (IOException e) {
  Toast.makeText(this, "Unable to open a serial socket with the device", Toast.LENGTH_SHORT).show();
}

이 지점은 아직 채널이 열리지 않았으니 connect() 메소드를 사용해 디바이스와 연결한다.

 try {
  btSocket.connect();
} catch (IOException e) {
  Toast.makeText(this, "Unable to connect to the device", Toast.LENGTH_SHORT).show();
}

연결되면 BluetoothSocket 오브젝트는 두 개의 Stream을 제공한다. 하나는 데이터를 보내기 위한 것(OutputStream)이고 나머지는 데이터를 받기 위한 스트림(InputStream)이다. 편하게 사용하기 위해 스트림을 통해 글자들을 쉽게 보낼 수 있게 해 주는 OutputStreamWriter 오브젝트를 사용할 수 있다.

try {
  OutputStreamWriter writer = new OutputStreamWriter(btSocket.getOutputStream());
  writer.write("Hello World!\r\n");
  writer.flush();
} catch (IOException e) {
  Toast.makeText(this, "Unable to send message to the device.", Toast.LENGTH_SHORT).show();
}

StreamWriter는 로컬 버퍼를 가지고 있다. 모든 데이터가 보내졌는지 확실하게 하기 위해 마지막에 flush()를 호출하는걸 잊지 말자.

마지막에 소켓을 닫는걸 잊으면 안된다.

try {
  btSocket.close();
} catch (IOException e) {
  Toast.makeText(this, "Unable to close the connection to the device", Toast.LENGTH_SHORT).show();
}

Say hello to...


이 앱은 첫번째 코드를 확장했다. 소스코드는 역시 저자의 github에서 다운받을 수 있다.
  • 폰에 페어링 된 디바이스를 가져온다.
  • 한 디바이스를 클릭하면 SPP를 사용해 연결한 다음 "Hello World!" 문자열을 보낸다.

앱을 테스트 해 보려면 블루투스가 장착된 PC가 필요하고 incoming connection을 허용하도록 설정 해 줘야 한다.

윈도우 트레이에 있는 블루투스 아이콘 위에서 마우스 오른쪽 버튼을 클릭한 다음 Open Settings를 선택한다.


COM Ports 탭을 선택한 다음 Add...을 클릭한다.


Incoming을 선택한다.


새 시리얼 포트의 이름을 기록해 둔다. 여기서는 COM56 이다.


터미널 에뮬레이터(여기서는 PuTTY를 사용)를 사용해서 시리얼 포트에 연결한다.


모든게 문제 없이 잘 되었으면 스마트폰에서 앱을 실행하고 PC를 클릭하면 앱이 블루투스를 통해 메시지를 보내고 전송 확인 메시지가 표시된다.

그리고 PC를 확인해 보면 전송된 메시지가 화면에 표시된다.





위의 앱을 테스트 해 보면 한가지 작은 문제점을 발견할 수 있었을 것이다. 커넥션이 연결되고 “Hello World” 메시지가 보내질 때 까지 앱의 GUI가 응답하지 않는다. 이유는 간단하다. 대부분의 메소드는 “blocking”을 사용한다. 즉 결과를 얻을 때 까지 (또는 타임아웃이 될 때 까지) 프로세스의 실행을 중단한다.

데이터를 예를 들어 만일 소켓에서 데이터를 가져오기 위해 read() 메소드를 호출하면 읽어 올 데이터가 들어올 때 까지 메소드는 실행을 중단한다.

이 문제를 해결하기 위해서 멀티태스킹 앱, 즉 앱이 다른 프로세스를 가지고 있어 각각이 서로 독립적으로 실행되는 앱을 작성하는 법을 배워야만 한다. 


스레드와 GUI


간단하게 하기 위해 앱은 한개 또는 그 이상의 프로세스(스레드)로 구성될 수 있고, 이 프로세스들은 안드로이드 OS에 의해 병렬로 실행된다. 이전 예제와 같이 간단한 앱은 메인 스레드라 불리는 하나의 스레드만 가지고 있다. 이 스레드는 앱의 GUI를 구성하는 컴포넌트들(텍스트 박스, 이미지, 버튼 등)을 관리한다.

멀티스레드 앱을 작성할 때 고려해야만 하는 첫번째 규칙은 “메인 스레드만이 GUI를 업데이트 할 수 있다”는 것이다.


이 규칙은 종종 프로그래머를 골치아프게 만든다. 별도 스레드가 블루투스 소켓에서 데이터를 받는 때를 생각해보자. 일반적으로 명령을 받으면 그에 따라 GUI를 업데이트 해야 한다…

보통 권장되는 해결책은 메인스레드에게 GUI를 업데이트 해 주도록 요구하는 것이다. 여기서는 AsyncTask 오브젝트를 사용하는 다른 방법을 소개하겠다.

AsyncTask


AsyncTask 오브젝트는 안드로이드에 포함되어 백그라운드에서 실행되면서 앱의 GUI와 상호작용을 해야만 하는 태스크들을 쉽게 관리할 수 있게 해 준다.

장점으로는 일부 메소드는 GUI(메인) 스레드에서 실행되고 나머지는 독립된 전용 스레드에서 실행된다는 것이다.

그러므로 개발자는 인터페이스를 업데이트 하기 위해서는 GUI 스레드에서 실행되는 메소드를 사용하고, (예를 들어, 소켓을 통해 데이터를 전송/수신하는것 같이) 메인 스레드를 블럭하면 안되는 백그라운드 동작은 두번째 스레드에서 실행되게 할 수 있다.


메소드들을 좀 더 자세히 보도록 하겠다.

  • onPreExecution() - GUI - 백그라운드 액티비티를 시작하기 바로 전에 실행. 사용자에게 애니메이션이나 메시지 등으로 요구한 동작이 시작된다는 것을 알려주는데 사용할 수 있음
  • doInBackground() - background - 백그라운드 태스크를 수행하는 메인 메소드
  • publishProgress() - background - 보통 doInBackground()에서 호출되어 테스크가 실행되는 중에 “progress”를 알려주는데 사용
  • onProgressUpdate() - GUI -  pubilshProgress()에 의해 호출되 GUI가 실행의 “progress”를 업데이트 할 수 있게 함
  • onPostExecute() & onCancelled() - GUI -  태스크의 끝에 (테스크가 캔슬될 때) 실행되는 메소드 
  • Android application

이 튜토리얼을 위해 개발한 안드로이드 앱은 블루투스를 통해 데이터를 송수신하는 것이다.

소스코드는 저자의github에서 다운받을 수 있다.

  • 사용자가 툴바에 있는 버튼으로 디바이스와 연결/연결해제를 할 수 있음
  • 사용자가 두 버튼중에 하나를 클릭하면 각각 “BUTTON1”, “BUTTON2” 명령을 보냄
  • 수신한것을 TextView에 표시

블루투스를 통한 통신은 BTAsyncTask라는 AsyncTask를 사용해 수행된다. 이것이 어떻게 동작하는지 확인해보자.

Connection


사용자가 페어링 된 디바이스를 선택하면 앱은 그 디바이스로 소켓을 오픈하고 BTAsyncTask 오브젝트의 새 인스턴스를 만들어 BluetoothAdapter에서 얻은 소켓을 넘겨준다.

그리고 나면 앱은 BTAsyncTask의 doInBackground 메소드를 시작시키는 execute() 메소드를 호출해서, 페어링 된 디바이스에서 새 데이터가 들어오기를 기다린다.



Data In


BTAsyncTask가 새 데이터를 받으면 publishProgress()를 호출하면서 데이터를 넘겨준다. 위에서 본 것 처럼 publishProgress() 메소드는 내부적으로 GUI 스레드레서 실행되고 있는 onProgressUpdate() 메소드를 호출해 수신한 데이터를 가지고 GUI를 업데이트 할 수 있다. 



Data Out


사용자가 버튼을 클릭하면 메인 스레드는 BTAsyncTask의 sendCommand() 메소드를 호출해 해당되는 명령을 보낸다.



데모


안드로이드 앱에 응답하기 위한 간단한 .Net 어플리케이션을 만들었다. 다음은 앱이 어떻게 동작하는가를 보여주는 짧은 비디오이다.





* 이 글은 Luca Dentella의 튜토리얼 시리즈 Android e Bluetooth 를 저자의 승락을 받고 번역한 글입니다. 흔쾌히 허락해 준 저자 Luca에게 감사드리며...



Un enorme grazie a Luca~

Bluetooth Programming in Android (1/2)

안드로이드 앱에서 블루투스를 사용하려면 각각 기능에 해당하는 permission이 필요하다. Permission은 안드로이드에게 앱이 어떤 기능을 필요로 하는지 알려주는 역할을 한다. 앱을 설치할 때 어떤 permission을 필요로 하는지 알려주고 허가할 것인지 물어보는 창이 열리는걸 본 적이 있을 것이다.

앱이 어떤  permission을 사용하는지 알려주기 위해 필요한 작업은 다음과 같다.

먼저 프로젝트의 AndroidManifest.xml 파일을 연다.


이 파일을 열면 아래와 같은 화면이 나오는데 'Permissions' 탭을 선택한 다음 오른쪽의 Add... 버튼을 누른다.



버튼을 누르면 나오는 화면에서 Uses Permission을 선택한다.



그 다음 android.permissions.BLUETOOTH를 선택하고 파일을 저장한다.



BluetoothAdapter


BluetoothAdapter는 폰의 블루투스 모듈을 사용하기 위한 오브젝트이다.

스태틱 메소드인 getBluetoothAdapter()를 호출해서 오브젝트의 디폴트 인스턴스를 가져온다.

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

만일 폰에 블루투스 모듈이 없으면 null을 리턴한다.  이 경우 Toast를 사용해 에러메시지를 표시하고 앱을 종료한다.

Toast.makeText(this, "This app requires a bluetooth capable phone", Toast.LENGTH_SHORT).show();
finish();

블루투스 모듈에 대한 인스턴스를 가져온 다음 모듈이 활성화 되어 있는지 확인해야만 한다. 모듈 활성화 여부를 확인하는데 isEnabled() 메소드를 사용한다.

mBluetoothAdapter.isEnabled();

모듈이 활성화 되어 있지 않으면 Intent를 사용해 사용자에게 활성화 시키도록 요청할 수 있다. 먼저 request를 식별하기 위한 상수를 정의한 다음 새 Intent를 만들어 OS에 보낸다.

private final int REQUEST_ENABLE_BT = 1;
...
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

앱은 백그라운드로 들어가고 아래와 같이 사용자에게 앱이 블루투스를 활성화 시키도록 허가할 것인지 묻는 메시지 팝업이 열린다.



사용자가 request를 허가(또는 거부)하면 안드로이드는 앱의 onActivityResult 메소드를 호출해서 request의 허가/거부를 확인할 수 있다.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == REQUEST_ENABLE_BT)
    if (resultCode == RESULT_OK) listPairedDevices();

페어링 된 디바이스


첫번째 예제에서 앱은 페어링 된 디바이스를 화면에 표시한다. 블루투스 디바이스는 연결해서 사용하기 전에 먼저 페어링 되어야만 한다.

getBondedDevices() 메소드는 BluetoothDevice 오브젝트 집합을 리턴한다.

Set pairedDevices = mBluetoothAdapter.getBondedDevices();

각 디바이스는 이름과 (서로 다른) 주소를 가진다. 루프를 사용해 TextArea에 페어링 된 디바이스들을 표시한다.

for (BluetoothDevice pairedDevice : pairedDevices) {
  textView2.append(Html.fromHtml("<strong>" + pairedDevice.getName() + "</strong>"));
  textView2.append(" (" + pairedDevice.getAddress() + ")\n");
}

아래가 앱의 스크린 캡춰이다.



여기서 사용한 소스코드는 전부 저자의 github에서 다운받을 수 있다.

 Discovery 와 Permissions


새 블루투스 디바이스를 찾기 위한 discovery 기능을 사용하려면 새로운 permission(BLUETOOTH_ADMIN)이 필요하다.



새 검색을 시작하려면 BluetoothAdapter 오브젝트의 startDiscovery 메소드를 호출한다.

mBluetoothAdapter.startDiscovery();

이 메소드는 비동기 메소드이다. 즉 이 메소드를 호출하면 안드로이드에게 새 검색을 시작하라는 요청을 넘겨주고 바로 리턴한다. 새 디바이스가 발견되거나 discovery가 끝난걸 앱에게 알려주기 위해서는 BroadcastReceiver를 사용해야만 한다.

Receiver 와 IntentFilter


앱은 안드로이드에게 어떤 이벤트가 발생한 걸 통보해 달라고 요청할 수도 있다. 예를 들어 문자메시지가 오거나 배터리가 거의 없을 때 같은 경우가 될 수 있다. 이런 이벤트들은 broadcast 메시지를 사용해 통보된다. 그러므로 앱은 BroadcastReceiver 오브젝트를 통해 메시지를 받을 수 있다.

BroadcastReceiver 오브젝트가 인스턴스화 되고 나면 OS에게 어떤 notification을 받을건지 알려줘야만 한다. 받기를 원하는 각 이벤트마다 필터(IntentFilter)를 만든다. 그리고 registerReceiver() 메소드를 사용해 안드로이드가 IntentFilter에 지정되어 있는 이벤트를 주어진 BroadcastReceiver에게 통보하도록 요구한다.



이 예제에서 우리는 2개의 이벤트를 모니터링 할 필요가 있다.
  • Bluetooth.ACTION_FOUND - 새 디바이스가 발견될 때
  • Bluetooth.ACTION_DISCOVERY_FINISHED - discovery가 끝날 때
해당하는 IntentFilter를 만들어 receiver를 등록한다.

IntentFilter deviceFoundFilter = new IntentFilter(Bluetooth.ACTION_FOUND);
IntentFilter discoveryFinishedFilter = new IntentFilter(Bluetooth.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, deviceFoundFilter);
registerReceiver(mReceiver, discoveryFinishedFilter);

BraodcastReceiver는 onReceive() 메소드를 꼭 만들어줘야만 한다. 이 메소드는 새 이벤트가 발생한 걸 알려주기 위해 안드로이드에 의해 호출된다.

mReceiver = new BroadcastReceiver() {
  public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();

action 스트링을 보면 어떤 이벤트가 발생했는지 알 수 있다.

if (BluetoothDevice.ACTION_FOUND.equals(action)) {
  // show the new device
}
if (BluetoothDevice.ACTION_DISCOVERY_FINISHED).equals(action)) {
  // enable the SCAN button
}


이 예제를 위한 앱은 매우 간단하다. 버튼을 누르면 검색을 시작하고 장치가 발견되면 리스트에 장치의 이름과 주소를 추가한다. 역시 소스코드는 저자의 github에서 다운받으면 된다.


Detect a state change


안드로이드는 멀티태스킹 OS이다. 당신의 앱이 실행되는 동안 다른 앱이나 사용자가 블루투스 모듈의 상태를 바꿔 놓을수도 있다. 예를 들어 모듈을 비활성화 시켜 버릴 수 있다. 그렇기 때문에 안드로이드에게 모듈의 상태에 어떤 변화가 생기면 그걸 통보하도록 요청할 수 있다.

먼저 OS에서의 notification을 받을 BroadcastReceiver를 만든다.

mReceiver = new BroadcastReceiver() {

이 receiver의 onReceive() 메소드에서 들어 온 메시지가 블루투스 모듈의 상태변화에 관한 것인지 확인한다.

public void onReceive(Context context, Intent intent) {
  String action = intent.getAction();
  if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {

상태변화에 관한 것이 맞다면, Intent의 extra parameter에서 변화된 현 상태를 가져올 수 있다. state는 int값이므로 getIntExtra 메소드를 사용해야만 하고 메소드를 호출할 때 파라메타의 이름과 디폴트값을 지정해주면 된다. 디폴트값은 지정한 이름의 파라메타가 없을 때 리턴할 값이다.

int actualState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);

그리고 receiver를 등록하는걸 잊으면 안된다.

IntentFilter stateChangedFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mReceiver, stateChangedFilter);

블루투스 모듈을 위한 4개의 상태가 정의되어 있다.

  • BluetoothAdapter.STATE_ON
  • BluetoothAdapter.STATE_OFF
  • BluetoothAdapter.STATE_TURNING_ON
  • BluetoothAdapter.STATE_TURNING_OFF
위에 설명한 걸 정리한 앱은 다음과 같다. 앱은 화면에 텍스트와 아이콘으로 현재 상태를 나타내고 상태가 변경될 때 마다 내용을 자동으로 업데이트 한다. 아래 데모 앱의 소스는 저자의 github에서 다운받으면 된다.







* 이 글은 Luca Dentella의 튜토리얼 시리즈 Android e Bluetooth 를 저자의 승락을 받고 번역한 글입니다. 흔쾌히 허락해 준 저자 Luca에게 감사드리며...



Un enorme grazie a Luca~