[Ruby] rqrcodeで連結QRコードを生成する（知識編）

# 連結QRコードとは？

* QRコードには長いデータを最大16個のQRコードに分割して、読み取りの際に結合する「連結機能」が存在します。（詳しくは、[QRコードドットコム](https://www.qrcode.com/about/) 参照）
* 日本においては、病院／クリニックから貰う処方箋で見かける機会が多いと思います。
* サードパーティ系の帳票出力ライブラリなどでは出力機能がサポートされていたりするのですが、なかなかOSSのライブラリでは見つからないので、従来から使っていた [rqrcode](https://github.com/whomwah/rqrcode) を改造して出力できるようにしてみました。
  * 今回は、上記の「処方箋」に記録する連結QRコードを出力することをターゲットとしているので、 `8bit_byte` モードのQRコードだけを対象にしています。


# どうすれば連結QRコードになる？

* 通常のQRコードの場合、書き込むデータのヘッダ部分は、以下のような構成になります。

  * 尚、QRコードには、1〜40というバージョン(=QRコードの大きさ)があり、これにより１つのQRコードに記録できるデータサイズが変わります。（[こちら](https://www.qrcode.com/en/about/version.html)を参照）
  * QRコードに対するバージョンの指定は、ヘッダ部分とは異なるところに書き込むらしく、ここでは割愛します。
  * SS の長さは、QRコードのバージョンとエンコーディングモードに応じて8〜16bitの間で可変です。

| - | 長さ | 記録する内容 |
|:-:|:-:|:-:|
| E | 4bit | エンコーディングモード(下表参照) |
| SS | (可変) | QRコードに記録するデータの長さを記録 |


| エンコーディングモード | モードを表す値 |
|:-:|:-:|
| `numeric` | 1 (0b0001) |
| alpha_numeric | 2 (0b0010) |
| `8bit_byte` | 4 (0b0100) |

* `8bit_byte` モードの連結QRコードの場合は、ヘッダ部分を以下のように記録します。
  * [こちら](https://programresource.net/2013/05/04/2188.html)を参考にさせて頂きました。

| - | 長さ | 記録する内容 |
|:-:|:-:|:-:|
| 3 | 4bit | 連結QRコードを示すモード(0b0011) |
| N | 4bit | 連結するQRコードの順序番号(0〜15の連番) |
| T | 4bit | QRコードの最後の順序番号(0〜15の連番) |
| PP | 8bit | 全てのデータのパリティ値(全てのbyteのXOR値) |
| E | 4bit | エンコーディングモード(8bit_byte = 4) |
| SS | (可変) | 1つのQRコードに記録するデータの長さを記録 |

* ということで、連結QRコードを出すには以下のような手順になります。
  * 連結QRコードのバージョン(大きさ)を決める。
      - 連結は最大で16個です。
      - また、連結QRコードだと、通常のQRコードより記録できるデータ量が2byte少なくなるようです。(データ量は20bit増えていると思うのですが・・何故か -2byteまで記録出来るようです)
      - 上記２点も加味して、バージョンを決める必要があります。
      - 通常全てのQRコードは、同じバージョン(大きさ)で作ります。(バージョンがバラバラなQRコードを連結できるかは試したことがありませんが、読めないリーダーもありそうなので避けたほうが無難です)
  * QRコードに収めるデータからパリティを計算する。(全てのbyteのXOR値=8bit)
  * QRコードに収めるデータを、1つのQRコードに収まるように分割する。(各QRコードのキャパシティ - 2byte で収まるようにする)
  * 必要な数のQRコードを生成して、それぞれレンダリングする。
     - 全てのQRコードに、同じ「パリティ値」と「最後の順序番号」を指定します。
     - 「順序番号」と格納するデータは、QRコードごとに指定します。
 
* 実際に rqrcode を改変しての連結QRコードの出力は [実践編](https://qiita.com/c-flat/items/7d9e50af04fa7f750a6b) で解説します。


# リーダーごとに挙動が違うので注意しましょう

* 薬局などの業務では、レーザー式のバーコードスキャナーなどが利用されています。これらは結構高価なので、動作検証用に機器を揃えるのは少々ハードルが高いです。（私は個人的にも興味があったので、ヤフオクで中古品を安価にGETしました。意外と頻繁に出品されているようです。）
* なので、バーコードスキャナを使った最終確認までの間は、連結QRコード読み取りに対応したスマホアプリを使って動作確認をしていました。（iOSアプリだと、私は3種類しか見つけていません）
* しかし、リーダーアプリも**バーコードスキャナー**も、製品によって結構挙動が違ったりします。
  - パリティ値が合っているかを確認していないものがあり~~そう~~**ました**。（実運用上は大した問題ではありませんが）
  - 連結QRコードの指定なのにQRコードが１つしかない（分割されていない）場合に、読み取れないものがあり~~そう~~**ました**。（データ量が少ない場合は、通常のQRコードで出力したほうが良さそうです）
  - 漢字(ShiftJIS)の上位バイトと下位バイトが、2つのQRコードに跨ってしまった場合に文字化けを起こすものがある。（アプリにこの挙動がよく見られます）

* 上記のことから、特に業務利用の場合は、実際に利用される機器などを想定して動作確認環境を揃えたほうが無難ですね。
  - バーコードスキャナなら、少し古めの機種も含めて、何種類か用意したほうが良いかも知れません。

## 色々読み取りテストしてみた

* ちょっと変な状態の連結QRコードを色々作って、４種類のリーダーで試してみました。

  * スキャナーA　→　某有名メーカのレーザー式のハンディスキャナー。但しかなり古い機種。(Win98もサポート)
  * アプリB　→　連結QRコードが読める某アプリで、スキャナーAのメーカが出している
  * アプリC　→　連結QRコードが読める某アプリ
  * アプリD　→　連結QRコードが読める某アプリ
  * アプリは全て2020/08/29現在のバージョンでの検証結果です。

| QRコードの状態 | スキャナーA | アプリB | アプリC | アプリD | 補足 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 連結だが1枚で収まっている | 反応しない | 反応しない | OK | OK | ※1 |
| 最後の1枚が制御コードだけ | OK | OK | 解析エラー | OK | CR/LF/EOFだけ格納|
| 2バイト文字が2つに跨っている | OK | OK | 解析エラー | 文字化け | |
| パリティ値間違え(全て) | 読取完了しない※２ | 読取完了しない※２ | 解析に問題発生 | OK | 全て同じパリティ値を指定|
| 1枚だけパリティ値間違え | 読取完了しない※２ | 読取完了しない※２ | 異なるQRコード | 異なるQRコード | |
| サイズが揃っていない | OK | OK | OK | OK | |

* ※１：ちなみに、同じメーカーの最近の機種では読み取れるらしいです。
* ※２：１枚ごと反応はしますが、全てのコードを読んでも読み取り完了になりません。
* 考察と感想
  * 2バイト文字(漢字など)による文字化けや解析エラーは、実運用ではちょっと致命的ですよね。（流石に業務用のスキャナーには、そんなバグは無いと思いますが）
  * **１枚に収まる場合は、連結じゃなく通常のQRコードで出力したほうが無難**なようです。
  * パリティについては、「ちゃんとVerifyしている」ものと「同じ連結QRコードかどうかのチェックにだけ使っている」ものがあるようですね。（正しい値を設定しましょう）
  * スキャナーAとアプリBは同じメーカなだけあって、流石に挙動が似ていますね。

## 参考文献

この記事は以下の情報を参考にして執筆しました。

* [QRコードドットコム](https://www.qrcode.com/)
* [Zxingを使って連結QRコードを読み書きする Part2:連結QRコードの読み取り](https://programresource.net/2013/05/04/2188.html)

## 更新履歴

* 2020.08.29 「色々読み取りテストしてみた」を追加しました。（バーコードスキャナを手に入れたので）