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

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 クラスで対応するらしいので現状はあらかじめペアリングしておくとかが必要です。

Androidのエミュレーターで特定のキー操作をシミュレートしたい

Created at: | Tag: Android

Android SDKのエミュレーターにはいくつかハードキーがついていますが、中にはついていないものがあります。

キーボードのキーなどはコンピューターのキーで代用できるのですが、 Android TVのアプリを作っているときなど再生/一時停止ボタン(テレビであればリモコンにある)を押したときの反応を実装する必要がある場合には困ります。

例えば onKey イベントで KeyEvent.KEYCODEMEDIAPLAY_PAUSE に反応するコードを書いてもどうやってテストすれば…となるわけです。

そこでエミュレーターには input という入力をシミュレートするコマンドがあり、adb shell で接続して実行することで押されたかのような挙動をテストできます。

root@generic_x86:/ # input keyevent KEYCODE_MEDIA_PLAY_PAUSE

input コマンドの第一引数に keyevent を、第二引数には KeyEvent のキーコード定数名を指定できます。

DocFX - .NET向けAPIドキュメントを生成するツール

Created at: | Tag: NETFx DocFX

誰も触れていなくて紹介しようと思いつつすっかり忘れていたのですがMarkdownGenerator - C#におけるAPI Reference生成のためのドキュメントツールを見て思い出したのでご紹介です。

DocFXとは

DocFXTools for building and publishing API documentation for .NET projectsGitHubのリポジトリの説明がある通り、 主に.NETプロジェクト向けのAPIドキュメント生成ツールで、GitHubの.NET Foundation Organizationの下でMicrosoftが開発しています。

昔であればSandCastleなどがありましたが、モダンなツールらしくメタデータやMarkdownなどからHTMLのサイトを生成します。

DocFXのサンプル

試しに、MraaSharpからMraaSharpのドキュメントを生成してみました。 設定やページは若干適当ですが雰囲気は感じていただけると思います。

DocFXができること

DocFXができることには以下のようなものがあります。

JekyllやHugoなどの静的サイトジェネレーターに近いですが、 そこまで自由なサイトではなく、ドキュメント生成に重きを置いている印象です。

プレビュー中に変更できなかったり、GitHub上のソースコードへ飛べるなどが最初から準備されているのはその辺の考えがあるのではないでしょうか。

DocFXを使って空のサイトを作る

何はともあれDocFXは使ってみるのが一番です。

利用方法はGetting Started with DocFXを見るとコマンドライン、NuGet、ソースからビルド、Visual Studio…といったいろいろな手段があるのですが、 てっとり早く試すのであればコマンドラインがおすすめです。

まずはDocFXのサイトからビルド済みの最新版をダウンロードします。ダウンロード後にアーカイブを展開すると docfx.exe が含まれているのが確認できると思います。

次にコマンドプロンプトを開いて下記のコマンドを実行してプロジェクトを作成します。

docfx init -q -o SampleDoc
Created folder C:\Users\Tomoyo\Downloads\docfx\SampleDoc\src
(略)
Created File C:\Users\Tomoyo\Downloads\docfx\SampleDoc\api\.gitignore
Created config file C:\Users\Tomoyo\Downloads\docfx\SampleDoc\docfx.json
Successfully generated default docfx project to C:\Users\Tomoyo\Downloads\docfx\SampleDoc
Please run:
        docfx "C:\Users\Tomoyo\Downloads\docfx\SampleDoc\docfx.json" --serve
To generate a default docfx website.
Info: Completed executing in 221.5922 milliseconds.

SampleDoc フォルダが作成されてプロジェクトの初期ファイルも生成されます。 -q オプションを外すと設定項目を一つひとつ入力できます。

サイトをビルドする

プロジェクトを作成したらサイトのHTMLを生成してみます。生成するにはSampleDoc フォルダに移動して次のコマンドを実行してください。

..\docfx build

実行すると _site フォルダにHTMLファイルが生成されます。インターネットに公開する場合にはこの _site 以下を何らかの方法でデプロイするとよいでしょう。

サイトを立ち上げてブラウザで表示してみる

ファイルが生成できたら確認のためにプレビューサーバーを起動してみましょう。

..\docfx --serve
Info: Config file docfx.json found, start generating metadata...
Info: No files are found with glob pattern src/**.csproj, excluding **/obj/**,**/bin/**,_site/**, under directory "C:\Users\Tomoyo\Downloads\docfx\SampleDoc"
(略)
Info: [Apply Theme]Theme is applied.
Serving "C:\Users\Tomoyo\Downloads\docfx\SampleDoc\_site" on http://localhost:8080

これで http://localhost:8080/ でサイトが立ち上がります。 ちなみにデフォルトのポート 8080 を利用中の場合には -p オプションを付けて実行することでポートを指定するとよいでしょう。

..\docfx -p 37564 --serve
(略)
Serving "C:\Users\Tomoyo\Downloads\docfx\SampleDoc\_site" on http://localhost:37564

サイトの停止はCtrl+Cです。なお実際はこのコマンドは build も兼ねています。

C#のプロジェクトからソースコードを生成してみる

さて、次はC#のプロジェクトからAPIドキュメントを生成してみます。ドキュメントを生成したいプロジェクトの一式を何か用意しておいてください。

本当はC#のプロジェクトとDocFXのプロジェクトは近い場所に置いておく方がいいと思うのですが、今回は全然違う場所にあるものを指定します。簡単ですので。

docfx.json が設定ファイルとなっているので、このファイルを開いてmetadata の src の場所に "cwd" としてC#のプロジェクトのフォルダパスを指定します(ここでは "C:\Users\Tomoyo\Documents\Work\GitWork\MraaSharp" を指定しています)。

{
  "metadata": [
    {
      "src": [
        {
          "cwd": "C:\\Users\\Tomoyo\\Documents\\Work\\GitWork\\MraaSharp",
          "files": [
            "src/**.csproj"
          ],

そして再度 docfx コマンドでプレビューサーバーを立ち上げると、自動的にC#のコードを解析してXMLドキュメントコメントを読み込み、HTMLを生成します(正確には一旦メタデータファイルを作る)。

プレビューサーバーが立ち上がったのちサイトにアクセスするとナビゲーションに"Api Documentation"が増え、APIのドキュメントにアクセスできます。

Markdownのページを追加してみる

ところでAPIからの生成以外に普通のWebページを追加することもできますので追加してみましょう。ページはMarkdownなどで書けるようになっています。

ページを追加するにはページの内容を書いたMarkdownファイルを作成し、各フォルダ階層に存在する目次ファイルである toc.yml に追記します。

まずはデフォルトで作られる articles フォルダに hello.md ファイルを作成してみましょう。

# Hello!
コンニチハ!

Markdownファイルを作ったら次は toc.yml に追記です。初期状態では intro.md があるのでその後ろに同じようにnameとhrefのセットを足します。

- name: Introduction
  href: intro.md
- name: Hello!
  href: hello.md

そしてまたdotfxコマンドでプレビューサーバーを起動して生成します。生成すると /articles/ 以下に追加したページが並んで表示できるのを確認できるはずです。

終わりに

というわけでDocFXを使うことで簡単に綺麗なドキュメントサイトを生成できるようになります。GitHubにライブラリを公開したけどAPIドキュメントどうしよう…というときにGitHub Pagesとセットで使ったりできます。

まだ少ししか触っていませんが結構細かくカスタマイズできるようになっているので色々調べてみたいところです。

まどすた #1 でMicrosoft Edgeの話をしたのとASP.NET CoreをBash on Windowsで起動する話

Created at: | Tag: Edge Event

まどすた #1 ~ //build/ 2016 振り返りというイベントが5月21日にありまして、 Microsoft Edgeのセッションをさせていただきました。

元々Windows Insider MeetupというイベントのLT(15分)の資料を元に、 30分という枠に向けて加筆修正を加えデモを中心としていくつか機能を紹介しました。

特にEdge上でのWindows HelloのデモはWindows Insider Meetupの際にも、 MSの方も初めて見たといっていたので多分日本でデモしたのはそれと今回ぐらいだと思います(エヘン。Edgeで体験できるデモサイトも公開しているので興味のある方はお試しください。

Cutting Edge! - Speaker Deck

ASP.NET CoreをBash on Windowsで起動する

ところでEdgeと関係なく、デモサイトはASP.NET Coreで実装したのですが、折角なのでLinuxで動かしてみよう→ とはいえLinuxでサーバー立ち上げるのめんどくさい…→ここにBash on Windowsがあるじゃろ?ということで"ASP.NET Core for Linux on Bash on Windows"でデモしました(ちなみに使っていたのはbashではなくzshです)。

さすがにそのままでは動かないのですがほどほど簡単に動かせます。動かすための手順はデモの時には触れなかったので少し書いておきます。

まず普通にBash on Windows上で.NET CoreのLinux、Ubuntuでのインストール手順を参考にそのままコピペしてインストールします。

するとdotnetコマンドを実行できるようになりますが、そのままではまだ動作しません。どうも何かのシステムコールでサポートしていないフラグを使っているようです(詳しいことは忘れました)。 そこで以下のコマンドを実行します。

apt-get install execstack # 入ってなければ
execstack -c /usr/share/dotnet/shared/Microsoft.NETCore.App/1.0.0-rc2-3002702/libcoreclr.so

さてこれでdotnetコマンドが動くようになります。試しに dotnet init と dotnet restore 、dotnet run を実行するとちゃんと動くことに驚くかもしれません。

ではASP.NET Coreのアプリケーションも?と思い、dotnet restore と dotnet run を実行するとサーバー(Kestrel)が起動しますが、LISTENはしているものの接続できない状態になります。 ネットワーク周りなのかlibuv周りなのかそのあたりがうまく動作しないようです。

そこで Program.cs を開いて WebHostBuilder で UseKestrel にオプションを渡すよう以下のように修正します。

var host = new WebHostBuilder() 
       .UseKestrel(options => 
       { 
           options.ThreadCount = 1; // この設定を足す
       }) 
       .UseContentRoot(Directory.GetCurrentDirectory()) 
       .UseIISIntegration() 
       .UseStartup<Startup>() 
       .Build();

これで dotnet run を実行するとアプリケーションを起動できます。キセキっぽさがありますけど…。

ちょっと手を加える必要がありますがUbuntu向けのCoreCLR、ASP.NET Coreが動作してしまうというのはかなり驚きです。 手軽にLinux環境を用意して色々なものを実行できるBash on Windowsは現状でもすごいのでもっと実装が良くなってくるのが期待できます。