본문 바로가기

iOS/Xcode

[스크랩] iOS와 블루투스 프로그래밍

출처 : http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=36787

iOS와 블루투스 프로그래밍

iOS에서 다른 애플 디바이스나 외부 장치 등과 인터페이싱하기 위해 블루투스를 사용한다. 블루투스는 비교적 근거리에 있는 장치들을 선 없이 연결해 명령이나 메시지 등을 주고 받거나 서로를 모니터링하도록 할 수 있는 기술이다. 최초의 아이폰에서는 전화를 걸거나 받을 수 있는 용도로만 제공됐지만(v2.0) 아이폰4에서는 데이터 교환도 가능한 블루투스 v2.1 A2DP라는 규격으로 배포되고 있다.

안경훈 linuxgood@gmail.com | 리눅스와 맥OS에 관심이 많으며, 모바일 디바이스에서 동작되는 사용자 중심의 프로그램을 개발하는 것에 흥미를 가지고 있다. 현재 삼성SDS에서 근무 중이다.

이번 호에서는 iOS에서 사용되는 블루투스의 사양과 특징 및 블루투스를 활용한 데이터 통신 방법에 대해 알아보고 실제로 프로그램을 작성해보자.


<화면 1> 블루투스 로고

블루투스는 1994년에 에릭슨이라는 회사에서 최초로 발표한 근거리 무선통신 규약이다. 다른 표준 규약들이 발전하는 방식과 유사하게 블루투스 SIG(Special Interest Group)가 정식으로 발족했으며, 1999년 5월 20일에 공식적으로 배포됐다(IEEE 802.15.1). 블루투스 SIG에는 소니에릭슨, IBM, 노키아, 도시바 등의 전자제품 회사들이 참여하고 있다.

블루투스는 2.45㎓의 대역폭을 사용해 통신을 하고 있으며, 현재 3.0 버전까지 나와 있다. 각 버전이 올라갈 때마다 속도의 향상과 데이터 통신을 할 수 있게 되는 등 기능의 변화가 있었다. 1.0 버전은 각각의 회사별로 호환성에 문제가 있는 경우가 많이 있었으며, 속도를 논의할 단계가 아니었다. 2002년에 정식으로 배포된 1.1 버전부터 723.1kbps에 달하는 속도를 내게 됐고, 버전 2.0부터는 2.1Mbps라는 속도를 낼 수 있어서 음성이나 간단한 command 뿐 아니라 각 장치가 서로 데이터를 주고받을 수 있는 수준이 됐다.

iOS에서 사용되는 블루투스 프로파일
블루투스는 장치에 따라 지원하는 프로파일들이 따로 있다. 이 프로파일들에 따라 지원하는 방식도 달라진다. <표 1>은 iOS4에서 지원되는 블루투스의 프로파일이다.


<표 1> iOS4에서 지원되는 블루투스 프로파일

각각의 프로파일은 다음과 같은 의미를 갖는다.

HFP(Hands-Free Profile)

일반적으로 핸즈프리를 지원하는 기기에서 사용되는 프로파일이다. 오직 음성만을 지원하며, 주로 자동차에 사용되는 블루투스 기기에서 사용된다. 현재 최신 버전은 1.5이며, iOS4에서 지원하고 있다.

PBAP(Phone Book Access Profile)

전화번호부에 접근하기 위한 프로파일이다. 아이폰에서 <홈> 버튼을 계속 누르고 있으면 시작되는 ‘음성명령’ 애플리케이션 등이 PBAP를 지원하는 프로그램이다.


<화면 2> 아이폰의 음성명령 애플리케이션

A2DP(Advanced Audio Distribution Profile)

헤드셋이나 스피커로 음악을 전송할 때 사용하는 프로파일이다. 아이폰3G와 iOS3 버전부터 이 방식이 지원되기 시작했다. 아이폰으로 음악을 들으려면 직접 화면을 보고 조절할 수 있다.

AVRCP(Audio/Video Remote Control Profile)

오디오 뿐만 아니라 비디오까지 컨트롤 할 수 있다. 아이폰3G, iOS4.1 버전부터 지원된다.

PAN(Personal Area Network Profile)

piconet이라고 불리기도 하는데, 최대 8개의 장치와 마스터-슬레이브로 연결될 수 있는 연결방식이다(IEEE 802.15에 정의돼 있다). 일반적으로 지원영역은 10m 정도이며, 기기간에 연결해 최대 100m까지 사용할 수 있다.
데이터 교환을 위해 필수적으로 지원돼야 하며, 초기 아이폰을 제외하고는 모두 지원된다.

HID(Human Interface Device Profile)

각종 키보드, 마우스, 게이밍 디바이스, 태블릿 등을 지원하는 프로파일이다. 아이폰3GS 이상, iOS4 이상부터 지원되기 시작했다. 이 프로파일을 이용하면 블루투스 키보드를 아이폰이나 아이패드와 연결할 수 있다.


<화면 3> 일반적인 HID 연결 방식(출처 : www.blutooth.com)

블루투스에 대해 좀 더 자세히 알고싶다면 ‘http://ko.wiki pedia.org/wiki/블루투스’를 참고하길 바란다.

블루투스 프로그램 만들기
이제 블루투스의 특징을 이용해 Xcode 상에서 블루투스 프로그램을 만드는 방법을 알아보자.

블루투스의 특징을 활용한 아이폰 프로그램 중에 ‘Bump’라는 앱이 있다. 이 앱은 아이폰끼리 서로 부딪힐 때 지정된 정보들을(명함, 연락처, 사진, 캘린더) 진동과 함께 전달한다. 우리도 이렇게 아이폰끼리 서로 부딪혔을 때 정보를 교환할 수 있는 프로그램을 만들어보자.

GameKit에 대해
아이폰으로 블루투스 프로그램을 하려면 여러 가지 프레임워크 중에서 GameKit이라는 것을 사용해야 한다. 이름에 Game Kit라는 단어가 들어가 있어서 게임과 관련된 것이라고 생각할 수 있는데, 사실상 네트워크와 인터넷과 관련된 여러 가지 기능들을 모아둔 프레임워크라고 생각하면 된다.


<화면 4> GameKit

GameKit는 Game Center, Game Voice controller와 Peer-to-peer Connectivity 등을 담당하는 API를 제공해주는데, P2P 연결에 블루투스를 사용하게 된다.

GKSession 클래스가 ad-hoc 블루투스 또는 로컬 무선 네트워크를 관리해준다. 블루투스를 위해 GKPeerPickerController 객체를 사용해 다른 장치와의 연결 등을 담당한다.

세션의 연결
블루투스 장치들간의 연결은 <그림 1>과 같은 형태로 이뤄진다. 장치들은 Peer ID라는 정보를 갖고 있어 상대방과 연결하게 된다. 연결시에는 GKSession이라는 클래스를 사용한다.


<그림 1> 블루투스 장치들간의 연결

다른 블루투스 장치 탐색
장치를 서로 찾을 경우에도 session mode라는 초기화 방법을 이용한다. 만들게 될 프로그램이 서버로 동작할 수도 있고, 클라이언트로 동작할 수도 있다. 서로를 찾게 하는 동작은 동시에 발생시킬 수도 있고 차례대로 발생시킬 수도 있다. 서버 역할을 하게 되는 앱은 세션의 이름을 string이나 sessionID 형태로 다른 장치에게 알릴 수 있다. 이 때 클라이언트로 동작할 장치는 서버의 sessionID를 찾아서 페어링한다.


<그림 2> 다른 장치를 찾을 경우의 동작

GameKit을 사용하면 각각의 장치를 16대까지 지원할 수 있지만 최소한으로 줄이는 것이 탐색시간을 줄이는 방법이다.

내부적으로 장치를 서버로 동작시키려면 GKSessionMode Server나 GKSessionModePeer의 initWithSessionID: displayName:sessionMode:를 사용한다. availabe 속성을 YES/NO로 설정해 사용할 수 있다.

클라이언트가 연결하겠다는 신호를 보내면 session:did ReceiveConnectionRequestFromPeer: 메소드를 사용해 peerID를 받아 들이고 화면에 표시한다.


<화면 5> 장치 #01(좌)과 장치 #02

오류가 발생되면 acceptConnectionFromPeer:error: 메소드가 호출되고 denyConnectionFromPeer:가 호출된다. 성공적으로 호출되면 session:peer:didChangeState:가 호출될 것이다. 그 후에 diaplayNameForPeer:가 호출돼 아이폰 화면에 연결할 장치의 이름이 표시된다. 블루투스에서는 다른 장치로 연결할 때 연결을 허락받는 절차가 있는데, connectToPeer: withTimeout:이라는 메소드를 사용해 대기시간을 갖는다.

데이터의 교환
연결된 블루투스 장치간에는 데이터 교환 등을 할 수 있다. 데이터의 형식은 사용자가 정의한 어떠한 형태로든 가능하다. 이 때 서로 데이터를 교환하는 중에 오류가 발생하면 sendData ToAllPeers:withDataMode:error:라는 방식으로 주변의 모든 장치로 데이터를 보냈을 때의 에러를 검출할 수 있다.

데이터를 전송할 때 1000Byte 미만으로 보내는 것을 권장한다. 전달할 수 있는 가장 큰 용량의 데이터 사이즈는 87KB로 정의돼있다.

일단 전달된 데이터는 receiveData:fromPeer:inSession: context: 메소드를 사용해 주고 받는다.

연결끊기
작성한 앱이 연결을 끓을 준비가 됐다면 disconnectFrom AllPeers 메소드를 호출한다. disconnectTimeout 메소드를 사용해 끊어진다는 신호를 얼마나 기다릴 것인지 설정할 수도 있다. 연결이 완전히 끊어지면 session:peer:didChangeState: 메소드를 호출해 상태를 처리할 수 있다.

실제 프로그래밍
이제 아이폰 상에서 동작하는 실제 프로그램을 만들어보자. 프로젝트의 이름은 BT로 한다.

1) View-based Application 템플릿으로 프로젝트를 생성한다(File | New Project...). 프로젝트의 이름은 srBluetoot로 한다.

2) Frameworks tree에서 마우스 오른쪽 버튼을 클릭해 Game Kit.framework를 추가한다.


<화면 6> GameKit.framework의 추가

3) 인터페이스 빌더를 사용해 화면을 <화면 7>과 같이 구성한다. 기능으로는 상대방 장치를 연결하도록 하는 <연결하기>, <연결끊기> 버튼과 메시지를 입력하는 TextField 박스, <메시지 전달하기> 버튼을 배치했다.


<화면 7> 인터페이스 빌더 UI 설계

4) 인터페이스 빌더에서 디자인한 버튼들을 코드로 연결한다. srBluetoothViewController.h 파일에 <리스트 1>과 같이 추가한다.

<리스트 1> GKSession과 UI 오브젝트의 선언

#import <UIKit/UIKit.h>
#import <GameKit/GameKit.h>

@interface srBluetoothViewController : UIViewController {
GKSession *currentSession;
IBOutlet UITextField *txtMessage;
IBOutlet UIButton *sendMessage;
IBOutlet UIButton *connect;
IBOutlet UIButton *disconnect;
}

@property (nonatomic, retain) GKSession *currentSession;
@property (nonatomic, retain) UITextField *txtMessage;
@property (nonatomic, retain) UIButton *sendMessage;
@property (nonatomic, retain) UIButton *connect;
@property (nonatomic, retain) UIButton *disconnect;

-(IBAction) btnSend:(id) sender;
-(IBAction) btnConnect:(id) sender;
-(IBAction) btnDisconnect:(id) sender;

@end

먼저 #import <GameKit/GameKit.h>를 사용해 framework 헤더를 포함한다. 또한 GKSession 클래스의 포인터 객체로 currentSession를 생성한다. 그 후 각각의 UI 버튼과 TextField를 만든다. 각각의 오브젝트에는 retain 방식으로 메모리를 관리하도록 하는 @property 코드를 꼭 넣어줘야 한다. 그리고 버튼을 눌렀을 경우 메시지를 보내는 동작을 위해 btnSend: 함수를 만들었고, 연결을 위해 btnConnect:, 함수의 연결을 끊기 위해 btnDisconnect: 함수를 작성한다.

5) 인터페이스 빌더에서 각각의 오브젝트들과 해당 함수 등을 <화면 8>과 같이 연결한다.


<화면 8> 인터페이스 빌더 연결

6) 아이폰의 진동모드는 AudioToolbox라는 프레임워크를 사용한다. 진동을 위해 다음과 같이 AudioToolbox.framework를 추가한다.

#import <AudioToolbox/AudioToolbox.h>

이 헤더를 포함시키고 실제로 사용할 때는 다음과 같은 코드를 사용한다.

AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);


<화면 9> 진동을 위한 Audion Toolbox.framework 연결

7) 이제 블루투스로 연결된 아이폰을 서로 부딪쳤을 경우 서로 모션 이벤트를 전달하는 코드를 작성해야 한다. <리스트 2>와 같이 srBluetoothViewController.m에 작성한다.

<리스트 2> 모션 이벤트 전달

// 서로 부딪쳤을 경우 최초로 이벤트를 인식하기 위한 부분
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self becomeFirstResponder];
}

// 반응을 인지할 것인지의 여부 결정
(BOOL)canBecomeFirstResponder {
return YES;
}

// 모션이 시작됐을 경우 진동과 함께 textField에 쓰여진 메시지를 블루투스로 연결된 다른 장치로 보내는 역할
(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);

NSData* data;
NSString *str = [NSString stringWithString:txtMessage.text];
data = [str dataUsingEncoding: NSASCIIStringEncoding];
[self mySendDataToPeers:data];
}


8) 이번에는 GameKit에 해당하는 Delegate를 추가한다. GKS essionDelegate, GKPeerPickerControllerDelegate srBluetoothAppDelegate.h에 추가한다.

<리스트 3> Delegate 선언 추가

#import <UIKit/UIKit.h>
#import <GameKit/GameKit.h>

@class srBluetoothViewController;

@interface srBluetoothAppDelegate : NSObject <UIApplicationDelegate,GKSessionDelegate,GKPeerPickerControllerDelegate> {
UIWindow *window;
srBluetoothViewController *viewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet srBluetoothViewController *viewController;

@end

9) 처음으로 앱이 시작될 때 <연결하기>, <연결끊기> 버튼의 상태를 표시하기 위해 <리스트 4>와 같이 입력한다. 각각의 버튼들은 상태에 따라 보이거나 사라진다.

<리스트 4> 로딩시 버튼 상태 설정

(void)viewDidLoad {
[connect setHidden:NO];
[disconnect setHidden:YES];
[super viewDidLoad];
}

10) TextField 메시지 박스와 현재 연결중인 세션의 메모리를 정리하기 위해 <리스트 5>와 같이 작성한다.

<리스트 5> TextField와 연결중인 세션의 메모리 정리

(void)dealloc {
[txtMessage release];
[currentSession release];
[super dealloc];
}

11) 각각의 오브젝트들에 대해 synthesize 코드를 작성한다. @synthesize는 @property로 지정한 각각의 오브젝트와 반드시 쌍으로 작성돼야 한다.

<리스트 6> @property에 대응하는 @synthesize 코드

srBluetoothViewController.h

@synthesize currentSession; //GKSession
@synthesize sendMessage;
@synthesize txtMessage;
@synthesize connect;
@synthesize disconnect;

12) 실제 동작시 사용될 각각의 함수들을 살펴보자. 이번 예제를 위해 다음과 같이 크게 세 가지의 IBAction 함수를 만들었다.

첫 번째로 GKPeerPickerController를 사용해 <연결하기> 버튼을 클릭했을 경우 동작하는 함수가 있다. 여기서 picker. delegate를 자신으로 설정한다. 그리고 연결시 picker의 연결 타입을 결정한다. GKPeerPickerConnectionTypeOnline 또는 GKPeerPickerConnectionTypeNearby로 설정한다. GKPeer...Online 타입은 인터넷 연결이 된 상태의 네트워크 상황에서의 타입을 지정한 것이며, GKPeer...Nearby 타입은 블루투스 타입으로 주변에 있는 장치와 연결하는 방식을 말한다. 또한 이 함수에서 <연결하기>, <연결끊기> 버튼을 보이게 하거나 그렇게 하지 않거나 하는 설정을 한다.

마지막으로 picker를 보이도록 한다(picker show).

<리스트 7> <연결하기> 버튼 클릭시 동작

(IBAction) btnConnect:(id) sender {
picker = [[GKPeerPickerController alloc] init];
picker.delegate = self;
picker.connectionTypesMask = GKPeerPickerConnectionTypeNearby;

[connect setHidden:YES];
[disconnect setHidden:NO];
[picker show];
}

두 번째로 <연결끊기> 버튼이 있다. 이 버튼을 사용해 블루투스 연결을 끊을 수 있다. 현재 GKSession의 포인터 객체로 연결된 세션의 모든 장치(Peer)들을 끊는다(disconnectFrom AllPeers 사용). 다음으로 현재 연결된 세션의 메모리를 반환한다. 그리고 각각의 버튼들을 보이거나 보이지 않게 설정한다. 이 동작을 완료하면 서로 연결된 블루투스 장치들의 연결이 종료된다.

<리스트 8> <연결끊기> 버튼 클릭시 동작

(IBAction) btnDisconnect:(id) sender {
[self.currentSession disconnectFromAllPeers];
[self.currentSession release];
currentSession = nil;

[connect setHidden:NO];
[disconnect setHidden:YES];
}

세 번째로 <메시지 전달하기> 버튼을 클릭하거나 두 대의 아이폰을 서로 충돌시켰을 경우에 발생하는 메시지 전달을 위한 동작을 만든다(이번 예제에서는 편의상 한글로 인코딩하지 않고 스트링을 ASCII로 보낸다). 스트링 객체는 data라는 이름으로 만든다. 또한 다른 장치로 메시지를 보내기 위해 mySendData ToPeers라는 함수를 하나 더 만든다.

<리스트 9> <메시지 전달하기> 버튼 클릭시 동작

(IBAction) btnSend:(id) sender
{
NSData* data;
NSString *str = [NSString stringWithString:txtMessage.text];
data = [str dataUsingEncoding: NSASCIIStringEncoding];
[self mySendDataToPeers:data];
}

mySendDataToPeers 함수는 NSData로 정의된 데이터가 현재 세션에 존재한다면 그것을 다른 연결된 블루투스 장치로 보내는 역할을 담당한다. 속성으로는 모든 장치로 데이터를 전달하도록 하는 sendDataToAllPeers라는 것과 데이터를 보낼 때 모드를 지정하는 withDataMode라는 것이 있다. 보내는 값으로 GKSendDataReliable, GKSendDataUnreliable이 있는데, 빠른 속도가 필요한 전달 모드에서는 전자를 사용하고, 좀 느려도 정확한 전달을 원할 때는 후자를 사용한다.

<리스트 10> 다른 장치로 데이터 전송

(void) mySendDataToPeers:(NSData *) data
{
if (currentSession)
[self.currentSession sendDataToAllPeers:data
withDataMode:GKSendDataReliable
error:nil];
}

13) 이제 버튼 함수 이외에 사용되는 함수들을 알아보자.

- peerPickerController 함수는 세션 객체를 생성하고 델리게이트를 설정하는 동작들을 담당한다. 또한 데이터를 전송받았을 때 해당 데이터를 핸들링 할 수 있는 데이터 리시브 핸들러를 설정한다. picker 델리게이트도 이 함수에서 지정한다.

<리스트 11> 세션 객체 생성과 델리게이트 생성

(void)peerPickerController:(GKPeerPickerController *)picker
didConnectPeer:(NSString *)peerID
toSession:(GKSession *) session {
self.currentSession = session;
session.delegate = self;
[session setDataReceiveHandler:self withContext:nil];
picker.delegate = nil

[picker dismiss];
[picker autorelease];
}


<화면 10> Cancel 이벤트 발생

TextField를 벗어날 경우 OSK(On Screen Keyboard)를 사라지도록 하는 코드

<화면 11>을 보면 OSK가 화면에서 나와 있다. TextField에서 벗어나거나 ‘다음문장’이라는 키보드의 키가 눌렸을 때는 자동으로 OSK가 사라지도록 해야 바람직하다. 다음과 같은 순서로 코드를 넣으면 이를 해결할 수 있다.

1) srBluetoothViewController.h 파일에 키보드 액션으로 사용될 함수를 정의한다. TextField를 벗어날 경우와 MainView의 어느 곳이든 선택됐을 경우 가상 키보드가 내려가도록 해야 한다.

- (IBAction)goAwaykeyBoard:(id)sender;
- (IBAction)tabBackgroud:(id)sender;

2) 액션 함수를 다음과 같이 작성한다. 키보드를 첫 번째로 반응하도록 하는 Responder 함수를 호출하도록 설정하며, MainView의 아무 곳이든 눌려지면 역시 resignFirstResponder가 동작하도록 한다.

(IBAction)goAwaykeyBoard:(id)sender
{
[sender resignFirstResponder];
}
IBAction)tabBackgroud:(id)sender
{
[txtMessage resignFirstResponder];
}

3) IB에서 <화면 11>과 같이 TextField에는 ‘Did End On Exit’라는 이벤트를 연결하고 tabBackground 함수는 ‘Control Touch Down’이라는 이벤트를 이어준다. 또한 이를 위해 MainView의 클래스 이름을 UIView에서 UIControl로 변경한다.


<화면 11> 가상 키보드를 위한 함수 연결

- peerPickerControllerDidCancel : 기기간의 연결을 취소했을 경우 동작하는 함수다. picker 델리게이트를 초기화하며, 메모리를 반환(release)한다. 또한 MainView가 있는 버튼의 상태를 설정해준다.

<리스트 12> 기기간의 연결 취소시 동작

(void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker
{
picker.delegate = nil
[picker autorelease];

[connect setHidden:NO];
[disconnect setHidden:YES];
}

<리스트 13> 연결된 장치 사이의 상태 체크

(void)session:(GKSession *)session
peer:(NSString *)peerID
didChangeState:(GKPeerConnectionState)state {
switch (state)
{
case GKPeerStateConnected:
NSLog(@"connected");
break;
case GKPeerStateDisconnected:
NSLog(@"disconnected");
[self.currentSession release];
currentSession = nil;

[connect setHidden:NO];
[disconnect setHidden:YES];
break;
}
}


<화면 12> 메시지 입력 화면(좌)과 메시지 수신 화면(메시지를 입력하면 메시지가 전달된 후 수신화면을 볼 수 있다)

다음으로 연결된 장치 상호간의 연결 상태를 체크하는 session 함수에 대해 알아보자. session 함수는 GameKit의 GKPublic Protocols.h라는 헤더에 정의돼있다. 장치간에 연결이 끊어지거나 다시 붙거나 하면 이 함수가 호출된다. 인자로 session 이름과 스트링으로 표시될 수 있는 peer, 변경된 상태를 표시해주는 didChangeState를 갖고 있다.

빌드한 후에 <연결하기>로 먼저 블루투스 장치를 연결하고 메시지를 입력한다. 블루투스 장비를 서로 부딪히면 <화면 11>과 같이 메시지가 전달된다.

이번 호에서는 코드를 중심으로 블루투스를 사용하는 각각의 장치가 어떤 방식으로 연결되고 데이터를 교환하는지에 대해 알아봤다. 현재 블루투스는 고속으로 데이터를 전송할 수 있는 프로파일 등이 준비되고 있으며 표준 역시 업그레이드 된 사양으로 발전하고 있다.

참고자료
1. iPadProgrammingGuide.pdf(http://developer.apple.com/library/ios/ documentation/General/Conceptual/iPadProgrammingGuide/iPadProgrammingGuide.pdf)
2. iPad Human Interface Guidelines(http://developer.apple.com/library/ios/ documentation/General/Conceptual/iPadHIG/iPadHIG.pdf)
3. Game Kit Programming Guide(http://developer.apple.com/library/ios/ #documentation/NetworkingInternet/Conceptual/GameKit_Guide/Introduction/Introduction.html)

'iOS > Xcode' 카테고리의 다른 글

NSThread 예제  (0) 2012.06.23
전역변수 추가  (0) 2012.06.23
전역변수 사용하기  (0) 2012.06.23
arc 에러관련  (0) 2012.06.23
[Xcode 4.2] 사라진 Window-Based 만들기  (0) 2012.06.23