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 は全然ダメなんですけど)。