C# - QRコード生成 - WiFi 設定

## はじめに

WiFi ルータを購入すると、設定用 QR コードが用意されているし、スマホ / PC でも登録済み WiFi について、設定用 QRコードの表示ができますね。
今回は、WiFi 設定用 QR コード生成を記載します。

### テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。
- Windows Forms - .NET Framework 4.8
- Windows Forms - .NET 8
- WPF - .NET Framework 4.8
- WPF - .NET 8

::: note
Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。
:::

## QRコード

### モデル２
> [QRコードのしくみ](https://www.keyence.co.jp/ss/products/autoid/codereader/basic2d_qr.jsp)
> [QRコードとは](https://www.qrcode.com/about/)

QRコード（Quick Responseコード）は、高速読み取りを重視したマトリクス型２次元コードです。
代表的なQRコードには「モデル１」「モデル２」「マイクロQR」があります。
現時点で、単純にQRコードと表記している場合は「モデル２」が該当します。

「モデル２」は、「モデル１」の位置検出用ファインダ パターンに加えて、QRコードの歪みによって生じる各セルの位置ずれを補正するための アライメント パターンの追加、誤り訂正機能の追加、および、より大容量のデータにも対応した機能拡張仕様です。

![QRCode-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3963267/fd90d8ea-9eeb-4703-80ca-8bbd341fc7f0.png)

誤り訂正機能は、コードが汚れていたり、破損していても、コード自身でデータを復元する機能で、下記４レベルが存在します。

| レベル | 誤り訂正能力 |
| :----: | ------- |
| L      | 約 ７%  |
| M      | 約 15%  |
| Q      | 約 25%  |
| H      | 約 30%  |

QRコードにおけるバージョンとは、QRコードの大きさを表し、データ量が増えるとバージョンが大きくなります。（誤り訂正機能で、高い誤り訂正能力を選択するほど、データ量が増加します）
バージョン１は、21 × 21セル（マイクロQRは11 × 11セル）で構成され、ひとつバージョンが上がると、４セル（マイクロQRは２セル）ずつ大きくなります。

### WiFi 設定

> [GitHub - codebude/QRCoder - Advanced usage Payload generators](https://github.com/codebude/QRCoder/wiki/Advanced-usage---Payload-generators#321-wifi)
> [GitHub - zxing/zxing WiKi - Barcode Contents](https://github.com/zxing/zxing/wiki/Barcode-Contents/1052ada88a5b0a0cd40261ee31b997e17db4686c#wi-fi-network-config-android-ios-11)
> [qrencodeを使ってWi-Fi接続用QRコードを作成する](https://dev.classmethod.jp/articles/create-wifi-qr-code-with-qrencode/)

WiFi設定用 QRコードのフォーマットは、特定の標準化団体による明確な規定はないようですが、一般的に使用されるフォーマットは以下の通りです。

```
WIFI:S:<SSID>;T:<暗号化方式>;P:<パスワード>;H:<true/false>;;
```

ちなみに、所有している NEC ATerm-WX3000HP2 に付属している、WiFi設定用 QRコード（プライマリ ２個）は、下記のような情報でした。（データは 0 でマスクしています）

```
WIFI:S:aterm-000000-g;T:WPA;P:0000000000000;;
WIFI:S:aterm-000000-a;T:WPA;P:0000000000000;;
```

設定項目は以下の通りで、各フィールドの順番は自由で構いません。

| パラメータ | 説明                                          |
| --------- | --------------------------------------------- |
| S         | SSID（ネットワーク名）                         |
| T         | 暗号化方式（例: WPA, WEP, WPA2-EAP, nopass）   |
| P         | パスワード（暗号化方式がnopassの場合は省略可能）  |
| H         | 隠しSSIDかどうか（trueまたはfalse、省略時 false） |
| E         | EAPメソッド（WPA2-EAPの場合のみ、例: TTLS, PWD） |
| A         | 匿名ID（WPA2-EAPの場合のみ）                    |
| I         | ID（WPA2-EAPの場合のみ）                        |
| PH2       | フェーズ2メソッド（WPA2-EAPの場合のみ）          |

エスケープ（冒頭に `\` 付与）が必要な特殊文字については、明確な規定がないため、あやふやみたいです。
確認した情報では、フォーマットから自明といえる `\`, `;`, `:` が共通で、`,`, `"`, `!` などが追加されているケースがありました。これらの特殊文字が利用されることは、めったにないので実害はないのでしょうね。

### C# 用ライブラリ

[NuGet Gallery | ZXing.Net](https://www.nuget.org/packages/ZXing.Net) は、QRコード生成機能のみでなく、QRコード読み取り機能も提供しています。
今回は、ZXing.Net を利用して、QRコード読み取りサンプルコードも記載することとします。

```
PM> NuGet\Install-Package ZXing.Net
```

[GitHub - micjahn/ZXing.Net - README.md](https://github.com/micjahn/ZXing.Net/?tab=readme-ov-file#important-notice-for-net-standard-and-net-50-and-above-target-platforms) に下記記載があります。

> **important notice for .Net Standard and .Net 5.0 and above target platforms**
>
> If you want to try the sample code above within a project which target .Net Standard or .Net 5.0 or higher then you have to add one of the additional nuget package for a specific image library: https://www.nuget.org/packages?q=ZXing.Bindings The main package of ZXing.Net for such platforms only contains the core classes which are not dependent on a specific assembly for image formats.

該当する [ZXing.Net.Bindings.Windows.Compatibility](https://www.nuget.org/packages/ZXing.Net.Bindings.Windows.Compatibility) サイトで README を確認します。

> **ZXing.Net.Bindings.Windows.Compatibility**
>
> ZXing.Net Bindings for Windows Compatibility Pack - use System.Drawing.Common with ZXing.Net for barcode reading and writing
> This package contains specific implementations of barcode reader and writer classes which are using types from the System.Drawing.Common library.

どうやら、BarcodeReader、BarcodeWriter について、System.Drawing.Common を利用する形態として、互換性パックを提供しているようですね。
もう少し詳しい情報があるかを確認していると、、、

> [Error CS0305 Using generic type 'BarcodeWriter ' requires type 1 arguments #458](https://github.com/micjahn/ZXing.Net/issues/458)
> [ZXing.Net 0.16.8 BarcodeReader<Bitmap> and BitmapLuminanceSource initialisation](https://stackoverflow.com/questions/74797869/zxing-net-0-16-8-barcodereaderbitmap-and-bitmapluminancesource-initialisation)

互換性パックは、単純に System.Drawing.Common 利用だけではなく、名前通りの「互換性」対応もしているということですね。
上記により .NET の場合は、ZXing.Net.Bindings.Windows.Compatibility も導入します。

```
PM> NuGet\Install-Package ZXing.Net.Bindings.Windows.Compatibility
```

## QRコード生成

> [QRコードを動的に生成して表示する - yotiky Tech Blog](https://yotiky.hatenablog.com/entry/zxing_qrcodegenerate)

### QrCodeEncodingOptions

ZXing - QRコードのオプション設定要素の一部を記載します。

- QrVersion
  - エンコードするQRコードの正確なバージョン（1から40までの整数値）を指定できます。指定したバージョンに収まらない場合は、ZXing.WriterException となります。 未指定（デフォルト値：null）の場合、情報量と誤り訂正レベルからバージョンを自動選択します。
- ErrorCorrection
  - QRコードの誤り訂正レベルを ZXing.QrCode.Internal.ErrorCorrectionLevel で指定します。L=7%、M=15%、Q=25%、H=30%、となっており、レベルが高いほど訂正能力が上がり、QRコードのサイズが大きくなります。
- Height、Width
  - バーコード画像の高さと幅を指定します。
- Margin
  - バーコード生成時に使用するマージンを指定します。既定値は 0 です。Margin で調整する場合は、NoPadding = true としてください。
- NoPadding
  - バーコード生成時にパディングを実施しない（true）か実施する（false）を指定します。既定値は false です。
- CharacterSet
  - 文字エンコードを指定します。
- PureBarcode
  - 入力文字列の表示有無を true/false で指定します。既定値は false です。
- GS1Format
  - データを GS1（流通コードの管理及び流通標準に関する国際機関）にエンコードするか否かを true/false で指定します。既定値は false です。

### サンプルコード

WiFi設定 QRコードを生成して、PNGファイルに保存するサンプルコードを記載します。

.NET Framework 4.8 と .NET 8 では、軽微な差異があるので、まず .NET Framework 4.8 のサンプルコードを記載して、次に .NET 8 の差分を記載します。

####  .NET Framework 4.8

```CSharp
string path  = System.IO.Path.GetTempPath() + "hoge.png";
WiFiConfig2PngFile("aterm-000000-g", "WPA", "0000000000000", false, path);
```

```CSharp
// WiFi設定 QRコードを生成して、PNGファイルに保存
private void WiFiConfig2PngFile(string ssid, string type, string passwd,
                                bool hiddenSSID, string path)
{
  // WiFi 設定データ作成
  var wifiConfig = GenerateWiFiConfig(ssid, type, passwd, hiddenSSID);

  // BarcodeWriter 生成
  var writer = new ZXing.BarcodeWriter
  {
    // バーコードの種類を選択
    Format = ZXing.BarcodeFormat.QR_CODE,
    // QRコードのオプション設定
    Options = new ZXing.QrCode.QrCodeEncodingOptions
    {
      ErrorCorrection = ZXing.QrCode.Internal.ErrorCorrectionLevel.M,
      Width = 300,
      Height = 300,
      Margin = 10,
      NoPadding = true,
      CharacterSet = "UTF-8"
    }
  };

  // QR コードを生成し、Bitmap オブジェクトに変換
  using(var bitmap = writer.Write(wifiConfig))
  {
    // PNGファイルとして上書き保存
    bitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);
  }
}

// WiFi 設定データ作成（エスケープ処理は割愛、必要に応じて追加してください）
private string GenerateWiFiConfig(string ssid, string type, string passwd,
                                  bool hiddenSSID = false)
{
  return $"WIFI:S:{ssid};T:{type};P:{passwd};{(hiddenSSID ? "H:true;" : string.Empty)};";
}
```

#### .NET 8

.NET 8 では、下記差分があるので、サンプルコードの一部を書き換えます。
- ZXing.BarcodeWriter を ZXing.Windows.Compatibility.BarcodeWriter に変更

```CSharp
// WiFi設定 QRコードを生成して、PNGファイルに保存
private void WiFiConfig2PngFile(string ssid, string type, string passwd,
                                bool hiddenSSID, string path)
{
  // WiFi 設定データ作成
  var wifiConfig = GenerateWiFiConfig(ssid, type, passwd, hiddenSSID);

  // BarcodeWriter 生成（互換パッケージ利用）
  var writer = new ZXing.Windows.Compatibility.BarcodeWriter
  {
＜以降は .NET Framework 4.8 と同一なので省略＞
```

## QRコード読み取り

### ZXing.BarcodeReader

BarcodeReader.Decode（互換パッケージも含む）では、QRコードのバージョン情報、セル数などは取得できません。

::: note
CODE PROJECT で公開されている [QR Code Encoder and Decoder C# Class Library for VS 2022 .NET 6.0 (Version 3.1.0)](https://www.codeproject.com/Articles/1250071/QR-Code-Encoder-and-Decoder-NET-Framework-Standard) の QRCodeDecoderLibrary を利用すれば、バージョン情報を取得できることをダウンロード可能なデモアプリで確認しました。

[NuGet Gallery | QRCodeDecoderLibrary](https://www.nuget.org/packages/QRCodeDecoderLibrary) として公開もされていますが、Ver 2.0.1 （.NET Framework でのみ利用可能）で登録後、更新されていません 、、、
:::

### サンプルコード

QRコードの PNGファイルを読み込み、デコードした値を取得するサンプルコードを記載します。

.NET Framework 4.8 と .NET 8 では、軽微な差異があるので、まず .NET Framework 4.8 のサンプルコードを記載して、次に .NET 8 の差分を記載します。

####  .NET Framework 4.8

```CSharp
// PNGファイルを読み込み QRコードをデコード
private string DecodeFromPngFile(string path)
{
  // PNGファイル読み込み
  using (var bitmap = System.Drawing.Image.FromFile(path) as System.Drawing.Bitmap)
  {
    if (bitmap != null)
    {
      // BarcodeReader 生成
      var reader = new ZXing.BarcodeReader();
      // QR コードをデコード
      var result = reader.Decode(bitmap);
      if (result == null)
      {
        // ERROR - TODO
        return null;
      }
      else
      {
        // result.BarcodeFormat
        // → QR_CODE
        // result.ResultMetadata
        // → ERROR_CORRECTION_LEVEL などの情報
        return result.Text;
      }
    }
  }
  return null;
}
```

#### .NET 8

.NET 8 では、下記差分があるので、サンプルコードの一部を書き換えます。
- ZXing.BarcodeReader を ZXing.Windows.Compatibility.BarcodeReader に変更
- 戻り値として null 許容参照型を明示

```CSharp
// PNGファイルを読み込み QRコードをデコード（戻り値として null 許容参照型を明示）
private string? DecodeFromPngFile(string path)
{
  // PNGファイル読み込み
  using (var bitmap = System.Drawing.Image.FromFile(path) as System.Drawing.Bitmap)
  {
    if (bitmap != null)
    {
      // BarcodeReader 生成（互換パッケージ利用）
      var reader = new ZXing.Windows.Compatibility.BarcodeReader();

＜以降は .NET Framework 4.8 と同一なので省略＞
```