ぷろじぇくと、みすじら。

Filtered by Tag: CSharp

LINQPadのコードにファイルをドラッグアンドドロップで渡したい

Created at: | Tag: CSharp LINQPad

LINQPadでちょっとしたツールやバッチスクリプト的なものを作ったときに処理対象のとしてファイルを取ることはよくあるのですが、大抵はコードにパスを直接書いていることがほとんどではないでしょうか。

ただ便利なものは往々にして対象ファイルを変えたりしたいとか誰かに渡したいこともあり、そんな時処理対象のファイルをドラッグアンドドロップでコードに渡せれば…と思ったりすることもあります。残念ながらLINQPadに標準で備わっているユーザー入力と言えば Console.ReadLineHyperlinq ぐらいでドロップを受け付けるユーティリティは備わっていません。

その時点で普通のWindowsアプリケーションとして作りましょうという気もするのですがそうはいってもめんどくさいですよね。というわけでなんとか渡せるようにする方法です。

解決策

LINQPadは通常出力をHTMLベースで行っていますが、自力でHTML以外のカスタム出力結果パネルを出すこともできます。カスタムな出力結果パネルはWindows FormsやWPFのコントロールをホストできるというものなので、それを利用して通常のWindowsのアプリケーションと同様にドラッグアンドドロップを受け付けるという形でドロップターゲット機能を実現できます。

というわけで以下のようなメソッドを用意することでドロップ用のパネルを表示し、ドロップしたファイルのパスを返せます。

// PresentationFramework.dll と PresentationCore.dll を References に追加しておきます
Task<string[]> WaitForFileDrop()
{
    var tcs = new TaskCompletionSource<string[]>();
    var running = Util.KeepRunning();
    var button = new System.Windows.Controls.Button
    {
        Content = "Drop Here",
        AllowDrop = true,
    };
    var outputPanel = PanelManager.DisplayWpfElement(button, "File Drop");
    button.DragEnter += (sender, e) =>
    {
        e.Effects = System.Windows.DragDropEffects.Copy;
    };
    button.Drop += (sender, e) =>
    {
        var dropPaths = e.Data.GetData(System.Windows.DataFormats.FileDrop) as string[];

        running.Dispose();
        outputPanel.Close();

        tcs.SetResult(dropPaths);
    };

    return tcs.Task;
}

やっていることは単純でドロップを受け付けるボタンを作り、出力パネルとして表示し、ドロップされたらパスを取り出して TaskCompilationSource<T> を通して呼びもとに返すだけです。

使い方はこんな感じです。必要に応じてMyExtensionsなどに収めておくと使い勝手がいいでしょう。

async Task Main()
{
    // ドロップを待ち受け
    var paths = await WaitForFileDrop();

    // ドロップされたファイルのパスを出力
    paths.Dump();

    // ドロップされたファイルを画像として表示
    paths.Select(x => Util.Image(x)).Dump();
}

これで人に渡したりバッチ的に使うときにちょっと便利に使えるようになるかもしれません。どうぞご利用ください。

CC2650STK SensorTagをUniversal Windows Platform APIから使う

Created at: | Tag: Windows UWP CSharp Bluetooth

Texas InstrumentsのSensorTag (CC2650STK)という様々なセンサーを持ったBluetooth LEの開発者向けデバイスを結構前ですが入手したので、それをUniversal Windows PlatformのAPI(アプリとは言っていない)を使ってコンソールアプリケーションから読み取ってみました。

Texas Instruments SensorTag (CC2650STK)

SensorTagの詳細はTIのサイトにあるのですが、「光、デジタル・マイク、磁気センサ、湿度、圧力、加速度計、ジャイロスコープ、磁力計、物体の温度、周囲温度の検出が可能な 10 個のセンサ」を持ち、コイン電池で動作し、Bluetooth Low Energyでホストと通信できる小さなデバイスです。そしてこれだけ詰まって4,000円程度と比較的安価なのも特徴です。

マクニカオンラインストアTIのサイトから購入することができ、なによりちゃんと技適を通っているので合法です。ごうほうです!ご・う・ほ・う!

そんなステキデバイスをコンソールアプリケーション(というかLINQPad)から使ってみたいなーと思ったので、Universal Windows Platform APIを使ってアクセスしてみました。

まずはじめに

あらかじめWindowsの設定画面からペアリングをしておきます。というのもペアリングする処理を入れるのはとても面倒というかそもそもUWPアプリ以外からはできないのです。

参考: アドバタイズしているBluetooth LEデバイスをUniversal Windows Platform APIでペアリングしたい

GATTサービス/キャラクタリスティックを取得する

それではデバイスからデータをとるためのコードを書いていきます。

まずはWindowsが認識しているデバイスの一覧から利用したいセンサーのGATTサービスを探してくる必要があります。センサーのGATTサービスのUUIDはTIのWikiPDFにあります。

今回は湿度センサーを使ってみるのでそのUUIDをセンサーのドキュメントから調べておきます。

// GATTのUUID (ここではHumidityセンサー)
// http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Gatt_Server
// http://processors.wiki.ti.com/images/a/a8/BLE_SensorTag_GATT_Server.pdf
var uuidHumidityService = new Guid("f000aa20-0451-4000-b000-000000000000");
var uuidHumidityData = new Guid("f000aa21-0451-4000-b000-000000000000");
var uuidHumidityConfig = new Guid("f000aa22-0451-4000-b000-000000000000");
var uuidHumidityPeriod = new Guid("f000aa23-0451-4000-b000-000000000000");

UUIDが分かったら、デバイス一覧から対応するデバイスを探して、さらにGATTサービスと設定用、データ通知用のキャラクタリスティックを取得します。

// サービスを持つデバイスを探す
var gattHumidityServices = await DeviceInformation.FindAllAsync(
    GattDeviceService.GetDeviceSelectorFromUuid(uuidHumidityService), null);

// デバイスIDからGattDeviceServiceを取得する
var gattHumidityService = await GattDeviceService.FromIdAsync(gattHumidityServices[0].Id);
// データと設定のCharacteristicを取得する
var charHumidityData = gattHumidityService.GetCharacteristics(uuidHumidityData)[0];
var charHumidityConfig = gattHumidityService.GetCharacteristics(uuidHumidityConfig)[0];

データを受け取る

キャラクタリスティックを取得したら、データを取得するためにデータが流れてくるキャラクタリスティックにイベントハンドラーを設定します。

// データの通知を受け取るイベントハンドラーを設定する
charHumidityData.ValueChanged += (sender, eventArgs) =>
{
    // データの処理方法はWikiを参照のこと
    // 例: http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User's_Guide#Data_3
    var data = eventArgs.CharacteristicValue.ToArray();
    var temperature = (BitConverter.ToUInt16(data, 0) / 65536d) * 165 - 40;
    var humidity = (BitConverter.ToUInt16(data, 2) / 65536d) * 100;

    Console.WriteLine($"Temperature={temperature:0.0}℃, Humidity={humidity:0.0}%");
};

イベントハンドラーではデータのバイト列を取得できるのでそれをセンサーごとの処理方法に従って処理して値を取り出します。例えば湿度センサーの場合にはunsigned int16が二つで、片方が温度、片方が湿度の元データとなります。

受信の準備ができたので最後は通知を有効にします。一つはCC2650の設定に値を書き込み、もう一つは通知を有効にする WriteClientCharacteristicConfigurationDescriptorAsync メソッドの呼び出しです。

// データの通知を有効にする
// CC2650に設定を書き込む(大抵の場合は1で有効、0で無効)
await charHumidityConfig.WriteValueAsync(new byte[] { 1 });
// データ通知を有効にする
await charHumidityData.WriteClientCharacteristicConfigurationDescriptorAsync(
    GattClientCharacteristicConfigurationDescriptorValue.Notify);

これで実行するとデータが流れてくるのを確認できたら出来上がりです。おめでとうございます。

ちなみにペアリングした後何もしていないとSensorTagの電源が落ちるのでボタンを押してあげてください。

というわけで以上のC#(LINQPad用)のコードの全体はGistに置いてあります。

SensorTag as Observable

ところでセンサーのデータはある一定の間隔で送られてValueChangedイベントハンドラーに流れてきます。 データが流れてきて処理するといえばみなさん大好きなReactive Extensions、いわゆるRxの出番です。

というわけでRxの形にしてみましょう…といっても ValueChanged イベントハンドラーがあるので Observable.FromEventPattern メソッドを使えば簡単です。

var humidityObservable = Observable.FromEventPattern<TypedEventHandler<GattCharacteristic, GattValueChangedEventArgs>, GattValueChangedEventArgs>(
    h => charHumidityData.ValueChanged += h,
    h => charHumidityData.ValueChanged -= h)
    .Select(x =>
    {
        // データの処理方法はWikiを参照のこと
        // 例: http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User's_Guide#Data_3
        var data = x.EventArgs.CharacteristicValue.ToArray();
        var temperature = (BitConverter.ToUInt16(data, 0) / 65536d) * 165 - 40;
        var humidity = (BitConverter.ToUInt16(data, 2) / 65536d) * 100;

        return new { Temperature = temperature, Humidity = humidity };
    })
    .Publish()
    .RefCount();

要するにイベントハンドラーの代わりに FromEventPattern でObservableにして、Select オペレーターでデータを処理して整えた形のストリームに変換します。

Observableになったらあとは好きなオペレーターを組み合わせて、Subscribe するだけです。

humidityObservable
    .Buffer(TimeSpan.FromSeconds(10)) // 10秒蓄えて
    .Select(x => x.Average(y => y.Humidity)) // 湿度平均にして
    .DistinctUntilChanged(x => Math.Round(x, 1)) // 少数点第一位までで変更があったら流す
    .Subscribe(x =>
    {
        Console.WriteLine($"最近の平均湿度 {x:0.0}%");
    });

上記の例は湿度の平均を出力する例ですが、これをイベントハンドラーで実装しようとするとすっきりしないというのが想像できるかと思います。

var sumHumidity = 0;
var lastAvgHumidity = 0;
var count = 0;
charHumidityData.ValueChanged += (sender, eventArgs) =>
{
    // データの処理方法はWikiを参照のこと
    // 例: http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User's_Guide#Data_3
    var data = eventArgs.CharacteristicValue.ToArray();
    var temperature = (BitConverter.ToUInt16(data, 0) / 65536d) * 165 - 40;
    var humidity = (BitConverter.ToUInt16(data, 2) / 65536d) * 100;

    sumHumidity += humidity;
    if (++count == 5)
    {
        var avgHumidity = sumHumidity / 5;
        if (avgHumidity != lastAvgHumidity)
        {
            lastAvgHumidity = avgHumidity;
            sumHumidity = 0;
            count = 0;
            Console.WriteLine($"最近の平均湿度 {avgHumidity:0.0}%");
        }
    }
};

このあたりデータが流れてくるセンサーというものの特性とRxの相性の良さを感じますね。センサーも一つではないですし、それ以外の外部のイベントもあるのでこういうものを組み合わせるときには実に強力です。

RxでSensorTagのセンサーのデータを一通り受信できるようにする雑なクラスも同じくあげてありますので参考になるかもしれません。 通知間隔を変更するといったものは実装してませんのでそのあたりも試してみると良さそうです。

アドバタイズしているBluetooth LEデバイスをUniversal Windows Platform APIでペアリングしたい

Created at: | Tag: Windows UWP CSharp Bluetooth

Windows 10以降にはBluetooth LEを扱うAPIが備わっていて、そのAPIを使うことでアドバタイジングパケットを送受信したり、ペアリングしたりできます。

そんなわけでBLEデバイスをペアリングしたいと思って試してみたのですが意外と罠が多いです。

ペアリングの流れ

UWP APIを利用してペアリングして使う流れは次のようになっています。

  1. BluetoothLEAdvertisementWatcher クラスでアドバタイジングしているデバイスを探す
  2. 見つけたらUIスレッド内でペアリングを実行する
    1. BluetoothLEDevice.FromBluetoothAddressAsync メソッドでBLEデバイスを取得する
    2. 取得したオブジェクトの DeviceInformation.Pairing.PairAsync メソッドでペアリング実行
  3. DeviceWatcher クラスで追加/更新されたデバイスを監視
  4. GATTサービスを取得する

まずUIスレッド内でペアリングを実行する必要がある、という点に注意が必要です。

次にペアリングが完了してもすぐ使えると思ったらそんなことはなく、接続完了するまで待たなければなりません。ペアリングを待って(await)も接続を待ったわけではないので今度はDeviceWatcherでWindows側の準備が完了をするのを待ちます。めんどくさい…。 ちなみにBluetoothConnectionStatusは利用可能な接続状態とは違うようです。

ここからはUIにデバイスリストを表示してそこにデバイスを表示して選択するのとおおよそ同じだと思います。

デバイスを監視しているとペアリング後になにやらいろいろ追加されます。主にGATTのサービスなどがデバイスとしてAdded/Updatedイベントにやってきます。 なおデバイスがインストールされると途中で名前が変更される可能性があるのでAddedとUpdated両方見た方がよさそうです。

しかしやってきたデバイスIDは BluetoothLEDevice や GattDeviceService として開けるかどうかは開いてみないとわかりません。なんというめんどくささ…。 実際StackOverflowにもtry/catchしときみたいな回答がついてます。

…のでDeviceWatcherで監視するときにFilterで利用したいGATTのUUIDを指定することをお勧めします。

DeviceInformation.CreateWatcher(GattDeviceService.GetDeviceSelectorFromUuid(new Guid("f000aa00-0451-4000-b000-000000000000")));

これであればAdded/Updatedイベントで確実に指定したもののデバイスIDが来るので取得できます。

var gattService = await GattDeviceService.FromIdAsync(new Guid("f000aa00-0451-4000-b000-000000000000"));

ちなみに GattDeviceService.FromIdAsync メソッドはデバイスが存在しないとExceptionを投げますが、誰か別なプロセスが利用中の場合 null を返すので完全に罠です。

いずれにせよペアリングとデバイス列挙は別なので、ペアリングしてもそのままの流れで使い始めることはできないということのようです。

コンソールアプリケーションで動かしたい

ところでUWPのAPIはコンソールアプリケーションからも扱えて、BLEで通信も確かにできるのですがコンソールアプリケーションからはペアリングできません。

想像するにペアリング用のUIを提供している都合なのだと思いますが DeviceInformationPairing.PairAsync メソッドを呼ぶと失敗します(そもそも元からDispatcher経由で呼ばないといけない)。

DeviceInformationCustomPairing クラスなら?と思ったのですが現状対応していないようで、将来的には DeviceInformationCustomPairing クラスで対応するらしいので現状はあらかじめペアリングしておくとかが必要です。

XamarinでもAndroid TVアプリが作りたい

Created at: | Tag: AndroidTv Xamarin CSharp

XamarinがついにVisual Studioの一部となって、Visual Studio利用者であれば追加コストなしで使えるようになりました。Starterではなくてサイズ制限のないフル機能を使えるのでまともな開発ができます。

Xamarinの良し悪しや意義とかは置いておくとして、Androidアプリの開発ができるということはAndroid TVのアプリも作れるということなのでデバッグ実行するまでの流れを試してみました。SonyのBRAVIA(2015)での手順ですがBRAVIA以外のAndroid TV(Nexus Playerなど)でも大体同じ流れだと思います。

動かすアプリはXamarinのデフォルトテンプレートで出来るもの(ボタンを押すとカウントが増えるもの)をそのまま使うことにします。

テレビ(Android TV)側の設定と接続

まずはテレビ側でデバッグをできるように設定します。と言ってもほぼ普通のAndroidと同じです。

  1. 設定から端末情報→ビルドの項目を連打して開発者向けオプションを有効にする
  2. システム機能設定カテゴリの開発者向けオプション→デバッグ→ADBデバッグを「入」

Get started | Sony Developer Worldにも詳しく書いてあります。

つぎにコンピューターからadbコマンドからテレビに接続します。 なお、adbコマンドはXamarin.Androidとともにインストールされたものは%LOCALAPPDATA%\Android\android-sdk\platform-toolsにあります。

adb kill-server
adb start-server
adb connect <IPアドレス>

この adb connect に指定するIPアドレスはテレビのIPアドレスです。ネットワーク設定あたりから確認できます。

C:\Users\Tomoyo\AppData\Local\Android\android-sdk\platform-tools>adb connect 192.168.1.100
connected to 192.168.1.100:5555

コマンドが成功するとコンピューター側には何も出ませんが、テレビ側にUSBデバッグの許可を求めるダイアログが出るのでOKを押してください。

テレビ側で許可をして adb devices で接続したIPのデバイスが出てくれば完了です。

C:\Users\Tomoyo\AppData\Local\Android\android-sdk\platform-tools>adb devices
List of devices attached
192.168.1.100:5555      device
emulator-5554   device

後はVisual Studioのデバッグ実行ボタンにターゲットとしてテレビが出てくるのでクリックするとテレビにアプリがデプロイされてデバッグ実行されます。なお、初回時は共有ライブラリをインストールするのでちょっと時間がかかります。

スクリーンショット: Visual Studio 2015のツールバーから起動

アプリケーションをAndroid TV対応にする

アプリケーションをテンプレートから作ったままだと、Android TV感のない単なるAndroidアプリになる上にホーム画面にも出てこない状態ですので少々対応が必要です。

スクリーンショット: アプリ画面

まず、Android TVの機能を含むライブラリ(v17 Leanback Library)やテーマを使えるように、Xamarin Components StoreからAndroid Support Library V17 Leanbackをインストールします。このコンポーネントをダウンロードするにはXamarinアカウントが必要です。

ちなみにAPI中でLeanbackという言葉が出てきたらそれはAndroid TV的なものを指しています。

そして次にアクティビティにテーマを設定します。 アプリケーションに一括でAndroid TV向けテーマを適用する場合にはAndroidManifest.xmlのapplication要素にtheme属性を追加します。

<application android:label="App17" android:theme="@style/Theme.Leanback"></application>

もしアクティビティ単位で適用したいのであれば、アクティビティのクラスについているActivityカスタム属性のThemeプロパティを設定します。

[Activity(Label = "App17", MainLauncher = true, Icon = "@drawable/icon", Theme = "@style/Theme.Leanback")]

これでタイトルバーが消えたり、ボタンがフラットになったりと見た目がAndroid TV向けっぽくなります。

スクリーンショット: アプリ画面

次にホームにアプリケーションを起動するアイコンというかタイルというかを表示するためのコードを追加します。Android TV向けのアプリケーションはホームから起動できるということを明示的に設定する必要があり、何も指定しないと表示されません。

設定は起動対象としたいアクティビティ、大抵はActivityカスタム属性のMainLauncherプロパティがtrueになっているアクティビティにIntentFilterカスタム属性を追加します。

[Activity(Label = "App17", MainLauncher = true, Icon = "@drawable/icon")]
[IntentFilter(new[] { Intent.ActionMain }, Categories = new[] { Intent.CategoryLeanbackLauncher })]
public class MainActivity : Activity
{
...
}

IntentFilterでカテゴリがCategoryLeanbackLauncherのActionMainを捕まえられるようにするという感じです。

これでホームにアイコンが出てくるようになります。

スクリーンショット: ホーム

ということで準備はできたのであとは頑張ってアプリを作るだけです。

Visual StudioでF5を押すとテレビにアプリを転送、起動してブレークポイントでブレークできるというのはなかなか面白い体験です。そもそもテレビにadbで接続してshellを叩けるという時点で不思議体験でもあります。

ぜひ皆さま、Android TVを買ってC#で開発しましょうということでたまにはアフィリエイトおいておきますね。

ソニー 地上・BS・110度CSデジタルハイビジョン液晶テレビ BRAVIA X8500C 55V型 KJ-55X8500C

Intel Edisonを買ったのでC#でも触りたいのです

Created at: | Tag: Edison CSharp NET

Intel Edison

最近、Intel Edisonをおすすめされたので買ってみました。

Arduinoなどと比べるとお高いのですが、その分性能も高くてSDカードのひと回り大きいぐらいのサイズで500MHzで動作するAtomとRAMが1GB、ストレージとして4GBを持ち、Wi-FiとBluetooth 4.0も持っていて技適も通っていてLinuxが動くという代物。すごい。ちなみに写真はBreakout Board Kitに乗せた状態のものです。

Edisonのセットアップは簡単で、ちょこちょこと初期設定するとsshで入れるようになり、Node.jsも標準で使えます。EdisonのピンというかIOを操作するためにMRAAというライブラリが用意されていてNode.jsのバインディングも用意されていて簡単に扱えるようになっています。

Intel Edison (Breakout Board Kit)でLチカ - Qiitaといった記事を参考にLチカをするのも簡単です。

C#を書けるようにする

やはりC#書きたいですね。LチカするにもC#でやりたい!…ということで最初はCoreCLRのネイティブコンパイルしたバイナリを持っていくか、CoreCLR自体を持っていこうと思っていたのですがそもそもLinux x86 portはまだのようなのできっとあるであろうMonoを探すことに。

探してみるとMonoのパッケージを提供している方がいらっしゃるのでありがたく利用させていただきます(EdisonのLinuxにはopkgというパッケージマネージャーが入っています)。

…と、簡単に入ります。

Monoがインストールできればあとはmonoコマンドでいろいろ実行できてこっちのものです。ちなみにMono 4.0なので少し古いのですが概ね問題ないでしょう。

C#からピンを操作できるようにする

しかしMonoからいざEdisonのGPIOのようなものを操作するにはsysfs経由 (/sys/class/gpio/)で操作する必要がありあまり使い勝手がよくありません。そこでNode.jsのようにMRAA(libmraa)をC#から使えるようにするライブラリがあれば…!と思ったので作ったのがMraaSharpです。

MraaSharpはだいぶ作りかけですが、libmraaをP/Invokeで呼び出すためMono以外に必要としません。本来であればMRAAはSWIGで各言語のバインディングを生成できるのですが、いろいろと(入れるのもビルドも)面倒なのでピュアC#で完結するものにしました。

使い方は簡単で、MraaSharp.dllを参照したうえで大よそMRAAと同じようなAPIを呼び出せます(ほとんど未実装ですが)。

using MraaSharp;

// 初期化 (mraa_init)
Mraa.Initialize();
// 情報を表示する (mraa_get_version, mraa_get_platform_name)
Console.WriteLine("Version: {0}", Mraa.Version);
Console.WriteLine("PlatformName: {0}", Mraa.PlatformName);

// ピンをGPIOの出力として開く
var gpio = new Gpio(MraaIntelEdisonMiniboard.J17_8, MraaGpioDir.Out);
Console.WriteLine("Gpio Pin: {0}", gpio.Pin);
Console.WriteLine("Gpio PinRaw: {0}", gpio.PinRaw);

while (true)
{
    gpio.Write(MraaGpioValue.High); // 出力をHIGHにする
    Thread.Sleep(1000);
    gpio.Write(MraaGpioValue.Low); // 出力をLOWにする
    Thread.Sleep(1000);
}

なおMonoをインストールするとC# ShellというMonoのC# REPLが一緒に入るので、それを起動してMraaSharpを読み込むとインタラクティブに試すことができます。Node.jsに負けてない!(MraaSharpがすべて実装済みとは言っていない)

というわけでC#でLチカできてよかったのです(1.8Vなので直接つなげない分、若干難易度高かった)。

MRAA + C#

A video posted by Mayuki Sawatari (@misuzilla) on