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



