🥽

GUIDE01 を Python から操る ③ HTTP 版 (REST で一番デバッグしやすい)

に公開

はじめに

GUIDE01用にアプリを構築するシリーズ、第 3 回は HTTP (REST API) 版です。
REST APIと比較的(?)よく使う仕組みなのでまずは自作で試してみるのにもちょうどいい題材かもしれないです。

プロトコル含めて様々な手段を利用できるので記事を関連記事としてまとめています。

# テーマ
GUIDE01 を Python から操る ① 開発環境 & 共通メッセージ仕様 開発環境 & 共通メッセージ仕様
GUIDE01 を Python から操る ② OSC 版 (低レイテンシ・小ペイロード) UDP / port 8000 / 低レイテンシ・小ペイロード向き
(本記事)GUIDE01 を Python から操る ③ HTTP 版 (REST で一番デバッグしやすい) REST / port 8080 / 一番デバッグしやすい
GUIDE01 を Python から操る ④ MQTT 版 (IoT 統合と Windows Mosquitto の罠) publish/subscribe / IoT 統合に強い
GUIDE01 を Python から操る ⑤ BLE 直叩き版 (PC 単体・GIF・タッチイベント) 公式アプリ非経由、GIF・タッチイベント取得

1. HTTP (REST API)について

3 プロトコルの中で 最も学習しやすく、デバッグしやすい のが HTTP です。ブラウザや curl、Postman で叩けるうえに応答が JSON で返ってくるので、初手でとりあえず動かしたい場合や、運用中にトラブルシュートするときの第一候補になります。

OSC のような UDP 制限もないため、大きいデータも理屈上は送れます (ただし実効スループットは BLE 帯域に律速されます)。


2. セットアップ

PC 側

追加インストール不要。Python 標準ライブラリで完結します。今回は画像送信も使う想定なので、 Pillow を追加します。

pip install Pillow

アプリ側

GUIDE01アプリ側の設定です。HTTP通信の場合も一度スマホのGUIDE01アプリを経由して実機に情報が転送されます。

  1. メニューから「(SDK)カスタム表示」→「プロトコル設定」
  2. ドロップダウンを HTTP に切替
  3. ポートが 8080 で待ち受けになっていることを確認
  4. [開始] ボタンを押下

3. エンドポイント一覧

メソッド パス 用途
GET /api/ping 接続確認
POST /api/scene/load シーンロード
POST /api/image/send 画像送信
POST /api/show 全表示
POST /api/hide 全非表示
POST /api/clear 全クリア

ping だけが GET、それ以外は POST です。

誤った呼び方 レスポンス
GET で /api/clear 等を呼ぶ 405 Method Not Allowed
POST で存在しないパスを呼ぶ 404 Not Found

POST 時の Content-Typeapplication/json を明示します。


4. サンプル: ping (接続確認)

最初の動作確認には curl が一番早いです。

curl http://192.168.0.65:8080/api/ping
# → {"status":"ok"}

Python だと標準ライブラリだけで書けます。

ping_http.py
import sys
import urllib.request


def main():
    if len(sys.argv) < 2:
        print("Usage: ping_http.py <APP_IP>")
        sys.exit(1)

    host = sys.argv[1]
    url = f"http://{host}:8080/api/ping"
    with urllib.request.urlopen(url, timeout=5) as r:
        print(f"{r.status} {r.read().decode()}")


if __name__ == "__main__":
    main()

実行:

python ping_http.py [IPアドレス]
# → 200 {"status":"ok"}

5. サンプル: テキスト表示 (scene/load)

scene.json で text のみのシーンを送ります。

text_http.py
import json
import sys
import urllib.request


def main():
    if len(sys.argv) < 3:
        print('Usage: text_http.py <APP_IP> "<text>"')
        sys.exit(1)

    host = sys.argv[1]
    text = sys.argv[2]
    url = f"http://{host}:8080/api/scene/load"

    scene = {
        "showStatusBar": False,
        "layers": [{
            "layerId": 0,
            "elements": [{
                "type": "text",
                "text": text,
                "x": 50, "y": 180,
                "fontSize": 48,
                "colorR": 255, "colorG": 255, "colorB": 0,
            }],
        }],
    }

    data = json.dumps(scene, ensure_ascii=False).encode("utf-8")
    req = urllib.request.Request(
        url, data=data,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=10) as r:
        print(f"{r.status} {r.read().decode()}")


if __name__ == "__main__":
    main()

curl で同じことをやる場合:

curl -X POST http://[IPアドレス]:8080/api/scene/load \
  -H "Content-Type: application/json" \
  -d '{"layers":[{"layerId":0,"elements":[{"type":"text","text":"Hello","x":220,"y":180,"fontSize":48,"colorR":255,"colorG":255,"colorB":255}]}]}'

応答例:

{"status":"ok","address":"/guide01/scene/load"}

返ってくる address フィールドに OSC 風のパスが入っているのが面白いところで、アプリ内部では HTTP / OSC / MQTT を OSC アドレス空間に正規化して処理していることが分かります。


6. サンプル: 画像送信 (image/send)

JPEG ファイルを Base64 化して送ります。OSC と違いサイズ制限がないので、quality=80 程度のままで OK です。

image_http.py
import base64
import io
import json
import sys
import urllib.request
from PIL import Image


def main():
    if len(sys.argv) < 3:
        print("Usage: image_http.py <APP_IP> <image_path>")
        sys.exit(1)

    host = sys.argv[1]
    image_path = sys.argv[2]
    url = f"http://{host}:8080/api/image/send"

    img = Image.open(image_path).convert("RGB")
    img = img.resize((624, 405), Image.LANCZOS)
    buf = io.BytesIO()
    img.save(buf, "JPEG", quality=80)
    jpeg = buf.getvalue()
    print(f"image: {len(jpeg)} bytes")

    payload = {
        "image": base64.b64encode(jpeg).decode("ascii"),
        "width": 624,
        "height": 405,
        "quality": 80,
        "keepAspect": True,
    }
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url, data=data,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=15) as r:
        print(f"{r.status} {r.read().decode()}")


if __name__ == "__main__":
    main()

実行:

python image_http.py [IPアドレス] sample.jpg
# image: 38214 bytes
# 200 {"status":"ok","address":"/guide01/image/send"}

ペイロードサイズ上限が事実上ないため、quality を下げる必要もありません。ただし実効スループットは BLE 帯域に左右されます。私の環境だと約 5 秒/枚でした。詰めすぎないように送信間隔を空けるのは OSC と同じです。


7. サンプル: クリア

clear_http.py
import sys
import urllib.request


def main():
    if len(sys.argv) < 2:
        print("Usage: clear_http.py <APP_IP>")
        sys.exit(1)

    url = f"http://{sys.argv[1]}:8080/api/clear"
    req = urllib.request.Request(
        url, data=b"{}",
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=5) as r:
        print(f"{r.status} {r.read().decode()}")


if __name__ == "__main__":
    main()

/api/clear は payload 不要 (空 JSON で OK) です。


8. 検証時に遭遇したこと

現象 原因と対処
Connection refused アプリの [開始] 状態を確認
405 Method Not Allowed POST/GET の取り違え。ping のみ GET、他は POST
404 Not Found パス typo (/api/clear のスラッシュなど)
画像送信後に応答が遅い BLE 帯域の物理制約。次の送信まで 5 秒程度空ける
scene/load を呼んだら直前の画像が消えた image/sendscene/load は排他関係。仕様どおり
連続送信で描画膠着 連続送信間隔が短すぎる。ログから処理時間を確認すること。画像の描画処理前にデータを何度も送すると、描画されなくなります。例えば5 秒以上の間隔を空ける、または image/send 連発を避ける

9. 公式手段にない機能

HTTP も他プロトコル同様、以下は実現できません。

  • 動的 GIF 送信: API なし → Vol.05 で扱う
  • タッチイベント受信: 公式手段なし → Vol.05 で扱う

これらは Vol.05 の BLE 直叩きルートで実現します。


10. まとめ

  • HTTP は REST / port 8080 / application/json POST
  • ping のみ GET、他は POST
  • 応答が JSON で返るので**デバッグしやすい
  • ペイロードサイズ上限なし。ただし実効スループットは BLE 帯域基準で
  • image/sendscene/load は排他、5 秒以上の送信間隔を意識

次回 (第 4 回) は MQTT 版で、publish/subscribe モデルと IoT 統合を扱います。


参考リンク

Discussion