Windows ストアアプリから C言語の fopen を利用してローカルファイルを開く方法

久し振りのC言語ですが、ざざっと書き下します。とあるところで調べ始めてちょっと突っ込んだところで、本当にできるのか?ってのを調査している途中です。いや、正直に言うと目的を達する手段としては結構危ういので、まともに C# で書き直すなり別な方法を取ったほうがいいのですが、最初の目論見が達成できるのかを確認しているところ。

C/C++のライブラリをそのまま使う

とあるC言語で作ったライブラリがあるとします。デスクトップアプリならば、そのまま静的リンクをするかDLL呼び出しをすれば良い訳で、OpenCVとかその手のC++絡みのライブラリはデスクトップ側で動かします。それを.NETで包むか、iOS内でリンクさせて動かすか、JavaからJNIを通して動かすかは色々と動作環境により方法が違うのですが、Windows ストアアプリ上で動かそうとすると結構な手間です。

ひとつの方法としては、C++/CXでストアアプリを使って、C言語のライブラリをリンクさせます。C++/CXのストアアプリからは、stlenとかstd:stringとかの標準的な関数は大方呼び出せます。大方呼び出せますというのは、ファイル系だとかシステム系の関数とかは難しそうで、環境変数を呼び出すgetenv関数はありません。逆に言えば、ロジック系のものであればそのまま動くだろうという目算が立ちます。そうなると、既存のC言語ライブラリをそのまま移植できるわけで、特にLinux系のオープンソースなライブラリをそのままビルドして持ってくることも可能だろう、と思われます。

フロントエンドをC++/CXで作るのも良いのですが、巷のサンプル的にはC#が圧倒的に多いのと(逆に言えばC++/CXのサンプルは非常に少ない)、async/awaitの非同期処理が結構やりづらいので C++/CX でフルのフロントエンドは作りたくないところです。そのような場合は、C言語のライブラリをうまくマッピングするC++/CXのライブラリを作っておいて、C#側に提供させます。ロジック部分は全体の一部分になることが多いので、大方のところは C# で書き、小難しくて Linux 等から移植しなければいけないところを C++/CX でブリッジを書くという具合になるわけです。

大体これで行けそうな気がするのですが、ふと、移植ができるかな?というライブラリを眺めてみると、ファイル系のアクセスがありました。

fopen関数は扱えるのか?

最初の設定やらデータやらを読み込む場合には、C言語のライブラリではfopen関数が普通に使われます。あちこちで、fread/fwrite関数しているところを、WinRT の StorageFile に直すのは結構手間です…と言いますか、ほぼ不可能です。StorageFile クラスのほうは、非同期処理が入ってしまって、どうにもこうにもファイル読み込み部分の書き換えが必須になってしまいます。部分的に fread 関数呼び出しているところあれば、なんとかなるのですが、このあたりが混在して散らばっていしまっていると修正するのはちょっとお手上げ状態ですね。いちから作る直したほうが良いのでは?と思うくらいで、だからこそ、ストアアプリのほうの移植が進まないのかもしれません。

C# でファイルアクセスをしているとファイル名を直接扱うことは少ないので、じゃあ、C言語で使う fopen 関数が使えるのか?という疑問が発生します。ストアアプリの C#/VB のほうでは System.IO.File クラスが無くなっていて直接ファイル名を指定して任意なところにアクセスすることはできません。しかし、「任意なところ」以外はできるわけで、アプリケーションに含まれているファイル(Assetsの下とか、インストール先の適当なフォルダとか)、アプリケーションデータフォルダとかはアクセスが可能です、以前、直接 C++/CX で「任意なファイル」をオープンしようと思ったのですが、駄目だったのであっさりあきらめていたのですが、今回はもうちょっと突っ込んで調べてみました。

結論から言うと、fopen 系のファイルアクセスも C#/VB と同じように、アプリのインストールフォルダ内やアプリデータのフォルダへはアクセスができます。fget/fread 関数を呼び出してテキストなりバイナリデータを読み込むことができます。おそらく、アプリデータ領域であれば fwrite することも可能でしょう。この辺りは、もうちょっと後で調べていきます。ひとまず、いまやりたいことは、最初の設定が必要だったので、fopen/fread の組み合わせだけが必要になります。

ライブラリ内の fopen が有効に働く

目標としては、オープンソース系の C/C++ ライブラリをそのままストアアプリでリンクをすることです。

NuGet Gallery | Xamarin.Tesseract 0.2.4
http://www.nuget.org/packages/Xamarin.Tesseract/

実はちょうど調べていたときに、Xamarin 上で動く Tesseract-OCR を見つけました。折しも作り始めたばかりで、なんといいタイミングでしょう、という感じです。中のソースを見ると解るのですが、Xamarin.Android/iOS のプロジェクトのそれぞれの Tesseract のバイナリ形式のライブラリが含まれます。なるほど。Android の場合は、そのまま JNI で wrap させればよいし、iOS の場合には Objective-C から呼び出せるわけですから、それを C# から呼び出せる形式に変えればよいわけです。中身のファイルアクセス系も、Android/iOS ならばそのまま動きそうですね。

が、ストアアプリの場合は、中身のファイルアクセス系がそのまま動くかどうかはわかりません。Tesseract の場合 OCR の学習データのためにバイナリデータを読み込みます。これは Tesseract 自身のコードを見るとわけあるのですが、あちこちに fread 関数が散っているんですよね。ワンパスで読み込んでいるのか、適当に seek しているのかは不明ですが、これらを StorageFile 系に変えるのはほぼ不可能です。

となれば、ライブラリ内の fopen 関数が正常に動くのかどうか?が問題になってきます。

これも結論を言えば、自作の静的ライブラリでは動きました。Tesseract 自身はまだ試していないのでなんとも言えませんが、getenv 関数のダミーを作って環境変数を適当に操作してやれば読み込めそうです。

C# から C言語ライブラリの fopen を呼び出す

C# から C言語のライブラリは直接呼び出せないので、ところどころに相互運用が必要になります。

– C# でストアアプリのフロントエンドを書く。
– C++/CX で Windows ランタイムコンポーネントを作る。
– C言語のライブラリを書く

という具合になります。フロントエンドを C++/CX で書けば、C# のストアアプリは必要なくなりますが、先にも書いたように、C# のほうがサンプル数が圧倒的に多いので C# でフロントエンドを書くほうがよいでしょう。

また、「C言語のライブラリを書く」ところは、C++/CX のライブラリにしてもよいのですが、これだと C++/CX の文法に寄ってしまったり、コードの保守が煩雑になってしまうので、別途ビルドした C/C++ ライブラリを静的にリンクさせるほうがよいでしょう。DLL の場合はどうだったのかはわかりません。DLL 自体の配布がややこしくなるので、これは別途要検討というところですね。

プロジェクト形式は、

– CheckApp — C# のストアアプリ
– CheckCLib — C言語のライブラリ
– CheckCppcx — 相互運用の C++/CX ライブラリ

になります。ストアアプリ内にある Assets/sample.txt を fopen して読み込もうという計画です。

C言語のライブラリ(CheckCLib)は、ごく普通に fgets でファイル読み込みをします。ここではバッファは面倒なので static で返していますが、別途バッファを渡すほうが安全です。このあたりメモリ操作系の malloc/free, new/delete の扱いも注意しないといけませんね。

char *Class2::OpenPath(const char *path)
{
	FILE *fp = fopen(path, "rt");
	static char buf[256] = { 0 };
	fgets(buf, 256, fp);
	fclose(fp);
	return buf;
}

これを C++/CX のライブラリから呼び出します。C# からは、char* を直接扱えないので String から変換させます。

Platform::String^ Class1::OpenPathC(Platform::String^ fpath)
{
	std::wstring w(fpath->Data());
	std::string s(w.begin(), w.end());
	const char *path = s.c_str();

	auto obj = new Class2();
	auto buf = obj->OpenPath(path);

	std::string ss(buf);
	auto text = ref new String(std::wstring(ss.begin(), ss.end()).c_str());
	return text;
}

C++/CX では文字列を Platform::String で扱います。この String は wchar_t になっているので、std::wstring と std::string を駆使して const char * に変換します。漢字が入っているとこれでは駄目なのですが、ASCII の範疇であればこれで十分です。

この C++/CX で作ったライブラリを C# のストアアプリから呼び出すと次のコードになります。

private void clickOpen6(object sender, RoutedEventArgs e)
{
    var path = "D:\work\OCR\sample\CheckApp\CheckApp\bin\x86\Debug\AppX\Assets\sample.txt";
    var obj = new Class1();
    var text = obj.OpenPathC(path);
    text1.Text = text;
    text2.Text = path;
}

パスがいやに直接的に指定していますが、これで動きます。実はアクセスさせてみると分かりますが、AppX 以下のファイルであれば普通に fopen できますが、それ以外の場所だとエラーになります。また AppX 内であってもプロジェクト内に含まれていないファイルにアクセスしようとするとエラーになります。きっちりと、ストアアプリの StorageFile 系のアクセス禁止と同じ法則が適用されているのが素晴らしいところです。これ、OS の API レベルでガードされているということですよね。

ここのパス名をどうやってとるのかというと、普通に ms-appx:/// を使うこともできます。

private async void clickOpen6(object sender, RoutedEventArgs e)
{
    var uri = new Uri("ms-appx:///Assets/sample.txt");
    var file = await StorageFile.GetFileFromApplicationUriAsync(uri);
    var path = file.Path;
    var obj = new Class1();
    var text = obj.OpenPathC(path);
    text1.Text = text;
    text2.Text = path;
}

こんな風に、一度 StorageFile.GetFileFromApplicationUriAsync を使って呼び出して、Path プロパティで取得すればよいのです。こうすると、いつも通り、ms-appx:/// でアクセスできるので、ファイルの絶対パスが分からなくても良いのです。

サンプルコード

テスト時に作ったサンプルコードはこちら。インクルードパス等が固定になっているかもしれませんが、参考まで。

CheckApp-v0.1-src.zip

おまけ

ストアアプリでファイルアクセスをするときには、非同期の await/async が必須になります。しかし、コードを見てみると気付くと思いますが、C言語の fopen 関数の場合は同期的に作ることになります。そういう意味では、ストアアプリの基準から逸脱しているようにも見えますが、逆に言えば、デスクトップアプリと同じような仕組みでファイルアクセスをする手段を得られる、ということでもありますね。ストアアプリの場合、http アクセスしてちらちらとデータを取ってくるのには適しているのですが、ローカル環境でがりがりと動かすにはちょっと道具不足、と言いますか昔ながらの C言語アクセスが楽だったりします。多分、std::cin/cout も使えるんじゃないかなと思うので、このあたりは後で試してみたいと思います。

補記

CreateFile2 function (Windows)
https://msdn.microsoft.com/en-us/library/windows/desktop/hh449422(v=vs.85).aspx

When called from a Windows Store app, CreateFile2 is simplified. You can open only files or directories inside the ApplicationData.LocalFolder or Package.InstalledLocation directories. You can’t open named pipes or mailslots or create encrypted files (FILE_ATTRIBUTE_ENCRYPTED).

とあるので、内部的に Windows ストアアプリも fopen も CreateFile2 関数の動きに準じてそうとのこと Special thanks @biac -san .

カテゴリー: 開発, C#, C++/CX | Windows ストアアプリから C言語の fopen を利用してローカルファイルを開く方法 はコメントを受け付けていません

Objective-C の storyboard を Swift に移植する

誰得?な感じですが、必要となったときに参考にしてください。なぜ、必要に迫られているかというと、Objective‐C逆引き大全555の極意 を Swift 版に移植中なのです。先日 WWDC で Swift2 が発表になったので、結果的には Swift2 に移植することになるわけですが、まあ、両方買うと、Objective-C と Swift2 と相互に変換できて便利、という組み合わせな訳です。

その中で、最初はちまちまと Swfit 用の storyboard を書き直していたわけですが、storyboard 自体は中身が xml だし、そのままプロジェクト間をコピーしたら動かないか?と思ったわけです。結論から言えば、動きました。ViewController のクラス指定と、IBAction/IBOutlet に注意する必要がありますが、うまく手順を踏めば、きれいに動きます。

逆引きの場合、storyboard に 100 枚以上の ViewController が貼り付けてあって、その上に各種のコントローラがペタペタとついているんですよね。以前は、1項目を1プロジェクトに対応させていたのですが、プロジェクト自体が大量になって(起動が)面倒くさいのと、実機に入れてちまちまと動かすのには適当な単位でプロジェクト化しておいたほうが動作確認しやすいためです。まあ、そんなこともあって、Master-Detail プロジェクトを使って 100 枚程度の ViewController を行き来できるようにしてあります。

こんな感じにメニュー化されていて、

image

こんな風に遷移させています。

image

 

移行手順

ざっと、移行させるための手順は以下の通りに。

  1. 既存の Objective-C のプロジェクトを開く。
  2. 新規の Swift プロジェクトを作成する。
  3. 新規の Main.storyboard の名前を変えて、既存の Obj-C の Main.storyboard をコピー。
  4. Swift 側に、同じ名前で ViewControler を作成する。
  5. Swift 側に、同じ名前で、IBOutlet と IBAction を仮に用意する。
  6. ViewController の Custom Class 名を、一度、書き直させる。
    いったん、別の名前に設定して、元に戻すのでok(内部的な ID を振り直すために必須、)
    image
  7. Outlet を確認する。
    同じ名前で付けてあるはずなので、すべてが接続された状態になる。
    image

この状態でビルドをして実行をするとうまく動きます。ViewController のマッピング自体は、実行時に行われるので、画面を遷移させたときにエラーになるかどうかが分かります。うまく Outlet が設定されていない場合や、6 の時点で ViewController の ID の再割り付けが失敗している場合は、アプリケーションエラーになり、AppDelegate.swift で例外が発生します(これが訳が分からないので、再度 ViewController の再割り付けを確認するのが無難)。

実は、便利なのは storyboard が参照している ViewController をすべて揃えなくても良いことです。これは表示時に動的に参照されるので、使うところだけの ViewController を用意すれば良いわけです。なので、1枚1枚確認してから先に進むことができます。

まあ、画面を移行させても、中身は Swift なので書き直しが必須になる訳ですが、これはこれでそのまま使えるかなと。Xcode の場合プロジェクト間で、コントロールのコピー&ペーストができないので、storyboard そのままを移行させると便利ですね。この方法は、Swift 1.2 から Swift 2 への移行にも使えます。

カテゴリー: Objective-C, Swift | Objective-C の storyboard を Swift に移植する はコメントを受け付けていません

.NETラボ勉強会 Windows IoT on RPi2 のビデオ

.NETラボ勉強会 で Windows 10 IoT Core on Raspberry Pi のハンズオンをやりました | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/7212

のビデオを撮ってもらったので、youtube にアップしました。固定カメラだったので、実験中のビデオをはほぼほぼカットして今って、雰囲気だけ5分に縮めてあります。手前のノートブックに Visual Studio 2015RC が入っていて、Raspberry Pi にアップロード。そのまま LED がチカチカするか、VGA のモニタに時刻が写るか、という具合です。ブレッドボードにタクトスイッチを付けて、モニタに表示させよう、というところが失敗していますが、このあたりはいずれ家で試してみて撮り直しをしましょう。

実況ビデオ

 

 

サンプルコード

winiot_rpi2_src.zip

カテゴリー: RaspberryPi, Win IoT | .NETラボ勉強会 Windows IoT on RPi2 のビデオ はコメントを受け付けていません

『絶歌』を否定する

あまり、批判的な記事を書くことはなくなったのだが、これだけは残しておこう。書いた後に、エディタから消し去ってしまうか、そのままブログに載せるかは別のこと。自分が何をしようとしているのかを対象化することに「意義」があること、の実践でもある。

当然のように批判を浴びている元少年Aによる「絶歌」である。元少年Aというと、如何にも更生したかのような(正確に言えば正常に戻ったかのような)言い方になってしまうが、酒鬼薔薇事件の犯人であり、そこには「元」という冠詞はつかない。むろん、「刑期」を終えたのだから、刑期の前のように人権は扱われるべきではあるものの、直接的な被害者、殺人という事件の特異性、今回の「手記」を出してしまうような状況を鑑みれば、被害者の視点から「反省していて受け入れられる人」となるかは疑問である。

酒鬼薔薇事件が起きた当時「なぜ、人を殺してはいけないのか?」という問いが「流行し」その後に少年法が改正されたはずだ。一度、少年法として緩くなったところに起こった事件で、その後、少年法が強化された。そんなきっかけとなる事件でもあり、ある「緩い少年法」のもとに裁かれたという事実がある。それを考えると、当時、犯人と思われる少年(容疑者という範疇だった)が保護されるものであり、また「幼年時期に起こした過ちは、なんらかの家庭環境にて影響があるものだから、本人だけに責任があるわけではない。それゆえに更生したのちは、社会に受け入れられるべきであり、そのためにも名前を公表するべきではない」という根拠から、常に名前は伏せられていた。今となっては、インターネットで手軽に検索できるものでもあるし、何処の誰だか特定も可能であろう。

「なぜ、人を殺してはいけないのか」の議論に関しては、色々な「有識者」がテレビ等で議論したにも関わらず結論はでなかった。非常に馬鹿馬鹿しい話ではないけれども結論がでなかった。憲法や刑法などで人を殺してはいけないという意見もあれば、人を殺したことにより自分が苦しむからとか、人を殺すこと自体が絶対駄目だとか、じゃあ戦争の場合はどうなんだとか、そういう些末な議論が多かった。それが「些末」に感じたのは、そこには被害者が不在だったからだ。直接的な被害者は「殺されてしまった」ので生きることができないし、残された遺族(あるいは子を殺された親)にとっては、殺人者を肯定できる意義は全くない。そこには、犯罪者への同意は存在しない。時にして、そういう自己卑下になることもあるが(自分の父親が交通事故にあったときがそれだ)、いや考えてみれば、他人と自分とは「違う」ということ、相手にとっては自分は他者であることを強く意識すれば同調なんてする必要はない。被害者にとっては、子を殺された厳然たる「被害者」という立場があり、相手はどうやっても「犯罪者」だ。
そういう、曖昧模糊とした他人行儀な議論を続けた末に『「少年A」この子を生んで』というものが出版されて、マスコミ等で話題になる。いまでこそ Amazon 等で批判を受けた批評が残っているが、当時の盛り上がりは、実に被害者が不在であることと示し、そこには「酒鬼薔薇事件」を消費する世間という姿があった。いや、当時はそうは思ってはいなくて、単に嫌な感じがしていただけなのだが、今の私だと解る。それは「事件」そのものを消費している世間(主にテレビ?)があり、その突飛な事件こと話題性こそがターゲットを被害者ではなく「犯罪者」のほうに目を向けさせて、厳然と存在する事実であった「殺人」さえもおぼろげにさせてしまっている。そこには、一般的な「反省」の姿や、思いやりの姿がない。

殺人者であり死刑囚であった永山則夫は獄中で小説を書き、獄中で結婚をし、獄中のまま死刑になった。殺人者にとって「小説」を書く自由があるのかどうか、また、書いた小説が賞を取るという「栄誉」を与えられてしまうのかどうか、という問題が当時はあった。勿論、永山則夫は成人男性であり、少年Aは未成年であり少年法の範疇である、という違いはあるかもしれないが、「絶歌」なる手記を出版してしまうという時期には32才という成人でもあり、そこにはなんらかの分別が必要であろう。それは、この文章自体のように書き連ねることの自由はあるが、その自由を「被害者」にまで行使して良いのか?という「分別」である。
永山則夫の小説は買ったことがある。が、『「少年A」この子を生んで』も『絶歌』も買わないだろう読まないだろう。なんらかの折りに資料として手を取るかもしれないが、その時は被害者の出した手記と同時に読むだろう。なぜだろう。そこには「殺人事件」を商品化する過程があり、それを消費する自分が見えてくるからだ。確かに、言論は自由ではあるし、ともあれば誰かを批判するための出版は自由であろう。それ揺れに、読まない自由もあるし買わない自由もある。が、この出版に関しては「言論の自由」以外のところにある厭らしさ、というか絶対的な否定感がある。おそらく、それが「殺人」に対して自己商品化する態度だ。

話を元に戻そう。「なぜ、人を殺してはいけないのか」の議論がなされ、結局のところ「なぜ、人を殺してはいけないのか」の結論が出なかった時代であった訳だが、自分の中では確信になるものがある。「人を殺すような人がはびこる世間は、自分にとって危険だ」からだ。おそらく、利己的な遺伝子と自分が属する社会の在り方(分化する社会)からの結論になる。人を殺すことの不利益もそうだから、人を殺す人が蔓延る世の中も私にとって不利益である。この論法から言えば、「酒鬼薔薇事件」を起こした犯人が「絶歌」という本を出版し、被害者の意見も聞かず自己満足のために出版へと踏切り世の中になんらかの「名」を成すというスタイル(まさしく、このスタイルそのものが「酒鬼薔薇事件」だということがわかる)を、私は肯定することはできない。かつ、私はそういうものを受け入れる社会は否定する。

カテゴリー: 雑談 | 3件のコメント

DataTable よりも List を使うと 10 倍早くなる(続編)

うちのサイトでは地味にアクセス数が多いページで、

意外と遅い DataTable 、なので List を使うと 5 倍早くなる | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2228/comment-page-1#comment-24840175301577487

というのがあります。もう4年程前の記事で、業務で VB.NET 2.0 を使わないといけなくて、LINQ が使えなかったので、DataTable にしようか、List にしようかという調査の記録です。今だと、もうちょっと色々なやり方があるのですが、ちょっとコメントとが付いたので、計測しなおしてみました。

■list を使う
■list に構造体を使う

上記2つのケースについて、他の3つのケースにて行っている
「あえて最初に行を追加しておく」処理の記述がないように
お見受けしますが、これは単なる誤記であり実際は記述の上
実行された計測結果ということでしょうか。

随分前だったので覚えていないのですが、おそらく比較コードのミスです。あらかじめ1万行いれておいたのは「更新系」をチェックする必要があったので、このままだと list のほうが有利に働きますよね。
気になったので Go4 と Go5 だけ抜き出して書き直しました。

実験コード

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    DataTable MakeDataTable()
    {
        var dt = new DataTable();
        for (int i = 0; i < 100; i++)
        {
            dt.Columns.Add(new DataColumn(&quot;x&quot; + i.ToString(), typeof(string)));
        }
        return dt;
    }

    double Go4()
    {
        var dt = MakeDataTable();//  new DataTable();
        // データ更新チェックのため、あらかじめ1万行を作る
        for (int i = 0; i < 10000; i++)
        {
            var row = dt.NewRow();
            dt.Rows.Add(row);
        }
        // 計測開始
        var start = DateTime.Now;
        var tm = DateTime.Now;
        for (int i = 0; i < dt.Rows.Count; i++)
        {
            var row = dt.Rows[i];
            for (int j = 0; j < 100; j++)
            {
                row[j] = tm.ToShortDateString(); tm.AddSeconds(1);
            }
        }
        var tend = DateTime.Now;
        var span = (tend - start).TotalSeconds;
        return span;
    }

    double Go5()
    {
        var dt = new List<object>();
        // データ更新チェックのため、あらかじめ1万行を作る
        for (int i = 0; i < 10000; i++)
        {
            var row = new object[100];
            dt.Add(row);
        }
        // 計測開始
        var start = DateTime.Now;
        var tm = DateTime.Now;
        for (int i = 0; i < dt.Count; i++)
        {
            var row = dt[i] as object[];
            for (int j = 0; j < 100; j++)
            {
                row[j] = tm.ToShortDateString(); tm.AddSeconds(1);
            }
        }
        var tend = DateTime.Now;
        var span = (tend - start).TotalSeconds;
        return span;
    }

    /// <summary>
    /// DataTable を利用
    /// </summary>
    /// <param name=&quot;sender&quot;></param>
    /// <param name=&quot;e&quot;></param>
    private void button1_Click(object sender, EventArgs e)
    {
        var t = this.Go4();
        label1.Text = t.ToString();
    }

    /// <summary>
    /// list を利用
    /// </summary>
    /// <param name=&quot;sender&quot;></param>
    /// <param name=&quot;e&quot;></param>
    private void button2_Click(object sender, EventArgs e)
    {
        var t = this.Go5();
        label1.Text = t.ToString();
    }
}

どちらも、1万行いれておいて、その行に対して逐一データを修正するというスタイルになっています。
コードは Visual Studio 2013 を使っています。

実験結果

DataTable 8.32, 8.12, 8.12 sec
list 0.88, 0.83, 0.83 sec

とう具合に、DataTable よりも list のほうが 10 倍早くなっています。
すべてオンメモリで動くために、後は CPU 速度の違いになるとは思うのですが、PC が異なるし(こっちの PC のほうが断然早いので)相対的な結果になります。以前の 4,5倍よりも開きが大きくなっています。絶対値の違いは、VB が C# よりも遅いのではなくて、計測する CPU の差です。

カテゴリー: 開発 | DataTable よりも List を使うと 10 倍早くなる(続編) はコメントを受け付けていません

.NETラボ勉強会 で Windows 10 IoT Core on Raspberry Pi のハンズオンをやりました

思い付きでやったので、準備不足…な感は否めませんが、ひとまずハンズオンっぽいものをやりました。.NETラボ勉強会の面子にビデオを撮ってもらったので、後で編集して Youtube 等に上げたいと思います。初動が悪かったので、Raspberry Pi の初心者ばかりが集まる=手ぶらでやってくる方がほとんどと思ったのですが、予想に反して Raspberry Pi 2 を持って来て下さった方が8名余りもいらっしゃいました。ありがとうございます。ちょっと、そっちのほうは予想しなかったので、ほぼ放置状態になってしまったのが申し訳ない/残念なのですが、ひとまず、Raspberry Pi 2 に Windows IoT Core を入れるのは結構大変である、ってのは実感して頂けたと思います。いやいや、あれだけ色々な人がやって、結構躓くのだから、家でひとりでやると2時間ぐらいうんうん唸ってしてまっても仕方がないのですよ。情報的には、英語のサイト Windows IoT – Get Started から辿れるところにありますが、各自の環境(Mac, Windows, 仮想環境, micro SD カードの状況などなど)が異なっていて、一筋縄にはいきません。挙句、終わりの5時頃にできたのは、LED をチカチカさせるところまでです。

image

ただし、その前に「どこで躓くのか?」を話したのと、後ろのほうで「何ができるのか?」を話したので、Hello world 的に Lチカを済ませた後に「どこに進めばよいのか」は何となく掴めたのではないか、とは思っています。Raspberry Pi 2 自体は 5,000円程度はするし、時間とお金を掛けるのであれば、それなりに「何か」を掴み取りたいものです。

土曜日には、あえて言いませんでしたが、Windows IoT Core on RPi で四苦八苦するよりは、Raspberry Pi にすんなり Rasbian を乗せて制御したほうが断然いいです。また、Linux に四苦八苦するよりは、Arduino を使ってさっくりとセンサー制御を学んだほうがいいです。そのあたりは、適材適所なところと、「将来的に何をするのか?」の目標を踏み間違えないようにすれば、無駄な学習をせずにすみます(勿論、あえて「無駄な学習」をして異なる知見を得る方法もあります。私もよくやりますが)。

Windows Iot Core on Raspberry Pi の準備

来週から一週間ほど 6/1(月)Windows 10 for Raspberry PI2 開発実習ハンズオンセミナー – connpass が開催されるので、躓きそうなところ(勝手に)支援しておきます。18:00 スタート 21:00 終了というハードスケジュール(平日なので夕方から開催。たぶん de:code に合わせたと思うのですが、かなりきついスケジュール)なので、どこかで躓くと、Lチカもできずに帰ることになってしまうので。

  • 開発環境が Windows 10 IP + Visual Studio 2015 RC となっていますが、現在の Windows 10 IP の build が非常に不安定なので、Windows 8.1 + VS2015RC の組み合わせも検討してみてください。.NET ラボのハンズオンでは半分ぐらいが Mac に Windows 10 IP を入れていましたが、最新のとそうでないものとが混在すると結構ややこしいです。
  • 古い Windows 10 IP だと micro SD カードに書き込む dism のコマンドが異なっています。矛盾していますが、Windows 10 IP は新しいバージョンを使って micro SD カードに焼きこみます。ある意味、micro SD カードへの焼きこみだけ Windows 10 IP で行って、開発環境は Windows 8.1 + VS2015RC という組み合わせが安定します。ちなみに、Visual Studio 2013 と 2015RC は混在できています(これは安定しています)。
  • Windows 10 IP + Visual Studio 2015RC の組み合わせの場合、VS2015RC を立ち上げるときには「開発者モード」にする必要があります。そうしないと、Windows IoT の XAML デザイナが動きません(Windows 8.1 の場合は、もともと動かないので、気にする必要はありません)。デザイナを有効にするためには、「設定」→「Update & security」→「for Developers」を開いて設定します。ちょっと前の Windows 10 IP では、このページが落ちてしまうので Windows 10 を入れた直後に VS2015RC を入れて起動するとXAMLデザイナが動かないときの対処方法 | Moonmile Solutions Blog を参考にして、グループポリシーを設定してください。
  • micro SD カードに焼きこむときに dism コマンドを使いますが、その中の「/ImageFile:flash.ffu」にある、flash.ffu というのは焼きこむファイル名です。Windows_IoT_Core_RPI2_BUILD.zip を解凍すると flash.ffu というファイルがあるので、カレントディレクトリを cd コマンドで移動させてください。コマンドプロンプトを管理者モードで開くと「c:¥¥windows¥¥system32」になります。そのまま dism コマンドをコピペすると flash.ffu が見つからないので書き込みエラーになります(意外と嵌る人が多い)。
  • 多分、Win IoT のプログラムはユニバーサルアプリで作ると思うのですが、あらかじめ Visual Studio 2015 RC を立ち上げて、Universal アプリのテンプレートをダウンロードしておきます。
    以下な、感じになっていれば ok です。回線が細いと、このアップデートだけで20分以上かかります。最初の Universal アプリを作ったときテンプレートがダウンロードされるので、適当なプロジェクトで1回だけ作成しておきます。

image

  • できれば、Raspberry Pi 2 に Windows 10 IoT Core を乗せて Hello world するまで | Moonmile Solutions Blog 等を読んで、micro SD カードを Raspberry Pi に挿して Windows IoT Core の起動だけは確認しておくと安心です。ここまでを含めてハンズオンかもしれませんが、ここをやっておくだけで MS 太田さんの手間がかなり省けます。
  • 電源キットは、2A 5V の…となっていますが、実はノートパソコンから USB 給電だけでも起動します。ただし、HDMI 接続をしているときはうまく立ち上がらないことが多い(起動はしているので、Powershell やアップロードなどは問題ない)ので、電源アダプタは持っておくといかもしれませんね。Android 用の急速充電器(2A版)があればそれを使うとよいです。おそらく、iPad の充電器も使えるのではないかと。まあ、行った先で若松通商さんで買うのもアリですね。

あとは、受講用に Azure を登録しておくとか諸々ありますが、それらは別途、connpass のドキュメントを見てください。

ハンズオンの実際

少しぐらいは資料を用意すればよかったのと、事前に作っておいた(実は、超音波センサーのプログラムは前日…というか当日の夜に作ってあった)プログラムは、配布すればよかったですね。なんとなく、Raspberry Pi を各自動かしてみてから、の頭があったので配布し忘れていたのですが。

image

組み込みに関しては、全くやったことはないけど興味がある方から、自前で液晶ディスプレイを持参できる方までさまざまです。特に Windows IoT on RPi の場合には、もともと Raspberry Pi を使っている方は、それなりに機材が揃っている(自前のハット/拡張ボードとか)のでそれを使いたいものです。ちなみ、Raspberry Pi の既存のハットなのですが、Windows IoT のピン番号が異なっていたり融通が効かなかったりするので、ほとんど使えないと思います。I2C オンリーならば大丈夫かも、ってことで I2C ボードだけは検証します。残念ながら手元の Sparkfun のモータードライバは使えませんでした。

あと、歴史的に Raspberry Pi よりも Arduino のほうが早い時期からあるため、電子工作的なキットでは Arduino のほうが豊富にあります。このため RPi はサーバー機やカメラ利用に使われることが多いのです。Linux が動くので、そのまま apt-get で作れるってのがメリットですね。ですが、Windows IoT Core の場合は、どちらかというと Arduino 寄りな GPIO 接続を狙っているらしく、そのあたりが中途半端になっています。アナログ入力が RPi にはないので、温度センサーもちょっと工夫しないといけません。まあ、そのあたりは、Arduino + Raspberry Pi の組み合わせで試行するのがよいでしょう。ハンズオンでも話しましたが、中国の Arduino Nano 互換機は 300円と非常に安く手に入ります。Arduino Uno 互換機も 700円程度です。この値段ならば、Arduino を PIC などのチップと同様に扱うことも可能です(まあ、それでも 100円とかに比べれば十分高いですが)。

image

ハンズオンでは、敢えて安いブレッドボードと、あえて安いLED/ジャンパーワイヤーを使っています。色々高いものもあるのですが、価格的には低いところから入れますよというのを強調したかったからです。

こんな風な Arduino 戦車であれば、3,500円位で作れます。Arduno Nano 互換機を使えばもっと安くなります。これは Windows Remote Arduino 用にしたもので、Windows ストアアプリや Xamarin.Android を使って Android スマートフォンから操作できます。

image

ロボットアームも、おもちゃのものであれば 5,000円弱からあります。これは株式会社イスペットが出しているグリッパーアームロボットです。サーボモータではないので安く作れています。ロボットアームは2,3年前あたりに流行ったのですが、高価なものが多かった(5万円位?)ためか今ではほとんどが発売中止になっています。うまくやると、グリッパーアームで積木を積むことができます。

image

以前、Kickstarter で出ていた meArm も持ってきました。これはサーボモーターが4つ付いて 3,500円ぐらいです。英国産です。Raspberry Pi 用に Sparkfan で I2C ボードも買ってあるので、いずれ Windows IoT で動かしてみましょう。一年前に meArmPi の動作メモ | Moonmile Solutions Blog あたりで、Raspbian + mono + F# で動かしたことがあります。

image

半田付けの電子工作の例として、RealSense コンテストのとき作った妖怪ウォッチ LED も持って行きました。これは抵抗付きなので、そのまま Raspberry Pi のピンから給電しても大丈夫です。

image

私の場合、Internet of Things というよりも、動かせるモノからスタートしているので他とはちょっと毛色が違うのですが、具体的に「何かが動く」とか「何を操作できる」という入り口がいいかなと思っています。手元の Raspberry Pi 5 台は小学校でのワークショップ用に買ってはみたのですが、Windows IoT だとディスプレイがないとちょっと辛い感じ(Raspbian だとなおさら)なので、この辺りは考え直そうかなと思案中ですね。

次は何をするか?

Windows IoT Core で Lチカをやったり、モーターを制御できた後は何をするのか?が問題ですよね。ホビー的にマルチコプターに進むのもいいのですが、ワタクシ的には地べたを這いずり回るほうが「安全」なので、キャタピラと車輪とアームのほうに進みます。位置情報なんかは「確率ロボテック」を読むとよいでしょう。ルンバの位置情報はこれを使っているハズです。あと、カメラとアーム制御なんかは、ROS に対応させるのも良いかなと。

という訳で、.NET ラボ勉強会スタッフの皆様と、会場を提供してくださった Microsoft さんに感謝です。

参考資料

いくつか Windows IoT Core on Raspberry Pi で参考になりそうな書籍を上げておきます。

サンプルコード

ハンズオンで使った(使う予定だった)サンプルコードを後で載せておきます。

  • いわゆる Hello World.
  • Lチカ
  • 超音波センサーで(おざなりに)距離計測
  • グリッパーアームロボットを遠隔操作
  • Windows Remote Arduino で Arduino 戦車をストアプリで操作
  • Windows Remote Arduino で Arduino 戦車を Android スマートフォンで操作(Xamarin.Android)

の予定

 

カテゴリー: RaspberryPi, Win IoT | 1件のコメント

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(&quot;No Bluetooth adapter found.&quot;))
        if adapter.IsEnabled = false then
            raise (Exception(&quot;Bluetooth adapter is not enabled.&quot;))
        let device = adapter.BondedDevices.FirstOrDefault( fun x -> x.Name = &quot;HC-06&quot; )
        if device = null then
            raise (Exception(&quot;Named device not found.&quot;))
        _socket <- device.CreateRfcommSocketToServiceRecord(UUID.FromString(&quot;00001101-0000-1000-8000-00805f9b34fb&quot;))
        _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 &quot;main&quot; 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 <- &quot;connected.&quot;;
        )
        buttonOpen.Click.Add( fun args -> 
            arduino.Open();
            buttonConnect.Text <- &quot;Firmata opend.&quot;;
            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=&quot;internetClient&quot; />
    <DeviceCapability Name=&quot;bluetooth.rfcomm&quot;>
      <Device Id=&quot;any&quot;>
        <Function Type=&quot;name:serialPort&quot; />
      </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(&quot;HC-05&quot;);
        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 = &quot;接続しました&quot;;
        });
    }

    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 戦車を動かす はコメントを受け付けていません