はじめに

「毎日ひとつだけ課題を出してくる、関西弁の師匠キャラ」を、卓上ロボット スタックチャン に宿らせました。PCアプリが今日の課題を決め、実機が声で言い渡し、達成報告はまたPCへ書き戻る——そういう習慣コーチです。

まずは、動いているところをご覧ください。

着想のもとは、水野敬也さんの自己啓発書『夢をかなえるゾウ』です。関西弁の神様が主人公に毎日ひとつずつ小さな課題を出し、こなすうちに習慣が変わっていく——あの「毎日1個だけ、言い訳させずにやらせる」という枠組みを、AIロボットの習慣コーチとして自分の生活に持ち込めないか、というのが出発点でした。

『夢をかなえるゾウ1』(水野敬也)の表紙

『夢をかなえるゾウ』(水野敬也)を Amazon で見る

※本記事にはアフィリエイトリンク(Amazon アソシエイト)が含まれます。

この記事でわかるのは、次の3つです。同じような会話ロボットを作る人が、そのまま使える形でまとめました。

  • 設計:会話と記憶をどう分担させ、課題ロジックをどう組むか
  • 土台選び:スタックチャンのどのOSSに乗れば「人格と課題ロジックだけ」に集中できるか
  • 技術的な落とし穴:Gemini Live を実機で動かすとき、内部RAMの奪い合いでどこが詰まるか

ソースコードは GitHub で公開しています(MIT License・app/(PCアプリ)/ firmware/(実機ファーム)/ docs/ の3構成)。README のとおり PCアプリだけで全機能が完結するので、実機がなくても手元で試せます。

ramtuc/ganesha-stackchan(GitHub)


🗺️ 全体像:PCが頭脳、実機が顔と声

いちばん大事な設計判断が、役割分担です。記憶と判断はPC、会話の口と耳はGoogle直結。音声はPCを経由しません。

習慣コーチのPCアプリ。今日の課題カードと関西弁の会話画面

PCアプリ側の画面。左に「今日の課題」カード(繰り越しバッジ・所要時間の目安つき)、右が師匠キャラとの会話。この課題を実機が声で言い渡す

なぜこの分担にしたのか。スタックチャンの実機(CoreS3)は前日の記憶を持てません。「いつも同じ課題ばかり出す」問題を避けるには、全履歴を踏まえて課題を決める頭脳がどこかに要る。そこでPCアプリを頭脳にし、実機は起動時にその日の課題を受け取って喋る、という形にしました。

flowchart LR subgraph PCBOX["PC アプリ = 頭脳と記憶"] Q["きょうの課題を決める
生成・繰り越し・履歴・連続記録"] end subgraph DEVBOX["スタックチャン実機 = 顔と声"] P["人格プロンプト(NVS)
表情・首振り・状態色"] end GEM["Gemini Live
音声認識・応答・音声合成"] PCBOX -->|"起動時に今日の課題と指示を渡す"| DEVBOX DEVBOX -->|"会話ログと達成報告を書き戻す"| PCBOX DEVBOX <-->|"音声を双方向ストリーム(PCを経由しない)"| GEM
  • PC(Python + FastAPI + 素のHTML/JS):課題の生成と繰り越し、達成記録、連続達成、ヒートマップ、口調切替
  • 実機(M5公式スタックチャンキット・CoreS3ベース+ESP-IDF 5.5):Gemini Liveで音声会話、表情・首振り、状態の色表示、なでなで操作、寝る待機モード
  • 連携:実機は起動時にPCから「今日の課題・履歴・指示」を受け取って話し、会話の書き起こしと達成報告をPCへ返す。PC不在なら単体動作に自動フォールバック

この「音声はGoogle直結」という一点が、後半で効いてきます。低遅延の自然会話を実現する代わりに、実機の内部RAMをTLSとWebSocketに食われる——そこがこの構成でいちばん詰まる場所でした。


🧭 習慣コーチの設計:毎日1つ・言い切り・繰り越し

作るものの骨格は、頭脳側(PCアプリ)から固めました。「PCチャット → 習慣ギミック → 実機 → 音声」の順に積み、まずテキストで課題を出すMVPから着手しています。

習慣コーチとして効いた設計判断は、次の3つでした。ここは会話AIロボットを作る人に、いちばん移せる部分だと思います。

  • 課題はハイブリッド方式:定番リストをそのまま出すのではなく、その日の状況に合わせてLLMがひとつだけアレンジして出す。マンネリと的外れの両方を避けられる
  • 達成はチャットで報告、未達は翌日へ繰り越し:できなかった課題は翌日に持ち越し、ハードルを下げ直す。「連続達成」を可視化して続ける動機にする
  • 課題は本人に選ばせず、言い切る:選択肢を並べて聞くのではなく、その場で決めて言い渡すキャラにする。口調は切替式(デフォルトは関西弁風)で、叱咤も嫌味も最後は前向きに着地させる

この「言い切る」設計は、後述の人格プロンプトにも直結します。選択肢を聞き返すキャラだと、毎朝の起動が相談になってしまい、習慣化の背中を押せないからです。


🧩 土台に使ったOSS:stackchan-idf で「作らない」を最大化する

この記事でいちばん読者に移せるのは、たぶんここです。スタックチャン用のOSSファームには、すでに何が入っていて、自分は何だけ書けばいいのか

土台に選んだのは、コミュニティ製の ESP-IDF ネイティブファーム ciniml/stackchan-idf です。会話ロボットを一から作ると重い部分が、最初からそろっています。

stackchan-idf が最初から持つもの 内容
ベース ESP-IDF 5.5 / C++20 ネイティブ
会話 Gemini Live クライアント(OpenAI Realtime 等にも対応)
表示 Avatar の表情描画・スペクトル解析によるリップシンク
駆動 SCS0009 シリアルサーボ制御(台形速度プロファイル・動作時のみ電源ON)
更新・設定 OTA(ロールバック付き)・BLE / Wi-Fi 設定・SoftAP プロビジョニング
音声合成 オンデバイス日本語TTS(hts_engine。ネット不要)

そして相性が良かったのが、手持ちのキット M5STACK-K151-R(CoreS3 ベース)です。stackchan-idf の cores3 ボードプロファイルが、このキットの構成を第一級サポートしています。具体的には——PY32 IOエキスパンダ/SCS0009 サーボ2軸/頭タッチセンサー Si12T/INA226 電池計。つまり キットを買えば、この構成がそのまま動く設計です。筐体・サーボ・基板の調達や組み立てに時間を取られず、本題に集中できました。

音声も実装済みでした。Gemini の Live API は、標準の API キーをクエリに付けてデバイスから直結できます(?key=<APIキー> の形)。中継サーバを立てる必要がありません。「実機から直接クラウドの音声モデルに繋ぐ」という一番の難所が、土台の時点で済んでいたわけです。

ライセンスの歩き方

公開・配布まで見据えるなら、ここは正確に踏んでおく価値があります(出典は同梱の LICENSE と THIRD_PARTY_NOTICES の記載)。

論点 要点
土台(stackchan-idf) Boost Software License 1.0。フォーク・改造・公開・商用までほぼ自由(コピーレフトなし)。ソース配布時に著作権表示とライセンス全文を同梱するのが唯一の実質的な義務
地雷 コミュニティ内に GPL-3.0 の表情アセットが混在する。持ち込むと成果物全体が GPL 化するため、使わない
無ライセンス部分 公式ファクトリファームの一部(app/ remote/)は LICENSE 表記がなく、既定では全権利留保。コードを持ち込まない
音声・TTS hts_engine は Modified BSD、同梱ボイスは CC BY。帰属表示を維持する
✅ 結論:自分が書くのは人格と課題ロジックだけ

Avatar もサーボも Gemini Live も OTA も、「作れば重いのに差別化にはならない部分」は ciniml/stackchan-idf に全部そろっている。新しく書くべきは『人格プロンプト』と『毎日1つの課題ロジック』だけだった。同じ道を選ぶなら、ファームは ciniml/stackchan-idf、ハードは公式キット(CoreS3ベース)が最短。

なお、このファームには内蔵の日本語TTS(相づち機能)も入っていますが、今回の師匠キャラには合わないため無効化しました。将来オフラインで喋らせたくなったときに戻せる、という位置づけです。


🛠️ ビルドと書き込み(このプロジェクト固有のメモ)

ビルドは ESP-IDF 5.5 系、ボードプロファイルは cores3。成果物はこの規模でした。

  • ファームバイナリ 3,579,120 B(アプリ領域の 85% 使用・残り15%)
  • フラッシュ書き込み 約20秒(1406 kbit/s)
⚠️ 書き込み後に実機が固まったら物理電源断

書き込み直後、実機が USB ダウンロードモードで固まり、ソフトリセット(idf.py の末尾リセットや monitor の DTR/RTS)では復帰しないことがあります。その時は物理的に電源を入れ直すのが確実です。

Windows の EIM(ESP-IDF Installation Manager)環境で踏む罠と回避手順——eim run がPATHを渡さない・-D を壊す・IDF_TOOLS_PATH 表示のズレ・BOM 問題など——は、このプロジェクト固有の話ではなく、当サイトの別記事に詳しくまとめてあります。同じ環境の方はそちらをどうぞ。


🧠 声が返らない原因は、内部RAMの奪い合いだった

ここがこの構成で最大の技術的な山場です。Gemini Live 利用者・ESP32 で TLS を張る人なら、誰でも踏み得る話なので、切り分けの過程ごと残します。

実機へ書き込むと、顔の表示・サーボ2軸・頭のタッチ認識までひととおり動きました。問題が出たのは Gemini Live をつないでから。音声認識は動く(こちらの発話が書き起こされる)のに、返事の声だけが出ない

最初はモデルを疑いましたが、これは誤診でした。動く回と動かない回でログを4本突き合わせると、成否は設定用HTTPサーバ(httpd)が起動できたかどうかと完全に相関していたのです。

# httpd が起動に失敗した回 → Gemini Live が動く
E (xxxx) diag: ALLOC_FAIL size=12288 caller=heap_caps_malloc | INT free=16619 largest=8704
E (xxxx) cfg-wifi: httpd_start: ESP_ERR_HTTPD_TASK
I (xxxx) conv-task: session ready

# httpd が起動に成功した回 → Gemini Live が死ぬ
I (xxxx) cfg-wifi: HTTP server listening on :80
E (xxxx) diag: ALLOC_FAIL size=1600 caller=heap_caps_aligned_alloc | INT free=6963 largest=5120

4本のログで結果がきれいに割れる条件は、モデルの違いではなく httpd が起動できたかの一点でした。HTTP server listening on :80 が出た回は必ず会話側が ALLOC_FAIL で落ち、httpd_start: ESP_ERR_HTTPD_TASK の回は必ず会話が成立していた。起動直後の INT freelargest(最大連続ブロック)を並べると、httpd が 12KB を取った回は連続ブロックが一桁KBまで痩せていました。

⚠️ 犯人は内部RAMの奪い合い

設定用httpd(12KBの連続した内部RAMを要求)と、Gemini LiveのTLS/WebSocketが、同じ内部RAMを奪い合っている。起動時のレースで勝った方だけが動く「起動ガチャ」状態だった。httpdが12KBを取れた回はGemini側が死に、取れなかった回はGemini側が動く。負けた回の内部RAM最小空きは、なんと 23バイトまで落ちていた。

内部RAMの実測推移を並べると、どこが逼迫していたかが一目で分かります。

状態 内部RAM 空き 最小 最大連続ブロック
Wi-Fi 停止 64.2 KB 55.5 KB 29.7 KB
Wi-Fi ドライバ稼働・未接続 約 54.7 KB 約 54 KB 29.7 KB
会話中・httpd あり(破綻 8.2 KB 23 B 4.6 KB
会話中・httpd 抑止(対策後 33.2 KB 24.3 KB 19.5 KB
最終形(全機能を載せた状態) 32.8〜36.7 KB 31.3 KB 21.5〜23.6 KB

PSRAM は常時4MB以上空いています。逼迫しているのは内部RAMだけ——TLSのハンドシェイクやDMAが要求する「連続した内部RAM」が枯れていたのです。

対策はシンプルで、効き目は最大でした。会話モードのときは設定用httpdを起動しない(設定はBLE経由でできるので実用上の損失は小さい)。これだけで起動時のレースそのものが消え、最大連続ブロックが 8.7KB → 19.5KB(+124%) に増えます。修正を当てると、実機は初めて声で返事をしました。

📌 学び:組み込みAIは「連続した内部RAM」が主戦場

ESP32-S3 では、TLSレコードやAES DMAが要求するのは「合計の空き」ではなく「連続したブロック」。PSRAMがいくら余っていても、内部RAMの連続領域が断片化すると通信が落ちる。切り分けでは空きヒープの総量だけでなく largest(最大連続ブロック)を必ず見ること、そして常駐する大物(設定用httpd)を最逼迫の瞬間に走らせないこと。この2点が効いた。


🔊 Gemini Live 直結とモデル世代交代への防御

音声は実機からGoogleのLive APIへ、WebSocketで双方向にストリームします(入力16kHz / 出力24kHz)。前述のとおりAPIキーをクエリで付けて直結でき、中継サーバは要りません。

wss://generativelanguage.googleapis.com/ws/.../BidiGenerateContent?key=<APIキー>

この構成で外せないのが、モデル世代交代への防御です。制作の過程で、音声(Live)側・テキスト側の両方でモデルの提供終了に遭遇しました。プレビュー版のモデルは提供期間が短く、ある日突然使えなくなり得ます。

対策は「モデル名をコードから分離する」ことに尽きます。モデル名を設定値(.env)に追い出し、コード側は3段(.env > models.json > コード内定数)でフォールバック。エラー時のUI文言にも「モデルが古い可能性。設定で変更可」と案内を出しておく。こうしておけば、次の世代交代は設定1つで対応できます。個人開発で長く使うロボットほど、この備えが効きます。

TLS付きのHTTP/WebSocketをESP32から安全に扱う設計(証明書バンドル・cJSONでのストリームパース・SNTPでの時刻同期)は、別記事で単体の題材として掘り下げています。


🧬 人格プロンプト設計:NVSの2048バイトに収める

実機の人格は、NVS(不揮発メモリ)の sys_prompt に格納します。上限は 2048バイト(日本語で約683文字)。この予算の中に、音声向けの設計を全部詰め込みます。

  • 1〜3文で短く話す・記号を使わない(音声で読み上げられるため)
  • 表情や首振りを使え・棒立ちで喋るな
  • 課題は本人に選ばせず、その場で決めて言い切る
  • 叱るときは人格ではなく行動を責める・罵倒は禁止・最後は前向きに着地する

これらを詰めると、最終的に 2,022 / 2,048 バイト(残り26) まで攻めることになりました。語尾のバリエーションや重複表現を削ってバイトを捻出する、という圧縮が要ります。ここで効いたのが、固定と可変を分ける設計です。

💡 固定は NVS・可変は RAM で連結

NVSの2048バイトに入れるのは変わらない人格だけ。今日の課題・履歴・状況別の指示といった可変情報は、セッション開始時にPCから受け取った短文(500バイト以内)を、人格の後ろにRAM上で連結して注入する。RAM上の連結はNVSの上限を受けないので、2022 + 236 = 2259バイト でも問題なく動く。容量が溢れそうなときは課題文の方を縮め、指示行(叱る・褒める・繰り越しの方針)は絶対に削らない、という動的配分にした。


🔗 PC⇔実機連携:課題と記憶をどう渡すか

PCと実機の連携は、3つのHTTPエンドポイントで成り立っています。

エンドポイント 用途 いつ呼ぶか
GET /context 今日の課題を確定し、人格へ注ぐ短文を受け取る セッション開始時に1回
POST /report 達成/未達成を記録する モデルが達成を検知して自発的に呼ぶ
POST /transcript 会話の書き起こしをまとめ送りする アイドル時・待機移行時
課題のアレンジ前後を表示するPCアプリの画面

課題カードの「出どころ」を開いたところ。定番リストの原題を、その人の状況に合わせて言い換えた「アレンジ前後」を透明化して見せている

設計思想として貫いたのは、フォールバックの徹底です。PCが落ちていても実機は単体で動く(素のTCP接続で2秒だけ到達確認し、居なければ諦めて人格だけで会話を始める)。逆に、実機から報告が来ない日はPC側が自動で「未達」にするので、システムは破綻しません。

「いつも同じ課題」問題への対策も、この連携があってこそです。PCが全履歴を「これは出すな」リストとしてLLMに渡し、新しい課題を毎回生成する。被ったら切り口を変えて再生成し、それも失敗したら定番マスタ(28件)へ多段フォールバックします。生成した課題も履歴に入るので、使うほど被らなくなります。

仕上げに、音声の記憶が繋がりました。実機の会話書き起こしを安全なタイミングでPCへまとめ送りし、要約して「前に話したこと」として引き継ぐ。音声で「ダンボールを片付けた」と報告したら、実機がツール経由でPCに達成を記録し、連続記録が点灯します。HTTPの発行は会話中の逼迫窓(前節の内部RAMが最も苦しい瞬間)を避け、アイドル時や待機移行時にだけ行うのがポイントです。


🎨 状態の見える化と操作

半二重(スピーカー再生中はマイクが物理的にOFF)という仕様のせいで、「こちらの声を聞いてくれない」時間帯があります。これを隠さず、顔の背景色とLEDで状態を可視化しました。「いま話しかけていいか」が一目で分かります。

状態 顔の背景色 LED 意味
Listening 暗い緑 緑(点灯) マイクON。話しかけてよい
Thinking 暗い青 青(呼吸) 応答待ち
Speaking 黒(通常) 白(点灯) 応答再生中。半二重なのでマイクはOFF
Reconnecting 暗い赤 赤(呼吸) 通信エラーからの再接続中
Standby 暗い紫 消灯 待機モード。マイクOFF・寝顔
緑の背景に汗マークつきの顔を表示したスタックチャン実機。左上にバッテリー97%の表示

Listening 状態の実機。顔の背景が緑になっているときはマイクONで、こちらから話しかけてよい合図。表情は目のわきに汗マークが出た状態で、左上にはバッテリー残量(97%)も常時表示される

顔の色は離れた席からでも分かるように、画面いっぱいの背景色で塗っています。上の写真のように緑が見えていれば「聞いている」、黒(通常)なら「喋っている」——話しかけるタイミングを迷わなくなりました。

操作は頭のタッチセンサー(前・中・後ろの3ゾーン)に割り当てました。誤発動しにくさを基準に選んでいます。

  • 通常のなでなで(前→後ろ):従来どおりの「なでなで♡」
  • 逆なで(後ろ→前):強制的にListening(聞く態勢)へ復帰。Speaking/Thinkingにスタックしたときの手動脱出口
  • 手のひらで2秒ホールド:待機モードのON/OFF。なでなでは各ゾーンを素通りするので、2秒の同時接触は誤発動しない

待機(寝る)ときは、まず寝姿勢に動いてからサーボの電源ごと落とします(完全脱力・無音)。起きるときは電源ON→姿勢復帰→琥珀色の反転演出+首をひと振り。「寝姿勢を取ってから電源を切る/電源を入れてから動かす」という順序を守るのが、静かで自然な待機の肝でした。

達成後のPCアプリ。進捗リングとヒートマップ

課題を達成として記録した後のPCアプリ。進捗リングと「積み上げ」のヒートマップ(GitHub風・70日)で継続を可視化する


📊 実測データで見る全体像

構成の手応えを、実測値でまとめておきます。

項目 実測値
ファームバイナリ 3,579,120 B(アプリ領域 85% 使用・残り15%)
フラッシュ書き込み 約20秒(1406 kbit/s)
人格プロンプト 2,022 / 2,048 バイト(残り26)
実機へ注入するコンテキスト 236〜499 / 500 バイト
コンテキスト取得 267〜323 ms(PC不在時は2秒で切り上げ)
セッション確立 電源ONから約8秒
会話実績 6ターン連続・切断0・再生レート 23,055〜23,761 sps(公称 24,000)
PCアプリ自己テスト 30 → 54 → 79 チェック(全パス)
課題マスタ 28件 + 生成課題(全履歴を除外して生成)
狭幅レイアウトのPCアプリ全体像

狭い画面幅でのPCアプリ全体。課題カード・積み上げ・これまでの記録・設定(口調切替)が縦に積まれる


🧯 トラブルシューティング:会話がうまくいかないとき

同じ構成でつまずいたときの切り分けを、フローチャートにしておきます。

flowchart TD S["会話がうまくいかない"] --> Q1{"声が返らない?"} Q1 -->|"はい"| M1["内部RAMの空きと最大連続ブロックを確認
ALLOC_FAIL が出ていないか"] M1 --> M2["会話モードでは設定用httpdを止める
連続ブロックを確保する"] Q1 -->|"いいえ"| Q2{"途中で切れる?"} Q2 -->|"モデル設定拒否のcloseが出る"| C1["提供終了の可能性
現行世代のLiveモデルへ移行"] Q2 -->|"起動直後にTLSが失敗"| C2["SNTPで時刻同期を待ってから接続"]

ポイントは、症状からいきなり原因を決めつけないことです。今回の「声が返らない」も、最初の見立て(モデルが悪い)は外れていました。ログの数値(内部RAMの largest、確保失敗の有無)を突き合わせて初めて、真犯人(起動時のRAM争奪)にたどり着けます。


✅ まとめ

振り返って、この構成で効いたのは次の3点でした。

  1. 土台のOSSに正しく乗る:Avatar・サーボ・Gemini Live・OTAという重い部分を stackchan-idf に任せ、自分は人格と課題ロジックに集中できた。公式キット(CoreS3)なら配線も込みでそのまま動く。
  2. 内部RAMの「連続ブロック」を主戦場と捉える:PSRAMがいくら余っていても、内部RAMの連続領域が枯れれば通信は落ちる。最逼迫の瞬間に常駐httpdを走らせない、という1箇所の判断が最大の効き目だった。
  3. モデル世代交代を前提に設計する:モデル名をコードから分離しておけば、提供終了に設定1つで追随できる。

「毎日ひとつだけ課題を出す習慣コーチ」というアイデア自体は素朴ですが、それを卓上ロボットの声と表情に宿すと、続ける動機が驚くほど変わります。次は男声のオンデバイスTTSを載せて、ネット不在でも喋れる状態を作る予定です。


❓ よくある質問(FAQ)

Q. スタックチャンとは何ですか?

A. ししかわ氏が開発・公開している卓上コミュニケーションロボットの愛称です。今回はM5Stack公式のキット(CoreS3ベース・サーボ2軸・マイク/スピーカー内蔵)を使い、コミュニティ製のESP-IDFファーム ciniml/stackchan-idf を土台にしました。

Q. 自分で作るなら、何から用意すればいいですか?

A. ハードは公式キット(CoreS3ベース。今回はK151-R)、ファームは ciniml/stackchan-idf が最短です。Avatar・サーボ・Gemini Live・OTAが実装済みなので、あなたが書くのは人格プロンプトと課題ロジックだけで済みます。本プロジェクトのソース(ramtuc/ganesha-stackchan・MIT License)も公開しており、PCアプリは実機を用意する前に単体で試せます。

Q. 音声会話はPCを経由しているのですか?

A. していません。音声は実機からGoogleのGemini Live APIへWebSocketで直結し、双方向にストリームします。PCが持つのは「今日の課題を決める頭脳」の役割で、音声そのものは通りません。これにより遅延を最小にしています。

Q. なぜ内部RAMが23バイトまで減ったのですか?

A. 設定用のHTTPサーバ(12KBの連続した内部RAMを要求)と、Gemini LiveのTLS/WebSocketが、同じ内部RAMを起動時に奪い合っていたためです。会話モードではHTTPサーバを起動しないようにして、最大連続ブロックを8.7KBから19.5KBへ回復させました。

Q. Gemini のモデルが提供終了になったらどうしますか?

A. モデル名をコード内に直書きせず設定値(.env)に分離してあるので、設定を1つ書き換えるだけで新しいモデルへ追随できます。実際、この制作では音声側・テキスト側の2回のモデル提供終了に対応しました。

Q. ライセンス面で気をつけたことは?

A. 土台の stackchan-idf は Boost Software License 1.0 でフォーク・改造・公開が自由ですが、コミュニティ内に混在するGPL-3.0の表情アセットは持ち込むと全体に伝播するため、一切使用していません。内蔵TTSエンジンや音声(Modified BSD / CC BY)の帰属表示は維持しています。


🙏 クレジット

本プロジェクトのソースコードは MIT License で公開しています。 https://github.com/ramtuc/ganesha-stackchan

このロボットは、ししかわ氏が開発・公開しているコミュニケーションロボット「スタックチャン」をベースにしています。 https://github.com/stack-chan/stack-chan

ファームウェアは ciniml 氏の stackchan-idf(Boost Software License 1.0)を土台にしています。 https://github.com/ciniml/stackchan-idf

習慣コーチというコンセプトは、水野敬也さんの『夢をかなえるゾウ』に着想を得ています。

▶ 『夢をかなえるゾウ』(水野敬也)を Amazon で見る


関連記事