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# 版に書き直す

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

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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 戦車に距離センサーを付けて自律化したときに試してみましょう。

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
29
30
31
32
33
34
35
36
37
38
39
40
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 パーマリンク