黄金週間前半ですね。かたりぃなです。 世間ではAIのMCPが流行ってるようなので少し調べてみたところ、とても面白いことができるのではないかと思った次第です。
大昔にモーションを検出してそれにあわせてLEDの色を変更できないかと試行錯誤していました。 これは加速度センサを使ってその値でLEDのRGB色を変更しようという試みです。 https://catalina1344.hatenablog.jp/entry/2014/12/14/220610
この図でいう上半分をAIとMCPに置き換えてみようというのが今回の試みです。
というわけで、目標は
「必殺技や決めセリフに応じて、演出を変更するAI」
ということにします。
ちなみに音声入力をリアルタイムに処理する方法がすぐには準備できなかったので、ClaudeDesktopのチャットにキーボードで入力することで対処します。
構成図を書くのが面倒なので、文字で簡単に載せておきます。
PC側は
ClaudeDesktop -> MCP -> 自作MCPサーバ -> HTTPへ
ラズパイ側は
HTTPから -> Python(FastAPI) -> GPIO -> 魔法陣テープLED
です。
Raspberry Pi 5の初期設定
機材はラズパイのスターターキットです。これで完了です。
https://akizukidenshi.com/catalog/g/g129362
テープLEDとかは冒頭の過去ブログに書いてあるものをそのまま使います。
今からマネする人は2SK2231が廃盤かもしれないので、適当に代替品を見繕う必要があるかもしれません。
OSはスターターキットに含まれている書き込み済みmicroSDカードが手に入るのでそれを使います。
どうしても変更したければラズパイ公式からイメージライターをダウンロードしてきてmicroSDカードに書けばOKです。
小さいラズパイもついでに買ってきたのですが、そちらのOSはイメージライターで書いてセットアップしてふつうに動きました。
セットアップ
HDMIで画面出力ができるので、画面を繋いでキーボードとマウス、あと必要があればLANケーブルを接続して設定します
これだけでした。 一つ気を付けるとすれば、5GHzのwifiは繋がりません。(規格名忘れた) なので、パスワード通らないとかあったら2.4GHzのほうに接続するようにすればOKです。
あまり高度なことしないのでこれで充分でしょう。
Lチカ儀式
とりあえず、GPIOを操作できるマイコンとかワンボードコンピュータを手に入れたら儀式です。 初めて触る言語でhello worldするようなものです。
コードはこんな感じ。 pythonでいけちゃうのとても便利ですね。
import lgpio import time # ライブラリを初期化 h = lgpio.gpiochip_open(0) LED_PIN = 26 # LEDピンを出力に設定 lgpio.gpio_claim_output(h, LED_PIN) try: while True: # LEDをオンにする lgpio.gpio_write(h, LED_PIN, 1) print("LED ON") time.sleep(1) # LEDをオフにする lgpio.gpio_write(h, LED_PIN, 0) print("LED OFF") time.sleep(1) except KeyboardInterrupt: print("プログラムを終了します") finally: # リソースの解放 lgpio.gpiochip_close(h)
ちなみにピン番号はラズパイにsshしてpinoutコマンドを打てば教えてくれます。 ボードの向きと1番がどこかを教えてくれてるので間違えないように注意しましょう。 26なので端っこですね。GNDの隣。わかりやすい位置にしました。
,--------------------------------. | oooooooooooooooooooo J8 : +==== | 1ooooooooooooooooooo : |USB2 | Wi Pi Model 5B V1.0 fan +==== | Fi +---+ +---+ | | |RAM| |RP1| +==== ||p +---+ +---+ |USB3 ||c ------- +==== ||i SoC |c|c J14 | ( ------- J7|s|s 12 +====== | J2 bat uart 1|i|i oo | Net | pwr\..|hd|...|hd|o|1|0 +====== `-| |-1o|m0|---|m1|--------------' J8: 3V3 (1) (2) 5V GPIO2 (3) (4) 5V GPIO3 (5) (6) GND GPIO4 (7) (8) GPIO14 GND (9) (10) GPIO15 GPIO17 (11) (12) GPIO18 GPIO27 (13) (14) GND GPIO22 (15) (16) GPIO23 3V3 (17) (18) GPIO24 GPIO10 (19) (20) GND GPIO9 (21) (22) GPIO25 GPIO11 (23) (24) GPIO8 GND (25) (26) GPIO7 GPIO0 (27) (28) GPIO1 GPIO5 (29) (30) GND GPIO6 (31) (32) GPIO12 GPIO13 (33) (34) GND GPIO19 (35) (36) GPIO16 GPIO26 (37) (38) GPIO20 GND (39) (40) GPIO21
LEDは秋月とか千石で売ってる適当な安いLEDでいいです。高輝度は面倒なことになるので、普通の緑とか赤がおすすめ。
電子工作に慣れないうちはこの「普通の」というのがとても難しいんですよね。 厳密にいうとI/Oポートの電圧とか電流の上限値とLEDの順電圧と最大定格電流が云々とか面倒な話になるので。
いわゆるプログラミング初心者が環境構築で挫折するようなものです。
というわけで、LED単体はそこまで高いものでもないので、2~3種類買っておいて、うまくいかないときは差し替えるでいいと思ってます。 慣れてくると抵抗とかコンデンサたくさんの種類持つようになるのと同じ。
回路としては
GPIO26 -> ブレッドボード上へ -> LEDアノード -> LEDカソード -> 抵抗1kくらいあればいい -> ラズパイのGNDへ
です。 チカチカすれば儀式完了です。
テープLEDの制御
テープLEDの制御では、先ほどのように単純にGPIOポートから駆動できません。 電流が足りないのです。 ので、電子工作の時間です。
2SK2231というMOSFETトランジスタのをスイッチとして使います。
トランジスタとは?ソフト屋さんが趣味でやるだけなら「ソフトウェアで回路のON/OFFができるやつ」って理解でひとまず充分です。もし気を付けるとすれば、電流量とかですね。
流したい電流量、駆動電圧に応じた半導体素子を選んであげる必要があります。
でかいスイッチは大電流を流せますが、そのスイッチを動かすためには大きな電圧が必要です。 なので、ラズパイの3.3Vとか5VでONにできる半導体で、LEDの電流を流せるやつであれば何でもOKです。
回路としてはフルカラーテープLEDがアノードコモン(電源+側が一本にまとまっていて、マイナス側のRGBが個別のピンになっている)なので、例えばRのピンを制御するにはこうなります。
GPIO -> 2SK2231のゲート テープLEDのR -> 2SK2231のドレイン 2SK2231のソース -> GND
です。
図には示していませんが、2つ抵抗を入れるのを忘れずに。
- GPIOとゲートの間のゲート抵抗
- GPIO-GND間のプルダウン抵抗
入れなくても趣味レベルであれば動きはしますが、お行儀良くないです。
これと同じことをG,Bにも行います。
GNDをラズパイとも繋ぐのを忘れずに。
これでRGBそれぞれで点灯/消灯ができるようになりました。
LEDの輝度をON/OFFじゃなくて段階的に処理したい。
明るさを変更したいのでPWMで輝度変更できるようにします。 ArduinoではPWMポートたくさんあったのですが、ラズパイは2つしかないみたいです。 pythonライブラリの使い方読んでもよくわからなかったので、自力でPWMやります。 こうなりました。
# スタンドアロンで動作するLED制御プログラム import lgpio import time import threading # GPIOピン番号を指定 h = lgpio.gpiochip_open(0) LED_PIN = 26 R_PIN = 2 G_PIN = 3 B_PIN = 4 # GPIO設定 lgpio.gpio_claim_output(h, LED_PIN) lgpio.gpio_claim_output(h, R_PIN) lgpio.gpio_claim_output(h, G_PIN) lgpio.gpio_claim_output(h, B_PIN) class LEDController(threading.Thread): def __init__(self, pin, frequency=100, duty_cycle=50): super().__init__() self.pin = pin self.frequency = frequency self.duty_cycle = duty_cycle self.running = True self.lock = threading.Lock() # デューティサイクル変更用のロック def run(self): period = 1 / self.frequency while self.running: with self.lock: on_time = period * (self.duty_cycle / 100) off_time = period * (1 - self.duty_cycle / 100) # LEDをオン lgpio.gpio_write(h, self.pin, 1) time.sleep(on_time) # LEDをオフ lgpio.gpio_write(h, self.pin, 0) time.sleep(off_time) def set_duty_cycle(self, duty_cycle): """デューティサイクルを変更する""" with self.lock: self.duty_cycle = max(0, min(100, duty_cycle)) # 0~100の範囲に制限 def stop(self): self.running = False lgpio.gpio_write(h, self.pin, 0) # LEDを消灯 # LED制御スレッドを作成 led_thread = LEDController(LED_PIN, frequency=100, duty_cycle=10) led_thread.start() led_thread_r = LEDController(R_PIN, frequency=100, duty_cycle=10) led_thread_r.start() led_thread_g = LEDController(G_PIN, frequency=100, duty_cycle=10) led_thread_g.start() led_thread_b = LEDController(B_PIN, frequency=100, duty_cycle=10) led_thread_b.start() try: # メインスレッドでデューティサイクルを変更する例 while True: new_duty_cycle = int(input("R:新しいデューティサイクルを入力してください (0-100): ")) led_thread_r.set_duty_cycle(new_duty_cycle) print(f"R:デューティサイクルを {new_duty_cycle}% に変更しました") new_duty_cycle = int(input("G:新しいデューティサイクルを入力してください (0-100): ")) led_thread_g.set_duty_cycle(new_duty_cycle) print(f"G:デューティサイクルを {new_duty_cycle}% に変更しました") new_duty_cycle = int(input("B:新しいデューティサイクルを入力してください (0-100): ")) led_thread_b.set_duty_cycle(new_duty_cycle) print(f"B:デューティサイクルを {new_duty_cycle}% に変更しました") except KeyboardInterrupt: print("プログラムを終了します") finally: # LED制御スレッドを停止 led_thread.stop() led_thread.join() led_thread_r.stop() led_thread_r.join() led_thread_g.stop() led_thread_g.join() led_thread_b.stop() led_thread_b.join() lgpio.gpiochip_close(h)
デューティー比とか難しい用語使ってますが、ある時間(たとえば100)に対して、どれくらいの期間ON/OFFにするかって話です。
出力が1と0を繰り返せば50%とかそういう話です。
積分した値が明るさに相当するわけです。
ちなみに、無理やりやってるので多少チラつきます。昔の蛍光灯みたいに。 どうしても気になるならコンデンサでも突っ込めばマイルドになると思います。
HTTP APIの実装
ここまででLEDを制御できるようになりました。
とはいえ毎回ラズパイにSSHしてコマンドを実行してってのはあまりに手間です。
APIを公開しましょう。 FastAPIってのがあるらしいので、適当にサンプルコードまねして作ります。 LEDスレッドを立てるところは同じなので、差分だけ乗せます。
import uvicorn from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() import lgpio import time import threading #~~~ # スレッドは前述のスタンドアロンプログラムと同じ #~~~ class RGBValues(BaseModel): r: int # 0~100の範囲 g: int # 0~100の範囲 b: int # 0~100の範囲 @app.post("/set_rgb") def set_rgb(rgb: RGBValues): """RGB値を受け取り、LEDのデューティサイクルを設定する""" # 各値を0~100の範囲に制限 r_duty = max(0, min(100, rgb.r)) g_duty = max(0, min(100, rgb.g)) b_duty = max(0, min(100, rgb.b)) # デューティサイクルを設定 led_thread_r.set_duty_cycle(r_duty) led_thread_g.set_duty_cycle(g_duty) led_thread_b.set_duty_cycle(b_duty) return {"message": "RGB values updated", "r": r_duty, "g": g_duty, "b": b_duty} @app.get("/") def read_root(): return {"message": "Hello World"} if __name__ == "__main__": # 0.0.0.0で全てのインターフェースをリッスン uvicorn.run(app, host="0.0.0.0", port=8000)
簡単ですね。
とりあえず
uvicorn app.main:app --host 0.0.0.0 --reload
起動するみたいです。
--hostを指定しておかないと他PCから叩けないので注意です。
テストするには
curl -X POST -H "Content-Type: application/json" -d '{"r": 200, "g": 10, "b": 10}' http://raspberrypi:8000/set_rgb
です。
Claude MCPサーバの構築
ラズパイの先にぶら下がってるテープLEDもとい、儀式用の魔法陣をHTTPから制御できるようになりました。 魔力を持たない人々であっても、スマホのアプリ、webアプリ、前述のcurlコマンドで制御できます。
今回はAIさんに制御してもらいます。 AIさんが外界に影響を及ぼす方法としてMCPを使います。
ここでMCPは詳しくは説明しませんが、Claudeのサンプルコードをコピって動かせば何が起きるかわかると思います。
Claude DesktopでのMCPの設定は完了済みとします。 手元の環境でのclaude_desktop_config.jsonはこのようになっています。 大抵の環境ではパスとかを適切に設定すればうまくいくと思います。
{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "C:\\Users\\PC_User\\Desktop", "C:\\Users\\PC_User\\Downloads" ] }, "blender": { "command": "uvx", "args": [ "blender-mcp" ] }, "weather": { "command": "uv", "args": [ "--directory", "C:\\Users\\PC_User\\Desktop\\AIWork\\ClaudeMpc\\weather", "run", "weather.py" ] } } }
MCPのサンプルコードが動いたら、そこに以下のコードを追加してみます。
単に先ほどのapiを呼んでるだけです。 Claude Desktopを再起動して、ツールが増えていれば成功です。
@mcp.tool() async def set_rgb_color(r: int, g: int, b: int) -> str: """Set RGB color LED. Args: r: Red component (0-100) g: Green component (0-100) b: Blue component (0-100) """ url = "http://raspberrypi:8000/set_rgb" payload = {"r": r, "g": g, "b": b} headers = {"Content-Type": "application/json"} try: async with httpx.AsyncClient() as client: response = await client.post(url, json=payload, headers=headers) response.raise_for_status() return f"RGB color set successfully: R={r}, G={g}, B={b}" except httpx.RequestError as e: return f"Failed to set RGB color: {e}" except httpx.HTTPStatusError as e: return f"API returned an error: {e.response.status_code} - {e.response.text}"
これでClaudeのプロンプトを良い感じに使ってあげれば、魔法陣のコントールができます。
実演
事前のプロンプトで準備させてから、実際のセリフを入れることにします。
実演準備
プロンプト
コスプレ用の電飾アイテムを作っています。地面に置いた魔法陣を、テープLEDでライトアップできるシステムです。 コスプレイヤーさんの声に反応して色を変えられるように実装しました。 set_rgb_colorを使って色を変えられることができます。 これよりコスプレイヤーの音声入力がこのプロンプトに入力されます。 まずはいったん消灯しておいてもらえますか? 次のプロンプトから決めセリフになるので、それに合わせてライトアップしてください。 よろしくお願いします。
これで準備が整いました。 消灯されたので、あとはセリフを入力すればいい感じに推測してライトアップしてくれます。
たとえば
カイザーフェニックス
プロンプト
この世のすべてを焼き尽くす炎、カイザーフェニックス!!!!
Claude
了解しました!炎のテーマですね。カイザーフェニックスの力強い炎を表現するために、赤と黄色を基調とした鮮やかな光で魔法陣を輝かせましょう!
リクエスト
{
`b`: 0,
`g`: 50,
`r`: 100
}
レスポンス
RGB color set successfully: R=100, G=50, B=0
Claude
カイザーフェニックスの炎の力が魔法陣に宿りました!鮮やかな炎の色で魔法陣が明るく輝いています。この世のすべてを焼き尽くす炎の力を表現するために、情熱的な赤とオレンジの光を選びました。次の決めセリフをお待ちしています!

エターナル・フォース・ブリザード
プロンプト
全てを凍らす冷厳なる氷結。エターナルフォースブリザード
Claude
了解しました!氷結のテーマですね。エターナルフォースブリザードの厳かな冷気を表現するために、青と白を基調とした冷たい光で魔法陣を輝かせましょう!
リクエスト
{
`b`: 100,
`g`: 50,
`r`: 0
}
レスポンス
RGB color set successfully: R=0, G=50, B=100
Claude
エターナルフォースブリザードの力が魔法陣に宿りました!凛とした青の光で魔法陣が神秘的に輝いています。全てを凍らす冷厳なる氷結の力を表現するために、透き通るような青と氷のような光を選びました。次の決めセリフをお待ちしています!

素晴らしいですね。こういうアドリブ的なこともサラっとやってくれるのがさすがAIですね。
ライトアップの反映までタイムラグがどうしても発生してしまいますが、色々面白い使い方ができそうです。
あと、写真撮り忘れましたが、徐々に明るくとかいう指定も良い感じにやってくれます。
感想と今後の展望
Windows11でも音声入力頑張ればできるんですが、手間かかりすぎなのでもう少しうまくやりたいところですね。
手順はこんなでした。
- Windowsの設定でアクセシビリティ機能を有効化する
「Voice Access Wakeup」とボイス入力
- 音声入力が起動する
「show numbers」とボイス入力
- フォーカスできるウインドウに番号が表示される
- ClaudeDesktopのテキストエリアの番号をボイス入力
- チャット入力ウインドウにフォーカスする
- 音声入力でプロンプトを入力
- 「show numbers」とボイス入力
- フォーカスできるウインドウに番号が表示される
- ClaudeDesktopの送信ボタンの番号を入力
- Claudeの送信ボタンが押せる
ちょっとシーケンス多すぎです。萎えます。
たとえばコスプレイベント会場とかで「写真いいですか~?」ってなったときに、ポーズをとるわけです。 そこで、ポーズをとりつつ必殺技を叫んだらすぐにライトアップしてほしいんですよ。それなのに↑みたいなシーケンスやってたら萎えるじゃないですか?
で、「他のポーズもお願いします~!」ってなってまたシーケンス繰り返すのか!?って話です。
Android版のClaudeなら音声で全部操作できるので、Android版にMCPが追加されるのを期待したいところですね。
あとは、他のアクチュエータもAIで良い感じに制御できる気がします。スマートスピーカー的な使い方もできちゃうわけですね。色々と夢が広がります。
それでは今回はこれくらいで!