« FM3マイコンをOpenOCD + Eclipseでデバッグ | トップページ | Bluetooth LE (BTLE)のAdvertisement packet format »

iPhoneからmbedをBluetooth LE (BTLE)で制御する

久しぶりの更新です。ずっと弾切れでしたが、最近Bluetooth LE (BTLE)を使ったiPhone (iOSデバイス)とマイコンボード間の通信に興味が湧き、mbedとの接続実験を行いました。

以前から、スマホとmbedやArduinoをつないで連携できるとよいなと思っていました。どうせならUSBなどの有線でなく、無線でつなぎたい、あとAndroidよりiOS派の自分としてはiOSとつなぎたい、というのが目標でしたが、皆様ご存知の通り以下の制約で挫折していました:

  • 以前のiOS (4.x以前)ではBluetoothのSPPが使えない
  • xBee WiFiを使って、TCP/IPのソケット通信でつなぐ方法はありそう。でも、APがないとダメ。

で、iOS5とiPhone 4SからBTLEがサポートされ、BTのプロファイル縛りから解放されていることをつい最近知りました。それで、色々と資料をあさり、iPhoneからmbedのLEDをon/offするサンプルを作ってみました。BTLEのGATT/ATTなど、プロトコルの仕組みはまだよく分かっていないのですが、動かしならが確かめて行きたいと思います。


素材

iOS側はiOS5からサポートされたCoreBluetooth Frameworkを使えばOKです。

マイコン側も色んな選択肢がありそうです:

  • ユカイ工学さんのkonasi :  BluetoothモジュールにGPIO/PWM/ADCなどを組み込んで、iOSから制御できる。Bluetoothのサービス・キャラクタリスティック操作など下回りを意識させないiOS用ライブラリも提供されており、すぐに使えそう。ただ少々お高いのと、執筆現在は品切れ中・・勉強がてら、CoreBluetoothやBTLEを意識する低レイヤーなところから始めても悪くない
  • ランニングエレクトロニクスさんのSBDBT : BTLEとシリアルインタフェースのブリッジとして動き、価格がお手頃。mbedやArduinoなどのマイコンボートと通信するためにはシリアルポート経由でコマンドを送受信するようなインタフェースを作る必要がありそう。モジュールが小型なので、ロボットの無線操縦なんかに向いているのかしら
  • RedBearLabさんのArduino BLE shield : Arduino用のライブラリやiOS用のAppもあり、お手軽に使えそう。お値段も手ごろですが、日本の技適がないため法的に使えません。
  • btstack : 組み込み向けのBluetooth stack。もともとはSPPをサポートしていたが、BTLEにも対応。HCI (Host-Controller Interface)から上の層を実装しており、下位層であるLL (Link Layer)/PHYは自分で持ってくる必要がある。下位層はBluetoothドングルを使うことを想定しているので、USB周りのドライバを用意する必要がある。

今回は、mbedを直接iOSから制御できるようにしようということで、btstackを使いました。問題はUSBのドライバをどうするかです。SBDBTもbtstackを使っていますが、PIC用なのでそのままでは使えません。一方、mbedではSPP版のbtstackをNorimasa Okamotoさんがmbedに移植されていました。

実装(移植)の方針として、OkamotoさんのSPP版btstackをベースに、上位プロトコル部分をBTLE (GATT/ATT)に差し替えました。mbedのリソース(現状はLED1だけですが・・)をBTLEのサービスやキャラクタリスティックとして見せる部分の処理は、ランエレさんのSBDBT用コード(ble-server.c)を参考にさせていただきました。


実装

mbed側のコードは、mbed.orgにポストしてあります。SBDBT同様に以下の動作を行います:

  • サービス(UUID: 0xFFF0)配下に3つのcharacteristicsがありcharacteristic 0xFFF2に00 or 01をライトすることでLED1のon/offを制御できます
  • LED3はbtstackが初期化されアドバタイズを始めた時点で点灯、マスターと接続が完了すると点滅します
  • LED2はアトリビュートのリードで点灯、ライトで消灯します。今回のiOS Appからの制御ではライトのみなので、消えっぱなしです

iOS側のコードは、以下の通りです(iOS 6.1 & Xcode 4.6を使用)。チュートリアルなどを参考にして書いてみました:

//
//  ViewController.h
//  mbed Control
//
//  Created by todotani on 2013/02/11.
//  Copyright (c) todotani. All rights reserved.
//

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

@interface ViewController : UIViewController <CBCentralManagerDelegate, CBPeripheralDelegate>

@property (weak, nonatomic) IBOutlet UISwitch *led1Sw;
@property (weak, nonatomic) IBOutlet UILabel *statusLabel;
@property (weak, nonatomic) IBOutlet UISegmentedControl *connectButton;

@end

----

//
//  ViewController.m
//  mbed Control
//
//  Created by todotani on 2013/02/11.
//  Copyright (c) todotani. All rights reserved.
//

#import "ViewController.h"

#define CONECTED     0
#define DISCONNECTED 1

@interface ViewController ()

@property (strong) CBCentralManager *manager;
@property (strong) CBPeripheral *peripheral;
@property (strong) CBCharacteristic *led1Characteristic;

- (void) updateLed1;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}


#pragma mark CoreBuletooth delegate

- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    NSLog(@"State Update:%d",[self.manager state]);
}

- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)aPeripheral
      advertisementData:(NSDictionary *)advertisementData
                   RSSI:(NSNumber *)RSSI
{
    self.statusLabel.text = [NSString stringWithFormat:@"Found(RSSI %@)", RSSI];
    [central stopScan];
    self.peripheral = aPeripheral;
    [central connectPeripheral:self.peripheral options:nil];
}

- (void) centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral
                  error:(NSError *)error
{
    NSLog(@"Failed:%@",error);
}

- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)aPeripheral
{
    NSLog(@"Connected:%@",aPeripheral.UUID);
    self.statusLabel.text = @"Connected";
    [aPeripheral setDelegate:self];
    [aPeripheral discoverServices:@[[CBUUID UUIDWithString:@"FFF0"]]];
}

- (void) centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral
                  error:(NSError *)error
{
    NSLog(@"Disconnected");
    self.statusLabel.text = @"Disconnected";
    self.connectButton.selectedSegmentIndex = DISCONNECTED;
    [self.manager stopScan];
}


// mbed Sericeを検出
- (void) peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error
{
    for (CBService *aService in aPeripheral.services) {
        NSLog(@"Service UUID:%@", aService.UUID);
        if ([aService.UUID isEqual:[CBUUID UUIDWithString:@"FFF0"]]) {
            [aPeripheral discoverCharacteristics:@[[CBUUID UUIDWithString:@"FFF2"]] forService:aService];
        }
    }
}

// LED1制御用characteristicsを検出
- (void) peripheral:(CBPeripheral *)aPeripheral didDiscoverCharacteristicsForService:(CBService *)service
              error:(NSError *)error
{
    for (CBCharacteristic *aChar in service.characteristics){
        NSLog(@"Characteristic UUID:%@",aChar.UUID);
        if ([aChar.UUID isEqual:[CBUUID UUIDWithString:@"FFF2"]]) {
            self.led1Characteristic = aChar;
            [self updateLed1];
        }
    }
}


#pragma mark ActionMethods

- (IBAction)changeConnection:(id)sender
{
    UISegmentedControl *mySegmented = sender;
    switch (mySegmented.selectedSegmentIndex) {
        case 0:
            [self.manager scanForPeripheralsWithServices: @[[CBUUID UUIDWithString:@"FFF0"]]
                                              options:@{ CBCentralManagerScanOptionAllowDuplicatesKey:@YES }];
            self.statusLabel.text = @"Scanning";
            break;
        case 1:
            if ([self.peripheral isConnected]) {
                [self.manager cancelPeripheralConnection:self.peripheral];
            } else {
                [self.manager stopScan];
                self.statusLabel.text = @"Disconnected";
            }
            break;
        default:
            break;
    }
}


- (IBAction)changeLed1:(id)sender
{
    if ([self.peripheral isConnected]) {
        [self updateLed1];
    }    
}


#pragma mark Private methods

- (void) updateLed1
{
    const unsigned char onData[]  = {0x01};
    const unsigned char offData[] = {0x00};
    
    if ([self.led1Sw isOn]) {
        [self.peripheral writeValue:[NSData dataWithBytes:onData length:1] forCharacteristic:self.led1Characteristic
                               type:CBCharacteristicWriteWithoutResponse];
    } else {
        [self.peripheral writeValue:[NSData dataWithBytes:offData length:1] forCharacteristic:self.led1Characteristic
                               type:CBCharacteristicWriteWithoutResponse];
    }
}

@end

動作の概要は以下です:

  • 105行目:Connectボタンが押されると、CBCentralManagerクラスのscanForPeripheralsWithServicesメソッドを呼び出してBTLEアドバタイズのスキャンを開始。この際、mbedの制御用に定義している、サービス UUID 0xFFF0を検索対象として指定(Arrayに複数の検索対象UUIDをセットできるが、このサンプルでは0xFFF0のみを指定)
  • 39行目:ペリフェラルが見つかると、centralManager:didDiscoverPeripheral: デリゲートがコールバックされます。ここでは何も考えずに、見つかったペリフェラルに接続要求を発行しています。スキャンを行う際に、mbedのサービスを指定していますので、mbed以外と繋がることはないですが、どのmbedかまでは区別していません。接続先をもっと厳密に制御したい場合は、認証などのセキュリティー機能を使うのでしょうか(このあたりはよく分かっていません)
  • 55行目:ペリフェラルとの接続が完了すると、centralManager:didConnectPeripheral:デリゲートがコールバックされます。ここで、見つかったペリフェラルのインスタンスに対して、discoverServicesメッセージを送って、サービスを検索します。引数として、UUID 0xFFF0を指定することで、mbed制御のサービスのみを検出するようにしています(引数をnilにすると、GATT基本サービスの1800や1801が見つかった際にもコールバックされるのでフィルターしておきます)
  • 74行目:サービスが見つかると、peripheral:didDiscoverServices:デリゲートがコールバックされます。今度は、ペリフェラルクラスに対して、discoverCharacteristicsメッセージを送ってキャラクタリスティックの検索を行います。サービス0xFFF0には3つのキャラクタリスティックが定義されていますが、LED1の制御に使うのは0xFFF2のみのため検索条件を指定します。
  • 85行目:キャラクタリスティックが見つかると、peripheral:didDiscoverCharacteristicsForService:デリゲートがコールバックされます。discoverCharacteristicsでフィルター条件をかけているつもりなのですが、3回コールバックされるため、処理対象とする0xFFF2を判定しています。これで、LED1の制御に必要なキャラクタリスティックがViewControllerクラスのプロパティとして設定されました
  • 133行目:スイッチの状態が変化すると、LED1制御用のキャラクタリスティックにon/offに該当する値をライトします

動作中の写真はこんな感じです:

IMG_0123

IMG_0984

 

今後の予定

今回は書き込みのみですので、mbed側のGPIO状態の読み取りを実装するつもり。


参考資料

« FM3マイコンをOpenOCD + Eclipseでデバッグ | トップページ | Bluetooth LE (BTLE)のAdvertisement packet format »

Bluetooth」カテゴリの記事

mbed」カテゴリの記事

コメント

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

« FM3マイコンをOpenOCD + Eclipseでデバッグ | トップページ | Bluetooth LE (BTLE)のAdvertisement packet format »

2017年2月
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28        
無料ブログはココログ