俺のラズパイ2で.NET Core 2.0なF#が動いたよ

俺のラズパイ2で.NET Core 2.0が動くまで | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/8732

では C# が動いたので、今度は F# で試してみましょう。
手順は、C# の時と一緒で、

  1. Raspberry Pi2/3でRasbianを用意する。
  2. PC上で、.NET Coreのプログラムを作る。
  3. PCからRaspberry Piにデプロイする(アセンブリをコピーする)

すれば ok です。コードが F# なので、デプロイするアセンブリに「FSharp.Core.dll」が含まれていれば、ああ F# で動いているね、ということになります。

F#なコンソールアプリを作る

プログラム言語を -lang で指定して、コンソールアプリを作ります。

dotnet new console -n hellofs -lang F#

できあがったコードは、こんな感じ。皆さん、見慣れた F# なコードですよね :)

// Learn more about F# at http://fsharp.org
open System
[<EntryPoint>]
let main argv =
    printfn &quot;Hello World from F#!&quot;
    0 // return an integer exit code

[/code]

ビルドする

ビルドは、C# と同じです。ARM 用に -r linux-arm のスイッチを付けて publish します。

dotnet build
dotnet publish -r linux-arm

転送する

ビルドができたら、WinSCP を使って publish フォルダ―を転送します。

この部分は、適当なバッチを作っておきたいところですね。

実行フラグを付ける

Tera term で Raspbian に接続して、chmod +x して実行権限を付けます。

chmod +x hellofs
./hellfs

いよいよ asp.net mvc code + F# ができるのか?

と思いきや、ビルド時にエラーがでています。

どうも Razor のコード出力あたりでエラーになっていますね。この部分、通常の ubuntu でやってもエラーになるので、普通に不具合じゃないかなと。プレビュー版だし。
確か、1.1 の時は mvc にしても linux 上(Raspberry Pi上じゃなくて、x64のUnbuntu上)
で動いたはずなので、いずれ直ると思います。

dotnet new webapi のほうは、linux-arm でも動くので、これは後で試します。次は、せっかくなので GPIO を使って .net core 2.0 + Lチカの巻へ。

 

カテゴリー: 開発, F#, RaspberryPi | 俺のラズパイ2で.NET Core 2.0なF#が動いたよ はコメントを受け付けていません

俺のラズパイ2で.NET Core 2.0が動くまで

俺のラズパイ3で.NET CoreなF#が動かないわけがない | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/8028

の記事が去年の8月末で、あれからほぼ1年が経ちました。経っているうちに、.NET Coreが2.0のプレビュー版になって、.NET Coreが動くようになりましたよ、という話。

core/roadmap.md at master ・ dotnet/core
https://github.com/dotnet/core/blob/master/roadmap.md#technology-roadmaps
Running a .NET Core 2 app on Raspbian Jessie, and deploying to the Pi with Cake | Jeremy Lindsay
https://jeremylindsayni.wordpress.com/2017/07/23/running-a-net-core-2-app-on-raspbian-jessie-and-deploying-to-the-pi-with-cake/
core/RaspberryPiInstructions.md at master ・ dotnet/core
https://github.com/dotnet/core/blob/master/samples/RaspberryPiInstructions.md
<b>C#</b>RaspberryPi 3に.NET Coreを入れてHello World! – Qiita
http://qiita.com/logikuma/items/de8c987dc2308a96256d

.NET Core 1.0 ではいつの間にか消えてしまった Raspberry Pi のサポートですが、.NET 2.0 になって復活していました。

動作環境

  • Raspberry Pi2と3の Rasbian/Ubuntu の環境で動く。
  • Raspberry Pi Zero では動かない。
  • Raspberry Pi 上ではビルドができない。
  • なので、WindowsやLinux上でビルドして、Raspberry Pi へデプロイする。

つまり、実行環境としての.NET Core関連のアセンブリをラズパイへ入れ込めば動作する。ビルドする場合は、別のPC/Linux/Macが必要、という訳。Zeroで動かないのは、.NET Coreの動作対象が armv7とarmv8 となっているためで、zero が使っている arm32 は対象になっていない、ということです。
となると、armv7/8 の cpu を使っている arm 関係の組み込みボードでもいけそうでは?という予想が立つので、おそらく nano pi あたりでも動くはずです。

Rasbian Pi上で動かす

現状では、.NET Coreのランタイムが

  • Rasbian(Raspberry Pi標準のOS)
  • Ubuntu
  • Windows IoT Core

上で動きます。Rasbianで動かすときは、Scratchとの連携とかnode.jsの代わりとか色々できそうです。Ubuntuは低電力の常時Webサーバーというのもありで、ちょっとしたサーバー機として使えます。Windows IoT Coreの場合は、モニタ絡みで先行きはUWP on .NET Standardな環境かなと思ったり。
自前ビルド環境がないのが辛いところですが(Rasbian/Ubuntuという開発環境なのに)、別途、.NET Core対応なコンパイラとかスクリプトを作れば、それはRaspberry Pi上でもするっと動くようになるのかなと思ったり、まあ、そういうときはmonoを使えばいい訳ですが。

さて、.NET Coreのランタイムかつアセンブリ(各種のDLLファイル)は、実行環境に何かのインストールをする必要はありません。すべて、ビルドするときのアセンブリで完結しています。ということは、Raspberry Pi 上でセルフビルドができないのですから、そもそも、何らかの.NET関係のアセンブリをインストールしておく必要はありません。

ということで、手順は簡単で、

  1. Raspberry Pi2/3でRasbianを用意する。
  2. PC上で、.NET Coreのプログラムを作る。
  3. PCからRaspberry Piにデプロイする(アセンブリをコピーする)

だけで十分です。

PC上で hello フォルダーを作成して dotnet new console します。

mkdir hello
cd hello
dotnet new console

あるいは

dotnet new console -n hello

Raspberry Pi のデプロイするために dotnet publish -r linux-arm します。

dotent build
dotnet publish -r linx-arm

ここで一度 build していますが、Raspberry Pi に転送するだけならば、publish だけで十分かもしれません。

hello/bin/Debug/netcoreapp2.0/linux-arm/publish の中身に hello を含む .NET Core 2.0 用のアセンブリがごっそり作られます。

publish フォルダの中身を、WinSCP などで Raspberry Pi に転送します。

Tera Term などで Raspberry Pi に繋ぎ、転送した publish フォルダにある hello だけに実行権限を付けます。

mv publish hello
chmod +x hello/hello

アセンブリに実行権限を付ける訳です。

後は、普通に hello を動かせば動作できます。

./hello

※ラズパイ上の dotnet コマンドはバージョンしか出さないので、おそらく dotnet-runtime-latest-linux-arm.tar.gz をダウンロード&展開する必要はないハズです。
※実行権限を chmode 775 -R すると全てのアセンブリに設定してしまうので、ピンポイントで chmod +x しています。

追記 2017/07/31

arm 上の dotnet コマンドですが、pc/linux にあるような dotnet run コマンドがありません…が、アセンブリを実行するために、dotnet ./hello.dll ってのができますね。なるほど。以下な感じで最新版を取ってきて、展開して使えます。

wget https://dotnetcli.blob.core.windows.net/dotnet/Runtime/release/2.0.0/dotnet-runtime-latest-linux-arm.tar.gz
mkdir /opt/dotnet
tar -xvf dotnet-runtime-latest-linux-arm.tar.gz -C /opt/dotnet
ln -s /opt/dotnet/dotnet /usr/local/bin/dotnet

のように /usr/local/bin/dotnet で動作できるようにしておいて

dotnet ./hello.dll

で実行します。PC/linux のほうにもこの機能はある。

このあたり、英語のブログ記事も錯綜している感じがするので、元ネタの

core/RaspberryPiInstructions.md at master ・ dotnet/core
https://github.com/dotnet/core/blob/master/samples/RaspberryPiInstructions.md

を参照するとよいでしょう。

Ubuntu上で動かす

Ubuntu 上で動かすときも基本は Rasbian と変わりません。
ただし、素の Ubuntu の場合、開発的なライブラリが少し足りないので apt-get で追加しておきます。

core/prereqs.md at master ・ dotnet/core
https://github.com/dotnet/core/blob/master/Documentation/prereqs.md

にあるライブラリを以下で追加します。

sudo apt-get install libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev uuid-dev unzip

これで、さっき dotnet publish -r linux-arm で作ったアセンブリを ubuntu のほうにコピーすると同じように動作します。

IoTのテンプレートを使う。

ここでは console のテンプレートを使った訳ですが、実は dotnet new –list すると、console 以外のテンプレートも使えることが分かります。

aps.net mvc の場合は dotnet new mvc とかのアレのやつですね。
ここに coreiot というテンプレートがあって、

dotnet new -i RaspberryPi.Template::*

とするとインストールができます(既に .net core 2.0 の dotnet コマンドに付いるかもしれません)。
このテンプレートを使って、

dotnet new coreiot -n helloiot

とした後で、vscode を開くと build.cake にビルドからデプロイまでを含めたCake(C# の makefile)を作ってくれます。

ビルド用の ps スクリプトを,

Invoke-WebRequest http://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1

でダウンロードして動かすと、pscp まで使ったデプロイまで一気にやってくれるはずなんですけどね…どうも PscpSettings の設定がうまくできていないらしく、うまく動いていません。

Cake – Home
http://cakebuild.net/

後でここは調べておきましょう。

そんな訳で

セルフビルドはできませんが、これで Rasberry Pi 2/3 上で .NET Core 2.0 が動作することが確認できました。これで何がやりたいかというと、F#でWeb APIが作りたいわけで、mono で作ってもよいのですが、どうせならば .net core + asp.net mvc core の組み合わせでやりたいのです。なんせ、PCよりも断然低電力なので常時立ち上げておいてもよいし、AzureやAWSよりもネットワーク的に近い位置に作れるから、これはこれで使い勝手がいいかなと。
と言う訳で、F#でのお試しは引き続き。

カテゴリー: 開発, RaspberryPi | 俺のラズパイ2で.NET Core 2.0が動くまで はコメントを受け付けていません

iOSから自己署名証明書で接続することができないので、Let’s Encryptの証明書を使う

iPhone(iOS 10以降)でUIWebViewを使って https 通信をするときに自己署名証明書を使っている場合、どうやっても接続ができなかった。

現状

色々調べたのだが、

ServicePointManager.ServerCertificateValidationCallback を使って、SSL証明書のエラーを回避する → そもそもここが呼ばれない。
Info.plist に NSExceptionDomainsとNSExceptionAllowsInsecureHTTPLoadsを追加して例外扱いにする → NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) のエラーになる。
非推奨の allowsAnyHTTPSCertificateForHost をオーバーライドして、ture を返す → そもそも、ここが呼ばれない。

自己証明書を使用しているサーバにHttpClientで接続 言語: C#
https://code.msdn.microsoft.com/windowsdesktop/HttpClient-fc321142
xcodeにてApp Transport Security(ATS)除外のドメインを複数設定する – Qiita
http://qiita.com/BurialMound/items/87b8c33e2c7ad767c9dc
SwiftでHTTPS通信時に自己認証証明書の警告によるエラーを無視させる – Steel Dragon 14106
http://raimon49.github.io/2015/05/27/ignore-verify-self-signed-certificate.html

ちなみに、WebView じゃなくて HttpClient とか NSURLSesstion の場合はうまく動いたりするので、WebView 固有の問題かもしれない。

最終的には、Xamarin.Forms を使うわけだが、その前段階として Xamarin.iOS と Swift で調査をしている。
「動いた」というページもあるのだが、

  • 「動いた」という記事自体が少し古い
  • 実機で動いても iOS シミュレータはだめっぽい、という記事がある。
  • Info.plist に IP を書いた場合は動かない。ドメイン名が必要。

とあるので、ここは最初に戻って、自己署名証明書じゃない正式な証明書を使うことを検討する。

ちなみに自己署名証明書は、↓を参考にすればok。

自己署名証明書の作成方法 – Qiita
http://qiita.com/akito1986/items/8eb41f5a43bb9421ae79

検証環境自体が、

  • HTTPS のサーバーは、外から見えない(検証機なので)
  • HTTPS のサーバーはドメインを持っていない(内部IPだけなので)
  • iOSシミュレーターでも確認したい。

という所謂、検証環境を想定しいている。

証明書を作る

証明書を作るのには、Let’s Encrypt が無償で使える。

Let’s Encrypt 総合ポータル
https://letsencrypt.jp/

適当なサーバーやクライアントの環境(Linuxかmacに限る)があれば、certbot コマンドで正式な証明書を作ることができる。この証明書は、そのまま外部のWebサーバーに載せることもできる、今回のような検証機に載せることもできる。

Let’s Encryptを使うときの制限は、

  • 証明書を発行するときに、ドメインを持ち外部公開されているサーバーが必要
  • 証明書の期限は3か月に制限されている

となる。外部に公開されているサーバーに証明書をおき、certbot を定期的に動作させて3か月の証明書期間を更新する仕組みなっている。
ただし、手動(manual)で証明書を取得する仕組みも用意されているので、必ずしも自動更新しなければいけない訳ではない。

問題は、「外部公開されているドメイン」が必要なところで、今回のように社内に検証機があって外部から見えないようになっている、グローバルIPを持っていない、ような場合には使えるのか?という疑問がある。なので、いきおい自己署名書になるわけだが、大丈夫、少しトリックを使うと使えるようになる。

実は、

  • Let’s Encrypt から見えるサーバー
  • 実際に証明書を使うサーバー

が同一である必要はない。Let’s Encrypt は、証明書を発行するときにレスポンスファイルを外部サーバーに置くことになるが、これは実際に証明書を使うサーバーとは異なっていても良いのだ。つまり証明書の servername だけ合わせておけばよいということになる。

証明書を作成する手順

Let’s EncryptでManual発行してみました | ぽちゃ猫.com
https://www.pochaneko.com/ietsencrypt-man/

を参考にしながら、certbot をインストールする。今回は mac 上でやっている。

1.仮アクセスできる外部サーバーを用意する。捨てドメイン名になるので ssl.moonmile.net のようにサブドメインを使うのが吉
2.sudo certbot certonly –manual で起動する
3.証明書に書き込むドメイン ssl.moonmile.net など を入れる

4.アクセスチェックのファイルが表示されるので、このファイルを先の外部サーバー ssl.moonmile.net に置く。
5.無事、証明書ができると /etc/letsencrypt/live に置かれる。

-rw-r--r--  1 root  wheel  543  7 23 18:38 README
lrwxr-xr-x  1 root  wheel   36  7 23 18:38 cert.pem -&amp;gt; ../../archive/moonmile.net/cert1.pem
lrwxr-xr-x  1 root  wheel   37  7 23 18:38 chain.pem -&amp;gt; ../../archive/moonmile.net/chain1.pem
lrwxr-xr-x  1 root  wheel   41  7 23 18:38 fullchain.pem -&amp;gt; ../../archive/moonmile.net/fullchain1.pem
lrwxr-xr-x  1 root  wheel   39  7 23 18:38 privkey.pem -&amp;gt; ../../archive/moonmile.net/privkey1.pem

6.シンボリックリンクされているので、本体をコピーする。

privkey.pem が秘密キー
cert.pem が証明書

7.検証機の WEB サーバー(今回は xamp)の C:/xampp/apache/conf/ に証明書を配置する

ssl.key に privkey.pem
ssl.crt に cert.pem

8.config/extra/httpd-ssl.conf に以下を追加する。ssl を有効にすることを忘れずに。

<VirtualHost *:443>
    DocumentRoot &quot;C:/xampp/htdocs&quot;
    ServerName ssl.moonmile.net
    SSLEngine on
    SSLCertificateFile &quot;conf/ssl.crt/cert.pem&quot;
    SSLCertificateKeyFile &quot;conf/ssl.key/privkey.pem&quot;
</VirtualHost>

これで HTTPS サーバーの準備が完了

9.iOSシミュレーターを動かす mac の /etc/hosts に ssl.moonmile.net を追加する。

172.16.0.12 ssl.moonmile.net

な風に追加する。「172.16.0.12」は検証サーバー機のIPアドレス。
iOSシミュレーターは、ホストの mac の /etc/hosts を共有しているようで、これで度命名「ssl.moonmile.net」でアクセスできます。

実行してみる

xcode で UIWebView を使って表示させてみると、

@IBAction func clickConnet(_ sender: Any) {
    let url = URL(string: "https://ssl.moonmile.net/")
    let req = URLRequest(url: url!)
    web.loadRequest(req)
}

こんな風に、https 接続して xamp トップページが表示される。当然、「nscurl –ats-diagnostics 」もさっくりと PASS する。

これは、info.plist の変更なしでいける…のがちょっと不思議なんだけど、何も設定しないから、そのままで審査が通るってことでしょう。

おまけ

ちなみに、UIWebView 限定なのであれこれやったが、SFSafariViewController を使うと自己署名証明書でもスルーしてくれる。通常のブラウザと同じように、自己署名だからという警告が1回だけ出るが、その後はキャッシュされるらしく警告がでなくなる。

ATS対策 外部ページの表示にはSFSafariViewController – Qiita
http://qiita.com/bird_tomita/items/2139d2b95c35a4e7d20f

なんか、適当な https を表示するだけだったら、こっちのほうが手軽でよいかも、ということで。

カテゴリー: 開発, Xamarin, iOS | iOSから自己署名証明書で接続することができないので、Let’s Encryptの証明書を使う はコメントを受け付けていません

Scratch から Arduino を操作しよう、というわけで NetScrattino を作る

Scratch を使って Arduino を動かそうとすると http://scratchx.org/ を使うのがいいのだろうけど、どうも自分の環境ではうまく動かない。オフラインの Scratch 2.0 の場合、ファイルメニューをシフトを押しながら開くと「実験的なHTTP拡張を取り込み」というのが出て、適当なHTTPサーバーを作ると繋がるらしいことが分かった。

サーバーを作るのが手間といえば手間なんだけど(ScratchXの場合は、Chrome拡張をインストールすると、Chrome側にHTTPサーバーを立てる仕組みになっている)、一度作っておけば、Arduino 以外に接続するのも楽ではないかなと思って、作ってみることにする。.NET で作れば HttpListener があるので、何とかなるのではないかな、と。

Scrattino 2 | Yengawa Systems
http://www.yengawa.com/scrattino2
Let’s Make With Arduino!
https://lets.makewitharduino.com/sample/scratch/

Scrattino2 のほうは、ArduinoにFirmataを入れるんだけど、HTTPサーバーはMac版しかない。Scratio のほうは、独自プロトコルにしてあって中身は Python で書かれている。
ざっと、Scratio で Scratch の拡張ブロックの操作を確認したところで、まあいけそうなことが分かったので Firmata への接続を作ることにした。

シリアル通信で Firmata に接続する

まずは、NetScrattino から Arduino にシリアル通信する。

シリアル通信は双方向に通信ができるので、NetScrattinoからコマンドを送信すると同時に、定期的に Arduino のほうからアナログピンの状態を送信してくれる。これを保持しておく。

protocol/protocol.md at master ・ firmata/protocol
https://github.com/firmata/protocol/blob/master/protocol.md
firmataプロトコル覚え書き
https://gist.github.com/hiroeorz/7868628

あたりを見ながら、ひとまずデジタルピンとアナログピンの読み書き、モードの設定、レポートの設定だけを送れるようにしておく。

// two byte digital data format, second nibble of byte 0 gives the port number (e.g. 0x92 is the third port, port 2)
// 0  digital data, 0x90-0x9F, (MIDI NoteOn, but different data format)
// 1  digital pins 0-6 bitmask
// 2  digital pin 7 bitmask 
member this.digitalWrite(pin,value) =
    let portNumber = (pin >>> 3) &&& 0xFF
    digitalInputData.[portNumber] <-
        if value = 0 then
            digitalInputData.[portNumber] &&& ~~~(1 <<< (pin &&& 0x07))
        else
            digitalInputData.[portNumber] ||| (1 <<< (pin &&& 0x07)) 
    let message = [|
        DIGITAL_MESSAGE ||| byte(portNumber) 
        byte(digitalInputData.[portNumber] &&& 0x7F)
        byte(digitalInputData.[portNumber] >>> 7)
    |]
    _socket.Write(message, 0, message.Length);

あれこれ面倒なので、F# で書いたのであった。
Arduino から非同期で送ってくるデータは、DataReceived で受け取る。

_socket.DataReceived.Add( fun (e) -> 
    while _socket.BytesToRead > 0 do
        let head = _socket.ReadByte() |> byte
        match head with
        // analog 14-bit data format
        // 0  analog pin, 0xE0-0xEF, (MIDI Pitch Wheel)
        // 1  analog least significant 7 bits
        // 2  analog most significant 7 bits
        | h when ANALOG_MESSAGE <= h && h <= ANALOG_MESSAGE + 15uy -> 
            let pin = int(h - ANALOG_MESSAGE)
            let lsb = _socket.ReadByte()
            let msb = _socket.ReadByte()
            let data = (msb <<< 7) ||| lsb
            analogInputData.[pin] <- data
        // two byte digital data format, second nibble of byte 0 gives the port number (e.g. 0x92 is the third port, port 2)
        // 0  digital data, 0x90-0x9F, (MIDI NoteOn, but different data format)
        // 1  digital pins 0-6 bitmask
        // 2  digital pin 7 bitmask 
        | h when DIGITAL_MESSAGE <= h && h <= DIGITAL_MESSAGE + 15uy -> 
            let pin = int(h - DIGITAL_MESSAGE)
            let lsb = _socket.ReadByte()
            let msb = _socket.ReadByte()
            let data = (msb <<< 7) ||| lsb
            digitalInputData.[pin] <- data
        | _ -> 
            // read off
            let d = _socket.ReadExisting()
            ()
)

HTTPサーバーを作って Scratch に応答する

Scratch の拡張ブロックは、JSON形式で書くことができて、こんな風になっている。

{
  "extensionName": "Net Scrattino",
  "extensionPort": 5410,
  "url": "https://github.com/yokobond/scrattino2",
  "blockSpecs": [
    [" ", "INPUT %m.digitalPinNames mode %m.inputPinModes", "setMode", "D2", "PULLUP"],
    [" ", "OUTPUT %m.digitalPinNames value %m.digitalValues", "digitalWrite", "D2", 0],
    [" ", "PWM %m.digitalPinNames value %d.pwmValues", "analogWrite", "D2", 0],
    [" ", "SERVO %m.digitalPinNames degree %d.servoValues", "servoWrite", "D2", 0],
    [" ", "Set Pin %m.digitalPinNames to %d.digitalPinModes mode", "setPinMode", "D2", "OUTPUT"],
    [" ", "LED %m.digitalPinNames is %m.OnOffValues", "digitalWrite", "D2", "ON"],
    ["-"],
    ["r", "A0", "a0"],
    ["r", "A1", "a1"],
    ["r", "A2", "a2"],
    ["r", "A3", "a3"],
    ["r", "A4", "a4"],
    ["r", "A5", "a5"],
    ["-"],
//    ["R", "value of %m.digitalPinNames", "pinValue", "D2"],
    ["r", "D2", "d2"],
    ["r", "D3", "d3"],
    ["r", "D4", "d4"],
    ["r", "D5", "d5"],
    ["r", "D6", "d6"],
    ["r", "D7", "d7"],
    ["r", "D8", "d8"],
    ["r", "D9", "d9"],
    ["r", "D10", "d10"],
    ["r", "D11", "d11"],
    ["r", "D12", "d12"],
    ["r", "D13", "d13"]
  ],
  "menus": {
    "digitalPinNames": ["D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12", "D13"],
    "analogPinNames": ["A0", "A1", "A2", "A3", "A4", "A5"],
    "digitalPinModes": ["INPUT", "INPUT_PULLUP", "OUTPUT", "PWM", "SERVO"],
    "inputPinModes": ["PULLUP", "PULLDOWN"],
    "digitalValues": [0, 1],
    "OnOffValues": ["ON", "OFF"],
    "pwmValues": [0, 64, 128, 192, 255],
    "servoValues": [0, 45, 90, 135, 180],
    "analogValues": [0, 256, 512, 768, 1023]
  }
}

blockSpecs にあるのがブロックの定義で、これを web api な形で呼び出す。
仕様は、https://wiki.scratch.mit.edu/w/images/ExtensionsDoc.HTTP-9-11.pdf に書かれている。ID を使って非同期にデータを送る方法は面倒なんだが、通常はポーリング(/poll)を送って、データを返すパターンが多いので、それだけならばそんなに難しくはない。
で、これも F# で実装してみる。

let mutable arduino = new FirmataNET.Arduino()

// Scratchから受信するためのHTTPサーバー
let Server( port ) =
    let listener = new System.Net.HttpListener()
    listener.Prefixes.Add(&quot;http://127.0.0.1:&quot;+(port |> string)+&quot;/&quot; )
    listener.Start()
    while true do
        let context = listener.GetContext()
        let res = context.Response
        let mutable data = &quot;&quot;
        let path = context.Request.Url.PathAndQuery
        match path with 
            | &quot;/poll&quot; -> 
                for i=0 to 5 do 
                    data <- data + String.Format(&quot;a{0} {1}\n&quot;, i, arduino.analogRead(i))
                for i=2 to 13 do 
                    data <- data + String.Format(&quot;d{0} {1}\n&quot;, i, arduino.digitalRead(i))
                // デバッグ出力
                let mutable debug = &quot;&quot;
                for i=0 to 5 do 
                    debug <- debug + String.Format(&quot;a{0} {1} &quot;, i, arduino.analogRead(i))
                debug <- debug + &quot;\n&quot;
                for i=2 to 13 do 
                    debug <- debug + String.Format(&quot;d{0} {1} &quot;, i, arduino.digitalRead(i))
                debug <- debug + &quot;\n&quot;
                // printfn &quot;%s&quot; path
                // printfn &quot;%s&quot; debug
            | &quot;/reset_all&quot; ->
                printfn &quot;/reset_all&quot;
                arduino.Reset()
                data <- &quot;ok&quot;
            | _ ->
                let pa = path.Split([|'/'|])
                match pa.[1] with   
                | &quot;digitalWrite&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = 
                        match pa.[3].ToUpper() with
                        | &quot;ON&quot; -> 1
                        | &quot;OFF&quot; -> 0
                        | _ -> pa.[3] |> int
                    arduino.digitalWrite( pin, value )
                | &quot;analogWrite&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = pa.[3] |> int
                    arduino.pinMode( pin, 0x03 )    // PWM
                    arduino.analogWrite( pin, value )
                | &quot;servoWrite&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = pa.[3] |> int
                    arduino.pinMode( pin, 0x04 )    // SERVO
                    arduino.analogWrite( pin, value )
                | &quot;setMode&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = if pa.[3] = &quot;PULLUP&quot; then 0x0B else 0x00
                    arduino.pinMode( pin, value )
                | &quot;setPinMode&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = 
                        match pa.[3] with
                        | &quot;INPUT&quot; -> 0
                        | &quot;OUTPUT&quot; -> 1
                        | &quot;PWM&quot; -> 3
                        | &quot;SERVO&quot; -> 4
                        | &quot;INPUT_PULLUP&quot; -> 11
                        | _ -> 1
                    arduino.pinMode( pin, value )
                | _ ->
                    data <- &quot;&quot;
                printfn &quot;%s&quot; path
        res.StatusCode <- 200
        let sw = new System.IO.StreamWriter( res.OutputStream )
        sw.Write( data )
        sw.Close()
    ()

Scratch で拡張ブロックを作ってみる

先に作った JSON を Scratch 2.0 に読み込ませると、自前で作ったブロックが使えるようになる。

旗をクリックしたときとか、スペースキーを押されたとき、などのイベントのブロックがあるが、テストをするときはブロック自体をダブルクリックすれば実行されるので、プロトタイプを作るときには結構便利。mBlock の場合だと、あらかじめ Arduino にデプロイしてしまうので、変更するに書き込まないといけないし。まあ、Firmata 自体がプロトタイプを作るためのものでもあるので、用途的にはちょうどよいかと思う。

簡易プロキシにUIを付ける

最初は、コマンドラインだけでやっていたのだが、Firmata を直接扱えたほうが便利なので、簡易プロキシ(NetScrattino)にUIを付けてみる。

これは WPF で作って、内部的に MVVM パターンになっているので、この解説はまた後で。

Scratchと連携させる

せっかくの Scratch なので、Arduino を操作するだけじゃなくて猫のほうも操作できるようにしておく。

これは、Lチカをしながら猫が走るパターン。LEDをマウスでクリックすると、Arduino上のLEDが光ると同時に絵のLEDも光る。

ざっと、簡単なものとして、

  • LEDの点滅
  • PWMでLEDの点灯
  • サーボを動かす
  • ポテンショメーター(回転とかスライダーとか)でアナログピンで読み取る

なところまでできた。後は、順次

スクラッチーノでScratchとArduinoをつなぐ – MeiDe Digital Craft 2016
https://sites.google.com/site/meidedigitalcraft2016/knowhow/scrattino-usage

にある実習を動かすようなプログラムが組めればよいかな。

コード

NetScrattino のコードはこちら

moonmile/NetScrattino: Simple Server to connect from Scratch to Arduino
https://github.com/moonmile/NetScrattino

これから

ScratchX
http://scratchx.org/#extensions

の拡張を見ていくと Kinect とか Leapmotion とかもある。環境が悪いのかよくわからにけど、うちの PC では ScratchX が動かないので何とも言えないのだけど、どうやら、COM 制限のような気がする。このあたりは、別の PC や Mac で試してみよう。

ローカルで実験する場合は、HTTP プロキシを作ったほうが応用範囲が広そうなので(.NETで作れるし)、カメラでの撮影を Scratch 側で制御するとか、物体認識を Scratch に持って来るというのもできそうな感じはする。

カテゴリー: 開発, F#, Scratch | Scratch から Arduino を操作しよう、というわけで NetScrattino を作る はコメントを受け付けていません

ちょっと雑だが、C# で JsonProvider もどきを作る

F# には TypeProvider というのがあって、動的にクラスを作り、それを F# のコードで扱えるものです。

F# Data: JSON 型プロバイダー
https://fsharp.github.io/FSharp.Data/ja/library/JsonProvider.html

でもって、C# には TypeProvider がないんので F# が羨ましかったり、いやそう言うなら F# で組めばいい訳ですが。以前、F# の XAML の TypeProvider を作ったときに、そのまま Xamarin の PCL には持って行けなくて諦めた覚えがあるんですが。今だともうちょっと工夫できるかも、ってことで、C# で TypeBuilder を使ってみます。

雑に JsonProvider を作る

JSON の文字列を渡して、それをプロパティに持つクラスを作ります。実は、Newtonsoft.Json は dynamic を持っているので、あまり意味はない…というか、結論から言えば T4 とか CodeDOM を使ったほうが早いのでは?という感じはします。

class Program
{
    static void Main(string[] args)
    {
        var json = @"{ name: 'tomoaki', num: 101 }";
        var jp = new JsonProvider(json);
        var t = jp.Make("SampleJson");

        var o = new SampleJson();
        o.name = "aaaa";
        o.num = "100";
    }
}
public class JsonProvider
{
    JObject root;
    Dictionary<string, string> dic = new Dictionary<string, string>();

    public JsonProvider( string json )
    {
        root = JObject.Parse(json);
        var cur = root.GetEnumerator();
        while ( cur.MoveNext() )
        {
            var it = cur.Current;
            Debug.WriteLine("{0} {1}", it.Key, it.Value);
            dic.Add(it.Key.ToString(), it.Value.ToString());
        }
    }

    public Type Make( string className )
    {
        var assemblyName = new AssemblyName("JsonProviderAssembly");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll");
        var mb = moduleBuilder.DefineType(className, TypeAttributes.Class | TypeAttributes.Public, typeof(object));

        foreach (var it in dic)
        {
            var propName = it.Key;

            FieldBuilder customerNameBldr = mb.DefineField("_" + propName, typeof(string), FieldAttributes.Private);
            PropertyBuilder custNamePropBldr = mb.DefineProperty(propName, PropertyAttributes.HasDefault, typeof(string), null);
            MethodBuilder custNameGetPropMthdBldr =
                        mb.DefineMethod("get_" + propName,
                            MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                            typeof(string),
                            Type.EmptyTypes);
            ILGenerator custNameGetIL = custNameGetPropMthdBldr.GetILGenerator();
            custNameGetIL.Emit(OpCodes.Ldarg_0);
            custNameGetIL.Emit(OpCodes.Ldfld, customerNameBldr);
            custNameGetIL.Emit(OpCodes.Ret);
            MethodBuilder custNameSetPropMthdBldr =
                        mb.DefineMethod("set_" + propName,
                            MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                            null,
                            new Type[] { typeof(string) });
            ILGenerator custNameSetIL = custNameSetPropMthdBldr.GetILGenerator();
            custNameSetIL.Emit(OpCodes.Ldarg_0);
            custNameSetIL.Emit(OpCodes.Ldarg_1);
            custNameSetIL.Emit(OpCodes.Stfld, customerNameBldr);
            custNameSetIL.Emit(OpCodes.Ret);
            custNamePropBldr.SetGetMethod(custNameGetPropMthdBldr);
            custNamePropBldr.SetSetMethod(custNameSetPropMthdBldr);
        }
        Type t = mb.CreateType();
        assemblyBuilder.Save(assemblyName.Name + ".dll");
        return t;
    }
}

ModuleBuilder.CreateType でクラスを作った後で、AssemblyBuilder.Save で保存します。F# の TypeProvider の場合はこれがビルド時に行われるので、ビルド時のアセンブリと実行時のアセンブリが異なるので不整合が起ります。じゃあ、どちらも .NET Frameworkの環境であったり、ビルド時に敢えて Xamarin.Forms の PCL に合うようなアセンブリを衝くてやれば良いのだろう、と考えているのですが、これはまた後で実験します。

さて、AssemblyBuilder.Save で保存した DLL を、プロジェクトから参照設定すると作成した SampleJson クラスが使えます。一度、実行して DLL を作らないと駄目ってところが、結局のところ T4 と同じで、あまり意味がない。F# の TypeBuilder のように自動でインテリセンスが効けばいいんだけど。
ちなみに、作成した JsonProviderAssembly.dll を参照設定して、SampleJson のインスタンスを作ろうとすると DLL がロックされて書き込めないというデッドロックな状態になります。ビルド時にコピーする処理が必要ですね。
あと、実行しないとアセンブリが作られないので、T4 にして、ビルド時にアセンブリを作って後から参照するとかにしないと。

クラスを定義するためのいくつかの方法: C# プログラミング 再入門
http://dotnetcsharptips.seesaa.net/article/416983160.html

これを見る限り、CSharpCodeProvider を使って文字列から生成するほうが楽そうですね。が、Xamarin.Android からは Microsoft.CSharp.CSharpCodeProvider が見当たらないので、これはこれで。

カテゴリー: 開発 | ちょっと雑だが、C# で JsonProvider もどきを作る はコメントを受け付けていません

TypeBuilderを使って、既存のクラスにメソッドを生やす

継承可能なDynamicObjectを作ろうとしたが挫折中 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/8672

なところで、Xamarin.Forms からイベントを探索しているのはリフレクションを使っているなあ、ということが分かったので、じゃあ元のクラスに仮のメソッドをつけて無視すればいいのでは?と考えました。

XAML で、Clicked イベントがついていた時に

<Button Text=&quot;Click me&quot; Clicked=&quot;Button_Clicked&quot; />

↓なように、動的に Button_Click を生やしたいわけです。

public class MainPage : Xamarin.Forms.ContentPage
{
    private void Button_Click(object sender, EventArgs e)	
    {
    }
}

本来ならば、コンパイル時にメソッド名が決まっていればよいので、普通に ContentPage を継承してあらかじめコードで Button_Click を付けておけばよいのですが、XamlPreview のように XAML だけを送る場合は動的に Button_Click に作りたいのですよね。

実行時に Button_Click メソッドを作る

TypeBuilder.CreateType メソッド (System.Reflection.Emit)
https://msdn.microsoft.com/ja-jp/library/system.reflection.emit.typebuilder.createtype(v=vs.110).aspx

というのがあって実行時にクラスが作れます。
最初に断っておきますが、これは Xamarin.iOS では動きません。動かないので、XamlPreview の目的に達しないのですが、まあ、忘備録的に記録しておくというとで。Xamarin.Android 上では動くし、どうやら .NET Core 上でも動くので他にも応用が利きそうかなと。

public class PageGenerator : IPageGenerator
{
    public Type Create()
    {
        var assemblyName = new AssemblyName("dynamicassembly");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
        var tb = moduleBuilder.DefineType("DynamicContentPage", TypeAttributes.Class, typeof(Xamarin.Forms.ContentPage));
            
        MethodBuilder meth = tb.DefineMethod(
            "Button_Clicked",
            MethodAttributes.Public,
            typeof(void),
            new Type[] { typeof(object), typeof(EventArgs) });
        ILGenerator methIL = meth.GetILGenerator();
        methIL.Emit(OpCodes.Ret);

        Type t = tb.CreateType();
        return t;
    }
}

IPageGenerator を定義しているのは、PCL プロジェクト内では動かないので DependencyService.Get するためです。
動的にアセンブリを作って、クラスを作成しています。AssemblyBuilderAccess.Run を指定するとメモリ上で動きますね。DefineType メソッドで継承先に Xamarin.Forms.ContentPage を指定します。
メソッド名は、DefineMethod で指定して、中身は GetILGenerator で作るという感じ。

こうすると、ContentPage を継承した DynamicContentPage というクラスが動的にできます。
これをメインのほうで、

var tg = DependencyService.Get<IPageGenerator>();
Type t = tg.Create();
ContentPage page = Activator.CreateInstance(t) as ContentPage;

とすれば、無事 ContentPage オブジェクトとして使えます。DynamicContentPage クラス自体は動的に作ったものなので、プログラムを書いているときには存在しません。なので、当然インテリセンスとかは効きません。

これ、ビルド時にアセンブリに落として参照設定すれば、F# の TypeProvider と同じ動きになるんじゃないかなと思うんですが、どうなんでしょう?

メソッドの中身を Expression.CompileToMethod で書ける?

動的に作成した Button_Clicked ですが、IL なので、ちょっと面倒くさい。

ILGenerator methIL = meth.GetILGenerator();
methIL.Emit(OpCodes.Ret);

じゃあ、Expression を使って、Expression.Lambda で既存のメソッドを呼び出せば楽じゃないか?と思って作ったのがこれ。CompileToMethod を使うと IL を吐き出してくれます。

var mi = typeof(PageGenerator).GetMethod("Button_Clicked", new Type[] { typeof(object), typeof(EventArgs) });
var arg1 = Expression.Parameter(typeof(object));
var arg2 = Expression.Parameter(typeof(EventArgs));
var lambda = Expression.Lambda(Expression.Call(mi, arg1, arg2), arg1, arg2);
lambda.CompileToMethod(meth);

でも、なぜか Xamarin.Android 上では実行時に CompileToMethod でダンマリになるという感じでうまくいかない。
何かしたいというと、動的に作った Button_Clicked から、既存のコードを呼び出して、それをさらに Desktop のクライアントに送れたら便利かなと思った次第なのですが、CompileToMethod がうまくいかない。

Xamarin.iOS では動かない

iOS では動的コードが動かないので、Xamarin.iOS では動きません。ビルドは通るけど、実行時に AssemblyBuilder.DefineDynamicAssembly で落ちます。

iOS で Emit 絡みがダメなのが分かったので、XamlPreview では使えないのだけど、デスクトップ側でアセンブリができるのだから、F# の TypeProvider っぽいのができないかなぁと思案中…なので続く。

カテゴリー: 開発, Xamarin | TypeBuilderを使って、既存のクラスにメソッドを生やす はコメントを受け付けていません

継承可能なDynamicObjectを作ろうとしたが挫折中

Xamarin.Forms 用の超軽量プレビューアを作る | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/8669

で、XAMLにクリックイベントが入っていると XamlLoader がパースエラーになるので、そのイベントをうまい具合に無視しなければいけないのですが、じゃあ、もともとある ContentPage クラスに後からイベントを追加できたらうまくスルーできるのではないか?と思って、継承可能な DynamicObject を探していました。

正確に言えば、DynamicObject は継承可能なので、

public class DynamicViewModel : DynamicObject
{
    Dictionary<string, object> dic = new Dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return dic.TryGetValue(binder.Name, out result);
    }
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        dic[binder.Name] = value;
        return true;
    }
}

な感じで DynamicObject を継承した ViewModel を作っておいて、後追いで次のようにプロパティを増やすことが可能です。

dynamic vm = new DynamicViewModel();
vm.Title = "Hello";
var title = vm.Title;

dynamic なので、インテリセンスは効かないけど、うまくくるめば XML や JSON をマッピングすることができます。ちなみに、Newtonsoft.Json.Linq.JObject を使うと、WPF の ViewModel としてそのまま使えます。何故か、Xamarin.Forms では使えないので、呼び出し方が微妙に違うのかなと。呼び出せるほうが不思議な感じがするのですが。.NET Framework と Profile259 の Runtime の違いかもしれません。

DynamicViewModel な方法は、ASP.NET の ViewBag にも使われているので割とポピュラーな手段です。詳細は、

メタプログラミング.NET | Kevin Hazzard, Jason Bock
https://www.amazon.co.jp/dp/4048867741

な本にも書いてあります。随分前だけど、

MVVMパターンでViewModelを楽に作る方法 – かずきのBlog@hatena
http://blog.okazuki.jp/entry/20100702/1278056325

なところで、MSDN マガジンへのリンクもあります。と言う訳で、じゃあ、DynamicObject を継承して ContentPage に後付けで Clicked なメソッドを生やすことができるんじゃないだろうか、と考えて、

public class SubPage : ContentPage, DynamicObject
{
    public SubPage() { }

    /*
    private void Button_Clicked(object sender, EventArgs e)
    {

    }
    */
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        // 読み捨て
        System.Diagnostics.Debug.WriteLine("called: " + binder.Name);
        result = null;
        return true;
    }
}

なことを考えたのですが、ダメです。C# は多重継承ができないから、ContentPage と DynamicObject の両方を基底に持つことはできないんですね。じゃあ、どっちかをインターフェースにして、内部で再実装させればいいと思ったわけで、となると DynamicObject のほうをインターフェースにしたいですよね。ってことであれこれ探すとそれっぽいものがありました。

remi/MetaObject: Simple dynamic method invocation for your .NET objects
https://github.com/remi/MetaObject

IDynamicMetaObjectProvider インターフェースを付けて、内部的に再実装しようという試みです。

public class DynamicContnetPage : ContentPage, IDynamicMetaObjectProvider
{
    #region MetaObject
    public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression e)
    {
        return new MetaObject(e, this);
    }
    #endregion

    Dictionary<string, object>; dic = new Dictionary<string, object>();

    public virtual System.Collections.Generic.IEnumerable<string> GetDynamicMemberNames()
    {
        // return Value.GetDynamicMemberNames();
        return new string[] { };
    }

    public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        result = null;
        return true;
    }
    public virtual bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return dic.TryGetValue(binder.Name, out result);
    }
    public virtual bool TrySetMember(SetMemberBinder binder, object value)
    {
        dic[binder.Name] = value;
        return true;
    }
}

こんな風に MetaObject を使っておくと、

public class SubPage : DynamicContnetPage
{
    public SubPage() { }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        // 読み捨て
        System.Diagnostics.Debug.WriteLine("called: " + binder.Name);
        result = null;
        return true;
    }
}

こんな風に、Clicked イベントやらを読み捨ててくれるはずです。中身で何か実装すれば、デバッグログとか通信っぽいものもできますね。このあたりは、実 DynamicObject のコードを見るといいのですが、中身的に IDynamicMetaObjectProvider インターフェースが DynamicMetaObject GetMetaObject(Expression parameter) を要求するのでメタデータを用意しておかないという仕組み&制限なのです。でもって、これが「式 Expression」を要求するというメタ構造になっていて、えらい大変なことになってます。

さて、これで万事解決と思いきや、いざコンパイルしてみると、MetaObject のコードがビルドできません。なんと、MetaObject は .NET Framework 専用なんですね。ああ、Xamarin.Forms の PCL は Profile259 なので .NET Runtime を使う訳なので、微妙に異なる訳です。仕方がないので、Runtime のほうに書き直そうかとしたら案の定 System.Reflection の中身が違うので、GetMethod を GetRuntimeMethod に直したりしながら、ええ、GetConstructor がないので、GetTypeInfo().DeclaredConstructors に変えてみたりと、あれこれとビルドが通るように修正。

で、なんとかビルドが通ったものを Xamarin.Forms の PCL に配置していざ、XamlLoader を動かすと、嗚呼、TryInvokeMember が呼び出される前に例外をはいて落ちてしまいます。どうやら、XAML に Clicked イベントを書くと XAML をパースするときに対応するメソッドを探してしまうらしいんですね。

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Xamarin.Forms.Xaml.XamlParseException: Position 13:37. No method Button_Clicked found on type XamlPreview.SubPage
  at Xamarin.Forms.Xaml.ApplyPropertiesVisitor.SetPropertyValue (System.Object xamlelement, Xamarin.Forms.Xaml.XmlName propertyName, System.Object value, System.Object rootElement, Xamarin.Forms.Xaml.INode node, Xamarin.Forms.Xaml.HydratationContext context, System.Xml.IXmlLineInfo lineInfo) [0x000de] in C:\BuildAgent3\work\ca3766cfc22354a1\Xamarin.Forms.Xaml\ApplyPropertiesVisitor.cs:310 
  at Xamarin.Forms.Xaml.ApplyPropertiesVisitor.Visit (Xamarin.Forms.Xaml.ValueNode node, Xamarin.Forms.Xaml.INode parentNode) [0x00070] in C:\BuildAgent3\work\ca3766cfc22354a1\Xamarin.Forms.Xaml\ApplyPropertiesVisitor.cs:63 
...

ここで、どんな方法で探しているのかが不明(たぶんリフレクション?)なので、ちょっとこの先は解らず。仕方がないから Xamarin.Forms のコードを読むか、別な対策を立てるか思案中。

 

あった、

in ApplyPropertiesVisitor.cs で XamlParseException 例外を発生させている。

		static bool TryConnectEvent(object element, string localName, object value, object rootElement, IXmlLineInfo lineInfo, out Exception exception)
		{
			exception = null;

			var elementType = element.GetType();
			var eventInfo = elementType.GetRuntimeEvent(localName);
			var stringValue = value as string;

			if (eventInfo == null || IsNullOrEmpty(stringValue))
				return false;

			var methodInfo = rootElement.GetType().GetRuntimeMethods().FirstOrDefault(mi => mi.Name == (string)value);
			if (methodInfo == null) {
				exception = new XamlParseException($&quot;No method {value} found on type {rootElement.GetType()}&quot;, lineInfo);
				return false;
			}

			try {
				eventInfo.AddEventHandler(element, methodInfo.CreateDelegate(eventInfo.EventHandlerType, rootElement));
				return true;
			} catch (ArgumentException ae) {
				exception = new XamlParseException($&quot;Method {stringValue} does not have the correct signature&quot;, lineInfo, ae);
			}
			return false;
		}
カテゴリー: 開発, XAML, Xamarin | 継承可能なDynamicObjectを作ろうとしたが挫折中 はコメントを受け付けていません

Xamarin.Forms 用の超軽量プレビューアを作る

自前の LoadFromXaml ができたので、実機に XAML を送り込んで表示するプレビューアを作ってみます。
その昔、

moonmile/XFormsPreviewer: Dynamic loading XAML file of Xamarin.Forms
https://github.com/moonmile/XFormsPreviewer

なるものを作り始めたのだが、自前で XAML をパースしているので、Forms のバージョンアップに追随できないし、そのうちに本家から Xamarin.Forms Previewer が出たり、Xamarin Live Player が出たりして、頓挫&忘れておりました。
が、どうも、俺の思っているプレビューアと違う、と思っていた次第で、やっぱり軽量なものを作ってみようかと。

コード

moonmile/XFormsXamlDynamicLoad:
https://github.com/moonmile/XFormsXamlDynamicLoad

– src/XamlPreview 実機やエミュレータに仕込むプレビューアプリ
– src/XamlPreviewUp 実機へXAMLをPOSTするWPFアプリ

使い方

  1. XamlPreview をビルドして、エミュレーターや実機にインストール(デバッグ実行)します。
  2. XamlPreview は http://172.16.0.16:8080/ な感じで簡易HTTPサーバーしてます。

  1. XAML を送り込む XamlPreviewUP を起動します。
  2. IP を設定して、プレビューしたい XAML ファイルをドロップします。

すると、エミュレーター/実機のXamlPreviewの表示が切り替わります。

XAML を送り込むタイミングは、ファイルをドロップしたときと XamlPreviewUP のボタンを押したときなので、Visual Studio で XAML を修正した後にボタンを押して画面を更新すればよいでしょう。これは、そのうち更新状態をチェックして自動のアップロードするようにする予定。

制限

内部的に Xamarin.Forms.Xaml.XamlLoader クラスの Load メソッドを使っているので、XAML ファイルだけだとパースエラーになる場合があります。
例えば、Button のクリックイベント(Clicked)が書いてあるとエラーになるという状態。これ、わざわざイベント先のメソッドの存在をチェックしているらしく、所謂イベント系を書いてしまうとエラーになります。
これは、XAML を送るときにイベント系のものを削ってしまおうかなと。

Binding の記述はそのままでも大丈夫なので、デザイン時のデータバインディングクラスを作るとかして、対応したいかなと。特に ListView のデモとかにはよいだろうと。

利点

Xamarin.Forms Previewer や Xamarin Live Player よりも圧倒的に軽いです。これは PCL ライブラリとかビルドせずに、XAML だけ送って表示しているので、最初のデザインとかGridの調節とかにや良いかなと。あと、XAML の練習用ですね。

ちょっと面白いところでは、SliderBindingsPage.xaml が動きます。

XAML 内だけでバインディングする方法でスライダーを動かすと文字が回転します。このあたりの XAML は https://github.com/xamarin/xamarin-forms-samples からピックアップしています。

コードの解説は後日

カテゴリー: 開発, Xamarin | Xamarin.Forms 用の超軽量プレビューアを作る はコメントを受け付けていません

リフレクションを使ってXamarin.FormsにXAMLを動的ロードする

ふと、LoadFromXaml が public になれば、と思って探して、Xamarin.Forms のコードを見ていったら、

Load Xaml Dynamically At Runtime ? Xamarin Forums
https://forums.xamarin.com/discussion/87727/load-xaml-dynamically-at-runtime
Please make XamlLoader Public ? Xamarin Forums
https://forums.xamarin.com/discussion/87810/please-make-xamlloader-public

需要はあるようなないような。以前、動的にロードしたかったのは Xamarin Live Player 以前の頃だったので、既にあるからまあ特にいらんだろうという感じもするし、XAML をサーバーからダウンロードして切り替えることができたら、少しは違うかもしれないと思ったり。

サンプル

moonmile/XFormsXamlDynamicLoad: Xamarin.FormsでXAMLを動的にロードするサンプル
https://github.com/moonmile/XFormsXamlDynamicLoad

拡張メソッドを定義

ContentPageのLoadFromXamlメソッドが private ならば、自前で作ってしまえ、ってことで自前で作ってしまう。

static class ContentPageExtensions
{
    public static TXaml LoadFromXaml<TXaml>(this TXaml view, string xaml)
    {
        Load(view, xaml);
        return view;
    }
    private static void Load(object view, string xaml)
    {
        var t = Type.GetType(&quot;Xamarin.Forms.Xaml.XamlLoader, Xamarin.Forms.Xaml&quot;);
        var mi = t.GetRuntimeMethod(&quot;Load&quot;, new Type[] { typeof(object), typeof(string) });
        var obj = mi.Invoke(null, new object[] { view, xaml });
        return;
    }
}

自前のLoadFromXamlから、本物のXamarin.Forms.Xaml.XamlLoader.Loadを呼び出す。実は、XamlLoaderクラスはinternalなので呼び出せないのだが、Type.GetTypeでロードして、GetRuntimeMethodを使うと呼び出せるんですね。

PrivateObject クラス?(Microsoft.VisualStudio.TestTools.UnitTesting)
https://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.testtools.unittesting.privateobject.aspx

と同じことをやります。

メインページから遷移するときに、自前で XAML をロードさせます。コード内に書いちゃうとリソースで XAML にしたほうが楽だろう(実際そうだし)と思うのですが、似たような感じでネット上から XAML をダウンロードすることも可能ということで。

private void Button_Clicked(object sender, EventArgs e)
{
    var xaml = @&quot;<?xml version=&quot;&quot;1.0&quot;&quot; encoding=&quot;&quot;utf-8&quot;&quot; ?>
<ContentPage xmlns=&quot;&quot;http://xamarin.com/schemas/2014/forms&quot;&quot;
        xmlns:x=&quot;&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;&quot;>
<StackLayout>
<Label Text=&quot;&quot;Hello Sub Page&quot;&quot; />
<Button Text=&quot;&quot;click me!&quot;&quot; Clicked=&quot;&quot;Button_Clicked&quot;&quot; />
<Label Text=&quot;&quot;{Binding Count, StringFormat='{0} clicked'}&quot;&quot; />
</StackLayout>
</ContentPage>
&quot;;
    /// XAML を動的にロードする
    this.Navigation.PushAsync(new SubPage().LoadFromXaml(xaml));
}

SubPage クラスのほうは MVVM も効くので、ViewModel クラスだけで取り廻して XAML のほうは動的に切り替えることもできるかな、と。

public class SubPageViewModel : ObservableObject
{
    private int count = 0;
    public int Count
    {
        get { return count; }
        set { SetProperty(ref count, value, nameof(Count)); }
    }
}
public class SubPage : ContentPage
{
    public SubPage()
    {
        vm = new SubPageViewModel();
        this.BindingContext = vm;
    }
    SubPageViewModel vm;
    private void Button_Clicked(object sender, EventArgs e)
    {
        vm.Count++;
    }
}

実行

カテゴリー: 開発, XAML, Xamarin | リフレクションを使ってXamarin.FormsにXAMLを動的ロードする はコメントを受け付けていません

F#でXamarin.Formsを使う

いつからできるようになったんだっけ?

Petzold Book Blog – Writing Xamarin.Forms Apps in F#
http://www.charlespetzold.com/blog/2015/10/Writing-Xamarin-Forms-Apps-in-FSharp.html

なところなので、実は随分前からある。

で、Visual Studio for Mac の「Blank Forms App」で F# が選べるようになっていたので試してみる。ちなみに、Windows のほうの Visual Studio 2017 には F# のテンプレートがないので、Mac で作ったものを Windows 側にコピーしている。

テンプレートが少しおかしいらしく、Android のほうの Xamarin.Forms が入らない(入れようとしてエラーになっている)ので手動で入れる。

Mac のほうで作るので UWP のプロジェクトはない。
きちんと *.xaml があるので、XAML がロードできる。

type SampleXFormsFPage() = 
    inherit ContentPage()
    let xaml = base.LoadFromXaml(typeof<SampleXFormsFPage>)

C# だと、自動生成された *.g.cs ファイルにLoadFromXamlがあって、内部的にクラスとXAMLをマッチングさせる。で、F# の場合は *.g.fs を生成しないので、これを直接使うという訳。

いわゆる x:Name がプロパティにマッピングされないので、自前で FindByName を呼び出す。OnAddメソ\ッドは、動的にリフレクションを使っているのか、AddHandler を使わずにマッピングされる。UWP の場合は、Click に対応するイベントコードが生成されるが、Xamarin.Forms の場合は、イベントが生成されないのでビルド時にはチェックされず、実行時に対応するイベントがないと例外が発生する。これは C# も F# も同じ。

namespace SampleXFormsF

open Xamarin.Forms
open Xamarin.Forms.Xaml
open System

type TodoItem() =
    member val Id = &quot;&quot; with get, set 
    member val Name = &quot;&quot; with get, set
    member val Done = false with get, set

type SampleXFormsFPage() = 
    inherit ContentPage()
    let _ = base.LoadFromXaml(typeof<SampleXFormsFPage>)
    let todoList = base.FindByName<ListView>(&quot;todoList&quot;)
    let newItemName = base.FindByName<Entry>(&quot;newItemName&quot;)
    let items = new System.Collections.ObjectModel.ObservableCollection<TodoItem>()

    do
        todoList.ItemsSource <- items

    override this.OnAppearing() = 
        base.OnAppearing()

    member this.OnAdd( sender : obj, e : EventArgs ) =
            let item = new TodoItem()
            item.Id <- Guid.NewGuid().ToString()
            item.Name <- newItemName.Text
            items.Add( item )

    member this.OnSelected( sender : obj, e : SelectedItemChangedEventArgs ) = ()
    member this.OnRefresh( sender : obj, e : EventArgs ) =  ()
    member this.OnComplete( sender : obj, e : EventArgs ) =  ()

XAML はこんな感じ。もともと、Mobile App のクイックスタートのコードを持ってきているので、後から Azure に対応させる。

<?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?>
<ContentPage xmlns=&quot;http://xamarin.com/schemas/2014/forms&quot; xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot; xmlns:local=&quot;clr-namespace:SampleXFormsF&quot; x:Class=&quot;SampleXFormsF.SampleXFormsFPage&quot;>
    <Grid RowSpacing=&quot;0&quot;>
        <Grid.RowDefinitions>
            <RowDefinition Height=&quot;Auto&quot; />
            <RowDefinition Height=&quot;*&quot; />
        </Grid.RowDefinitions>
        <ActivityIndicator Grid.RowSpan=&quot;2&quot; HorizontalOptions=&quot;Center&quot; VerticalOptions=&quot;Center&quot; IsVisible=&quot;False&quot; IsEnabled=&quot;True&quot; x:Name=&quot;syncIndicator&quot;/>
        <StackLayout Grid.Row=&quot;0&quot; BackgroundColor=&quot;#5ABAFF&quot; Padding=&quot;10,30,10,5&quot;>
            <Label TextColor=&quot;#555555&quot; Text=&quot;Azure App Service&quot; />
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition Width=&quot;Auto&quot;/>
                </Grid.ColumnDefinitions>
                <Entry x:Name=&quot;newItemName&quot; Placeholder=&quot;Item name&quot; />
                <StackLayout x:Name=&quot;buttonsPanel&quot; Grid.Column=&quot;1&quot; Orientation=&quot;Horizontal&quot; HorizontalOptions=&quot;StartAndExpand&quot;>
                    <Button Text=&quot;+&quot; MinimumHeightRequest=&quot;30&quot; Clicked=&quot;OnAdd&quot; />
                </StackLayout>
            </Grid>
        </StackLayout>
        <ListView x:Name=&quot;todoList&quot; ItemSelected=&quot;OnSelected&quot; IsPullToRefreshEnabled=&quot;true&quot; Refreshing=&quot;OnRefresh&quot; Grid.Row=&quot;1&quot;>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <ViewCell.ContextActions>
                            <MenuItem Clicked=&quot;OnComplete&quot; Text=&quot;Complete&quot; CommandParameter=&quot;{Binding .}&quot;/>
                        </ViewCell.ContextActions>
                        <StackLayout HorizontalOptions=&quot;StartAndExpand&quot; Orientation=&quot;Horizontal&quot; Padding=&quot;15,5,0,0&quot;>
                            <StackLayout Padding=&quot;5,0,0,0&quot; VerticalOptions=&quot;StartAndExpand&quot; Orientation=&quot;Vertical&quot;>
                                <Label Text=&quot;{Binding Name}&quot; />
                            </StackLayout>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

実行してみる


ちなみに、フロントが C# でも良いならば、適当な UWP プロジェクトを取ってきて、F# の PCL を参照設定することで動作できる。

カテゴリー: 開発, F#, Xamarin | F#でXamarin.Formsを使う はコメントを受け付けていません