廻る技術の覗き穴スマートフォン用の表示で見る廻る技術の覗き穴この広告は、90日以上更新していないブログに表示しています。Extensionsを使って、ROSのC#クライアントを作りました。Garage社が開発しているロボットアプリケーションのためのソフトウェア開発基盤です。WikiROSについてざっくりと知るには、以下のページが分かりやすいです。ysuga.netROSでは、ノードと呼ばれるロボットのソフトウェアモジュールをネットワークを通じて連携させてシステムを構築するわけですが、そのためのフレームワークやビルド・パッケージングの仕組みを提供しています。ロボット用とは言いますが、分散システムを構築するフレームワークなので、ロボット以外のアプリケーションにもいろいろと活用できそうです。ROSのクライアントライブラリROSのノード間で連携する機能としては、3種類のモデルの通信機能が用意されています。Publisher/Subscriber型のメッセージ通信(Topic)RPC型のメソッド呼び出し(Service)データ共有機能(Parameter)これらの通信を行うクライアントライブラリの実装指針が下記のページで示されています。Wiki現在のところクライアントライブラリの実装としては、C++、Python、LISP、Java、Lua、Rubyなどがありますが、C#の実装はまだないようです。ものがリリースされていました。これはC++のコードをC#でラップしていて、Linux+mono環境でのみ動作するものみたいです。RosSharpはすべてC#で実装していて、Windowsでしか動きません。SDKと組み合わせたいときには有用なんじゃないでしょうか。非同期プログラミング時代は非同期!スマートフォンやタブレットなどの端末が普及し、ユーザにストレスを与えないレスポンスのよいアプリケーションを開発することが重視されてきています。いったアプリケーションを開発するためには、非同期処理が重要となるわけですがなかなか実装が難しい。2012)ではasync/await構文がサポートされ、非同期処理が簡潔に書けるようになります。8の新しいアプリケーション実行基盤であるWinRTでは、50msec以上時間がかかる可能性のある処理は、非同期APIしか用意されていないそうです。Blogsというわけで、RosSharpでは通信周りなど時間のかかる処理については、すべて非同期呼び出しとなるように実装しました。Extensions(Rx)などがありますが、1回きりの非同期メソッド呼び出しはTask、データが非同期に連続的に流れてくるようなところはRx、と使い分けるのが良いようです。RosSharpでもその方針に従って実装しています。ExtensionsRosSharpのサンプルコードでは、RosSharpのコードを見てみましょう。同期呼び出しによるサンプルは以下のようになります。Nodeが生成されるまで待つ。Subscriberの生成。Subscriberが生成されるまで待つ。Pattern)によるサンプルは以下のようになります。Pattern)とかに比べると、ネストが深くならずにすむのでシンプルに書けますが、慣れてないと読み書きしにくいのではないでしょうか。Nodeが生成されたら呼び出される。Subscriberが生成されたら呼び出される。TaskContinuationOptions.OnlyOnFaulted);最後に、async/awaitを使ったサンプルは以下のようになります。*1同期型とほとんど同じ書き方になってシンプルですね。RCで動作確認していますが、まだリリース前で今後仕様が変わる可能性もあるのでご注意ください。ノード生成が完了するまでは呼び出し元に処理を返す。非同期でSubscriberを生成する。Subscriber生成が完了するまでは呼び出し元に処理を返す。メッセージを購読する。Console.WriteLine(ex.Message);}ちなみに、全部Rxで書いてみると以下のようになります。);非同期処理で気をつけるべきところさて、TaskやRxを使ってコードを書いているといろいろとハマったので注意すべき点を挙げてみます。(当たり前のことばかりかもしれませんが・・・)TaskにしろRxにしろ、例外をどのように伝搬させるかをしっかりと設計すること。正常系だけ先に書いて、あとで例外処理を追加していくと可読性の低いコードになってしまいました。ハンドルしてない例外があると、GCが動いたときにファイナライザで例外が発生するので、どこに問題があるのか追いかけづらいです。Rxではメッセージの購読の必要がなくなったらきちんと破棄すること。今回メモリリークを多数発生させてしまいました。ロックのタイミングに注意すること。ロックの範囲が広すぎると非同期処理のメリットも減ってしまうので、できるだけロック範囲が狭くなるように考える必要があります。RosSharpではあまり考慮できていないのですが、処理のキャンセルや進捗状況についても考える必要があります。NuGet対応ROSはOSと言うだけあって、ビルドやパッケージングするための基盤が用意されています。RosSharpはそのあたりにまったく依存していません。Galleryに登録してあるので、簡単に使い始めることができます。Language)ファイルで定義することができます。今回は、そのためのパーサをF#+FParsecで作りました。オフサイドルールのパーサに関しては、以下の記事のものを使わせていただきました。2つのアンコールシリアライザのパフォーマンスRosSharpはノード間でメッセージ通信を行う際に、当然メッセージのシリアライズ・デシリアライズを行います。下記の記事を見て、RosSharpのシリアライザのパフォーマンスはどんなものかなと思ったので、試しに計測してみました。Bufferより少し遅いくらいですね。これはRosSharpがすごいということではありません。RosSharp以外のシリアライザは、リフレクションを使ってクラス構造を解析しつつ、シリアライズを行っています。Language)ファイルを書いて、そこから特定の型専用のシリアライズ・デシリアライズ処理のソースコードを生成しています。いうわけでフェアじゃないです。Bufferがこの速度だということに驚きですね。2012がリリースされたら、async/awaitで全面的に書き直して、Rx2.0にも対応させたいですね。テストやドキュメントが不足しているので追加していきたいと思います。あとは、プロトタイプだけ作って放置してあるReactiveRTMもちゃんと実装しようかな。*1:この処理を呼び出すメソッドにはsyncキーワードを付与する必要があります。*2:Rx2.0ではTaskとの連携が強化されるのでもう少し簡潔に書けるようになります。PARTAKEの27日目(ボーナスステージ最終日)です。F#は以前勉強してたこともあったんですが*1、最近はちょっとご無沙汰してました。関数型プログラミング入門」を読んで勉強しなおしてみましたが、以前よりすんなりと頭に入ってきたように思います。日本語の書籍があるというのはありがたいですね。見るFParsec最近は、RTStorageやReactiveRTMというアプリケーションやフレームワークを作ったりしています。ZOETROPEの日記)のですが、立ち消えになってしまったのか、MGrammarは先行きがよく分からない状況です。いうわけで、今回はFParsecを使ってみました。F#からFParsec-0.9.1をダウンロードしてきてビルドします。ところが、いきなり以下のようなメッセージが出てビルドに失敗してしまいました。文字'・'は予期されていません。これは、FParseCS/Strings.csにShift-JISで読み込めない文字(0x91,0x92)が含まれていることが原因のようです。文字コードとしてはシングルクォーテーション的なものなので、シングルクォーテーションに書き換えるか、UTF8に変更して正しい文字に書き換えればよさそうです。シングルクォーテーションに置き換えてビルドしました。IDLをパースしてみます。最初は四苦八苦しましたが、なんとかパースできるようになりました。長いのでgistにおいて置きます。IDLパーサまだconst関連が処理できなかったり、プリミティブ型と似た(longXxxxのような)名前の型名が処理できなかったりしますが、基本的な文法はだいたいパースできていると思います。FParsecの使い方に関しては下記の記事/サイトが非常に参考になりました。F#FsUnitかNaturalSpecかパーサの動作確認をするためには、テストフレームワークが必須です。F#のテストフレームワークを調べてみると、どうやらFsUnitかNaturalSpecが有名なようです。どちらもNuGetから入れられますね。NuGetすばらしい。今回は以下のバージョンのものを入れて比較してみました。1.2.17.1まずFsUnitですが、パース結果の判別共用体(AST)を比較したときにテストに失敗すると、メッセージがこんな感じになってしまいます。<idl.ast+Definition+Interface>これではテストに失敗しても何がおかしいのか分かりません。FsUnitはテストの実行をNUnitに丸投げしているだけなので、オブジェクトの中身までは表示してくれないんですね。"%A"」で表示メッセージを組み立てているので、以下のように具体的な値の違いが分かります。("Hoge",[],[],null)これでテストも捗りますね。今回はNatualSpecを使うことにしました。NaturalSpecをNuGetで入れた直後は、以下のようなエラーが発生してビルドできませんでした。バージョンと一致する、このライブラリのバージョンへの明示的な参照を作成してください。読み取れませんとりあえず、FSharp.Core.dllとFSharp.PowerPack.dllを参照から削除すれば動きましたが、ローカル環境と異なるバージョンのアセンブリが含まれてるんですかねー?Roslynパースができてしまえば、あとはT4でソースコード生成するなり、CodeDOMでアセンブリを組み立てるなり自由自在です。今回はせっかくなので、Roslynを使ってみましょう。RoslynはまだCTP版なので未実装な機能がたくさんありますし、今後仕様が変わる可能性もあるのでご注意を。Roslynも以下のバージョンのものがNuGetで入れられます。IDLの文法で書いたstructから、自動実装プロパティを持つC#のクラスのソースコードとアセンブリを生成します。名前付き引数で指定したいがtypeがキーワードなのでだめ。0実行結果は以下のように表示されます。このコードをコンパイルしたtest.dllも生成されています。IDLをFParsecでパースして、RoslynでSyntaxTreeを組み立てて、ソースコードやアセンブリを出力してみたという紹介記事でした。RoslynはC#やVBのパーサはあるものの独自パーサを持っていないので、こういう使い方もありなのかもしれません。2011はこれでおしまいですが、この1ヶ月間はいろんな視点から書かれたF#の記事を読むことができて非常に楽しかったです。ZOETROPEの日記に、twitter経由で@neueccさんからいくつかコメントをいただきました。RxとパフォーマンスとユニットテストとMoles再びという素敵な記事も!というわけで、補足記事を書いておきます。ToList改行コードがくるまでのデータをリストにまとめる処理をAggregateを使って以下のように書いていました。}));これは、ToListやToArrayで置き換え可能です。'\n').ToList();ToListのほうが可読性も高くていいですね。Extensionsでは使えないそうですが。)イベントの解除漏れObservableSerialPortでは、EOFを受信したときやエラーが発生したときにOnCompletedやOnErrorを呼び出していますが、その後にイベントの登録解除をしていないため、イベントの解除漏れが発生してしまいます。以下のようにCompositeDisposableを使って、明示的に登録解除する必要があります。disposable;}neueccさんの記事ではさらにスマートな実装になっています。SerialData.Eof)でEOFがきた時点でOnCompletedが呼ばれるようにしてあり、さらにDataReceivedとErrorReceivedをTakeUntilで結合することで、エラーが発生したらDataReceivedも解除されるようになっています。かずきのBlog@hatenaが分かりやすいです。拡張メソッドObservableSerialPortのようにIObservableを実装したクラスを用意するか、neueccさんのように拡張メソッドを用意するか、ちょっと悩むところです。今回の場合はSerialPortのOpenやSendをObservableSerialPortに移譲させるのも面倒なので、拡張メソッドのほうがいいんじゃないかと思います。Moles実センサを持っていたとしても、センサを準備するのが面倒だったり、わざとエラーを起こすことは難しかったりするので、モックを使ったテストはとても有効です。neueccさんのコードをベースに具体的なデータを受信するテストを書いてみました。ZOETROPEの日記をみて思い出したり。SerialPort::BytesToRead/SerialPort::Readで、計測データを返すようにする。"\n\n");}任意のタイミングでイベントを発行できるので素敵ですね。実センサでは発生しにくいパターンのテストも簡単に書けそうです。最初からこうやってテストを書いておけばよかったと反省・・・Molesは以前使ったときに、テストの実行が非常に重い印象があったのですが、今回はそうでもありませんでした。以前使ったときのPCのスペックが低かっただけのようです。ちなみに、ReSharper6.0のテストランナーだとMolesのテストが動かないバグがありました。6.1にアップグレードすればOKです。ATNDの20日目です*1。前日はid:masakitkさんのWindows.FormsだってTDD!でした。Extensionsがロボットの制御に使えるのではないかということで、いくつかエントリを書いてきましたが、今回はより具体的なセンサを使ったプログラミングについて考えてみます。本エントリではNuGetから以下のパッケージを参照に追加して使っています。ZOETROPEの日記URGセンサ今回は、北陽電機のURGシリーズのセンサを対象にしたいと思います。センサ|商品情報|北陽電機株式会社URGシリーズは、スキャナ式レンジセンサ(測域センサ)と呼ばれるもので、水平方向にレーザを走査して、センサの周囲にある物体までの距離を計測するセンサです。ロボットに搭載して周囲の障害物を見つけるために使われたりしています。PCとURGセンサをUSBケーブルで接続するとシリアルポートとして認識されるので、C#ではSerialPortクラスを使って通信することができます。PCとURGセンサの間ではSCIPという通信プロトコルによりメッセージをやり取りします。SCIPでは基本的にPCからセンサにコマンドメッセージを送信して、リプライメッセージを受け取るものとなっています。1つのメッセージは改行コード(ラインフィード)区切りの複数行文字列で構成されており、メッセージの区切りは改行コードが2つ並びます。計測開始コマンドを送信すると、その後は周期的にセンサから計測データが送信されてきます。コマンドの解説を参考にしてください。監視可能なSerialPortクラスそれではまず、下記のページを参考にして、監視可能なSerialPortクラス(すなわちIObservableを実装したクラス)を作ってみます。}}SerialPortのDataReceivedイベントを、Observable.FromEventPatternを使ってRxで扱えるように変換し、受信したデータを1バイトずつObserverに通知しています。EOFを受信したらOnCompleted、エラーが発生したらOnErrorで通知するようにしています。ObservableSerialPortを使うと、メッセージの受信処理は以下のように記述できます。.Subscribe();SCIPでは受信メッセージが改行コードで区切られているので、TakeWhileで改行コードが現れるまでデータを取得しAggregateで連結します。TakeWhileを使うと改行コードが来た時点で購読が解除されるので、Observable.DeferとRepeatで受信処理を繰り返し行うようにします。パフォーマンスの問題ここまでのプログラムで、受信したデータが1行ごとに通知されるようになりました。受信したバイト配列をいったん1バイトずつバラバラにして、Aggregateで再度連結するという実装になっているため、パフォーマンスは非常に悪そうです。URGセンサでは1回の受信データは最大3kB強になるので、3kBのデータを1000回処理したときにかかる時間を計測してみました。約7000msec(1メッセージ7msec)100倍の性能差がありますね*2。URGセンサでは最大25msec周期でデータが送られてくるので、データを結合するだけで7msecもかかっているようではちょっと使い物になりません。パフォーマンスアップというわけで、1バイトずつ通知を行うのではなく、1行ごとに通知を送るようにしてみましょう。入れて、Observerに通知する。入れ、次回のデータ受信時に処理する。Item1だけをObserverに通知する。}}シリアル通信では、1回の受信データが必ず1メッセージだとは限らないので、Scanメソッドを使って前回の受信データと最新の受信データを結合するようにしています。分かりにくい(というか気持ち悪い・・・)ですが、これでパフォーマンスが大幅に向上しました。コマンドの送信とリプライの受信ObservableSerialPortを使って、コマンドの送受信を行ってみましょう。SCIPでの1メッセージは2つの改行コードで区切られているので、空文字列がくるまでTakeWhileで読み込み、Aggregaeで連結すれば、1メッセージを取得することができます*3。).Repeat();これで、例えばバージョン情報を取得する処理(VVコマンド)は以下のように書けるようになります。VVコマンドの戻り値を待つ。vvObserver.Timeout(TimeSpan.FromSeconds(3)).Wait();計測結果の描画センサから送信されたメッセージをを取得できるようになったので、計測データをWPFアプリケーションで描画してみましょう。Homeを使うと便利です。XAMLでは、Polygonを使って計測結果を多角形で描画するようにします。計測開始ボタンもつけておきましょう。</Canvas></Window>続いてViewModelでは受信メッセージをデコードし、極座標から直交座標に変換、そして直交座標から画面上の座標に変換して描画します*4。最初の3行はエコーバック、エラーコード、タイムスタンプなので飛ばす。(本来はエラー処理します。4行目以降は、チェックサム(最後の1文字)を取り除いて結合する。3キャラエンコーディング方式なので、3つずつまとめる。センサに設定可能なパラメータであるが、今回は固定値とする。}}あとは、PointのリストをPointCollection型に変換するConverterを用意しておけばOKです。}}起動して計測開始ボタンを押すと、こんな感じでセンサの計測データが表示されます。Extensionsを使うことによって、センサの制御がずいぶんとスッキリ書けることが少しでも伝わったでしょうか。スレッドや排他制御などがまったくでてこないのでとてもシンプルですね(違った種類の難しさはあるんですけどね・・・)。ATNDはid:suerさんです。*1:ちなみに今日は僕の誕生日です。ついでにちょうど今日、本ブログのPVが10万を超えました。