バーコード作成 Web アプリ 【Raspberry Pi & Flask】

Raspberry Pi

ローカルネットワーク上の Raspberry Pi を使って、バーコードを作成する Web アプリを作ってみます。
以下の環境で動作確認をしています。
環境:
・ Raspberry Pi (bullseye、サーバーとして使用、OpenCV をインストール済み)
・ Windows パソコン(クライアントとして使用)
・ Wi-Fi ネットワーク(Raspberry Pi と Windows パソコンはネットワークに接続済み)

背景 ~ バーコード作成 Web アプリを作ってみる!

以前、ローカルのパソコンでバーコードを作成する Python のスクリプトについてまとめました。
そこで今回は、バーコード作成の Web アプリ化を試みます。

Web サーバーとしては、ローカルネットワークにつながっている Raspberry Pi を使うことにします。
Raspberry Pi に標準で入っている Python と Flask を使うことで、必要最小限の手間で、Web アプリを作ってみます。ラズパイで任意の文字列でのバーコード作成が可能になります。

バーコードは、今、ブームになっている AI や物体検出、画像認識、情報技術などの先駆けとなった自動認識技術です。
エラー検出やエラー訂正、符号理論などの数学や新技術も、バーコードを使ったシステムで社会実装されてきました。バーコードのような古い技術は、消え去るといわれた時期もありましたが、QRコードのようにスマートフォンに入り込み、無人レジでも導入されています。
定型作業の自動化・リアルタイム処理、在庫管理、業務効率化、ミス防止、品質向上、ロボットやドローンの自律動作など、アイディア次第で、いくらでもバーコードの応用範囲は広がっています。

設定手順

python-barcode のインストール

① バーコードの作成にあたり、パッケージ python-barcode が必要です。
インストールしていない場合は、Raspberry Pi でターミナルを起動し、以下のコマンドでインストールをしてください。

pip install python-barcode

② 以下を参考に、バーコードを扱うためのフォルダ(”barcode1″)を Raspberry Pi 内に作成してください。
例: /home/pi/barcode1/
③ ②のフォルダ内で、テキストファイルを作成し、barcode_encode1.py という名前で下記のスクリプトをコピー&ペーストして保存してください。
例: /home/pi/barcode1/barcode_encode1.py
※ バーコードを作成するスクリプトです。このスクリプトは単体でも動きますが、このスクリプトで記載した関数とパスを Flask のスクリプト app.py から呼び出すことで、Web アプリ上でバーコードを作成します。
④ Raspberry Pi を Web サーバーとして動かすため、下記を参考に公開用のフォルダ(ルートディレクトリ)を作成してください。
例: home/pi/flask1/barcode1/
⑤ ④のフォルダ内に app.py という名前でテキストファイルを作成し、下記のサンプルスクリプトをコピー&ペーストして保存してください。
例: home/pi/flask1/barcode1/app.py
⑥ さらに、④のフォルダ内に “template” という名前でフォルダを作成してください。
⑦ ⑥のフォルダ内に “barcode_encoder1.html” という名前でテキストファイルを作成し、下記のサンプルスクリプトをコピー&ペーストして保存してください。バーコードを作成するための画面のテンプレートとなっており、Flask の app.py から参照して使います。
例: home/pi/flask1/barcode1/template/barcode_encoder1.html

使い方

Web サーバーの起動・停止

⑧ Raspberry Pi のターミナルを起動し、④のフォルダ(ドキュメントルート)に移動してください。
例:

cd /home/pi/flask1/barcode1

⑨ 以下のコマンドを実行してください。

flask run --host=0.0.0.0 --port=5000

→ ”Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)” という文字列が表示されたら、Web サーバーの起動成功です。
※ Web サーバーを停止する場合は、[ctrl] と [c] のキーを同時に押してください。
※ ここで、”5000″ の数値(ポート番号)は、他の数値に変更しても問題ありませんが、以下でブラウザで参照する際に、同じ値となるようにしてください。

ブラウザからのアクセス

⑩ ローカルネットワーク内にある別のパソコン(例:Windows パソコン)で、ブラウザを起動し、以下を参考に、⑨の Web サーバーにアクセスしてください。
例: 192.168.1.33:5000/barcode_encoder1

→ バーコード作成の画面(”barcode generator”)が表示されたら、サイトへのアクセスまで成功です。

バーコード作成 Web アプリの使い方

⑪ バーコード作成の画面(”barcode generator”)上で、半角で数値やアルファベットを入力し、[enter] キーを押してください。バーコードが作成できます。
※ バーコードの種別により、半角数値のみ、桁数固定等となっているものなどがあります。
※ 一例として、Code128 は、半角での、数値とアルファベットの大文字・小文字に対応しています。文字数は可変です。
Code39 は、数値とアルファベット大文字に対応です。文字数も可変です。
JAN は、冒頭が”45″または”49″で始まる数値の13桁固定で、末尾の1桁がチェックデジット(誤動作チェック用の数値、JAN の仕様に基づき自動で付加)です。

うまく動いたら

・ うまく動いたら、バーコードをいくつか作ってみてください。バーコードの種別も変更して、Web アプリの動きやバーコードの仕様などを確認してみてください。作成したバーコードは、コピーして Excel や Word などに貼りつけて利用できます。
・ また、HTML を修正し、バーコード作成画面のデザインをもっと洗練したものにしてみてください。HTML を修正した場合は、Flask を再起動してください。
・ 今回、バーコードを作成するスクリプト barcode_encode1.py を app.py から読み出して利用しています。app.py の参照先を書き換えることで、自作した他のスクリプトについても同様に呼び出して、Web アプリ化していくことができます。
・ 上記の URL を、ブラウザのお気に入りに登録することで、インターネット上のウェブサービスと同様の使い勝手で、上記の Web アプリを使えるようになります。
なお、Raspberry Pi や Web サーバーの自動起動や自動停止についても、このサイトでまとめています。興味のある方は参照してみてください。

スクリプトの説明

バーコード作成スクリプト barcode_encode1.py の説明

・ barcode_encode1.py は、バーコードの画像を作成するスクリプトです。単独でコンソールから実行しても機能するように記載しています。
・ コマンドプロンプトで、このスクリプトのあるフォルダに移動し、”python barcode_encode1.py [エンコードする文字列]” + [enter] とすることで、バーコードを作成することができます。このスクリプトを下記の app.py から呼び出すことで、バーコードの画像を Web アプリ上で表示します。
・ 冒頭のインポート文で、使用するパッケージをインポートしています。from barcode.writer … とした行で、バーコードに関するプログラムを呼び出しています。画像を扱うため、cv2(OpenCV ) もインポートしています。
・ def check_string1() という行で、バーコードの文字列のチェックをしています。バーコードの種別 type1 に応じ、変換する文字列 data1 の文字数、数値であるか否か、などをチェックし、エンコードができない場合は、エラー用の文字列を返します。
・ def encode1() という行以下で、バーコードを作成しています。文字列をチェックし、エラーがなければ、class1 = とした行でバーコードの種別に応じたバーコードのクラスを作り、文字列 data1 を入れることでバーコードの画像データ buf1 を生成します。バーコードの画像は numpy を使って OpenCV の画像形式に変換し、結果を返します。バーコードが作成できない場合は、白地の画像を作ります。
・ if __ name __ == … とした行以下は、barcode_encode1.py のスクリプトをコンソールから直接呼び出したときの処理です。”python barcode_encode1.py [エンコードする文字列]” の形で、スクリプトを実行したとき、引数で与えられた文字列を上記の encode1() 関数に入れてバーコードの画像を表示します。何かキーを押すと表示を消してプログラムが終了します。また、バーコードは Code128 としていますが、同様に他の種別をプログラム内で設定することで、自由に改変できるようにしています。

Flask のスクリプト app.py の説明

・ 冒頭で、Flask その他で必要なパッケージをインポートしています。
・ sys.path.append() としたところの2行で、上記の barcode_encode1.py が入っているフォルダを指定し、スクリプト barcode_encode1.py を読み込んでいます。
・ app = Flask() の部分は Flask を使うための定型的な記載です。
・ def get_image2() は、バーコードの画像をクライアント側の HTML に送るため、base64 という形式に変換する関数です。
・ @app.route(“/barcode_encoder1”) としたところで、サイトの /barcode_encoder1 にアクセスしたとき、barcode_encoder1() 関数を実行し、テンプレート barcode_encoder1.html を返すようにしています。
・ barcode_encoder1.html の内部では、バーコードの作成ボタンをクリック等すると、エンコードしたい文字列等のデータを /barcode_encoder1_1 に送るよう、定義しています。
・ @app.route(“/barcode_encoder1_1”, …) としたところでは、HTML よりバーコードの文字列が送られてきたら、barcode_encoder1_1() 関数を実行し、送られてきたデータから、バーコードの種別 type1 とエンコードしたいデータ data1 を取得します。このデータを上記の barcode_encode1.py 内の encode1() 関数に入れることで、バーコードの画像 image1 と、エラー出力用の文字列 str1 を取得します。
バーコードの画像 image1 を base64 形式に変換し、image2 を得ます。バーコードの画像 image2 とバーコードの種別 type1 とエンコードした文字列 data1 はすべて平易な文字列となっています。そこで、これらの3つの文字列を “\n\n” で連結し、連結した文字列 str2 をクライアント側の HTML に返します。
・ なお、コメントアウトしている @app.route(“/”) の部分は、サーバーのトップ画面を設定する場合の記載例です。トップ画面を作っている/作るようでしたら、このコメントアウトの部分を外して、トップ画面が表示されるようにしてください。
・ また、def get_image1() もコメントアウトしています。これは、クライアント側から画像データを受け取る場合のスクリプトの例です。今回は、クライアント側からは文字列しか受け取っておらず不要ですので、コメントアウトしています。後日、この機能を入れたプログラムについても公開しようと思います。

バーコード作成画面 barcode_encoder1.html の説明

・ barcode_encoder1.html の冒頭では、画像領域 img1 と、文字列用の領域 div1、div2 の表示等を設定しています。
・ <body> … </body> の中の <form> … </form> の部分で、バーコードの文字列入力のためのテキストボックスと、バーコードの種別を設定するドロップダウンリスト <select> … </select>、ボタン、ダウンロード用のリンクの文字列を設定しています。
・ <img … > としたところと、<div … ></div> としたところで、サーバーから返ってきた画像を表示するための画像領域と、文字列の領域(3か所)、の計4か所を定義しています。
・ <script> … </script> としたところで、各動作を定義しています。
・ txt11.addEventListener(“keydown”, … ) としたところで、テキストボックス txt11 でキーを押したときのイベントを監視し、keydown1() 関数を実行するようにしています。
・ check_string1() は、入力された文字列をクライアント側でもチェックする処理です。クライアント側の処理としては、全角があったらはじくといった程度のチェックとしています。サーバー側の処理をより軽くしたい場合は、この部分のチェックをより厳しくしてください。
・ function keydown1() としたところで、キーを押し下げたときの処理を記載しています。
キーを監視して、[enter] キーが押された(keycode == 13)とき、関数 request1() を実行します。
・ async function request1() としたところで、送受信を行う際の処理を定義しています。
サーバーの /barcode_encoder1_1 にアクセスすることとし、文字列チェックで問題がなければ(str0 === “”)、textbox1 の内容とバーコードの種別の option1 の値の文字列を作り、fetch() 関数という Fetch API の機能を使って送信を試みます。
サーバーからデータが返ってきたら(resp1.ok)、response1() 関数を実行します。
・ response1() 関数では、受け取った文字列のデータを “\n\n” で分割し、画像データと3つの文字列をそれぞれの領域に書き出します。

まとめ

バーコードを作成する Web アプリについて、スクリプトを作成し、ポイントをまとめました。
これで、バーコードの作成について、ローカルアプリでも Web アプリでも自由自在です。任意の文字列をバーコードにすることが可能です。

他にも、ウェブカメラなどを使ったバーコードリーダ、Windows パソコンなどで動くバーコード作成アプリ、selenium や chromedriver を使ったWebサイトの自動操作など、インストール手順やスクリプトなどをまとめています。もし、関心があるようでしたら、関連リンクなども参照してみてください。

関連リンク
・ バーコードリーダ Web アプリ 【Raspberry Pi & Flask】
・ 
バーコード作成ソフトウェア 【Python & Tkinter】
・ Raspberry Pi でファイルアップローダ 【Flask】
・ Raspberry Pi で Flask を動かしてみる 【Python】
・ Raspberry Pi でブラウザを自動操作してみる 【Python】
・ 初期設定のまとめ 【OpenCV & Raspberry Pi】

外部リンク [PR] 
・ python-barcode Getting started
・ Flask Development Server
・ Raspberry Pi 5 (8GB RAM) 技適対応品
・ Raspberry Pi 5 8GB 技適対応品/アクティブクーラー
・ Raspberry Pi 5用 27W USB-C PD(電源アダプタ)

サンプルスクリプト

バーコード作成スクリプト barcode_encode1.py

import io 
import cv2 
import sys 
import numpy as np1 
import barcode as bc1 
from barcode.writer import ImageWriter 

def check_string1(type1, data1): 
  type2 = type1.lower() 
  type3 = type1.replace("jan", "ean13").replace("isbn", "ean13") 
  data2 = data1 
  str1 = ""                   # error notation 
  if type3 == "ean13": 
    if data1.isdigit() == False: 
      data2 = "" 
      str1 = "EAN13 can only contain numbers. " 
    elif len(data2) < 12: 
      data2 = "" 
      str1 = "EAN13 must have 12 digits. " 
    elif type2 ==  "isbn": 
      if data2[0:3] != "978" and data2[0:3] != "979": 
        data2 = "" 
        str1 = "ISBN must start with 978 or 979. " 
    elif type2 == "jan": 
      if data2[0:2] != "45" and data2[0:2] != "49": 
        data2 = ""
        str1 = "JAN must start with 45 or 49. " 
  elif type2 == "ean8": 
    if data1.isdigit() == False: 
      data2 = "" 
      str1 = "EAN8 can only contain numbers. " 
    elif len(data2) < 7: 
      data2 = ""
      str1 = "EAN8 must have 7 digits. " 
  elif type2 == "upca": 
    if data2.isdigit() == False: 
      data2 = "" 
      str1 = "UPC can only contain numbers. " 
    elif len(data2) < 11: 
      data2 = "" 
      str1 = "UPC must have 11 digits. " 
  return data2, str1 

def encode1( type1, data1 ): 
  data1, str1 = check_string1( type1, data1 ) 
  if str1 == "": 
    buf1 = io.BytesIO() 
    class1 = bc1.get_barcode_class( type1 ) 
    barcode1 = class1( data1, writer=ImageWriter() ) 
    barcode1.write(buf1) 
    buf1.seek(0) 
    img1 = np1.frombuffer(buf1.getvalue(), dtype=np1.uint8) 
    img2 = cv2.imdecode(img1, cv2.IMREAD_COLOR) 
    img3 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB) 
  else: 
    img1 = np1.ones((280, 360, 3), np1.uint8 ) * 255 
    val1, img1 = cv2.imencode(".png", img1) 
    img2 = cv2.imdecode(img1, cv2.IMREAD_COLOR) 
    img3 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB) 
  return img3, str1 

if __name__ == "__main__": 

  data1 = sys.argv[1] 
  type1 = "code128"      # Code128, Code39, JAN, ISBN, EAN13, EAN8, UPC-A 

  img1, str1 = encode1( type1, data1 ) 
  cv2.imshow("image", img1) 
  cv2.waitKey(0) 
  cv2.destroyAllWindows() 

Web サーバー(Flask)用のスクリプト app.py


from flask import Flask, render_template, request 
import os 
from PIL import Image as im1 
import sys 
import cv2 
import base64 

sys.path.append( "/home/pi/barcode1" ) 
import barcode_encode1                                   # barcode_encode1.py 

app = Flask(__name__) 

# @app.route("/") 
# def index1():
#   return render_template( "index.html" ) 

# def get_image1( file1 ): 
#   image0 = file1.stream.read() 
#   image1 = cv2.imdecode(np1.frombuffer(image0, np1.uint8), cv2.IMREAD_COLOR) 
#   return image1 

def get_image2( image1 ): 
  image2 = cv2.imencode('.png', image1)[1].tobytes()
  image3 = base64.b64encode( image2 ).decode("utf-8")    # A-Z, a-z, 0-9, +/, =      26+26+10+2 = 64     
  return image3 

@app.route("/barcode_encoder1")                                  # barcode encode 
def barcode_encoder1(): 
  return render_template("barcode_encoder1.html") 

@app.route("/barcode_encoder1_1", methods=["POST"])  
def barcode_encoder1_1(): 
  type1 = request.form["option1"] 
  data1 = request.form["textbox1"] 
  image1, str1 = barcode_encode1.encode1( type1, data1 ) 
  image2 = get_image2( image1 ) 
  str2 = image2 + "\n\n" + type1 + "\n\n" + data1 + "\n\n" + str1 
  return str2 


バーコード作成用の画面の例 barcode_encoder1.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <title>barcode generator</title>
    <meta http-equiv="Content-Type" content="text/html;">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        #img1 { 
            display: none; 
        } 
        #div1 { 
            color: #efefef; 
        } 
        #div2 { 
            color: #efefef; 
        } 
    </style>
</head>

<body onload="onload1()" style="background-color:#efefef;">
    <h1>barcode generator v0.1</h1>
    <form> 
        <input type="text" id="textbox1" name="textbox1" value="" sytle="width:100px;" > 
        <select id="option1" name="option1" onchange="request1()" > 
            <option value="code128" selected>Code128</option> 
            <option value="code39">Code39</option>
            <option value="jan">JAN</option>
            <option value="isbn">ISBN</option>
            <option value="ean13">EAN13</option>
            <option value="ean8">EAN8</option>
            <option value="upca">UPC-A</option>
        </select>   
        <input type="button" value="encode" onclick="request1()" />   
        <a id="lnk1" download="barcode1.png">download</a>
    </form>
    <img id="img1" src="" alt="image" style="margin-top:15px; margin-left:20px; margin-right:20px; " ><br>
    <div id="div1"></div> 
    <div id="div2"></div> 
    <div id="div3"></div> 
    <script> 
        let txt11 = document.getElementById("textbox1"); 
        txt11.addEventListener("keydown", function( event1 ) { keydown1( event1 ); }); 
        function onload1() { 
            txt11.focus(); 
        } 

        function check_string1() { 
            const str1 = txt11.value; 
            str2 = ""; 
            let regex1 = /^ [^\\x01-\\x7E\\xA1-\\xDF]+$/; 
            let reslt1 = str1.match(regex1); 
            if (reslt1) { 
              str2 = "NG";
            } 
            return str2;  
        } 

        function keydown1( event1 ) { 
            if (event1.keyCode == 13) { 
                event1.preventDefault(); 
                event1.stopPropagation(); 
                request1(); 
            } 
        } 

    async function request1() {
        const url1 = "/barcode_encoder1_1";
        const str0 = check_string1(); 
        if (str0 === "") { 
            const str1 = "textbox1=" + document.getElementById("textbox1").value + "&option1=" + document.getElementById("option1").value;
            try {
                const resp1 = await fetch(url1, { method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded"}, body: str1 });
                if (resp1.ok) {
                    const str1 = await resp1.text();
                    response1(str1);
                } 
            } catch ( error1 ) {console.error("Fetch error:", error);}
        } 
    }

    function response1( str1 ) {
        const a1 = str1.split("\n\n");
        const img11 = document.getElementById("img1");
        const div11 = document.getElementById("div1");
        const div21 = document.getElementById("div2");
        const div31 = document.getElementById("div3");
        const lnk11 = document.getElementById("lnk1");
        img11.src = "data:image/png;base64," + a1[0];
        img11.style.display = "block";
        div11.innerHTML = a1[1];
        div21.innerHTML = a1[2];
        div31.innerHTML = a1[3];
        lnk11.href = img11.src;
    } 

    </script>
</body>
</html>

 

タイトルとURLをコピーしました