PR

【後編】数千点の部品管理を自動化!ラズパイで作るデジタルピッキングシステム(Excel連携&Web UI編)

ハードウェア制御 / 電子工作
スポンサーリンク

はじめに

前編では、膨大な配線を1本にまとめる「アドレス指定型LED(WS2812B)」の導入と、ラズパイからの点灯テストを行いました。 今回の後編では、Excelデータ(MasterとOrder)を読み取り、Flaskで立ち上げたサーバーを経由してスマホ操作でLEDを光らせる要素設計のゴールを目指します

スポンサーリンク

ロードマップ

デジタルピッキングシステムの要素設計ロードマップです。
後編となる本記事はSTEP2~STEP3まで実施します

STEP1:環境構築

STEP1の記事はこちらをクリック

  • ラズパイとLED(WS2812B)の物理接続
  • Pythonライブラリ(rpi_ws281x等)のインストール
  • プログラムからLEDを1個だけ光らせる(疎通確認)

STEP 2:要素技術の連携

  • ExcelファイルをPythonで読み込む
  • 入力したIDに紐づく特定のLEDを光らせる
  • NEXT: Order シートを読み込んで「部品名・数量・LED番号」をセットで取得する

STEP 3:UI(操作画面)の実装

  • Flask(Webサーバー)を立ち上げる
  • タブレットのブラウザに「部品名」と「数量」を大きく表示する
  • 「完了」ボタンを押すと次の部品に進むロジックを作る
スポンサーリンク

STEP 2:Excelデータとの連携と自動巡回

Excelファイルの準備

まずは、読み取り用のExcelファイルを準備します。

psrts_list.xlsxのダウンロード

  • ファイル名:parts_list.xlsx
  • Sheet名:「Master」と「Order」
  • Sheet: Masterの列名PartsID部品名LED_Index
  • Sheet: Orderの列名指示ID部品ID数量

Sheet名:Master

Sheet名:Order

部品構成および結線図

部品構成、結線図については前編をご確認下さい

テストコードの実行

正しくExcelファイルからデータを読み取り、対応するLEDを光らせることができるかテストを実施します。 STEP2は、Orderシートに記載した順番にLEDが切り替わる「自動巡回ピッキング」のテストです

権限を持たせて実行

※実行には権限が必要です
ニンジンはラズパイ上でPythonを動かす場合はThonnyで動作させますが、単純にThonnyを立ち上げると動作しません。
「sudo」を付けてThonnyに権限を持たせて立ち上げます。

ファイルを選択するボタンが無いため
通常モードに変更します
「Switch to reqular mode」をクリック

「OK」をクリックして通常モードへ

左上に「File」が表示されるので
そちらから、実行したいファイルを選択して下さい。

テストコード:test_led_Excel.py

テストコードの確認はこちらをクリック
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()

test_led_Excel.pyはこちらからダウンロードできます

重要!
※Excelファイル:parts_list.xlsxはラズパイのOSのデスクトップ上に置いて下さい

テスト結果:test_led_Excel.py

ターミナルに対応する情報が表示され
3番目のLEDが点灯します。

次へ進めるため「Enter」を押します。

ターミナルに対応する情報が表示され
1番目のLEDが点灯します。

次へ進めるため「Enter」を押します。

ターミナルに対応する情報が表示され
5番目のLEDが点灯します。

次へ進めるため「Enter」を押します。

ターミナルに対応する情報が表示され
7番目のLEDが点灯します。

次へ進めるため「Enter」を押します。

ターミナルに対応する情報が表示され
6番目のLEDが点灯します。

次へ進めるため「Enter」を押します。

Orderに記載していた5件のアイテムが完了したら「全てのピッキングが完了しました!」と表示されれば成功です!

スポンサーリンク

STEP 3:Flaskを使ったWeb UIの実装

ラズパイ上でイメージ通りの動作が確認できたので、次はこの動きをネットワーク上で再現します。
Pythonの「Flask(フラスク)」というライブラリを使って、ラズパイ自身をWebサーバーにしてしまいます。
こうすることで、タブレットに専用アプリを入れる必要がなく、ブラウザ(ChromeやSafari)からラズパイにアクセスするだけで操作画面が表示されます。

Excelファイルの準備

STEP2と同じExcelファイルを使用します。

部品構成および結線図

部品構成もSTEP1、STEP2と同じです。

部品構成、結線図については前編をご確認下さい

テストコードの実行

いよいよ、ラズパイをサーバーとしてWebUIで操作してみます。

権限を持たせて実行

※実行には権限が必要です
ニンジンはラズパイ上でPythonを動かす場合はThonnyで動作させますが、単純にThonnyを立ち上げると動作しません。
「sudo」を付けてThonnyに権限を持たせて立ち上げます。

テストコード:web_picking.py

テストコードの確認はこちらをクリック
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)

web_piking.pyはこちらからダウンロードできます。

テストのやり方

重要!
※Excelファイル:parts_list.xlsxはラズパイのOSのデスクトップ上に置いて下さい

重要!!
実施する際は「ラズパイ」+「PC or スマホ」は同一ネットワーク下にてテストを実施して下さい。

▶ボタンをクリックして
web_picking.pyを実行します

PCの場合
左記画面のWebUIが立ち上がります。

スマートフォンの場合
左記画面のWebUIが立ち上がります。

WebUIにつながらない時は
つながらない時は?

使用しているネットワークが、デバイス間通信がパブリック設定(禁止)になっていないか確認してみて下さい。

テスト結果:web_picking.py

動画のように「完了(次へ)」をクリックする事に、次のアイテムが表示され、対応するLEDが点灯すれば成功です

スポンサーリンク

まとめと今後の課題

これでデジタルピッキングの心臓部分の要素設計が完成しました。
ただし、実際の現場で数千~数万点という数の暴力に対応するためには、以下のような課題があります。

  • 中継基板の導入:Arduino Unoなどを中継基板として活用する。
  • データベース化:Excelデータでは動作が重くなるため、部品マスターをSQLで持たせる。
  • BOM連携:構成表(BOM)の読み込みを端末側で行う。
  • 等など

課題はまだまだありますが、根幹の設計はこんな感じでしょうか。
ぜひ皆さんも、ご自身の環境に合わせてピッキングシステムを構築してみてください!

スポンサーリンク

他のシリーズ





【ラズパイPico W入門】愛犬を守る!格安IoT温湿度見守りLINE Botの作り方(第1回:ハードウェア準備&温度取得編)
Raspberry Pi Pico Wと温湿度センサーで愛犬の留守番を見守るIoTシステムを自作!第1回はパーツ準備、回路接続からThonnyを使った温度取得まで解説。初心者が必ず躓く「USBケーブルの罠」など、実体験を交えて優しく紹介します。
【CNC自作】1064nm赤外線レーザーで金属刻印!GRBL設定変更とLightBurn素材テスト(第8回)
Arduino+GRBLの自作CNCに1064nm赤外線レーザー(LASER TREE)を搭載!ドライバー基板の配線やGRBLの必須パラメータ設定($32=1)、LightBurnを使ったアルミ板への金属刻印テストまで、CNCをレーザー加工機化する全手順を詳しく解説します。
【CNC自作】本番アルミ筐体完成!CNCjs(GRBL)でのテストと「Check Door」エラーの意外すぎる罠(第7回)
自作CNCマシンの本番用アルミフレーム筐体が完成!GRBLの環境移行設定(移動量・加速度)やペンプロッター化による動作テストを公開。さらにGコードの日本語コメントが引き起こす「Check Door」エラーの意外すぎる原因(UTF-8問題)と解決策を詳しく解説します。
【後編】数千点の部品管理を自動化!ラズパイで作るデジタルピッキングシステム(Excel連携&Web UI編)
ラズパイとPythonで部品管理を自動化!デジタルピッキングシステム自作の【後編】では、Excelデータ(構成表)の読み込みと、Flaskを使ったWeb UIの実装手順を解説します。専用アプリ不要!スマホのブラウザから棚のLED(WS2812B)を操作する実践的なコードを大公開。
【前編】数千点の部品管理を自動化!ラズパイで作るデジタルピッキングシステム(ハードウェア構築編)
「部品探しに時間がかかる…」そんな悩みを解決!Raspberry Pi(ラズパイ)とアドレス指定型LED(WS2812B)を使って、数千点の部品管理を自動化するデジタルピッキングシステムの作り方を解説します。前編は膨大な配線を1本にまとめるハードウェア構築編です。

コメント