【C#】今更ながらWxBeacon2のデータをPCで受信しよう(簡易版)

この記事は[防災アプリ開発 Advent Calendar 2024](https://adventar.org/calendars/9939) 9日目の記事です。

←(8): [台湾の中央気象署(CWA)から地震情報を取得し震度分布図を生成する](https://qiita.com/iku55/items/6af3dcfb485ddf1a51a7) (iku55さん)
→(10): [日本の気象情報を配信してるAPIを探してみた](https://qiita.com/wagira/items/b9e901c3aef8acbc2373) (Wagiraさん)


# 前書き
今年も参加させていただきます。と言っても11月にやったものですが。
すでに生産・販売終了しているWxBeacon2ですが、地震観測画面に出したかったのでやってみました。

## 関係ない話

今年(1~11月)のコーディング時間が約300時間、自宅学習時間も約300時間という怠慢受験生です。大学は金沢大学を目指しています。
私は観測垢名として"GifuCam"を名乗っていますが、合格したら引っ越すので新垢作成か名前変更をします。どちらにせよ定住するかわからないので市・地域の名前を名乗るのはなぁと思っていますが案がないのでどなたかください。

---

# 概要
- C#ではUWPでやるのが一番いい感じです。たぶん。
- C#は公式ライブラリも例もあるので簡単にできる...と言いたいですが何か所がつまずいたので(今更誰得感ありますが)書いときます。 
- よくわからないので雑ですが許してください。 
- 地震観測画面で出しているものは[これです](https://github.com/Ichihai1415/WxBeacon2Viewer) (ソースコードのみ)。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052941/7d107dbb-4bd5-73fc-4061-1f0df89d457f.png)

# 内容

## ライブラリの準備
ウェザーニューズのGitHubに[WxBeacon2-csharp](https://github.com/weathernews/wxbeacon2-csharp)というライブラリとサンプルソフトのソースがあります。これを使えばいいですが、更新日がなんと**2017/11/18**！ 7年前です。ソースしかないので自分でビルドする必要がありますが、互換性的な問題で環境によってはうまくいかないかもしれません。
というわけで、ちょっと最適化したフォーク [WxBeacon2-csharp-Extend](https://github.com/Ichihai1415/wxbeacon2-csharp-extend) を作成しました。良ければ使ってください。

とりあえず、READMEにある通りVS Installerの**Windows アプリケーション開発** -> **ユニバーサル Windows プラットフォーム ツール**が必要なので入ってなければ入れましょう。
元のウェザーニューズのものから作成する場合、`git clone`かダウンロードして`SampleApp`か`WxBeacon2`をビルドして`Weathernews.WxBeacon2.dll`を用意してください。
`SampleApp`を実行してみてもいいです。　
私のWxBeacon2-csharp-Extendなら[Releases](https://github.com/Ichihai1415/wxbeacon2-csharp-extend/releases)のものを使ってもらって構いません。

## 作る
1. **UWPのプロジェクトを作成**
空白のアプリ (ユニバーサル Windows) を選択し、作成。
作成できたら、ソリューション エクスプローラー→参照→参照の追加で`Weathernews.WxBeacon2.dll`を参照しusingしてください。

2. **コードの編集**
namespaceを`WxBeacon2Viewer`としています。
私みたいにConsoleやWinFormsしかやったことないとxamlの扱いが難しいですがいろいろできるので頑張りましょう。
```xml:MainPage.xaml
<Page
    x:Class="WxBeacon2Viewer.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WxBeacon2Viewer"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" Loaded="Page_Loaded" Unloaded="Page_Unloaded"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Margin="50,50,0,0"/>
    </Grid>
</Page>
```
追加は` Loaded="Page_Loaded" Unloaded="Page_Unloaded" `と`<TextBlock x:Name="textBlock" HorizontalAlignment="Left" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Margin="50,50,0,0"/>`の部分だけです。
```cs:MainPage.xaml.cs
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Weathernews.Sensor;
using Windows.UI.Core;

namespace WxBeacon2Viewer
{
    public sealed partial class MainPage : Page
    {
        private WxBeacon2Watcher wxBeacon2Watcher = new WxBeacon2Watcher();

        public MainPage()
        {
            InitializeComponent();
            wxBeacon2Watcher.Received += WxBeacon2Watcher_Found;
        }

        private async void WxBeacon2Watcher_Found(object sender, WxBeacon2 beacon)
        {
            var latest = await beacon.GetLatestDataAsync();
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                textBlock.Text = latest.ToString();
                beacon.Dispose();
            });//async内なのでUIをいじるときはこの中でやらないと例外になることが　エラーメッセージとかなしでソフトが落ちるみたい(イベントビューアーに出る)
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            textBlock.Text = "searching...";
            wxBeacon2Watcher.Start();
        }

        private void Page_Unloaded(object sender, RoutedEventArgs e)
        {
            textBlock.Text = "exiting...";
            wxBeacon2Watcher.Stop();
        }
    }
}
```
分かりやすいように進捗も表示しています。スマホアプリでは「探しています」->「接続しています」という順ですが「接続しています」の段階を表示するにはライブラリ側を調整する必要がありそうです。
3. **Bluetoothの設定**
ソリューション エクスプローラーで`Package.appxmanifest`を開き、機能 を押して`Bluetooth`をオンにしてください。**こうしないといつになっても取得できません**(特に例外は出ないようです)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052941/291ef9d5-e752-8c66-9935-246e36e16e97.png)

これで実行して数秒待つとこのように表示されます。形式は`WxBeacon2Data.ToString()`を探してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052941/0c4365ab-f74c-212f-66c7-d8f44c763c8c.png)

### ところで、コンソールやらフォームアプリじゃダメなの？

色々試してみましたが、うまくいきません。
```
System.PlatformNotSupportedException: 'Operation is not supported on this platform.'
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052941/4ec5bada-a39a-1126-ff76-d67dd07e297d.png)
(プロジェクト名はテスト用のやつだから気にしないで)

よくわかりませんが、ウェザーニューズのライブラリの方法だとUWPじゃないとだめなようです。

https://hakase0274.hatenablog.com/entry/2019/12/22/200000

こんな方法もあるようですがライブラリな関係でうまくいかない？です。表示ソフトにUwpDesktopを入れつつライブラリのコードを移植すれば行けるのかな？(未検証)

まあおとなしくUWP使いましょう。

## うまく切断できない？
`WxBeacon2Watcher.Dispose()`をすれば切断される...と思いましたがDisposeしてもスマホから接続できません。うまくいかないので、ライブラリをフォークし修正することにしました(それが[WxBeacon2-csharp-Extend](https://github.com/Ichihai1415/wxbeacon2-csharp-extend)です)。
ChatGPTに聞きながら調整したところ、問題なくできました(ソフト側でも`GC.Collect()`が必要かもしれません)。
```c#:WxBeacon2Watcher.cs
/// <summary>
/// WxBeacon2Watcherのインスタンスを破棄します
/// </summary>
/// <remarks>ChatGPTを使用し適切な破棄ができるようにしました</remarks>
public void Dispose()
{
    if (bluetoothLEAdvertisementWatcher != null)
    {
        bluetoothLEAdvertisementWatcher.Received -= BluetoothLEAdvertisementWatcher_Received;
        Stop();
        bluetoothLEAdvertisementWatcher = null;
    }
    GC.SuppressFinalize(this); //こうしないと後で参照するデータも消える(Disposeのタイミングによる)
    GC.Collect();//なんかうまく切断されないことがあるため
}
```

## なんか落ちる
定期的に取得するソフトを作りましたが、なぜか1日に1回くらい落ちる問題がありました。
UWPはエラー画面ないんですね。イベントビューアーにログが出力されます。

<details><summary>イベントビューアーに出るログ</summary>
障害が発生しているアプリケーション名: WxBeacon2Viewer.exe、バージョン: 1.0.0.0、タイム スタンプ: 0x62a3c35e <br />
障害が発生しているモジュール名: twinapi.appcore.dll、バージョン: 10.0.22621.4391、タイム スタンプ: 0x7d2b73d3 <br />
例外コード: 0xc000027b <br />
障害オフセット: 0x00000000000de7f3 <br />
障害が発生しているプロセス ID: 0x0xBEF8 <br />
障害が発生しているアプリケーションの開始時刻: 0x0x1DB45D0426E4D8B <br />
障害が発生しているアプリケーション パス: C:\Ichihai1415\source\vs\WxBeacon2Viewer\WxBeacon2Viewer\bin\x64\Debug\AppX\WxBeacon2Viewer.exe <br />
障害が発生しているモジュール パス: C:\Windows\System32\twinapi.appcore.dll <br />
レポート ID: aec255e4-e85f-4a09-8e84-bd316a4b1a85 <br />
障害が発生しているパッケージの完全な名前: 71b5621e-d9d0-4d4d-a67a-83f0b370ae66_1.0.0.0_x64__6253hxqnnbdsc <br />
障害が発生しているパッケージに関連するアプリケーション ID: App
</details>

https://jpdscore.github.io/blog/debugging/application-error-eventlog/#3-%E5%86%8D%E7%99%BA%E6%99%82%E3%81%AE%E6%83%85%E5%A0%B1%E6%8E%A1%E5%8F%96%E3%81%AB%E5%90%91%E3%81%91%E3%81%A6%E3%81%AE%E8%A8%AD%E5%AE%9A-Windows-Error-Reporting

これだけではよくわからないのでWindows Error Reportingのプルダンプの設定をして、WinDbgで解析したところ、以下の内容でした。

```sh:WinDbg
0:019> !analyze -v

（略）

KEY_VALUES_STRING: 1

（略）

    Key  : Failure.Bucket
    Value: STOWED_EXCEPTION_IRestrictedErrorInfo_TEXT__T_[_r_X___X_L_________s_________80131500_System.Private.CoreLib.dll!Unknown

    Key  : Failure.Hash
    Value: {6fe3eed3-e2cc-9aa0-5711-3a6de5f443b0}

    Key  : SerializedError.Original
    Value: サービスのスキャンに失敗しました

    Key  : SerializedError.Sanitized
    Value: サービスのスキャンに失敗しました
    
（略）

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 00007ff9a99ee7f3 (twinapi_appcore!Windows::ApplicationModel::Core::CoreApplication::ForwardLocalError+0x00000000000000e3)
   ExceptionCode: c000027b
  ExceptionFlags: 00000001
NumberParameters: 2
   Parameter[0]: 000001d3638ff730
   Parameter[1]: 0000000000000001
   
（略）
```
どうやら普通にライブラリ側で取得失敗時に投げられる例外でソフトが落ちていたようです。適当にtry-catchします。
```c#:MainPage.xaml.cs
//wxBeacon2Watcher.Received += WxBeacon2Watcher_Found;
//↑このようにイベントハンドラを設定している場合

private async void WxBeacon2Watcher_Found(object sender, WxBeacon2 beacon)
{
    try
    {
        //処理コード
    }
    catch (Exception ex)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            T_info.Text = "get failed! -> " + ex.Message;//適当に表示
        });
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052941/234f15fe-cce0-d287-70ca-d10e707b038d.png)
非同期メソッド内例外(しかもライブラリ側)なので扱いが面倒ですね。

## 過去情報取得
ウェザーニューズのライブラリにないのでできないと思いましたが、頑張ればできそうです。

https://qiita.com/matsuda_tkm/items/a964399f583aab3803ce

さすがにそこまで時間はないので気が向いたらやります。

## その他
- 接続しっぱなしで自動で受信？されますが、接続しっぱなしだとスマホアプリで取得できなくなるので定期的に接続受信切断を繰り返すのがいいんですかね？(観測画面で出しているものはそうしてる)

# まとめ
- 一応今でもUWPで受信できるソフトを作ることができます。 
- UWP、つまずき所が多い。 
- ライブラリを移植する必要がありますがUWP以外でもできるかもしれません。 
- ウェザーニューズ公式ライブラリそのままだとうまく切断されないようなので通常通りスマホアプリでも取得したい場合少し調整する必要があります。公開しているのでぜひ(2回目)。 
- 未検証ですが、過去情報の一括取得は面倒ですができそうです。