デジタルピッキング 後編
はじめに:Excelの部品表とWeb UIでピッキングを自動化する
こんにちは、ニンジンです🥕
前編では、配線を1本にまとめられる「アドレス指定型LED(WS2812B)」をRaspberry Piにつなぎ、1個だけ点灯させるところまで進めました。
この後編では、Excelの部品表(MasterとOrderの2シート)を読み込み、Flaskでラズパイ自身をWebサーバーにして、タブレットのブラウザから「部品名・数量」を見ながら棚のLEDを光らせるピッキングシステムを組み立てます。
この記事で分かること
- pandasでExcelの2シート(Master:部品マスタ / Order:ピッキング指示)を読み込む方法
- 指示IDに紐づくLED番号を引いて、順番に自動巡回で点灯させるコード
- Flaskでラズパイをタブレット向けWebサーバーにする方法(
host='0.0.0.0') - 「完了」ボタンで次の部品に進むWeb UIの作り方
- WS2812Bの実行に
sudoが必要な理由と、タブレットからつながらないときの対処
STEP2:Excelデータとの連携と自動巡回
Excelファイルの準備
読み取り用のExcelファイル parts_list.xlsx を用意します。シートは2つです。
- Masterシート:
PartsID/部品名/LED_Index(部品マスタ) - Orderシート:
指示ID/部品ID/数量(ピッキング指示)
parts_list.xlsx はラズパイOSのデスクトップ(/home/pi/Desktop/)に置いてください。sudo権限でThonnyを起動する
テストコード:test_led_Excel.py
Orderシートの順番どおりにLEDが切り替わる「自動巡回ピッキング」のコードです。Enterを押すたびに次の指示へ進みます。
import board
import neopixel
import pandas as pd
# LED設定
PIXEL_PIN = board.D18
NUM_PIXELS = 60
pixels = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, brightness=0.1, auto_write=False)
def start_picking():
# 1. データの読み込み (CSVファイル名に合わせる)
try:
path = '/home/pi/Desktop/parts_list.xlsx'
df_master = pd.read_excel('parts_list.xlsx', sheet_name='Master', engine='openpyxl')
df_order = pd.read_excel('parts_list.xlsx', sheet_name='Order', engine='openpyxl')
except Exception as e:
print(f"ファイル読み込み失敗: {e}")
return
print("--- ピッキング開始 ---")
# 2. 指示(Order)を1行ずつループ
for index, row in df_order.iterrows():
p_id = str(row['部品ID'])
qty = row['数量']
# 3. MasterからLEDの場所を探す
master_row = df_master[df_master['PartsID'].astype(str) == p_id]
if not master_row.empty:
led_idx = int(master_row['LED_Index'].values[0])
p_name = master_row['部品名'].values[0]
# 4. LED点灯
pixels.fill((0, 0, 0)) # 一旦全部消す
pixels[led_idx] = (0, 255, 0) # 緑色に点灯
pixels.show()
# 5. コンソールに指示表示
print(f"\n【指示 {index + 1}/{len(df_order)}】")
print(f"場所: {led_idx}番の棚")
print(f"部品: {p_name} (ID: {p_id})")
print(f"数量: {qty} 個")
# 6. 作業完了の待機
input("取り出したら [Enter] を押して次へ...")
else:
print(f"警告: ID {p_id} がマスタに登録されていません。")
# 7. 全件終了
pixels.fill((0, 0, 0))
pixels.show()
print("\n--- 全てのピッキングが完了しました!お疲れ様でした。 ---")
if __name__ == "__main__":
start_picking()テスト結果
STEP3:Flaskを使ったWeb UIの実装
ラズパイ単体での動作が確認できたので、次はこの動きをネットワーク越しに再現します。Pythonの Flask でラズパイ自身をWebサーバーにすれば、タブレットに専用アプリを入れなくても、ブラウザからアクセスするだけで操作画面が使えます。
sudo thonny で起動します。テストコード:web_picking.py
Flaskのルーティング(/ で表示、/next で次へ、/reset で最初に戻る)と、タブレット向けの大きな表示のHTMLテンプレートをまとめたコードです。
import board
import neopixel
import pandas as pd
from flask import Flask, render_template_string, redirect, url_for
app = Flask(__name__)
# --- 設定エリア ---
PIXEL_PIN = board.D18
NUM_PIXELS = 60
EXCEL_FILE = '/home/pi/Desktop/parts_list.xlsx'
# LED初期化
pixels = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, brightness=0.1, auto_write=False)
# データ読み込み
df_master = pd.read_excel(EXCEL_FILE, sheet_name='Master', engine='openpyxl')
df_order = pd.read_excel(EXCEL_FILE, sheet_name='Order', engine='openpyxl')
# 現在のピッキング進行状況を記憶する変数
current_index = 0
# --- HTMLデザイン (タブレット向けUI) ---
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Picking System</title>
<style>
body { font-family: sans-serif; text-align: center; background-color: #eef2f5; padding: 20px; margin: 0; }
.logo { width: 40px; height: 40px; margin-right: 10px; }
.card { background: white; border-radius: 15px; padding: 40px 20px; box-shadow: 0 8px 16px rgba(0,0,0,0.1); max-width: 600px; margin: 0 auto; }
.progress { color: #666; font-size: 1.2em; margin-bottom: 10px; }
.part-id { color: #888; font-size: 1.5em; }
.part-name { font-size: 3em; font-weight: bold; margin: 10px 0; color: #333; }
.qty-box { background-color: #fff4e5; border-radius: 10px; padding: 20px; margin: 20px 0; border: 2px solid #ff8c00; }
.qty-label { font-size: 1.5em; color: #ff8c00; }
.qty { font-size: 5em; font-weight: bold; color: #d9534f; margin: 0; }
.btn { display: block; width: 100%; padding: 5px; font-size: 2.5em; background-color: #ff8c00; color: white; border: none; border-radius: 15px; text-decoration: none; font-weight: bold; cursor: pointer; box-shadow: 0 4px 6px rgba(0,0,0,0.2); box-sizing: border-box; }
.btn:active { transform: translateY(4px); box-shadow: none; }
.btn-reset { background-color: #6c757d; font-size: 1.5em; padding: 15px; margin-top: 30px; }
.finished { font-size: 2.5em; color: #5cb85c; font-weight: bold; }
</style>
</head>
<body>
<div class="header">
<h2 style="color:#ff8c00; margin:0;">Digital Picking</h2>
</div>
<div class="card">
{% if finished %}
<p class="finished">🎉 全てのピッキングが完了しました!</p>
<a href="/reset" class="btn btn-reset">最初からやり直す</a>
{% else %}
<div class="progress">指示 {{ current }} / {{ total }}</div>
<div class="part-id">ID: {{ p_id }}</div>
<div class="part-name">{{ p_name }}</div>
<div class="qty-box">
<div class="qty-label">取り出す数量</div>
<div class="qty">{{ qty }} <span style="font-size: 0.4em; color: #333;">個</span></div>
</div>
<a href="/next" class="btn">完了 (次へ)</a>
{% endif %}
</div>
</body>
</html>
"""
def update_led():
"""現在のインデックスに合わせてLEDを点灯する関数"""
global current_index
pixels.fill((0, 0, 0)) # 一旦全部消す
if current_index < len(df_order):
row = df_order.iloc[current_index]
p_id = str(row['部品ID'])
master_row = df_master[df_master['PartsID'].astype(str) == p_id]
if not master_row.empty:
led_idx = int(master_row['LED_Index'].values[0])
pixels[led_idx] = (0, 255, 0) # 緑色に点灯
pixels.show()
# --- Webサーバーのルーティング設定 ---
@app.route('/')
def index():
global current_index
# 全件完了している場合
if current_index >= len(df_order):
pixels.fill((0, 0, 0))
pixels.show()
return render_template_string(HTML_TEMPLATE, finished=True)
# 現在の指示データを取得
row = df_order.iloc[current_index]
p_id = str(row['部品ID'])
qty = row['数量']
master_row = df_master[df_master['PartsID'].astype(str) == p_id]
p_name = master_row['部品名'].values[0] if not master_row.empty else "不明な部品"
# 画面描画と同時にLEDを更新
update_led()
return render_template_string(
HTML_TEMPLATE,
finished=False,
p_name=p_name,
p_id=p_id,
qty=qty,
current=current_index+1,
total=len(df_order)
)
@app.route('/next')
def next_item():
global current_index
current_index += 1
return redirect(url_for('index'))
@app.route('/reset')
def reset():
global current_index
current_index = 0
return redirect(url_for('index'))
if __name__ == "__main__":
# 起動時にLEDをリセット
pixels.fill((0, 0, 0))
pixels.show()
# 外部(タブレット)からのアクセスを許可してポート5000で起動
app.run(host='0.0.0.0', port=5000)テストのやり方
parts_list.xlsx はラズパイのデスクトップに置きます。つながらないときは
使っているWi-Fiで「デバイス間の通信」がブロックされていないか確認してください(ルーターの「プライバシーセパレーター」や「ネットワーク分離」がオンだと、同じWi-Fiでも端末どうしが通信できません)。
テスト結果
「完了(次へ)」を押すたびに次のアイテムが表示され、対応するLEDが点灯すれば成功です。動画で動きを確認できます。
よくある質問(FAQ)
Qsudo thonny で起動しないと動かないのはなぜですか?
AWS2812B(NeoPixel)は、ラズパイのPWMやDMA、メモリに直接アクセスして制御します。この操作にはroot権限が必要なため、Thonnyをsudo付きで起動する必要があります。
Qparts_list.xlsx はどこに置けばよいですか?
AラズパイOSのデスクトップ(/home/pi/Desktop/)に置きます。コード内のパス(EXCEL_FILE)と一致していれば別の場所でも動きます。
QタブレットからWeb UIにアクセスできません。
A①ラズパイとタブレットが同じWi-Fi(同じサブネット)にあるか、②ルーターの「プライバシーセパレーター/ネットワーク分離」がオフか、③http://ラズパイのIPアドレス:5000 でアクセスしているか、を確認してください。
Qhost='0.0.0.0' は何を意味しますか?
Aラズパイ自身以外(タブレットなど)からのアクセスを受け付ける設定です。これを省くと 127.0.0.1(ラズパイ本体のみ)になり、外部の端末からは開けません。
QMasterシートとOrderシートの関係が分かりません。
AMasterは「部品ID ↔ LED番号 ↔ 部品名」の対応表です。Orderは「今回ピッキングする指示(部品IDと数量)」のリストです。コードはOrderを1行ずつ読み、部品IDでMasterを引いてLED番号を求めます。
Q実運用で数千点を扱うにはどうすればよいですか?
AExcelは件数が増えると読み込みが重くなるので、部品マスタをSQLiteなどのデータベースに移します。LEDの本数はArduinoを中継基板にして拡張します。記事末尾の「今後の課題」も参照してください。
Follow me on X
本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕
@ninjin_py_vba をフォローシリーズ記事一覧
電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。
-
FILE.01 — IoT
Raspberry Pi Pico W × GASラズパイPico W×GASで自作!「LINEで動く温湿度&スマートリモコン」完全ロードマップ
「外出中のペットの室温が心配…」そんな思いからスタートしたIoT自作プロジェクトの総集編。温湿度監視からエアコン遠隔操作まで、仕組みをゼロから作りたい方のための全5回の開発記録です。
シリーズを読む -
FILE.02 — CNC
Arduino × レーザー刻印CNCCNC自作シリーズ
高精度な加工を目指し、本格的な自作CNC製作に挑戦中。現在は基幹パーツであるオリエンタルモーターの納品を待つ「設計・準備編」を公開。ハードとソフトの両面から、理想のマシンを形にする過程をリアルタイムにお届けします。
シリーズを読む -
FILE.03 — XY軸制御
Raspberry Pi × ステッピングモーターXYテーブルシリーズ
Raspberry Piとステッパモーターを使い、ゼロから2軸制御に挑む記録。OS設定から回路設計、多軸制御のPythonコードまで、躓きやすいポイントを徹底図解。電子工作初心者が「動く感動」を味わうための実戦ガイドです。
シリーズを読む -
FILE.04 — 業務自動化
Python × Excel × CustomTkinter【Python開発記】NINJIN Mail制作記
PythonとExcelを連携させ、実務で即戦力となるメール送信ツールを開発。CustomTkinterによるUI構築や、ミスを防ぐテンプレート活用術など、現場の「痒い所に手が届く」自動化ノウハウを細部まで丁寧に解説します。
シリーズを読む -
FILE.05 — 業務自動化
Python × Excel × GUIアプリ化Invoice Maker(請求書・見積書自動作成)
手作業の請求書作成から卒業!PythonでExcelデータを読み込み一括PDF化する「Invoice_Maker」の作り方を全3回で解説。基本ロジックからGUIアプリ化まで、実務で役立つ自動化ノウハウが満載です。
シリーズを読む














コメント