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

拡張をChromeからMicrosoft Edgeに移植する

Created at: | Tag: Edge Translation

これはExtensions: Porting Chrome extensions - Microsoft Edge Developmentの何となく翻訳です

拡張をChromeからMicrosoft Edgeに移植するのはMicrosoft Edge Extension Toolkitの助けを借りれば簡単に行えます。この開発者ツールはAPIをブリッジするとともに manifest.json ファイルにあるエラーを明らかにし、パッケージ化されていないChromeの拡張をパッケージ化されていないMicrosoft Edgeの拡張へと変換します。

API ブリッジ

Chrome APIからMicrosoft EdgeのAPIへシームレスな移植を可能にするため、2つのスクリプトが拡張のフォルダーに追加されます。それらのスクリプトはAPIをブリッジ(必要に応じてpolyfil)するため、バックグラウンドスクリプトまたはコンテンツスクリプトに含まれるChrome固有のコードの変更について心配する必要はないということを意味します。

変換後、拡張のマニフェストファイルに "-ms-preload" キーとともに以下の項目が含まれていることに気がつくでしょう:

"-ms-preload": {
  "backgroundScript": "backgroundScriptsAPIBridge.js",
  "contentScript": "contentScriptsAPIBridge.js"
}

Microsoft Edge Extension Toolkitを利用する

以下の手順はChromeの拡張をWindows 10 Anniversary Update エディションのMicrosoft Edgeでを変換し動かす方法についてです:

  1. Microsoft Edge Extension Toolkitをインストールします
  2. Chromeの拡張のフォルダーを安全のためにコピーします。変換処理はコードを上書きします
  3. Microsoft Edge Extension Toolkit を実行し、拡張のコピーを読み込みます load extension button
  4. ツールのテキストエディターに報告されるすべてのエラーを修正します。修正した後、"Re-validate" を選択してエラーをチェックします
    extension-toolkit finding errors
  5. "Save files" を選択します

これでToolkitを終了して、拡張をMicrosoft Edgeで読み込めます!

既知のプラットフォームの問題をEdgeHTML issue trackerで探すことができます。もし新しい問題を見つけたと思ったらissueをオープンしてください!

Anniversary Update後の環境でドメインに参加しているとWindows Helloを設定できない

Created at: | Tag: Windows Hello

Windows 10 のVersion 1607、いわゆるAnniversary Updateが適用され、かつコンピューターがActive Directory ドメインに参加している場合にWindows Helloを設定できないことがあります。

正確にはWindows Helloを設定できないのではなく、PINコードの設定ができない状態になります。

設定できない状態では"Some settings are managed by your organization(一部の設定は組織によって管理されています)"と表示されPINコードの設定ボタンがグレーアウトします。

これはAnniversary Updateでドメイン参加時のPINコードのポリシーが変わったために発生します。

Anniversary Update以前に設定している場合にはPINコードを削除しない限り利用できます。

つまりOSをクリーンインストールした状態でAnniversary Updateの場合にはドメインに参加するとPINコードの設定ができなくなる場合があるということになります。

解決方法

この解決方法は二種類あります。

  1. Windows Hello for Business を展開する
    • 試してはいないですが多分Windows Hello for Businessはそれ用のPINのインフラがあり、それを使うようになるようです(通常のPINはConvinience PINと呼ばれている)
  2. グループポリシーでConvinience PINコードの設定を許可するポリシーを設定する
    • Windows Server 2012 R2では"Turn on PIN sign-in"を"Enabled"に
    • Windows 10/2016では "Turn on convenience PIN sign-in"を"Enabled"に

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 のキーコード定数名を指定できます。