次の方法で共有



July 2016

Volume 31 Number 7

最新のアプリ - UWP での Wi-Fi スキャナーのビルド

Frank La La

ここ 10 年、至るところで Wi-Fi を使えるようになりました。顧客の利便性を考え、無料 Wi-Fi を提供しているショップやカフェが増えています。ほぼすべてのホテルは、宿泊客向けになんらかのワイヤレス インターネットを用意しています。ほとんどの人が、自宅でワイヤレス ネットワークを利用しています。イーサーネットのコネクターを備えたタブレットやモバイル デバイスはほとんどないため、最近の生活には Wi-Fi が不可欠になっています。とは言え、Wi-Fi についてじっくりと考える機会はあまりありません。

そこで、疑問が生まれます。こうした大量の Wi-Fi ネットワークはどうなっているのでしょう。 数はどのくらいでしょう。 安全でしょうか。 チャネルはどうなっているでしょう。 どのような名前が使われていて、 それを地図にできないでしょうか。 Wi-Fi ネットワークのメタ データからは何かわかることがありますか。

最近犬の散歩をしながらスマートフォンを眺めていたときのことです。偶然 Wi-Fi ネットワーク接続画面が目に入り、面白いネットワーク名がいくつか表示されていることに気付きました。これがきっかけで、実用的な名前ではなく、コミカルな名前を選んでいる利用者がどれくらいいるのか気になりました。そこで、近隣のワイヤレス ネットワークをスキャンして地図にすることを思いつきました。このプロセスを自動化すれば、通勤中でもワイヤレス ネットワークをスキャンして地図にすることができます。理想は、このプログラムを Raspberry Pi で実行し、ワイヤレス ネットワークを定期的にスキャンして、データを Web サービスに記録することです。これなら、スマートフォンを何度もちら見することはなくなります。

結論としては、ユニバーサル Windows プラットフォーム (UWP) を利用すれば、Windows.Devices.WiFi 名前空間に含まれる多数のクラスを使って、ワイヤレス ネットワークにアクセスできます。UWP アプリは、スマートフォンや PC だけでなく、Windows 10 IoT Core を実行する Raspberry Pi 2 でも実行できます。これで、プロジェクトをビルドする準備は整いました。

今回は、UWP に直接組み込まれる API を使って Wi-FI ネットワークをスキャンする基礎を取り上げます。

Windows.Devices.WiFi 名前空間

Windows.Devices.WiFi 名前空間のクラスには、圏内のワイヤレス アダプダーとワイヤレス ネットワークをスキャン、調査するのに必要なものがすべて含まれています。Visual Studio で新しい UWP プロジェクトを作成し、「WifiScanner」という新しいクラスと、以下のプロパティを追加します。

public WiFiAdapter WiFiAdapter { get; private set; }

複数の Wi-Fi アダプターを利用できるシステムもあるため、利用する Wi-Fi アダプターを選ぶ必要があります。InitializeFirstAdapter メソッドは、システムで一番最初に列挙されるアダプターを取得します (図 1 参照)。

図 1 最初にシステムに接続された Wi-Fi アダプターの検索と初期化

private async Task InitializeFirstAdapter()
{
  var access = await WiFiAdapter.RequestAccessAsync();
  if (access != WiFiAccessStatus.Allowed)
  {
    throw new Exception("WiFiAccessStatus not allowed");
  }
  else
  {
    var wifiAdapterResults =
      await DeviceInformation.FindAllAsync(WiFiAdapter.GetDeviceSelector());
  if (wifiAdapterResults.Count >= 1)
    {
      this.WiFiAdapter =
        await WiFiAdapter.FromIdAsync(wifiAdapterResults[0].Id);
    }
    else
    {
      throw new Exception("WiFi Adapter not found.");
    }
  }
}

Wi-Fi 機能の追加

Wi-Fi へのアクセスをチェックし、RequestAccessAsync メソッドが false を返すと例外をスローしています。今回の目的には、Wi-Fi ネットワークをスキャンし接続できるデバイス機能が必要なので、このチェックを行っています。この機能はマニフェスト プロパティ エディターの [機能] タブには一覧されません。この機能を追加するには、Package.appxmanager ファイルを右クリックし、[コードの表示] をクリックします。

Package.appxmanager ファイルの XML がそのまま手を加えずに表示されます。[機能] ノード内部で、以下のコードを追加します。

<DeviceCapability Name="wifiControl" />

ここで、ファイルを保存します。これで、アプリから Wi-Fi API にアクセスできるようになります。

ワイヤレス ネットワークの探索

操作対象の Wi-Fi アダプターし、アクセスできるようになったところで、次は実際に Wi-Fi ネットワークをスキャンします。スキャンするコードは非常簡単で、WifiAdapter オブジェクトの ScanAsync メソッドを呼び出すだけです。以下のメソッドを WifiScanner クラスに追加します。

public async Task ScanForNetworks()
{
  if (this.WiFiAdapter != null)
  {
    await this.WiFiAdapter.ScanAsync();
  }
  }

ScanAsync を実行すると、WifiAdapter の NetworkReport プロパティに情報が設定されます。NetworkReport は WiFiNetworkReport のインスタンスで、AvailableNetworks と List<WiFiAvailableNetwork> を含みます。WiFiAvailableNework オブジェクトは、特定のネットワークに関するデータ ポイントを数多く含んでいます。ネットワークに接続しなくても、さまざまなデータ ポイントから、サービス セット識別子 (SSID)、信号強度、暗号化方式、アクセス ポイントの稼働時間などをすべて検索できます。

利用可能なネットワークをすべて反復するのは実に簡単です。 以下のコードに示すように、単純な従来の CLR オブジェクト (POCO) を作成し、WiFiAvailableNetwork オブジェクトからのデータをいくつか含めます。

foreach (var availableNetwork in report.AvailableNetworks)
{
  WiFiSignal wifiSignal = new WiFiSignal()
  {
    MacAddress = availableNetwork.Bssid,
    Ssid = availableNetwork.Ssid,
    SignalBars = availableNetwork.SignalBars,
    ChannelCenterFrequencyInKilohertz =
      availableNetwork.ChannelCenterFrequencyInKilohertz,
    NetworkKind = availableNetwork.NetworkKind.ToString(),
    PhysicalKind = availableNetwork.PhyKind.ToString()
  };
}

UI のビルド

完成版のプロジェクトでは、UI を使わずにアプリを実行する予定でした。しかし、開発やトラブルシューティングを行うときに圏内のネットワークと、そのネットワークに関連するメタデータを表示できると便利だと考えました。また、現在 Raspberry Pi を所持していなくても開発を行いたい開発者にも役立ちます。図 2 に示すように、このプロジェクトの XAML は簡単で、スキャンの出力を格納する複数行テキストボックスがあるだけです。

図 2 UI 用 XAML

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid.RowDefinitions>
    <RowDefinition Height="60"/>
      <RowDefinition Height="60"/>
      <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <TextBlock FontSize="36" Grid.RowSpan="2" >WiFi Scanner</TextBlock>
  <StackPanel Name="spButtons" Grid.Row="1" Orientation="Horizontal">
    <Button Name="btnScan" Click="btnScan_Click" Grid.Row="1">Scan For
      Networks</Button>
  </StackPanel>
  <TextBox Name="txbReport" TextWrapping="Wrap" AcceptsReturn="True"
    Grid.Row="2"></TextBox>
  </Grid>
</Page>

位置情報データの取得

アプリに価値を追加するため、ワイヤレス ネットワークをスキャンするたびに、スキャンの位置情報も記録します。これにより、後から興味深い洞察と、データの表示を実現できます。さいわい、UWP アプリに位置情報を追加するのは簡単です。ただし、アプリに位置情報機能を追加する必要があります。この機能を追加するには、ソリューション エクスプローラーで Package.appxmanifest ファイルをダブルクリックし、[機能] タブをクリックして機能の一覧にある [場所] チェック ボックスをオンにします。

以下のコードは UWP に組み込まれる API を使って位置情報を取得します。

Geolocator geolocator = new Geolocator();
Geoposition position = await geolocator.GetGeopositionAsync();

位置情報を取得したら、次はそのデータを格納します。以下の WiFiPointData クラスは、位置情報データとその位置で見つかったネットワークについての情報を格納します。

public class WiFiPointData
{
  public DateTimeOffset TimeStamp { get; set; }
  public double Latitude { get; set; }
  public double Longitude { get; set; }
  public double Accuracy { get; set; }
  public List<WiFiSignal> WiFiSignals { get; set; }
  public WiFiPointData()
  {
    this.WiFiSignals = new List<WiFiSignal>();
  }
}

この時点で、デバイスが GPS デバイスを搭載している場合を除いて、位置情報を解析するために、アプリからインターネットへの Wi-Fi 接続が必要です。オンボードの GPS センサーがない場合は、モバイル ホットスポットを用意して、ノート PC や Raspberry Pi 2 からモバイル ホットスポットに接続します。この場合、報告される位置情報がやや正確性に欠けることになります。位置認識 UWP を作成するベスト プラクティスについては、Windows デベロッパー センターの記事「位置認識アプリのガイドライン」(bit.ly/1P0St0C)を参照してください。

繰り返しスキャン

移動中にスキャンして地図にするシナリオの場合、アプリは定期的に Wi-Fi ネットワークをスキャンする必要があります。これを実現するには、一定間隔で Wi-Fi ネットワークをスキャンするために、DispatchTimer を使用します。DispatchTimer のしくみについては、bit.ly/1WPMFcp(英語) のドキュメントを参照してください。

Wi-Fi のスキャンには、システムによって数秒かかることもあります。以下のコードでは、DispatchTimer をセットアップして、10 秒ごとにイベントを発生させています。最も遅いシステムでも十分な間隔になるようこの秒数を設定しています。

DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 10);
timer.Tick += Timer_Tick;
timer.Start();

タイマーは 10 秒おきに Timer_Tick メソッドのコードを実行します。以下のコードは Wi-Fi ネットワークをスキャンして、結果を UI のテキストボックスに追加します。

private async void Timer_Tick(object sender, object e)
{
  StringBuilder networkInfo = await RunWifiScan();
  this.txbReport.Text = this.txbReport.Text + networkInfo.ToString();
}

スキャン結果の報告

先ほど説明したように、ScanAsync メソッドが呼び出されたら、スキャン結果が List<WiFiAvailableNetwork> に格納されます。結果を取得するには、リスト全体を反復処理します。図 3 のコードがこの処理を行い、WiFiPointData クラスのインスタンスに結果を格納します。

図 3 スキャン中に見つかったネットワークをすべて反復処理するコード

foreach (var availableNetwork in report.AvailableNetworks)
{
  WiFiSignal wifiSignal = new WiFiSignal()
  {
    MacAddress = availableNetwork.Bssid,
    Ssid = availableNetwork.Ssid,
    SignalBars = availableNetwork.SignalBars,
    NetworkKind = availableNetwork.NetworkKind.ToString(),
    PhysicalKind = availableNetwork.PhyKind.ToString(),
    Encryption = availableNetwork.SecuritySettings.NetworkEncryptionType.ToString()
  };
  wifiPoint.WiFiSignals.Add(wifiSignal);
  }

シンプルでも多くの分析データを提供する UI にするため、WiFiPointData をコンマ区切り値 (CSV) 形式のテキストにして、UI のテキストボックスに設定します。CSV は比較的単純な形式で、分析のために Excel や Power BI にインポートすることができます。WiFiPointData を変換するコードを 図 4 に示します。

図 4 WiFiPointData の変換

private StringBuilder CreateCsvReport(WiFiPointData wifiPoint)
{
  StringBuilder networkInfo = new StringBuilder();
  networkInfo.AppendLine("MAC,SSID,SignalBars,Type,Lat,Long,Accuracy,Encryption");
  foreach (var wifiSignal in wifiPoint.WiFiSignals)
  {
    networkInfo.Append($"{wifiSignal.MacAddress},");
    networkInfo.Append($"{wifiSignal.Ssid},");
    networkInfo.Append($"{wifiSignal.SignalBars},");
    networkInfo.Append($"{wifiSignal.NetworkKind},");
    networkInfo.Append($"{wifiPoint.Latitude},");
    networkInfo.Append($"{wifiPoint.Longitude},");
    networkInfo.Append($"{wifiPoint.Accuracy},");
    networkInfo.Append($"{wifiSignal.Encryption}");
    networkInfo.AppendLine();
  }
  return networkInfo;
}

データの表示

当然ですが、クラウド サービスをセットアップしてデータを表示するのは手間がかかります。そこで、アプリで生成した CSV データをコピーして、テキスト ファイルに貼り付けることにしました。その後、拡張子 .CSV のファイルに保存します。次に、そのデータを Power BI Desktop にインポートします。Power BI Desktop は、powerbi.microsoft.com から無料でダウンロードできます。これにより、データの表示や検証が容易になります。

アプリからデータをインポートするには、Power Bi Desktop のスプラッシュ スクリーンで [データを取得] をクリックします。

次の画面で [CSV] を選択して [接続] をクリックします。ファイル ピッカー ダイアログで、アプリからコピーして貼り付けたデータを含む CSV ファイルを選択します。

ファイルが読み込まれたら、画面右側にフィールドの一覧が表示されます。Power BI Desktop の使い方については、今回扱う範囲を超えているためここでは説明しませんが、図 5 に示すように、Wi-Fi ネットワークの位置情報と SSID、そのネットワークが使っている暗号化プロトコルを表示するのはそれほど難しくはありません。

Wi-Fi スキャナー アプリが収集したデータの Power BI による表示
図 5 Wi-Fi スキャナー アプリが収集したデータの Power BI による表示

驚いたことに、約 3 分の 1 のネットワークはまったく暗号化されていません。中にはさまざまな企業がセットアップしたゲスト ネットワークもありますが、そうでないものもあります。

実践的応用

最初の目的は、近隣の W-Fi ネットワークと、そのネットワークに付けられた面白い名前を知ることでしたが、このプロジェクトに追加できる興味深い実用的な機能があります。Wi-Fi の信号強度と位置情報を自動的かつ簡単に地図にできると便利です。このアプリを実行する IoT デバイスを装備した路線バスを走らせると、その都市の状況がわかります。 市当局は Wi-Fi ネットワークの普及状況を測定し、近隣住民の所得データとの関連性を調べることもできます。市会議員なら、そのデータを基に政策を決定できます。コミュニティが市街地や特定の地域に公共 Wi-Fi を用意すれば、有償で技術者を送り込まずに、リアルタイムで信号強度を測定できます。市当局は、保護されていないネットワークが多い地域を特定して、コミュニティのサイバー セキュリティを向上するため、ターゲットを絞ったプログラムを作成できます。

小さな範囲では、Wi-Fi ネットワークをすばやくスキャンする機能は、ネットワークをセットアップする際に便利です。多くのルーターは、ルーターがブロードキャストするチャネルをユーザーが変更できるようにしています。これについての優れた例は、「Wi-Fi Analyzer」(bit.ly/25ovZ0Q) というアプリで、数多くある機能の中に、近くにあるワイヤレス ネットワークの強度と周波数を表示する機能があります。この機能は新しい場所に Wi-Fi ネットワークをセットアップするときに役に立ちます。

まとめ

UI からテキスト データをコピーして貼り付ける処理にはスケーラビリティがありません。どのような種類のディスプレイも備えていない IoT デバイスでアプリを実行することが目標であれば、UI を用意しないで、アプリからクラウドにデータを送る必要があります。次回は、クラウド サービスをセットアップして、アプリが取得したすべてのデータを取り込む方法を紹介する予定です。また、Windows IoT Core を実行する Raspberry Pi 2 にこのソリューションを配置するする方法も取り上げます。


Frank La Vigne は、Microsoft Technology and Civic Engagement チームのテクノロジ エバンジェリストで、より良いコミュニティを形成するためにユーザーによるテクノロジの活用を支援しています。**FranksWorld.com (英語) に定期的にブログ記事を投稿し、Frank’s World TV (youtube.com/FranksWorldTV (英語)) という YouTube チャンネルを主催しています。

この記事のレビューに協力してくれた技術スタッフの Rachel Appel、Robert Bernstei、および Jose Luis Manners に心より感謝いたします。