This site is an archive of ama.ne.jp.

Binary EyeとCloudflare Workersで作るバーコード読み取りシステム

概要

コンビニやスーパーなどのPOSレジや、図書館の蔵書管理システムでは、商品や書籍に印字・貼付されたバーコードをスキャナーで読み取ることで商品の在庫や書籍の貸し出し状況を効率的に管理しています。このようなシステムは、小売店や図書館だけではなく、同人サークルでの在庫管理や家庭での日用品整理などにも適用できます。

しかし、これらのシステムでは、レジに一体化したバーコードスキャナーや、手で持って使用するハンドスキャナーといった専用のデバイスを用意する必要があります。スキャナーの購入だけで数千円~数万円の費用がかかるため、単なる趣味の範囲で気軽に試すのは難しいかもしれません。また、これらのデバイスの中にはバーコードしか読み取れないものがあり、新たにQRコードなどの二次元コードによる管理システムを利用する際に、せっかく購入したスキャナーを使い回せないという懸念も残ります。

この記事では、専用のバーコードスキャナーを購入せずに実現できるシンボル読み取りシステムの実装例について紹介します。具体的には、汎用のカメラ付きAndroid搭載デバイス(スマートフォンやタブレット)に一次元/二次元コードを読み取るBinary Eyeというアプリをインストールし、Cloudflare Workersを用いて構築した小さなサーバーと連携することで、蔵書情報を記録できるようにします。

シンボル読み取りシステム

初めに「シンボル読み取りシステム」を次の特徴を備えたものと定義します。

  1. 平面に表示された機械判読可能なパターン(シンボル)を 取得できる こと
  2. 読み取り装置から得られたシンボルを、処理可能なデータに 変換できる こと
  3. 変換されたデータを処理できるモジュールに対して 送信できる こと
  4. 送信されたデータを目的に応じた方法で 処理できる こと

シンボル読み取りシステムは、小売店における売り上げ管理や図書館の蔵書管理などでよく利用されています。いわゆるPOSレジでは、JANコードと呼ばれる13桁または8桁の数字(0~9)を表すバーコードを読み取ることで、迅速な会計や正確な在庫管理を実現できます。

JANコードの例

図書館では、書籍の裏表紙に印刷されたISBNを含むバーコード(EANコード)や、管理用に貼付された独自の数字を示すバーコード(主にCODABAR/NM-7)を読み取って、書籍情報の登録や蔵書の貸し出し状況を管理しています。

ISBNの例

このようなシンボルには、バーコードのように一次元的な直線の組み合わせでデータを表現するフォーマットだけではなく、QRコードのように二次元的な表現を持つフォーマット(二次元コード)も利用できます。二次元コードはバーコードに比べて多くの文字種を利用でき、記録容量も多いため、シンボル自体に数字だけではなく名前や価格などの情報を持たせることも可能です。

QRコードの例

シンボル読み取りシステムには、シンボルを取得・変換するデバイスが必要です。一般的には、POSレジに一体化したバーコードスキャナーや、USBやBluetoothなどで接続できるハンドスキャナーといった専用のデバイスを用意しなければなりません。これらのデバイスは読み取りが比較的高速で、シンボルを読み取るという目的に特化した使いやすい設計になっています。

しかし、このようなデバイスの購入には数千円~数万円の初期費用がかかりますし、当然読み取りのたびにデバイスを利用可能にしておく必要があります。これは、できるだけ費用を抑えたいケースや、使用頻度が低いシーンでは導入の障壁になるでしょう。また、安価なスキャナーはレーザーを用いたバーコード読み取り専用のものが多く、二次元コードを用いたシステムに切り替える際に使い回すことができません。同様に、安価なスキャナーはほとんどが有線接続であり、利用できる場所も制限されます。

以下、シンボル読み取りシステムを構築するために専用のスキャナーを購入する際のメリット・デメリットをまとめます。

  • メリット
    • シンボルを読み取る機能に特化しており使いやすい
    • 比較的高速にシンボルを読み取って入力できる
  • デメリット
    • 導入に数千円~数万円の費用がかかる
    • 読み取りに対応しているフォーマットが少ない場合がある
    • 有線接続しか利用できない場合がある

これらの課題を踏まえて、この記事では、汎用のカメラ付きAndroid搭載デバイス(スマートフォンやタブレット)にシンボル読み取りアプリを導入することで、スキャナーが担っていたシンボルの取得・変換機能をより安価かつ柔軟に実現する方法を考えます。

Binary Eyeとデータ転送機能

Android搭載デバイスをシンボル読み取りシステムのスキャナーとして使うには、以下の特徴を備えたシンボル読み取りアプリの導入が必要です。

  • システムで使用したいシンボルの読み取りに対応していること
  • 読み取り結果を処理するモジュールに 送信できる こと
  • 読み取り結果の確認画面を表示せずに連続で読み取る機能があること

Androidで利用できる一次元/二次元コード読み取りアプリは多数ありますが、読み取った結果を自動的に送信できるものはそう多くありません。ほとんどのアプリでは「共有」などのメニューからSNSやチャットアプリにテキストを送信できるものの、これでは1つ1つの処理に時間がかかってしまいますし、操作があまりに面倒です。また、商品情報の検索やURLの展開に特化したアプリでは、毎回読み取り結果が表示されたりブラウザが立ち上がってしまうことで、読み取るたびにカメラが遮られてしまう場合もあります。

スマートフォンをスキャナーとして扱い、PCへシンボルの読み取り結果を入力できるようにする製品はいくつか存在します。Barcode to PCは、スマートフォンで読み取ったデータを別途PCで起動したサーバーで受け取り、キーボード入力として転送したりファイルに記録したりできます。SHINOBI無線バーコードリーダーといったアプリも、専用のサーバーソフトをPCで起動して読み取り結果を受け取るという仕組みのようです。

これらのアプリは、一般的なスキャナーのように読み取り結果をキーボード入力として渡すことができるため、非常に便利です。一方で、スキャナーとサーバー間が独自規格で通信しているため、片方を別の実装に置き換えたり、拡張するのが難しいという一面もあります。また、スマートフォンアプリの開発、ネゴシエーションや通信の設計、OSごとのキー入力エミュレートなどの実装……など高い開発コストの割に利用シーンがニッチなせいか、いずれも開発があまり活発ではない印象です。

そのため、今回はスキャナーとサーバーの結合を弱めて分かりやすいインターフェースでやり取りする点を意識し、単純なHTTPリクエストで通信を行います。具体的には、Binary Eyeというアプリのデータ転送機能を使ってスキャナーを実現することにしました。通常のスキャナーのように直接読み取り結果を入力できるわけではありませんが、必要ならサーバーのキューを参照して擬似的な入力機能を実現したり、IFTTTなどの連携サービスを接続したり、柔軟な組み合わせで対応できます。

Binary Eyeは、多くの一次元/二次元コードを高速に読み取ることができるシンプルなオープンソースのアプリです。シンボルの読み取りにはZXing(Zebra Crossing)ライブラリを使用しており、対応フォーマットもこのライブラリに依存しています。普段よく見かけるのはEAN-13(JANコードやISBN)、CODABAR/NM-7(図書館での管理用)、QRコード程度でしょう。他のフォーマットには、物流業界や医療業界、その他工業用の部品識別などでよく利用されているものがあるようです。

Binary Eyeでは、シンボルの読み取り時に自動でGETまたはPOSTリクエストを送信できます。リクエスト方式の設定に応じて、主に content という名前でパラメータが付与されるので、サーバーではこのパラメータを取り出して検索や登録などの処理を行います。

Binary Eyeのデータ転送設定

リクエスト方式 XXX を読み取ったときの挙動
GET でコンテンツを付加 URLの後ろに直接 XXX を付与してGET
GET で文字列のコンテンツを付加 ?content=XXX というクエリを付与してGET
POST application/x-www-form-urlencoded content=XXX というボディをPOST
POST application/json {content: "XXX"} というボディをPOST

これらのリクエストを受け取るサーバーとして、Cloudflare Workersにデプロイできるサンプルを示します: amane-katagiri/binary-eye-receiver-sample

import { Hono } from "hono";
const app = new Hono();

app
  .get("/", async (c) =>
    c.text(`${c.req.method} content: ${c.req.query("content") ?? "no content"}`)
  )
  .post(async (c) =>
    c.text(
      `${c.req.method} content: ${
        (await c.req.parseBody())["content"] ??
        (await c.req.json<{ content: string }>())["content"] ??
        "no content"
      }`
    )
  );

export default app;

ワーカーの用意が面倒なら、テストのために用意した https://binary-eye-receiver-sample.amanejp.workers.dev/1を使ってみてください。いずれもリクエストやログの解析は行っていません。正常にURLが設定されていれば、読み取り後にトーストで GET content: XXX または POST content: XXX と読み取ったコンテンツがおうむ返しで表示されるはずです。

Binary Eyeのデータ転送機能のテスト

ISBNによる蔵書記録システム

サーバーでシンボルの読み取り結果を受け取れるようになったので、これらのデータを 処理できる ようにします。今回は、書籍の裏表紙に印字されたISBN(上段のもの)から書籍情報を検索し、データベースに格納するサーバーを実装することにしました: amane-katagiri/akasha

ISBNとは、978または979から始まる13桁の番号です。地域ごとに割り振られたグループ記号(日本: 4)と合わせると、2023年1月現在、日本の書籍に割り振られたISBNは全て9784から始まっています。かつてISBNは10桁でしたが、EAN-13に組み込む際に先頭の国コードとしてBooklandという架空の国を割り当てられ、13桁になりました。

現時点でakashaが備えている機能は以下の通りです。

  • 書籍情報を本棚ごとに格納して管理できます。
    • 本棚のIDを知っていれば、本棚の情報や本棚にある書籍の一覧を取得できます。
    • 本棚のIDを知っていれば、書籍情報を追加・更新・削除できます。
    • 本棚の追加・変更・削除や一覧取得は管理者のみが行えます。
  • 書籍情報は楽天ブックス書籍検索APIopenBDをISBNで検索して取得します。
  • 取得した書籍情報はデータベース(Cloudflare D1)に格納されます。
  • 書籍情報の追加・更新はBinary Eyeから使用しやすいインターフェースになっています。
    • content=XXX 形式のPOSTを受け取ってISBNとして解釈します。
    • トーストに表示する際に読みやすい長さの日本語で処理結果を返します。
  • ISBNが渡されなかった場合は、その旨を通知して入力を無視します。
  • 書籍情報が見つからない場合でも、個別に手動で必要な情報を設定できます。

数ヶ月前にCloudflare D1へのアクセスが解放されたので、書籍情報の保管先として積極的に利用しました。D1はWorkersから利用できるSQLiteベースのデータベースサービスであり、アルファ版なので一定の機能・性能制限があったり、バグ2が残っている部分もありますが、ステートレスなワーカーと手軽に組み合わせられるデータベースの第一歩としてはかなり便利です。

書籍情報の取得に複数のAPIを利用したのは、楽天ブックスAPIの不安定さとopenBDの収録範囲の狭さをお互いにカバーするためです。楽天ブックスAPIは収録範囲が広いので多くの書籍情報を取得できますが、1回/秒のリクエスト制限があったり、低頻度のアクセスでも正常なレスポンスを得られないことがあるのであまり信頼できません。一方、openBDは収録範囲がわずかに狭いものの応答は高速で安定しています。また、JSONスキーマが提供されているのも好感が持てます(楽天ブックスAPIでは解析しにくい申し訳程度の表が用意されているだけです)。

そのため、まずはopenBDに問い合わせを行い、見つからなければ楽天ブックスAPIを利用するというフローを採用しています。ちなみに、国立国会図書館サーチ APIも候補に入っていましたが、XMLのパースが面倒だったのと、返ってくる書籍情報に巻数が含まれないようだったので早々に利用を諦めました。

まとめ

  • バーコードや二次元コードを読み取って処理できるシンボル読み取りシステムは、小売店や図書館などで広く利用されており、趣味や家庭内での活用も期待できます。
  • カメラ付きのAndroid搭載デバイスにBinary Eyeをインストールすれば、読み取り結果をHTTPで送信するスキャナーとして利用できます。
  • シンボル読み取りシステムの例としてCloudflare Workersを用いてISBNから書籍情報を取得するサーバーを実装し、Binary Eyeと連携できることを示しました。

  1. 「GET でコンテンツを付加」なら https://binary-eye-receiver-sample.amanejp.workers.dev/?content= とします。 

  2. 追加・更新のタイミングで更新日時を設定するトリガー定義を追加しようとしたところ、おそらく BEGIN ... END あたりのパースに失敗したのか incomplete input というエラーが表示されて実行できないバグに当たってしまいました。