ローカルネットワーク上の 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
⑥ さらに、④のフォルダ内に “templates” という名前でフォルダを作成してください。
⑦ ⑥のフォルダ内に “barcode_encoder1.html” という名前でテキストファイルを作成し、下記のサンプルスクリプトをコピー&ペーストして保存してください。バーコードを作成するための画面のテンプレートとなっており、Flask の app.py から参照して使います。
例: home/pi/flask1/barcode1/templates/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>