konashi で iOS と Gadgeteer 間で Bluetooth LE 通信して LED を光らせてみた!

f:id:iseebi:20130901233234j:plain

Gadgeteerが結構おもしろかったので即ポチして動かしてみた で Cerberos をつかいましたが、今のこういうキット使って何かやるというネタはやはり iOS で近接通信というのが多いのです。

そこで、Motorola RAZR IS11MでBluetooth LEが使えた で使った konashi を使って iOS から Gadgeteeer とBLE通信してみました。

konashi と Gadgeteer をつなぐ

前回、Gadgeteer のコネクタの中に流れている信号は業界標準の規格になっていると書きましたが、この中で Socket U として定義されている UART というものがあり、konashi はちょうどこの UART で通信する端子を持っています。

konashi のUART ラインに繋ぐために、Gadgeteer の Breakout Module を購入しました。Breakout Module を使うと、Gadgeteerソケットの信号線を端子に引き出すことができます。
ただ、端子にはピンもソケットもついていないので残念ながらはんだづけが必要になります。(「いやだ絶対はんだ付けしたくない」とおもってGadgeteerにしたというのに…!)

秋月電子通商細ピンヘッダ1x20分割ロングピンソケット 1x42、それからはんだごてやブレッドボード等一通り必要なものを買いそろえました。

届いたらさっそくはんだづけして、ブレッドボードにさせるピンタイプと、出先で手軽に使えるソケットタイプを作りました。

f:id:iseebi:20130901233313j:plain

さて、実際の結線ですが、以下のような感じです。

f:id:iseebi:20130901233413p:plain

・Pin1(+3.3V) を konashi の +3V 端子に
・Pin4(TX) を konashi の UART RX 端子に
・Pin5(RX) を konashi の UART TX 端子に
・Pin10(GND) を konashi の GND 端子に

3.3V と 3V で合ってないからホントはいけない気がする。

Gadgeteer のプログラムを書く

では、Gadgeteer のプログラムを書いていきます。まず、.NET Gadgeteerの新しいプロジェクトを作成します。

f:id:iseebi:20130901233456p:plain

メインボードはCerberosを使用します。

f:id:iseebi:20130901233528p:plain

次に参照設定でGadgeteer.Serialを追加します。

f:id:iseebi:20130901233553p:plain

デザイナを開いて3番ポートにLED7Rモジュール、8番ポートにUSB Client SPモジュールをつなぎます。LED7Rモジュールは7つのLEDが円周状に配置されているモジュールで、Cerberosスターターキットに含まれています。

f:id:iseebi:20130901233607p:plain

また、実際の配線では6番ポートにBreakout Moduleでつながったkonashiを接続していますが、モジュールにはなっていないのでモジュールを直接プログラムで定義して通信します。

まず、Konashi.cs を作成し、モジュールを定義します。

namespace GadgeteerApp2
{
    public class Konashi : GTM.Module
    {
        public delegate void KonashiDataReceivedEventHandler(Konashi sender, string text);
 
        public event KonashiDataReceivedEventHandler DataReceived;
 
        private GTI.Serial serial;
        private byte[] readBuffer = new byte[16];
 
        /// <summary></summary>
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public Konashi(int socketNumber)
        {
            var socket = GT.Socket.GetSocket(socketNumber, true, this, "U");
            socket.EnsureTypeIsSupported('U', this);
 
            serial = new GTI.Serial(socket, 9600, GTI.Serial.SerialParity.None, GTI.Serial.SerialStopBits.None,
                8, GTI.Serial.HardwareFlowControl.NotRequired, this);
            serial.DataReceived += serial_DataReceived;
            serial.AutoReadLineEnabled = false;
            serial.ReadTimeout = 500;
            serial.Open();
        }
 
        void serial_DataReceived(GTI.Serial sender, System.IO.Ports.SerialData data)
        {
            lock (this)
            {
                if (data == System.IO.Ports.SerialData.Chars)
                {
                    var readLength = 0;
                    string text = string.Empty;
                    using (var ms = new MemoryStream())
                    {
 
                        do
                        {
                            readLength = sender.Read(readBuffer, 0, readBuffer.Length);
                            if (readLength > 0)
                            {
                                ms.Write(readBuffer, 0, readLength);
                            }
                            else
                            {
                                break;
                            }
                        } while (true);
                        ms.Seek(0, SeekOrigin.Begin);
                        using (var sr = new StreamReader(ms))
                        {
                            text = sr.ReadToEnd();
                        }
                    }
                    if (DataReceived != null)
                    {
                        DataReceived(this, text);
                    }
                }
            }
        }
 
        public void Write(string text)
        {
            try
            {
                serial.Write(text);
            }
            catch (Exception ex)
            {
                Debug.Print(ex.ToString());
            }
        }
 
    }
}

次に、この Konashi モジュールを使ってProgram.cs に移ってプログラムを入力します。

namespace GadgeteerApp2
{
    public partial class Program
    {
        private Konashi konashi;
 
        // This method is run when the mainboard is powered up or reset.   
        void ProgramStarted()
        {
            // Use Debug.Print to show messages in Visual Studio's "Output" window during debugging.
            Debug.Print("Program Started");
 
            konashi = new Konashi(6);
 
            konashi.DataReceived += (s, e) =>
            {
                Debug.Print(e);
 
                if (e == "0")
                {
                    led7r.TurnLightOn(0, true);
                    konashi.Write("Light 0 accepted\n");
                }
                else if (e == "1")
                {
                    led7r.TurnLightOn(1, true);
                    konashi.Write("Light 1 accepted\n");
                }
                else if (e == "2")
                {
                    led7r.TurnLightOn(2, true);
                    konashi.Write("Light 2 accepted\n");
                }
                else if (e == "3")
                {
                    led7r.TurnLightOn(3, true);
                    konashi.Write("Light 3 accepted\n");
                }
                else if (e == "4")
                {
                    led7r.TurnLightOn(4, true);
                    konashi.Write("Light 4 accepted\n");
                }
                else if (e == "5")
                {
                    led7r.TurnLightOn(5, true);
                    konashi.Write("Light 5 accepted\n");
                }
                else if (e == "6")
                {
                    led7r.TurnLightOn(6, true);
                    konashi.Write("Light 6 accepted\n");
                }
                else
                {
                    konashi.Write("Unknown\n");
                }
            };
            konashi.Write("Arrival\n");
        }
    }
}

※usingは省略しています。プログラム全体、gist においておきました。→ https://gist.github.com/iseebi/6403080

konashi SDK を使って iOS 側のプログラムを書く

次に、konashi-ios-sdk を使って iOS 側のプログラムを書きます。CocoaPodsがあれば一発で入るのでセットアップしてきてください。
新しいプロジェクトを作って、同じフォルダに下記の内容で Podfile を作り、pod install します。

platform :ios, "6.1"
pod "konashi-ios-sdk"

あとは、UART 読み取りの Notification を受信できるようにして、接続完了時にはUARTの使用とレートの設定をし、受信結果をTextViewに出力するようにして、

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [Konashi addObserver:self selector:@selector(ready) name:KONASHI_EVENT_READY];
    [Konashi addObserver:self selector:@selector(uartRead) name:KONASHI_EVENT_UART_RX_COMPLETE];
}

- (void)ready
{
    [Konashi uartMode:KONASHI_UART_ENABLE];
    [Konashi uartBaudrate:KONASHI_UART_RATE_9K6];
}

- (void)uartRead
{
    unsigned char c = [Konashi uartRead];
    NSLog(@"UartRx %d", c);
    NSString *str = [NSString stringWithFormat:@"%c", c];
    self.textView.text = [self.textView.text stringByAppendingString:str];
    
}

Findボタンをおいてそのアクションにkonashiの検索処理を書いて、Sendボタン押したらTextFieldに入れた内容を送れるようにしたら完了です。簡単!

- (IBAction)findPushed:(id)sender
{
    [Konashi find];
}

- (IBAction)sendPushed:(id)sender
{
    NSData *d = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
    unsigned char *ptr = (unsigned char *) [d bytes];
    for (int i = 0; i < [d length]; i++) {
        [Konashi uartWrite:*(ptr + i)];
    }
}

こうしてできたのが、先の動画です。

まとめ

こうしてGadgeteerを使ってBLEで遊べるようにしました。ようやく今の世の中で使えるようになった感ありますね。
実はkonashi SDK、今回はじめてまともに触りましたが、ちょっとクラスメソッドだらけでどうなのという感あるものの、プロトタイピングには十分ありだと思いました。

Gadgeteerはソケット繋ぐだけで使えますし、C#でプログラムが書けるのでC#erがiOSと連携する実デバイス作るのが一気にハードル下がってすごくよいと思います。

あとやはりKonashiが単体で機能強すぎるので高い感じがするのと、Breakout Moduleに要はんだ付けというのがいけてない。Gadgeteer用に技適通過したモジュールがきてくれるといいのですが。