からめもぶろぐ。

俺たちは雰囲気で OAuth をやっている

C# で IBS-TH1 のバッテリー残量を取得する

blog.karamem0.dev

この記事の続き。ひとつ買ってなんとなくうまく稼働していたので、別の部屋のためにもうひとつ買ったりしていましたが、最初の個体が電池切れでお亡くなりになっているのに気付きました。だいたい 4 か月くらいですかね。ネットの情報では IBS-TH1 MINI のほうは 1 か月くらいしか持たないという情報もあるのでまあまあそんなものかなと。いずれにしても電池が切れるのに気付かないのもよくないのでバッテリー残量を取りたいよねという話。

スマートフォンのアプリではバッテリー残量を表示できているのでできなくはないことはわかっています。調べてみると GATT ではどうやってもバッテリー残量は取れなくて、アドバタイズで取るみたいです。あまり詳しくないので間違ってたら申し訳ないですが、アドバタイズと GATT の違いは UDP と TCP の違いみたいな感じでしょうか。アドバタイズはペリフェラル (今回でいうと IBS-TH1) が定期的にブロードキャストするパケットのことです。この Manufacture Specific というデータ領域にバッテリー残量が入っています。ただ、面倒なのは、アドバタイズにはパッシブ スキャンとアクティブ スキャンがあって、アクティブ スキャンでないと Manufacture Specific が入ってこないのと、Manufacture Specific の先頭 2 バイトの企業識別子は含まれないので、その点を考慮する必要があります。

言葉だけだとわからないのでコードを載せておきます。

public static class Program
{

    private const string MacAddress = "{{macaddress}}";

    private static async Task Main()
    {
        var watcher = new BluetoothLEAdvertisementWatcher();
        watcher.ScanningMode = BluetoothLEScanningMode.Active;
        watcher.Received += (sender, e) =>
        {
            var macAddress = string.Join(":",
                BitConverter.GetBytes(e.BluetoothAddress)
                    .Reverse()
                    .Select(x => x.ToString("X2")))
                .Substring(6);
            if (string.Equals(macAddress, MacAddress, StringComparison.OrdinalIgnoreCase) != true)
            {
                return;
            }
            var manifactureData = e.Advertisement
                .GetSectionsByType(BluetoothLEAdvertisementDataTypes.ManufacturerSpecificData)
                .Select(x => x.Data.ToArray()).FirstOrDefault();
            if (manifactureData == null)
            {
                return;
            }
            var t = (double)BitConverter.ToInt16(manifactureData, 0) / 100;
            var h = (double)BitConverter.ToInt16(manifactureData, 2) / 100;
            var b = manifactureData[7];
            Console.WriteLine("{0:s} 温度: {1}, 湿度: {2}, バッテリー: {3}", DateTime.Now, t, h, b);
        };
        watcher.Start();
        await Task.Delay(Timeout.Infinite);
    }

}

アクティブ スキャンの場合はアドバタイズ フレームの受信後にスキャン要求を送って追加の情報を取得する方法で、Manufacture Specific はこの追加の情報として入ってくるデータなので、BluetoothLEAdvertisementWatcher.Received イベントのすべてのイベント データに Manufacture Specific が入ってくるとは限りません。また、BluetoothLEAdvertisement.ManufacturerData プロパティは先頭 2 バイトを取った形でデータを返しますので、GetSectionsByType メソッドで生のデータを取ってくる必要があります。

実行するとこんな感じでデータが取れるはずです。

2021-09-05T16:17:26 温度: 27.36, 湿度: 62.99, バッテリー: 100
2021-09-05T16:21:32 温度: 27.35, 湿度: 62.99, バッテリー: 100
2021-09-05T16:21:34 温度: 27.35, 湿度: 62.99, バッテリー: 100
2021-09-05T16:21:36 温度: 27.35, 湿度: 62.99, バッテリー: 100
2021-09-05T16:21:40 温度: 27.35, 湿度: 62.99, バッテリー: 100
2021-09-05T16:22:33 温度: 27.35, 湿度: 62.99, バッテリー: 100

まあ確かに取れるには取れるのですが、取れるタイミングが非常に安定しない (上記の例だと 1 件目のあとに 3 分間空きがあってそのあと連続してデータが返ってくる) ため、扱いが非常に面倒くさい感じはありますね。

Capreze 1.8.0 を公開しました

Capreze 1.8.0 を公開しました。
Capreze は画面キャプチャ用のウィンドウ リサイズ ツールです。ウィンドウを正確なサイズにリサイズしてくれます。

github.com

オフセットの計算を自身のウィンドウがあるディスプレイで計算していたのですが、選択されたウィンドウがあるディスプレイで計算するようにしました。また、ウィンドウを追跡しやすくするためにプロセス ID を表示するようにしました。

SPClientCore 4.0.0 を公開しました

SPClientCore 4.0.0 を公開しました。

SPClientCore は PowerShell Core 向けの SharePoint Online 管理モジュールです。

www.powershellgallery.com

変更点は以下の通りです。

  • いいねに関するコマンドレットを追加しました。
  • 画像列に関するコマンドレットを追加しました。
  • ナビゲーションを設定するコマンドレットを追加しました。
  • 接続のキャッシュを最後にアクセスしたサイトではなくドメインごとに持つようにしました。これによりサイトの切り替えのエクスペリエンスが向上します。またキャッシュを削除するコマンドレットも追加しました。
  • 取得可能なプロパティを最新化しました。
  • いくつかの内部処理を変更しました。

提供されているコマンドレットの数は 282 になりました。

SharePoint REST API を使ってリスト アイテムの画像列を更新する

SharePoint Online には新しい列の種類として 2020 年より画像列というものが追加されています。これまでカスタム リストで画像を扱う場合は添付ファイルに設定することが多かったのですが、画像を列として表現できるようになったため、利便性が高くなっていると思います。ところでやはり気になるのはプログラムからどうやって設定するの?というところです。CSOM ではまだ対応していないようですが、REST API を使ってできそうなのでやってみました。

前提

画像列は列のデータとして画像が埋め込まれているわけではなく、画像は別のドキュメント ライブラリにあって、そこへの情報を JSON 形式で持っているという形になります。通常 UI から画像をアップロードしたときは「サイトのリソース ファイル」に格納されます。なので手順としては、

  • サイトのリソース ファイルに画像をアップロード
  • リスト アイテムの列に JSON 情報を入れて更新

という流れになります。

手順

画像のアップロード

UI からアップロードしたときは「/SiteAssets/Lists/{{listid}}」のフォルダーにファイルが格納されます。自分でフォルダーを作成するのは大変なので UploadImage という専用の REST API を使うことにします。URL は以下のような形で本文にファイルのコンテンツを含めます。

POST https://example.sharepoint.com/sites/TestSite1/_api/web/uploadimage(listtitle=@v1,imagename=@v2,listid=@v3,itemid=@v4)?@v1='Test List 1'&@v2='image.png'&@v3='1c36d431-f9a0-43b2-8284-cae8ccf5ca3b'&@v4=1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJ0eX...

結果として以下のような JSON が返却されます。

{
  "odata.metadata": "https://example.sharepoint.com/sites/TestSite1/_api/$metadata#SP.SPImageItem",
  "Name": "image.png",
  "ServerRelativeUrl": "/sites/TestSite1/SiteAssets/Lists/1c36d431-f9a0-43b2-8284-cae8ccf5ca3b/image.png",
  "UniqueId": "2e8f34a1-4237-4bb6-8941-61af7e985204"
}

リスト アイテムの更新

画像列は内部的には複数行テキスト列になっています。なので通常と同様に JSON 文字列を設定してあげれば大丈夫です。JSON を渡すのではなく JSON 文字列を渡すので注意してください。基本的には画像のアップロードの結果で取得した情報をもとに作成できるはずです。

POST https://example.sharepoint.com/sites/TestSite1/_api/web/lists('1c36d431-f9a0-43b2-8284-cae8ccf5ca3b')/items(1)
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJ0eX...
If-Match: *
X-HTTP-Method: MERGE

{
  "ImageField1": "{\"type\":\"thumbnail\",\"fileName\":\"image.png\",\"fieldName\":\"ImageField1\",\"serverUrl\":\"https://example.sharepoint.com\",\"serverRelativeUrl\":\"/sites/TestSite1/SiteAssets/Lists/1c36d431-f9a0-43b2-8284-cae8ccf5ca3b/image.png\",\"id\":\"2c5377bf-ec2f-4b44-9fa4-9867881d4bf5\"}"
}

JSON のみを展開すると以下のような感じになります。

{
  "type": "thumbnail",
  "fileName": "image.png",
  "fieldName": "ImageField1",
  "serverUrl": "https://example.sharepoint.com",
  "serverRelativeUrl": "/sites/TestSite1/SiteAssets/Lists/1c36d431-f9a0-43b2-8284-cae8ccf5ca3b/image.png",
  "id": "2c5377bf-ec2f-4b44-9fa4-9867881d4bf5"
}

ちなみに JSON の最小構成としては

{
  "type": "thumbnail",
  "serverRelativeUrl": "/sites/TestSite1/SiteAssets/Lists/1c36d431-f9a0-43b2-8284-cae8ccf5ca3b/image.png",
}

のようにいくつかのプロパティを省略しても動作するのですが、結果の表示のされ方が異なります。すべてのプロパティを設定した場合は Microsoft Graph の /drives/{{driveid}}/items/{{itemid}}/thumbnails を呼び出します。設定しない場合は serverRelativeUrl が呼び出されます。サムネイルを使うことでパフォーマンスが向上するため設定したほうがいいでしょう。

docs.microsoft.com