Firmata を使って Xamarin.Android から Arduino に接続(F#版)

Firmata を使って Xamarin.Android から Arduino に接続する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/7185

これの F# 版を作ります。Xamarin.Android は主に C# で作ることが多いでしょうが、オール F# で作ることができます。Visual Studio 2013 では、Visual F# の Android テンプレートがあるので、そのまま使えます。

Firmata.NET を F# 版に書き直す

少し書き方が違いますが、ざっと書き下したのが以下のコードです。

namespace Firmata.NET

open System
open Android.App
open Android.Content
open Android.Runtime
open Android.Views
open Android.Widget
open Android.OS

open Android.Bluetooth
open System.Linq
open Java.Util

module ARDUINO = 
    let INPUT = 0
    let OUTPUT = 1
    let LOW = 0
    let HIGH = 1
    

type Arduino() as this =
    let INPUT = 0
    let OUTPUT = 1
    let LOW = 0
    let HIGH = 1

    let DIGITAL_MESSAGE = 0x90uy    // send data for a digital port
    let ANALOG_MESSAGE = 0xE0uy     // send data for an analog pin (or PWM)
    let REPORT_ANALOG = 0xC0uy      // enable analog input by pin #
    let REPORT_DIGITAL = 0xD0uy     // enable digital input by port
    let SET_PIN_MODE = 0xF4uy       // set a pin to INPUT/OUTPUT/PWM/etc
    let REPORT_VERSION = 0xF9uy     // report firmware version
    let SYSTEM_RESET = 0xFFuy       // reset from MIDI
    let START_SYSEX = 0xF0uy        // start a MIDI SysEx message
    let END_SYSEX = 0xF7uy          // end a MIDI SysEx message


    let mutable _socket:BluetoothSocket = null
    let mutable autoStart = false
    let mutable delay = 0

    let mutable digitalOutputData = Array.zeroCreate(16)
    let mutable digitalInputData = Array.zeroCreate(16)
    let mutable analogInputData = Array.zeroCreate(16)

    do
        if autoStart = true then
            delay <- 0 
            this.Connect()
            this.Open()

    /// Connect Bluetooth on Arduino.
    member this.Connect() =
        let adapter = BluetoothAdapter.DefaultAdapter
        if adapter = null then
            raise (Exception("No Bluetooth adapter found."))
        if adapter.IsEnabled = false then
            raise (Exception("Bluetooth adapter is not enabled."))
        let device = adapter.BondedDevices.FirstOrDefault( fun x -> x.Name = "HC-06" )
        if device = null then
            raise (Exception("Named device not found."))
        _socket <- device.CreateRfcommSocketToServiceRecord(UUID.FromString("00001101-0000-1000-8000-00805f9b34fb"))
        _socket.Connect()

    member this.Open() =
        // let mutable command = Array.create<byte>(2)
        for i=0 to 5 do
            let command = [|
                REPORT_ANALOG ||| byte(i)
                1uy
            |]
            _socket.OutputStream.Write( command, 0, command.Length )
        for i=0 to 1 do
            let command = [|
                REPORT_DIGITAL ||| byte(i)
                1uy
            |]
            _socket.OutputStream.Write( command, 0, command.Length )

    member this.Close() = 
        _socket.Close()
        _socket <- null

    member this.digitalRead(pin:int):int =
        (digitalInputData.[pin >>> 3] >>> (pin &&& 0x07)) &&& 0x01
    member this.analogRead(pin:int):int =
        analogInputData.[pin]

    member this.pinMode(pin,mode) =
        let message = [|
            SET_PIN_MODE
            byte(pin)
            byte(mode)
        |]
        _socket.OutputStream.Write( message, 0, message.Length )

    member this.digitalWrite(pin,value) =
        let portNumber = (pin >>> 3) &&& 0xFF
        digitalOutputData.[portNumber] <-
            if value = 0 then
                digitalOutputData.[portNumber] &&& ~~~(1 <<< (pin &&& 0x07))
            else 
                digitalOutputData.[portNumber] ||| (1 <<< (pin &&& 0x07)) 
        let message = [|
            DIGITAL_MESSAGE ||| byte(portNumber) 
            byte(digitalOutputData.[portNumber] &&& 0x7F)
            byte(digitalOutputData.[portNumber] >>> 7)
        |]
        _socket.OutputStream.Write(message, 0, message.Length);
            
    member this.analogWrite(pin,value) = 
        let message = [|
            ANALOG_MESSAGE ||| (byte(pin) &&& 0x0Fuy)
            byte(value &&& 0x7F)
            byte(value >>> 7)
        |]
        _socket.OutputStream.Write(message, 0, message.Length);

    member this.setDigitalInputs( portNumber, portData ) =
        digitalInputData.[portNumber] <- portData

    member this.setAnalogInput( pin, value ) =
        analogInputData.[pin] <- value

定数が module を使っているのは愛嬌として、メッセージの配列を作るところは、直接作れるので若干楽ですね。ビット演算子が「&&&」や「|||」を使わないといけないので、文字数的に冗長なのは残念な感じがしますが、まあ、これはこれで良しということで。
int から byte へのキャストが頻発するのは、F# の宿命です。型を合わせないといけないので、アップキャストだけでなくダウンキャストに対しても、明示的な型のキャストが必要になります。このあたりインターフェースプログラミングをしているとちょっと冗長な感じになります。

マニフェストを設定する

Bluetooth を扱うための、パーミッションの設定は C# と同じです。

MainActivity

ざっと書いたので、ボタンのクリックイベントのところが雑ではありますが、C# よりも短くかけます。フィルタを多用する場合やフローチャート的に状態遷移する場合は、関数型言語 F# を使うとすんなりと書けるはずなんですけどね。このあたりは、Arduino 戦車に距離センサーを付けて自律化したときに試してみましょう。

type MainActivity () =
    inherit Activity ()

    let mutable buttonConnect:Button = null
    let mutable buttonOpen:Button = null
    let mutable buttonLedOn:Button = null
    let mutable buttonLedOff:Button = null
    let mutable arduino:Arduino = new Arduino()

    override this.OnCreate (bundle) =

        base.OnCreate (bundle)

        // Set our view from the "main" layout resource
        this.SetContentView (Resource_Layout.Main)

        // Get our button from the layout resource, and attach an event to it
        buttonConnect <- this.FindViewById<Button>(Resource_Id.buttonConnect)
        buttonOpen <- this.FindViewById<Button>(Resource_Id.buttonOpen)
        buttonLedOn <- this.FindViewById<Button>(Resource_Id.buttonLEDon)
        buttonLedOff <- this.FindViewById<Button>(Resource_Id.buttonLEDoff)


        buttonConnect.Click.Add( fun args -> 
            arduino.Connect();
            buttonConnect.Text <- "connected.";
        )
        buttonOpen.Click.Add( fun args -> 
            arduino.Open();
            buttonConnect.Text <- "Firmata opend.";
            arduino.pinMode(5, ARDUINO.OUTPUT );
            arduino.digitalWrite(5, ARDUINO.LOW);
        )
        buttonLedOn.Click.Add( fun e -> this.OnClickLedOn(buttonLedOn,e) )
        buttonLedOff.Click.Add( fun e -> this.OnClickLedOff(buttonLedOff,e) )

    member this.OnClickLedOn(sender,e) =
        arduino.digitalWrite(5, ARDUINO.HIGH)
    member this.OnClickLedOff(sender,e) =
        arduino.digitalWrite(5, ARDUINO.LOW)

動かす

見た目は C# 版と変わりませんが、F# のアプリが動いています。

Windows に乗せ換えて(実は、ストアアプリ版の Firmata ライブラリも作ってある)、F# からコマンドライン的に使えると、Ruby や Node.js のようにスクリプト言語のように使うことが可能です。このあたり、先の firmata のサイトに Haskell があるので、比較するのも面白いかなと(私は Haskell は全然ダメなんですけど)。

カテゴリー: Android, Arduino, F#, Xamarin | Firmata を使って Xamarin.Android から Arduino に接続(F#版) はコメントを受け付けていません

Firmata を使って Xamarin.Android から Arduino に接続する

Windows Remote Arduino を利用して Arduino 戦車を動かす | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/7168

では、Windows がオープンソース化している Firmata ライブラリを利用して Arduino に接続したわけですが、Firmata プロトコル自体は公開されているので、どのような言語でも誰でも作れます。

firmata/arduino
https://github.com/firmata/arduino

github の readme を眺めると、.NET 実装もあります。ソースを見ていくと COM 経由で Arduino に USB ケーブルを刺して使うライブラリになっていますが、これをちょっと修正すれば Bluetooth のシリアル通信対応にできますよね。ということで、Bluetooth 2.0 のシリアル通信である RFCOMM を使って書き換えていきます。

Firmata.NET | imagitronics.org
http://www.imagitronics.org/projects/firmatanet/

2 つある中では、Firmata.NET のほうがコードが短かったので、これを利用します。このコードを使って、Android 上から Firmata を通して Arduino を操作できるようにしましょう。確か、ブラウザや Node.js から使うパターンが多いのですが、Xamarin.Android から C# で扱えるとネイティブアプリとして作れるので便利でしょう。ちなみに、コード自体は、短いので Xamarin の Starter 版(無償版)でも動作確認ができました。無償版の場合 128KB 制限なので、そのなかで収まっていると思われます(正確な大きさはわからない)。

RFCOMM 版の Firmata を作る

ざっくりと移植したのが以下です。Android の Bluetooth を使うために、BluetoothAdapter.DefaultAdapter を利用しています。

class Arduino
{
    public static int INPUT = 0;
    public static int OUTPUT = 1;
    public static int LOW = 0;
    public static int HIGH = 1;

    private const int MAX_DATA_BYTES = 32;

    private const int DIGITAL_MESSAGE = 0x90; // send data for a digital port
    private const int ANALOG_MESSAGE = 0xE0; // send data for an analog pin (or PWM)
    private const int REPORT_ANALOG = 0xC0; // enable analog input by pin #
    private const int REPORT_DIGITAL = 0xD0; // enable digital input by port
    private const int SET_PIN_MODE = 0xF4; // set a pin to INPUT/OUTPUT/PWM/etc
    private const int REPORT_VERSION = 0xF9; // report firmware version
    private const int SYSTEM_RESET = 0xFF; // reset from MIDI
    private const int START_SYSEX = 0xF0; // start a MIDI SysEx message
    private const int END_SYSEX = 0xF7; // end a MIDI SysEx message

    // private SerialPort _serialPort;
    private int delay;

    private int waitForData = 0;
    private int executeMultiByteCommand = 0;
    private int multiByteChannel = 0;
    private int[] storedInputData = new int[MAX_DATA_BYTES];
    private bool parsingSysex;
    private int sysexBytesRead;

    private volatile int[] digitalOutputData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    private volatile int[] digitalInputData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    private volatile int[] analogInputData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    private int majorVersion = 0;
    private int minorVersion = 0;
    // private Thread readThread = null;
    private object locker = new object();

    /*
    Guid serviceGuid = Guid.Parse("00001101-0000-1000-8000-00805f9b34fb");
    RfcommDeviceService rfcommService;
    StreamSocket socket;
    DataWriter writer;
    DataReader reader;
    */

    BluetoothSocket _socket;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="serialPortName">String specifying the name of the serial port. eg COM4</param>
    /// <param name="baudRate">The baud rate of the communication. Default 115200</param>
    /// <param name="autoStart">Determines whether the serial port should be opened automatically.
    ///                     use the Open() method to open the connection manually.</param>
    /// <param name="delay">Time delay that may be required to allow some arduino models
    ///                     to reboot after opening a serial connection. The delay will only activate
    ///                     when autoStart is true.</param>
    public Arduino(string serialPortName, Int32 baudRate, bool autoStart, int delay)
    {
        /*
        _serialPort = new SerialPort(serialPortName, baudRate);
        _serialPort.DataBits = 8;
        _serialPort.Parity = Parity.None;
        _serialPort.StopBits = StopBits.One;
        */
        if (autoStart)
        {
            this.delay = delay;
            this.Connect();
            this.Open();
        }
    }

    /// <summary>
    /// Creates an instance of the Arduino object, based on a user-specified serial port.
    /// Assumes default values for baud rate (115200) and reboot delay (8 seconds)
    /// and automatically opens the specified serial connection.
    /// </summary>
    /// <param name="serialPortName">String specifying the name of the serial port. eg COM4</param>
    public Arduino(string serialPortName) : this(serialPortName, 115200, true, 8000) { }

    /// <summary>
    /// Creates an instance of the Arduino object, based on user-specified serial port and baud rate.
    /// Assumes default value for reboot delay (8 seconds).
    /// and automatically opens the specified serial connection.
    /// </summary>
    /// <param name="serialPortName">String specifying the name of the serial port. eg COM4</param>
    /// <param name="baudRate">Baud rate.</param>
    public Arduino(string serialPortName, Int32 baudRate) : this(serialPortName, baudRate, true, 8000) { }

    /// <summary>
    /// Creates an instance of the Arduino object using default arguments.
    /// Assumes the arduino is connected as the HIGHEST serial port on the machine,
    /// default baud rate (115200), and a reboot delay (8 seconds).
    /// and automatically opens the specified serial connection.
    /// </summary>
    public Arduino() : this(Arduino.list().ElementAt(list().Length - 1), 115200, false, 8000) { }


    public void Connect()
    {
        BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter;
        if (adapter == null)
        {
            throw new Exception("No Bluetooth adapter found.");
        }
        if (!adapter.IsEnabled)
        {
            throw new Exception("Bluetooth adapter is not enabled.");
        }
        BluetoothDevice device = (from bd in adapter.BondedDevices
                                    where bd.Name == "HC-06"
                                    select bd).FirstOrDefault();
        if (device == null)
        {
            throw new Exception("Named device not found.");
        }
        _socket = device.CreateRfcommSocketToServiceRecord(UUID.FromString("00001101-0000-1000-8000-00805f9b34fb"));
        _socket.Connect();
        return;
    }

    /// <summary>
    /// Opens the serial port connection, should it be required. By default the port is
    /// opened when the object is first created.
    /// </summary>
    public void Open()
    {
        // _serialPort.Open();

        // Thread.Sleep(delay);

        byte[] command = new byte[2];

        for (int i = 0; i < 6; i++)
        {
            command&#91;0&#93; = (byte)(REPORT_ANALOG | i);
            command&#91;1&#93; = (byte)1;
            // _serialPort.Write(command, 0, 2);
            _socket.OutputStream.Write(command, 0, command.Length);
        }

        for (int i = 0; i < 2; i++)
        {
            command&#91;0&#93; = (byte)(REPORT_DIGITAL | i);
            command&#91;1&#93; = (byte)1;
            // _serialPort.Write(command, 0, 2);
            _socket.OutputStream.Write(command, 0, command.Length);
        }
        command = null;

        /*
        if (readThread == null)
        {
            readThread = new Thread(processInput);
            readThread.Start();
        }
        */
    }

    /// <summary>
    /// Closes the serial port.
    /// </summary>
    public void Close()
    {
        // readThread.Join(500);
        // readThread = null;
        // _serialPort.Close();
        _socket.Close();
        _socket = null;
    }

    /// <summary>
    /// Lists all available serial ports on current system.
    /// </summary>
    /// <returns>An array of strings containing all available serial ports.</returns>
    public static string[] list()
    {
        // return SerialPort.GetPortNames();
        return new string[] { "HC-06" };

    }

    /// <summary>
    /// Returns the last known state of the digital pin.
    /// </summary>
    /// <param name="pin">The arduino digital input pin.</param>
    /// <returns>Arduino.HIGH or Arduino.LOW</returns>
    public int digitalRead(int pin)
    {
        return (digitalInputData[pin >> 3] >> (pin & 0x07)) & 0x01;
    }

    /// <summary>
    /// Returns the last known state of the analog pin.
    /// </summary>
    /// <param name="pin">The arduino analog input pin.</param>
    /// <returns>A value representing the analog value between 0 (0V) and 1023 (5V).</returns>
    public int analogRead(int pin)
    {
        return analogInputData[pin];
    }

    /// <summary>
    /// Sets the mode of the specified pin (INPUT or OUTPUT).
    /// </summary>
    /// <param name="pin">The arduino pin.</param>
    /// <param name="mode">Mode Arduino.INPUT or Arduino.OUTPUT.</param>
    public void pinMode(int pin, int mode)
    {
        byte[] message = new byte[3];
        message[0] = (byte)(SET_PIN_MODE);
        message[1] = (byte)(pin);
        message[2] = (byte)(mode);
        // _serialPort.Write(message, 0, 3);
        _socket.OutputStream.Write(message, 0, message.Length);
        message = null;
    }

    /// <summary>
    /// Write to a digital pin that has been toggled to output mode with pinMode() method.
    /// </summary>
    /// <param name="pin">The digital pin to write to.</param>
    /// <param name="value">Value either Arduino.LOW or Arduino.HIGH.</param>
    public void digitalWrite(int pin, int value)
    {
        int portNumber = (pin >> 3) & 0x0F;
        byte[] message = new byte[3];

        if (value == 0)
            digitalOutputData[portNumber] &= ~(1 << (pin & 0x07));
        else
            digitalOutputData&#91;portNumber&#93; |= (1 << (pin & 0x07));

        message&#91;0&#93; = (byte)(DIGITAL_MESSAGE | portNumber);
        message&#91;1&#93; = (byte)(digitalOutputData&#91;portNumber&#93; & 0x7F);
        message&#91;2&#93; = (byte)(digitalOutputData&#91;portNumber&#93; >> 7);
        // _serialPort.Write(message, 0, 3);
        _socket.OutputStream.Write(message, 0, message.Length);
    }

    /// <summary>
    /// Write to an analog pin using Pulse-width modulation (PWM).
    /// </summary>
    /// <param name="pin">Analog output pin.</param>
    /// <param name="value">PWM frequency from 0 (always off) to 255 (always on).</param>
    public void analogWrite(int pin, int value)
    {
        byte[] message = new byte[3];
        message[0] = (byte)(ANALOG_MESSAGE | (pin & 0x0F));
        message[1] = (byte)(value & 0x7F);
        message[2] = (byte)(value >> 7);
        // _serialPort.Write(message, 0, 3);
        _socket.OutputStream.Write(message, 0, message.Length);
    }

    private void setDigitalInputs(int portNumber, int portData)
    {
        digitalInputData[portNumber] = portData;
    }

    private void setAnalogInput(int pin, int value)
    {
        analogInputData[pin] = value;
    }

    private void setVersion(int majorVersion, int minorVersion)
    {
        this.majorVersion = majorVersion;
        this.minorVersion = minorVersion;
    }

    /*
    private int available()
    {
        return _serialPort.BytesToRead;
    }
    */
} // End Arduino class

接続あたりは、

Android から Bluetooth+RFCOMM を利用してモーター制御をする | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/6826

と同じように書いています。独自に RFCOMM を使った場合は自前で Android/Arduino のプロトコルを作らなければいけませんが(とはいえ、自分の場合は 8 バイト固定にしてあるの簡単)、Firmata プロトコルを使うと、GPIO 等をそのまま使う分には手軽です。
バイナリ送信をしているとこもそのまま移植。今回はテスト的なものなので、Android の受信側は省略しました。もうちょっと整理して、そのうち github へ。

マニフェストを設定する

Bluetooth を扱うので、パーミッションを設定しておきます。
たぶん、”BLUETOOTH” だけチェックすれば ok です。

UI と MainActivity

こんな画面を作っておきます。

5ピンに LED をつけるので、pinMode などを設定します。

public class MainActivity : Activity
{
    Arduino arduino;
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        // Set our view from the "main" layout resource
        SetContentView(Resource.Layout.Main);

        // Get our button from the layout resource,
        // and attach an event to it
        arduino = new Arduino();
        FindViewById<Button>(Resource.Id.buttonConnect).Click += (s, e) => { 
            arduino.Connect();
            FindViewById<Button>(Resource.Id.buttonConnect).Text = "connected.";
        };
        FindViewById<Button>(Resource.Id.buttonOpen).Click += (s, e) => { 
            arduino.Open();
            FindViewById<Button>(Resource.Id.buttonOpen).Text = "Firmata opend";
            arduino.pinMode(5, Arduino.OUTPUT);
            arduino.digitalWrite(5, Arduino.LOW);
        };

        FindViewById<Button>(Resource.Id.buttonLEDon).Click += OnClickLedOn;
        FindViewById<Button>(Resource.Id.buttonLEDoff).Click += OnClickLedOff;
    }

    void OnClickLedOn(object sender, EventArgs e)
    {
        arduino.digitalWrite(5, Arduino.HIGH);
    }
    void OnClickLedOff(object sender, EventArgs e)
    {
        arduino.digitalWrite(5, Arduino.LOW);
    }
}

Connect と Open は同時にやってもいいと思います。RFCOMM へのアクセスを Sync のほうの非同期関数を使えばよかったのですが、ひとまず同期的に作っています。まあ、受信回り(温度や湿度データとか)をきちんと作って、await/async を使えば結構すっきりするハズです。

実行してみる

ビルドをして実機で実行してみます。うちの Android は 4.1.2 という古いタイプなのですが正常に動作しました。Bluetooth 経由なので、アクセスポイントとかが必要ないので戸外でも使えますよね。まあ、戸外で使って、どうということはないのですが。

これはこれで整理して、後で Arduino 戦車も動かせるように組み直しいきましょう。あと、適当な距離センサーや加速度センサーを付けて、値をとれるようにいておきます。

カテゴリー: Android, Arduino, Xamarin | Firmata を使って Xamarin.Android から Arduino に接続する はコメントを受け付けていません

Windows Phone 10 から Windows Remote Arduino を利用する

Windows Remote Arduino を利用して Arduino 戦車を動かす | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/7168

先日の続きで、Windows Phone 10 のユニバーサルアプリで Windows Remote Arduino を使います。UI は、Windows ストアアプリで作ったものをそのまま流用します。Windows 10 上の Visual Studio 2015 RC ならば、そのまま XAML デザイナが動くのですが、手元の PC では Windows 8.1 上に VS2015RC を入れているのでデザイナが動きません…が、基本的なところは 8.1 の XAML と同じなのでそのままコピーして使えます。

ms-iot/remote-wiring の Microsoft.Maker.win10 のほうをコピーして使います。

image

Package.appxmanifest ファイルに、bluetooth.rfcomm の記述を追加しておきます。

 

 
  <Capabilities>
    <Capability Name="internetClient" />
    <DeviceCapability Name="bluetooth.rfcomm">
      <Device Id="any">
        <Function Type="name:serialPort" />
      </Device>
    </DeviceCapability>
  </Capabilities>

 

8.1 のときと違って、m2: のプレフィックスが要りません。これを、ARM でビルドして、Windows Phone 10 に送り込みます。手元では Lumia 1520 で確認しています。

https://pbs.twimg.com/media/CFTG3JGUIAA-SJe.jpg

あらかじめ、Bluetooth とのペアリングをしておく必要があるのは Windows ストアアプリのときと同じです。初回起動時のみ接続先の Bluetooth を使うかどうかのダイアログが表示されます。

コードは、Windows Remote Arduino を利用して Arduino 戦車を動かす | Moonmile Solutions Blog のものと全く同じなので省略。ユニバーサルアプリで作ったので、そのままデスクトップの Windows 10 でも動くはずです(動作は確認していませんが)。そんなわけで、ひとまず、Windows ストアアプリと Windows Phone アプリから動作したので完了。

これを C# 版にして、Xamarin.Android で動くようにすれば、Android から Firmata を使って Arduino を操作することも可能になりますね…と、まずは C++ から C# への移植作業をしないといけないのですが、それはまた後日。

カテゴリー: Arduino, Win IoT, Windows Phone | Windows Phone 10 から Windows Remote Arduino を利用する はコメントを受け付けていません

Windows Remote Arduino を利用して Arduino 戦車を動かす

Windows Remote Arduino を Arduino Uno/Nano で試す | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/7158

の続きです。Arduino に Firmata を入れることによってクロスプラットフォームで Arduino をリモート操作することが可能です。ということで、手元にある Arduino 戦車に Firmata を入れ直し、Windows Remote Arduino を使ってタブレットから動かしてみます。

L293D 利用して Arduino 戦車を作る | Moonmile Solutions Blog で作った Arduino 戦車は自前で シリアル通信部分を書いているのですが、Firmata のフォーマットに直せば、Ruby とか Node.js からも動かせるようになりますよね。そのあたりの相互通信は Alljoyn でやるようになるハズなのですが、ひとまず Firmata で動かします(デファクトスタンダードっぽいし)。

image

こんな感じの UI を作って、ボタンをタップして Arduino 戦車を動かします。コードで、AddHandler しているのは、ボタンをタップしている間だけモーターを動かしたかったからです。途中で通信が暴走すると、戦車自体が暴走してしまうので、デフォルトで戦車が止まるようにしました。

プロジェクト構成は、Microsoft.Maker.* をソリューションに加えてビルド。メインの ArduinoTankFirmata プロジェクトから各プロジェクトを参照設定しておきます。

image

ざっと書いたのが以下のコードです。Arduino 戦車の Bluetooth モジュールの名前が「HC-05」になっています。モータ駆動のためのピン(5-7,8-10)をいちいち制御しないといけませんが、Arduino の Sketch と同じように書くことができます。

 
public sealed partial class MainPage : Page
{
    BluetoothSerial bluetooth;
    RemoteDevice arduino;

    public MainPage()
    {
        this.InitializeComponent();
        this.Loaded += MainPage_Loaded;

        foreach (var b in new Button[] { ledRed, ledYellow, ledBlue })
        {
            b.AddHandler(PointerPressedEvent, new PointerEventHandler(OnLedPressed), true);
            b.AddHandler(PointerReleasedEvent, new PointerEventHandler(OnLedReleased), true);
        }
        tankStop.Click += (s, e) => { MotorLeftStop(); MotorRightStop(); };
        foreach (var b in new Button[] { tankForward, tankBack, tankLeft, tankRight })
        {
            b.AddHandler(PointerPressedEvent, new PointerEventHandler(OnTankPressed), true);
            b.AddHandler(PointerReleasedEvent, new PointerEventHandler(OnTankReleased), true);
        }

    }

    void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        bluetooth = new BluetoothSerial("HC-05");
        arduino = new RemoteDevice(bluetooth);
        bluetooth.ConnectionEstablished += bluetooth_ConnectionEstablished;
        bluetooth.begin(9600, SerialConfig.SERIAL_8N1);
    }

    async void bluetooth_ConnectionEstablished()
    {
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
            this.textStatus.Text = "接続しました";
        });
    }

    const int pinLedRed = 13;
    const int pinLedYellow = 12;
    const int pinLedBlue = 11;

    const int pinMotorAPower = 10;
    const int pinMotorAOut1 = 9;
    const int pinMotorAOut2 = 8;
    const int pinMotorBPower = 5;
    const int pinMotorBOut1 = 6;
    const int pinMotorBOut2 = 7;

    void OnLedPressed(object sender, PointerRoutedEventArgs e)
    {
        if (sender == ledRed) arduino.digitalWrite(pinLedRed, PinState.HIGH);
        if (sender == ledYellow) arduino.digitalWrite(pinLedYellow, PinState.HIGH);
        if (sender == ledBlue) arduino.digitalWrite(pinLedBlue, PinState.HIGH);
    }
    void OnLedReleased(object sender, PointerRoutedEventArgs e)
    {
        if (sender == ledRed) arduino.digitalWrite(pinLedRed, PinState.LOW);
        if (sender == ledYellow) arduino.digitalWrite(pinLedYellow, PinState.LOW);
        if (sender == ledBlue) arduino.digitalWrite(pinLedBlue, PinState.LOW);
    }

    void OnTankPressed(object sender, PointerRoutedEventArgs e)
    {
        if (sender == tankForward) { MotorLeftForward(); MotorRightForward(); };
        if (sender == tankBack) { MotorLeftBack(); MotorRightBack(); };
        if (sender == tankLeft) { MotorLeftBack(); MotorRightForward(); };
        if (sender == tankRight) { MotorLeftForward(); MotorRightBack(); };

    }
    void OnTankReleased(object sender, PointerRoutedEventArgs e)
    {
        MotorLeftStop();
        MotorRightStop();
    }

    void MotorLeftStop()
    {
        arduino.digitalWrite( pinMotorAPower, PinState.LOW );
        arduino.digitalWrite(pinMotorAOut1, PinState.LOW);
        arduino.digitalWrite(pinMotorAOut2, PinState.LOW);
    }
    void MotorLeftForward()
    {
        arduino.digitalWrite(pinMotorAPower, PinState.HIGH);
        arduino.digitalWrite(pinMotorAOut1, PinState.LOW);
        arduino.digitalWrite(pinMotorAOut2, PinState.HIGH);
    }
    void MotorLeftBack()
    {
        arduino.digitalWrite(pinMotorAPower, PinState.HIGH);
        arduino.digitalWrite(pinMotorAOut1, PinState.HIGH);
        arduino.digitalWrite(pinMotorAOut2, PinState.LOW);
    }
    void MotorRightStop()
    {
        arduino.digitalWrite(pinMotorBPower, PinState.LOW);
        arduino.digitalWrite(pinMotorBOut1, PinState.LOW);
        arduino.digitalWrite(pinMotorBOut2, PinState.LOW);
    }
    void MotorRightForward()
    {
        arduino.digitalWrite(pinMotorBPower, PinState.HIGH);
        arduino.digitalWrite(pinMotorBOut1, PinState.LOW);
        arduino.digitalWrite(pinMotorBOut2, PinState.HIGH);
    }
    void MotorRightBack()
    {
        arduino.digitalWrite(pinMotorBPower, PinState.HIGH);
        arduino.digitalWrite(pinMotorBOut1, PinState.HIGH);
        arduino.digitalWrite(pinMotorBOut2, PinState.LOW);
    }
}

このプログラムを、去年の de:code で貰った Toshiba のタブレットに入れて動作確認してます。Toshiba タブレットに Windows 10 IP を入れようとしたのですが、失敗してしまったので、Windows 8.1 になります。

https://pbs.twimg.com/media/CFJIDyFVEAAvMYf.jpg

ライントレースのような自律的な動きをする場合は、Firmata 経由では冗長な処理になっていしまうのですが(センサーを BT で飛ばして PC で制御するというスタイルになるので)、Arduino のセンシングデータをそのまま送ってきたり、今回のようにユーザーが直接コントロールする場合には、さっくりと作れてよさそうです。GPIO の使い方は Sketch に似ているんですが、個人的には .NET micro 風にしてもよかったのでは?と思っていますが。まあ、そのあたりは自分で拡張メソッドを作ればよいわけで、その辺はいずれ。

カテゴリー: Arduino, Win IoT | Windows Remote Arduino を利用して Arduino 戦車を動かす はコメントを受け付けていません

Windows Remote Arduino を Arduino Uno/Nano で試す

ホーム – 組込みシステム開発技術展 | ESEC の開催中だし、de:code の準備で忙しいのか、誰もがスルーしたままの Windows Remote Arduino を試してみました。

image

Windows Remote Arduino が何をするものかを簡単に言うと、Arduino にサーバーを入れて、Bluetooth 経由などで Windows 8.1/10 や Windows Phone から GPIO などを操作しようという仕組みです。Arudino 側には Firmata というライブラリがあって、すでに色々な言語(Ruby とか Javascript/Node.js など)からアクセスできるようになっています。Arduino IDE では、「ファイル」→「スケッチの例」→「Firmata」→「StandardFirmata」で、コードが開けるのでビルドしてインストールができます。

部品を準備する

サイトを見れば、さっくりと動く?のかどうかはわかりませんが、まあ試行錯誤をすれば30分程で動くようにはなります。手持ちの Arduino と Bluetooth モジュールによってちょこちょことソースを書き換えないといけないのが、嵌りどころではあるのですが。

  • Visual Studio 2013/2015RC
    Microsoft.Maker のライブラリをビルドするに必要です。本ブログで解説するコードは Windows 8.1/VS2013 で動作確認しています。
  • Windows 側の Bluetooth ドングル
    ノート PC だったりすると内蔵なんですが、据え置き PC だとドングルを刺して。
  • Arduino Uno など。
    Firmata が動けば何でも。
  • Bluetooth モジュール
    Microsoft のサイトでは、SparkFun Bluetooth Mate Silver を使っていますが何でもいいです。私は手持ちの HC-05/06 で動作確認しています。BT2.0 のシリアル通信を使うので BLE の必要はありません。
  • ブレッドボード、ジャンパーワイヤー、LED など
    ここは電子工作的に。

Arduino に Firmata を焼く

「ファイル」→「スケッチの例」→「Firmata」→「StandardFirmata」を開いて、ビルドします。

image

この時、Arduino 側に差し込む Bluetooth のシリアル通信速度を設定し直します。デフォルトでは、57600 のように高速になっているので、HC-05 の初期値の 9600 に設定し直します。このあたりは、手持ちの Bluetooth モジュールに合わせます(私の AT コマンドが面倒で、そのまま 9600 で使っています)

Firmata.begin(9600);

Windows Remote Arduino のライブラリをダウンロードする

Windows IoT – SetupPCWRA の Option 1: Install the NuGet package を使えば、一発で Nuget で取れるようなことが書いてありますが…まだ、NuGet 上にはありません(Windows 8.1からも見つからない)。仕方がないので github からダウンロードしてビルドをします。

ライブラリは、以下からダウンロードできます。win8.1 と win10 の違いは、win8.1 が従来の Windows ストアと Phone のユニバーサルアプリで作ってあって、win10 は新しい Universal なプロジェクトで作ってあります。どちらもライブラリとし使うだけなので、UI は必要ないのですが、どうやらコードが C++/CX で書いているためなのか、8.1 では PC と Phone とのライブラリを別々に作らないとだめなようです。win10 のほうはひとつにまとまっています。

ms-iot/remote-wiring · GitHub
https://github.com/ms-iot/remote-wiring

これと、Lチカをさせるためのコードをダウンロードします。

ms-iot/windows-remote-arduino-samples · GitHub
https://github.com/ms-iot/windows-remote-arduino-samples

内部的に RFCOMM を使っているのでマニフェストファイルを手作業で修正する必要があります。remote-wiring のプロジェクトを参照させるために、remote-wiring フォルダにライブラリのプロジェクトを丸ごとコピーしておきます。

 

public MainPage()
{
    this.InitializeComponent();
    // bluetooth = new BluetoothSerial("RNBT-E072");
    bluetooth = new BluetoothSerial("HC-06");

    arduino = new RemoteDevice(bluetooth);
    bluetooth.ConnectionEstablished += OnConnectionEstablished;

    //these parameters don't matter for bluetooth
    bluetooth.begin(9600, SerialConfig.SERIAL_8N1);
}

 

ここでも、シリアル通信の速度「9600」と、Bluetooth モジュールの名前「HC-05」のように設定し直しておきます。あとは、x86 あたりでビルドをすれば ok です。x64 でビルドをすると XAML デザイナが動かないので、ひとまず x86 で動作確認するとよいでしょう。Any CPU で動かないのは C++/CX を使っているためです。

ちなみにLチカさせているところはこんな感じ。RemoteDevice クラスで取得した arduino オブジェクトを使って、digitalWrite してます。このあたりは Android の Scketch に似せているみたい。
個人的には .NET Micro に寄せたほうが良いのでは?と思うのですが、そこは OSS なので自己拡張で。

private void OnConnectionEstablished()
{
    //enable the buttons on the UI thread!
    var action = Dispatcher.RunAsync( Windows.UI.Core.CoreDispatcherPriority.Normal, new Windows.UI.Core.DispatchedHandler( () => {
        OnButton.IsEnabled = true;
        OffButton.IsEnabled = true;
    }));
}

private void OnButton_Click( object sender, RoutedEventArgs e )
{
    //turn the LED connected to pin 5 ON
    arduino.digitalWrite( 5, PinState.HIGH );
}

private void OffButton_Click( object sender, RoutedEventArgs e )
{
    //turn the LED connected to pin 5 OFF
    arduino.digitalWrite( 5, PinState.LOW );
}

Bluetooth とペアリングする

Windows 8.1 のとき、BT2.0を接続する場合はペアリングが必要です。Windows 10 で BLE 接続する場合はペアリングなしでいけるらしいのですが、そこはまだ確認してません。

image

HC-05 のピンコードは「1234」です。

Arduino Uno で動かす

Bluetooth モジュールへの配線は MS のサイト で確認してもらうとして(RX/TXを接続するだけの簡単なものです)Uno 互換機で動かした結果がこんな感じです。

https://pbs.twimg.com/media/CFAp8IxUgAA88WX.jpg:large

デスクトップのストアアプリから Lチカができますね。まあ、Lチカだけやっても面白くないので、後でモーターかサーボモーターを動かしてみましょう。センサーの類も取得できるはずですね

Arduino Nano で動かす

USB 給電ではなくて、乾電池から給電させて Arduino Nano で動かします。これは、互換機なんで 300円前後のものです。Firmata + Windows の組み合わせが、手軽に?Windows Remote Android で実現ができます、ってことですね。

https://pbs.twimg.com/media/CFA0RLJUEAAlymU.jpg:large

ここでは、PC から扱っているわけですが、Windows Phone 経由にもできるだろうし、あるいは Ruby や Node.js を使っても同じインターフェースでできるということです。そのあたりは、クライアントアプリの切り替えが自由になって、便利な仕組みです。

カテゴリー: Arduino, Win IoT | Windows Remote Arduino を Arduino Uno/Nano で試す はコメントを受け付けていません

Win IoT に固定IPを指定する

ハンズオン用に RPi2 を持っていくのですが、現状の Win IoT では WiFi が使えません。MS 品川会場でホットスポットが使えないので、ローカルネットワークを組むことになりますが、DHCP でしか IP を割り振るとなるとルータが必要になって結構大変なので、固定 IP を指定するようにします。そうすると、ハブだけでいけます。インターネットには繋がりませんが。

GUI で設定できるような項目はないのですが、Win IoT に PowerShell でログインができるので、これを使います。

Win Iot on RPi にログインする

ログイン自体は以下のようにします。このあたり、powershell を知っていないと難しいんですが…知っていれば簡単なものなのでしょうか?

Enter-PsSession -ComputerName minwinpc -Credential minwinpc\Administrator

アドレスを確認する

PowerShell で IP アドレスの設定
https://www.upken.jp/kb/powershll-setipaddress.html

を参考にしながら設定していきます。上記のサイトでは IPv6 ですが、IPv4 に読み替えれば設定ができます。

Get-NetIPAddress で InterfaceIndex を確認します。大抵の場合、2 です。

Get-NetIPAddress

IPAddress         : 172.16.0.8
InterfaceIndex    : 2
InterfaceAlias    : Ethernet
AddressFamily     : IPv4
Type              : Unicast
PrefixLength      : 24
PrefixOrigin      : Dhcp
SuffixOrigin      : Dhcp
AddressState      : Preferred
ValidLifetime     : 23:03:26
PreferredLifetime : 23:03:26
SkipAsSource      : False
PolicyStore       : ActiveStore

固定IPを設定する

New-NetIPAddress で設定をします。PrefixOrigin が “Manual” になることでわかります。

New-NetIPAddress -InterfaceIndex 2 -IPAddress "172.16.0.99" -PrefixLength 24

IPAddress         : 172.16.0.99
InterfaceIndex    : 2
InterfaceAlias    : Ethernet
AddressFamily     : IPv4
Type              : Unicast
PrefixLength      : 24
PrefixOrigin      : Manual
SuffixOrigin      : Manual
AddressState      : Tentative
ValidLifetime     : Infinite ([TimeSpan]::MaxValue)
PreferredLifetime : Infinite ([TimeSpan]::MaxValue)
SkipAsSource      : False
PolicyStore       : ActiveStore

IPAddress         : 172.16.0.99
InterfaceIndex    : 2
InterfaceAlias    : Ethernet
AddressFamily     : IPv4
Type              : Unicast
PrefixLength      : 24
PrefixOrigin      : Manual
SuffixOrigin      : Manual
AddressState      : Invalid
ValidLifetime     : Infinite ([TimeSpan]::MaxValue)
PreferredLifetime : Infinite ([TimeSpan]::MaxValue)
SkipAsSource      : False
PolicyStore       : PersistentStore

ネットマスクを 255.255.255.0 にする場合は 24 にします。

固定IP を変更する

Remove-NetIPAddress した後に New-NetIPAddress します。逆でも IP を指定すれば大丈夫みたいです。

DHCP に戻す

DHCP が割り振る IP に戻すには Set-NetIPInterface を使います。PrefixOrigin が “Dhcp” に戻ります。

Set-NetIPInterface -InterfaceIndex 2 -AddressFamily IPv4 -Dhcp Enabled

IPAddress         : 172.16.0.8
InterfaceIndex    : 2
InterfaceAlias    : Ethernet
AddressFamily     : IPv4
Type              : Unicast
PrefixLength      : 24
PrefixOrigin      : Dhcp
SuffixOrigin      : Dhcp
AddressState      : Preferred
ValidLifetime     : 23:59:47
PreferredLifetime : 23:59:47
SkipAsSource      : False
PolicyStore       : ActiveStore

コンピュータ名を変える

以下のように setcomputername で変更ができます。名前を確認したいときは hostname です。

setcomputername minwinpc1 

このあたりは、Linux と似ているので想像がつくんですけどね。Linux コマンドとの対応表がほしい。

カテゴリー: RaspberryPi, Win IoT | Win IoT に固定IPを指定する はコメントを受け付けていません

Win IoT on RPi で WebView を試す

基本、デバイスには豊富な UI は必要ない…のかもしれませんが、Win IoT Core には WebView というコントロールがあります。WebView 自体はストアアプリにもあるので、どれだけ表示されるかは IoT Core の実装次第になるわけですが、RPi2 の場合はそれなりに GPU が使える(描画の高速化も売りにしている)ので、手軽に画像やテキストを表示させるための WebView にも期待したいところです。たぶん、DOM 解析もできるから HTML をダウンロードしてきてパースしたり、逆にリソースに含めてある HTML を読みだして jQuery で制御するという技も可能なはずです。

実験用アプリ

WebView を貼り付けた簡単なアプリを作っておきます。私の RPi の場合、USB マウス/キーボードの両方とも効かないで、PC から URL を送る仕組みを作っておきます。先日作った SimpleWebServer を利用してアドレスを送れるようにします。

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.Loaded += MainPage_Loaded;
    }

    SimpleWebServer _server;
    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        _server = new SimpleWebServer();
        _server.OnReceived += _server_OnReceived;
        _server.Start();
        // this.webView.Navigate(new Uri(textUrl.Text));
    }

    private async void _server_OnReceived(string data)
    {
        var url = data.Substring(1);    // 最初の "/" のみ消す
        // GET 受信
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
            () => {
                textUrl.Text = url;
                this.webView.Navigate(new Uri(url));

            });
        // 応答を送信
        _server.SendResponse("response: " + data);
    }
}

こんな風に送られてきたデータを、WebView.Navigate しているだけです。

いろいろ動かしてみる

ノーマルに自分の wordpress のサイトを表示させます。User Agenet がモバイルになっているらしく、スマートフォンの画面が表示されます。このあたりは、WebView のほうで指定が可能なはずです。

https://pbs.twimg.com/media/CEso-6tUEAAJYKr.jpg

重たそうな microsoft.com を表示させます。中央のダイアログが出てきたところを見ると、Javascript が動いていることがわかりますね。ちなみに、マウスが動かないので、これ以上進めませんが(苦笑)。

https://pbs.twimg.com/media/CEso-5SUsAEIDdM.jpg

どうせなので、Raspberrypi.org も表示させます。レイアウト崩れもないので、CSS がきちんと対応しているのでしょう。

https://pbs.twimg.com/media/CEsqnvNVAAAeonC.jpg:large

twitter.com も表示できます。キーボードが無反応なので、投稿とかはできませんが、タイムラインが表示されているところをみると、Twitter API がきちんと呼び出されているようです。Win IoT は SSL/TLS 対応なので https も通ります。

https://pbs.twimg.com/media/CEssUhTUUAAkmqq.jpg:large

ちょっと複雑なところで、SVGとjQueryで絵を描いているようなアニメーションを実装する方法 | 株式会社LIG にある SVG + jQuery のアニメーションを表示させます。えらく遅いですが、きれいに SVG と jQuery が動きます。これだけ動けば、たいていのサイトは表示できるのではないかと。

https://pbs.twimg.com/media/CEst9ZLUsAAwL8I.jpg:large

Youtube は HTML5 モードで動いているはずです。マウスでクリックできないので、動画が再生できるかどうかは不明なのですが(あちこちのサイトを探したけど、自動再生にはなりませんでした)、まあ、大丈夫なのでは。

https://pbs.twimg.com/media/CEswOxWUMAAQKkL.jpg:large

実験的に mpeg 動画を自サイトにアップして表示させると、うまく動きます。読み込みに少々時間がかかりますが、きれいに動きているのでこれで十分かと。音声が出なかったのは、4線プラグのほうで出ているのか、HDMI の音声出力に対応していないのかは不明です。

https://pbs.twimg.com/media/CEs0IdqUsAAjr6n.jpg:large

これらは、WebView コントロール内で表示させているのですが、XAML 内に適当なコントロール(メディアコントロールなど)を貼り付けても動くと思います。Flash とか ogg は無理そうなので、そのあたりは今後の OSS 系のライブラリに期待です。C++/CX が使えるので、デコード関係のものを持ってくれば動くんじゃないですかね。ちなみに、プロジェクトは Universal で作っているので、そのまま Windows Phone や PC でも動作が可能です。この実験プログラム自体も、一度 PC 上で動作確認してから RPi2 で動かしています。

カテゴリー: RaspberryPi, Win IoT | Win IoT on RPi で WebView を試す はコメントを受け付けていません

.NETラボ勉強会 5/23(土)に Windows 10 IoT Core on Raspberry Pi のハンズオンをやります

今月の 5/23 土曜日に MS 品川で Win IoT on RPi のハンズオンを急遽やります。単純に今月のネタが決まっていなかったのと、たまたま手元に RasPi 2 が 5台あるので思い付きでやる企画です。

//build/ で Win IoT on RPi が発表された記念と、de:code 直前の前夜祭気分で。

.NETラボ 勉強会 2015年5月 : .NET Lab
http://www.dotnetlab.net/dnn/2015/05/net%e3%83%a9%e3%83%9c-%e5%8b%89%e5%bc%b7%e4%bc%9a-2015%e5%b9%b45%e6%9c%88/

「ハンズオン」とは言え、特に学習用のプリントとかは用意しません。Raspberry Pi 2 に Windows IoT Core を入れて試してみたい方が対象ですね。手元に RPi2 がある方は持ってきて貰ってよいし、手元にない方は、増田が5台分持っていくので、それを使ってもらいます(人数が多い場合は、適当にグループ実習みたいな感じを想定しています)。

.NET ラボで側で用意するものは、

  • Raspberry Pi 2 を 5台 + micro SD カード(Win IoT インストール済み)
  • RasPi をつなぐためのローカルネットワーク(ハブ、ルータ、ケーブル等)
  • LED、サーボモータ、センサーの類を諸々
  • HDIM 付きの液晶モニタ(MSさんにお頼み中)
    時期的にVGAモニタしかないそうなので、HDMItoVGA 変換器を募集中

あとは増田の手元にあるロボットアーム、meArm、Arduino戦車あたりを持って行って、実際に動かせるところまで「楽しみ」ます。

https://pbs.twimg.com/media/CEdk0FiUkAABnwg.jpg:large https://pbs.twimg.com/media/CEV4sMOUsAAL6gL.jpg:large

各自用意して頂くのは

  • Visual Studio 2015 RC が入ったノート PC(Windows 8.1でも可)
  • 適宜 LANケーブル、電源タップ

あたりです。なくっても、まあ見てるだけで楽しいかもしれません。

スケジュール(予定)

1.Windows IoT Core の簡単な説明と Raspberry Pi 2 との組み合わせ
2.簡単なLチカの実習
3.HDMI 接続で液晶モニタへ表示する。
4.サーボモータ制御などを具体的に

な感じでお昼から、午後いっぱい程度で済ませる予定。そのあとは、適当なライトニングトークスと懇親会ですかね。人数は特に絞ってはいませんが、常連さんを含めていつも程度(20名ちょっと?)を想定しています。

申し込みはこちら

5月23日 .NETラボ勉強会 2015年5月(東京都)
http://kokucheese.com/event/index/294991/

 

カテゴリー: RaspberryPi, Win IoT | .NETラボ勉強会 5/23(土)に Windows 10 IoT Core on Raspberry Pi のハンズオンをやります はコメントを受け付けていません

Windows 10 を入れた直後に VS2015RC を入れて起動するとXAMLデザイナが動かないときの対処方法

素の Windows 10 に Visual Studio 2015 RC を入れた後に、Universal アプリを作って XAML デザイナを開こうとすると「Enable Developer Mode for Widnws 10 …」というエラーが出ます。対処方法は書いてあるのですが、英語なのと、実際「設定」→「Update & security」→「for Developers」を開こうとすると、画面が落ちて設定できないという現象に陥ります。仕方がないので、ローカルグループポリシーエディタを使います。

Enable developer mode dialog that is displayed in Visual Studio

Enable your device for development – Windows app development
https://msdn.microsoft.com/en-us/library/windows/apps/dn706236.aspx#GroupPolicy

ここに書いてある設定通り進めばよいのですが、設定の手順が英語なので項目が見つけづらい。ヘルプが日本語化されていないので、メモとして残しておきます。

  1. コマンドプロンプトで、gpedit.msc を実行
    エラーダイアログが出るが、気にしない。
  2. 「管理テンプレート」→「Windows コンポーネント」→「アプリパッケージの展開」をクリック
  3. 「信頼できるすべてのアプリのインストールを許可する」をダブルクリックして、「有効」にする。
  4. 「Allows developement of Windows Store apps and …」をダブルクリックして、「有効」にする(これだけ日本語化されていない)。

image

これで、XAML デザイナが正常に動くようになります。

Windows IoT の場合は、「10″ IoT Device …」でレイアウトしていけば ok です。RPi2 を使って HDMI 接続するときは普通に HD ディスプレイになるので、サイズが違うんですけどね。

image

カテゴリー: Windows 10 | Windows 10 を入れた直後に VS2015RC を入れて起動するとXAMLデザイナが動かないときの対処方法 はコメントを受け付けていません

Win IoT/RT で StreamSocketListener を利用して簡易WebServerを作る

Win IoT on RPi では、HttpListener がありません。と言いますか、Universal アプリ自体に、System.Net.HttpListener がないので、何か代替案を考えないといけません。ってことで、すっかり忘れていた、Windows.Networking.StreamSocketListener を使うわけですが、ちょっと嵌ったのでメモを残しておきます。

StreamSocketListener をストアアプリで使う

もともと、(確か)Windows 8.1 で導入されてストアプリでも HTTP プロトコル待ちのサーバーが作れたところからスタートです。以前、HttpListener と TcpListener の違いと Firewall と netsh の設定 | Moonmile Solutions Blog; なところで、HttpListener を駆使していたのですが(この記事を書いたのは Windows 8 の頃)、netsh やら firewall やらの設定が大変で、どうにかならないかと思っていたところなのですが、Windows 8.1 ではあっさり解決されてしまいました。

でもって、当時のブログやら調べ直していたところ、Stack Overflow などを見ると、なかなかうまくいかないひとが多いようです。サンプルコードてきには、

Windows 8 StreamSocket sample サンプル 言語: C#, C++, JavaScript Visual Studio 2013 用
https://code.msdn.microsoft.com/windowsapps/StreamSocket-Sample-8c573931

が唯一正しいような気がします。本家にあたりましょう。とはいえ、この本家のサンプルにも落ち度があって、StreamSocketListener を使って HTTP プロトコル待ちをしているところは書かれていないんですよね。どちらかというと TcpListener に近いところにあるので、TCP/IP のデータを生で扱うようなものです。が、通常の Web API 的に使おうと思うとそのままでは結構面倒なので、HTTP プロトコル風にできると便利ですね。

幸いにして、Win IoT のサンプルの中に

App2App WebServer
https://github.com/ms-iot/samples/tree/develop/App2App%20WebServer

が、HTTP プロトコルを簡易実装しているので抜き出してみました。
ちなみに、Windows.Networking.StreamSocketListener 自体は、WinRT 内のクラスなのでデスクトップアプリから直接使えません(間接的に WinRT を参照設定させることで利用できますが)。デスクトップアプリの場合は、従来通り、HttpListener か TcpListener を使うとよいでしょう。

簡易的な WebServer クラスを実装する

ざっと実装したものが以下です。GET コマンドにしか対応していませんが、Web API で REST を使う限りはこれで十分です。まあ、そのうち画像を送る程度には POST に対応したいところですが。

/// <summary>
/// 簡易WebServerクラス
/// 単純な GET コマンドのみ対応する
/// </summary>
public class SimpleWebServer
{
    StreamSocketListener listener;
    StreamSocket socket;
    public event Action<string> OnReceived;

    // public Windows.Networking.HostName LOCALHOST { get; set; }
    public int PORT { get; set; }
    public SimpleWebServer()
    {
        // this.LOCALHOST = NetworkInformation.GetHostNames().Where(n => n.Type == Windows.Networking.HostNameType.Ipv4).First();
        // this.LOCALHOST = new Windows.Networking.HostName("localhost");
        this.PORT = 8080;
    }

    /// <summary>
    /// 受付開始
    /// </summary>
    public async void Start()
    {
        listener = new StreamSocketListener();
        listener.ConnectionReceived += Listener_ConnectionReceived;
        // await listener.BindEndpointAsync(LOCALHOST, PORT.ToString());
        await listener.BindServiceNameAsync(PORT.ToString());
    }
    private async void Listener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
    {
        socket = args.Socket;
        var dr = new DataReader(socket.InputStream);

        /// GET ヘッダを取り出し
        StringBuilder request = new StringBuilder();
        uint BufferSize = 1024;
        using (IInputStream input = socket.InputStream)
        {
            byte[] data = new byte[BufferSize];
            IBuffer buffer = data.AsBuffer();
            uint dataRead = BufferSize;
            while (dataRead == BufferSize)
            {
                await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);
                request.Append(Encoding.UTF8.GetString(data, 0, data.Length));
                dataRead = buffer.Length;
            }
        }
        // GET method を取り出し
        string requestMethod = request.ToString().Split('n')[0];
        string[] requestParts = requestMethod.Split(' ');
        var text = requestParts[1];

        /// GETコマンドの受信イベント
        if (this.OnReceived != null)
        {
            OnReceived(text);
        }
    }
    /// <summary>
    /// レスポンスを返す
    /// </summary>
    /// <param name="text"></param>
    public async void SendResponse( string text )
    {
        if (socket == null) return;

        byte[] bodyArray = Encoding.UTF8.GetBytes(text);
        MemoryStream stream = new MemoryStream(bodyArray);
        string header = String.Format("HTTP/1.0 200 OKrn" +
                            "Content-Length: {0}rn" +
                            "Connection: closernrn",
                            stream.Length);
        var dw = new DataWriter(socket.OutputStream);
        dw.WriteString(header);
        dw.WriteString(text);
        await dw.StoreAsync();
    }
}

利用するときは、こんな感じです。クライアントから受信をしたときの OnReceived イベントで処理を行います。応答は、そのまま SendResponse メソッドを使います。このあたり、ストリームにしてもよいでしょう。

SimpleWebServer _server;
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    this.textIP.Text = NetworkPresenter.GetCurrentIpv4Address();

    _server = new SimpleWebServer();
    _server.OnReceived += _server_OnReceived;
    _server.Start();
}

private async void _server_OnReceived(string data)
{
    // GET 受信
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
        () => { textGet.Text = data; });
    // 応答を送信
    _server.SendResponse("response " + data);
}

マニフェストを書き換える

現状 Visual Studio 2015 RC ではマニフェストファイル(Package.appxmanifest)の GUI が出て来ず、XML を直接編集することになります。
StreamSocketListener クラスで HTTP プロトコル待ちをさせたい場合は、internetClientServer(インターネット クライアント/サーバー)とprivateNetworkClientServer(プライベートネットワーク)を追加してください。これを書き忘れているブログが多くて、巷で「繋がらない」というのは、大方このせいです。

  <Capabilities>
    <Capability Name="internetClientServer" />
    <Capability Name="privateNetworkClientServer"/>
  </Capabilities>

これを設定しておくだけで、netsh やら firewall やらの設定をしなくて良くなります。多分、ストアアプリの範疇になるので、Windows 本体の TCP/IP のほうから切り離されている(WinRTで包まれている)のではないか、と想像します。

BindServiceNameAsync メソッドを使う

StreamSocketListener でサーバーを作るときに、どの URL とポートで待つのか?を指定するのですが、実際のところ、複数のアプリで同一ポートを共有できないので、実質ポートだけしてすれば ok です。また、BindEndpointAsync メソッドで、エンドポイントで指定される「ホスト名」を設定することができるのですが、あえて「localhost」のように同一マシン内に制限したり、「home-pc」のようにホスト名をわざわざ指定して外部アプリから使わせないようにしたりしない限りは、BindEndpointAsync メソッドを使う意味がありません。と言いますか、ここでホスト名を指定するのが結構面倒です。わざわざ名前を揃えるために IP アドレスを取ってきているものもあるのですが(stack overflow では大抵ここで嵌っています)、時に制限が必要ない場合はポート番号だけ指定する BindServiceNameAsync メソッドで十分です。このあたりは、先の MSDN のサンプルコードにも書いてあります。

IEから呼び出してみる

簡単な REST で呼び出せるようにしておくと、IE のようなブラウザからの操作が簡単にできます。

ブラウザで、アドレスを指定すると、

ストアアプリ側で受信できる

これは Windows 10 のユニバーサルアプリで作っていますが、ARM でコンパイルし直せばそのまま Windows IoT on RPi 上で動きます。このあたりは、UAP(Univesal Application Platform)の有利な点ですよね。タイトルが、for WiFi になっていますが、現在のところ Win IoT on RPi では WiFi のドングルは動作しません(リリース時には動作するとのこと)。現状では、有線 LAN オンリーですね。

という訳で、これで LAN 経由で Win IoT on RPi を制御する目途が立ったので、Raspberry Pi から GPIO で制御をする仕組みを入れていきます。ひとまず、先日買って Arduino で動かせるようになったロボットアームを動かす方を先にしようかなと。

カテゴリー: RaspberryPi, Win IoT, WinRT | Win IoT/RT で StreamSocketListener を利用して簡易WebServerを作る はコメントを受け付けていません