戻る

かわいいデスクトップロボット — ESP-VoCatで声に耳を傾ける顔と、ブラウザで動くエミュレータ

ほとんどのデスクガジェットは情報を表示するだけです。私が欲しかったのは、人に反応するものです。話しかけるとこちらを見て、答えてくれ、誰もいないときには何か面白いことをするロボットです。Espressif ESP-VoCat v1.2にはそのためのハードウェアが備わっています。ESP32-S3 SoC、360×360の丸いタッチスクリーン、2つのマイク、スピーカー、バッテリーを猫耳付きのケースに収めています。

VoCat Eyesは私がそのために書いた顔であり、Cute Desktop Robotというシリーズの最初のロボットです。主な特徴は次のとおりです。

ファームウェア、エミュレータ、ツールはcute-desktop-robotリポジトリでオープンソース公開しています。

エミュレータの3Dビューに表示されたVoCat Eyesロボット — 青い猫耳付きESP-VoCat筐体と、その丸いスクリーンに映るロボットの顔

上の画像はエミュレータの3Dビューです。Espressifが公開した筐体モデルの中に、エミュレートされたロボットのライブスクリーンが表示されています。

そしてこちらが、同じファームウェアを実行している実際のデスク上のロボットです。

実機上のロボット — YouTubeで見る。

ブラウザでロボットを試す

下のエミュレータは、WebAssemblyにコンパイルされたロボットのC++コードを実行します。ESP32-S3上で動くのと同じ顔エンジン、ステートマシン、画面が動作します。ハードウェア層だけがシミュレートされた部品に置き換わっています。アカウントもインストールも不要です。エミュレータにはネットワークがないため、音声会話だけは実機が必要です。

フレームが小さすぎる場合は、エミュレータを全画面で開いてください。

ウィンドウは3つの部分に分かれています。左側は3Dのロボット、その下がステートマシン、右側が感情グラフとセンサーのパネルです。試すには次のようにします。

  1. ロボットの画面をタップする。 3Dロボットの丸いスクリーンを一度クリックします。ロボットが次のアニメーションを再生します。
  2. 設定を開く。 時計とバッテリーが表示されるまで画面をクリックし続け、次に時計を一度クリックします。設定メニューが開きます。行の左半分をクリックすると値が下がり、右半分をクリックすると上がります。メニューを閉じるにはクリックし続けます。
  3. 片側から話しかける。 右側のパネルのSENSORSの下で、fromの隣のスライダーをある角度までドラッグし、**talk [V]**をクリックします。床の紫の点がシミュレートされた声の発生源を示し、ロボットの目がそちらを向きます。
  4. ステートマシンを観察する。 ロボットが行動を変えるたびに、ステートマシン内の対応するボックスが点灯し、下部のバーが次にそれを変えうるタイマーを示します。
  5. ロボットを回転させる。 画面の外側のどこかをドラッグして3Dビューを回転させ、マウスホイールでズームします。Mを押すと画面のフラットビューに切り替わります。

エミュレータを一度クリックすればキーボードも使えます。1から9で表情を表示し、Yでうなずき、Uで首を振り、Hで吹き出しを表示します。タップと設定は実機でも同じように動作します。

次のスクリーンショットは、デスクトップPC上でのエミュレータウィンドウ全体の様子です。

デスクトップ版エミュレータ全体 — 3Dのロボット、感情グラフ、スライダー付きのセンサー、タイマー付きのステートマシン

ハードウェア

ESP-VoCatはケース、バッテリー、スクリーンが組み付けられた状態で届きます。ESP-VoCatユーザーガイドにボードの詳細が記載されています。1つのESP32-S3モジュールがすべての処理を担います。16 MBのフラッシュメモリと16 MBのPSRAM(外部RAM)を搭載しています。PSRAMがあるからこそ360×360の顔が実現できます。16ビットカラーの1フレームは253 KBで、SoCの内部RAMには収まりません。

次の図は、ファームウェアが使用する部品と、それぞれをESP32-S3に接続するバスを示しています。

ブロック図 — ESP32-S3はST77916丸型LCDをQSPI経由で駆動し、CST816SタッチとBQ27220バッテリーゲージをI2C経由で読み取り、マイクはES7210経由で取得し、ES8311とそのアンプを通じてI2S TDM経由で再生し、Wi-Fi経由でGemini Liveと通信する

2つの部品には追加の作業が必要でした。

ロボットが顔を描く仕組み

ロボットは33 msごと、つまり1秒間に約30回、顔を再描画します。次の図は、そのうちの1フレームをセンサーからスクリーンまで追跡したものです。

フレームパイプライン — SensorsがSensorStateを生成してBrainに渡し、BrainがFaceEngineの表情を選び、FaceEngineがCanvasに図形を描画し、Canvasが変更された24ピクセルタイルを特定し、VocatLcdがそれだけをQSPI経由で送信する。Listenerタスクは音声、バング、ウェイクワードをSensorsに供給し、会話はset_faceとplay_actをBrainに供給する

図の各ブロックはファームウェアの1モジュールです。

すべての表情とアニメーション

エミュレータには facetool というコマンドラインツールが含まれており、ウィンドウを開かずにロボットの画面をレンダリングできます。以下の動画はこのツールから生成されたものです。各動画では、あるグループのすべての顔が同時に再生されます。まず各顔への変化、次にその顔自体が表示されます。

表情

穏やか・考え中(16種類)— 通常、好奇心、集中、懐疑、退屈、困惑、傾聴など。

幸せ(15種類)— 小さな微笑みから大笑い、ラブラブ、目が星になる表情まで。

悲しい(8種類)— 心配、懇願、泣く、孤独、罪悪感など。

怒り(6種類)— イライラから激怒、そして嫌悪まで。

驚き・警戒(7種類)— 驚き、怖がる、目を回す、気絶、催眠など。

眠い(4種類)— 眠い、あくび、眠っている、疲れた。

アニメーション

アニメーションはコード内では「アクト」と呼ばれ、短いシーンです。多くは目の横にオブジェクトが描かれます。雨雲、コーヒーカップ、レーダースクリーンなどです。Brainはロボットがアイドル状態のときにこれらを再生し、画面をタップすると次のアクトが再生されます。

ボディランゲージ(9種類)— うなずき、首振り、目を回す、くしゃみ。

画面エフェクト(5種類)— グリッチ、再起動、スキャナーライン、落下するコード。

オブジェクト付き(18種類)— ハート、雨、花火、泡、ボール、ダンス。

日常のもの(9種類)— 本、ヘッドフォン、コーヒー、ロケット、雪。

ゲーム・丸型画面エフェクト(6種類)— Pac-Man、Pong、虹、レーダー、水族館。

同じグリッドのアニメーションGIFはリポジトリのREADMEにあります。

ロボットが話す仕組み

会話にはGemini Live APIを使用しており、1つのWebSocket接続で音声ストリームを受信し、音声ストリームで応答します。執筆時点では、ロボットはgemini-2.5-flash-native-audioモデルを使用しています。以下の図は音声の経路を示しています。

音声パイプライン — ES7210マイクが16 kHzでListenerタスクに音声を送り、GeminiLiveタスクが100 msのチャンクをGemini Live APIに送信して24 kHzの音声を受信し、Playerとスピーカー用にリサンプリングします。ツール呼び出しはTalkに送られ、表情を変更します。Playerは再生中にListenerをミュートします

ロボットは1つのマイクの音を16 kHzのPCM音声として、100 msのチャンクで送信します。Geminiは24 kHzの音声で応答し、ロボットはそれを16 kHzに変換して再生します。

会話は半二重方式です。ロボットは聞くか話すかのどちらかで、同時に両方は行いません。スピーカーはマイクから数センチしか離れていないため、ロボットは応答の再生中にマイクストリームをミュートします。そうしないとGeminiが自分の声を聞いてしまいます。相手が話し終えたかどうかはGemini側が判断します。

Geminiはまた、会話中に呼び出せる4つのツール(関数)を受け取ります。set_face、play_act、look、show_textです。会話が悲しい方向に進むと、Geminiはset_face("sad")を呼び出せます。ロボットに同意を求めると、play_act("yes")を呼び出してロボットがうなずきます。各セッションはローカルの時刻と日付を含む短いテキストメッセージで始まるため、Geminiはそれらについての質問に答えられます。

開発時間の大部分は3つの問題に費やされました。

  1. セキュア接続。 ロボットはTLS経由でGeminiに接続します。ArduinoWebsocketsライブラリのsetInsecure()関数はESP32-S3では効果がないため、TLSハンドシェイクが失敗しました。ファームウェアは現在、Googleの2つのルート証明書(GTS Root R1とR4、2036年まで有効)を組み込んでいます。また、証明書の検証には現在の日付が必要なため、NTPによる時計の同期を待機します。
  2. 内部RAM。 「Hi ESP」ウェイクワードはEspressifの音声認識ライブラリであるESP-SR上で動作し、約100 KBの内部RAMを占有します。TLSライブラリはハンドシェイクに内部RAMを必要とし、最初の試みはSSL - Memory allocation failedで失敗しました。ロボットは現在、会話が始まるとESP-SRを停止して107 KBを解放し、会話が終わると再び読み込みます。ESP-SRの読み込みには約1.2秒かかります。
  3. マイクストリームの欠落。 ある日、ロボットはユーザーに挨拶した後、その後のすべてを無視しました。目は依然として私の声を追っていたので、マイクは明らかに動作していました。原因は、ファームウェアがGeminiへの音声ストリームを有効にしていなかったことでした。シリアルコンソールがGeminiによる聞き取り内容の文字起こしを表示するようになったため、同じ問題はすぐに発見できるでしょう。

Geminiを使用するにはGoogle AI StudioのAPIキーが必要で、APIはGoogleのGemini API料金に従って使用量に応じて課金されます。キーはローカルのsecrets.hファイルに保存され、リポジトリにコミットされることはありません。

ロボットの画面での設定

一度セットアップすれば、ロボットにコンピュータは不要です。画面を長押しすると、時計、バッテリー残量、Wi-Fiの状態が表示されます。時計をタップすると設定が開き、以下を変更できます。

Wi-Fiの行をタップすると、範囲内のネットワークの一覧と、大文字、数字、記号を備えたオンスクリーンキーボードが開きます。ESP32-S3はネットワークへの接続中にスキャンできないため、ファームウェアはスキャン中に接続試行を一時停止し、その後再開します。パスワードが間違っている場合は、時計画面にその理由が表示されます。ESP32-S3は2.4 GHzネットワークのみをサポートしています。

ロボット、PC、ブラウザで共通のコードベース

ロボットのコードは、ディスプレイ、タッチ、オーディオのドライバを直接呼び出しません。顔、Brain、画面はsrc/hwとsrc/audioにある薄いハードウェア層を経由します。エミュレータはその層を置き換えるため、残りのコードは3つのターゲット向けに変更なしでビルドされます。

src/内の共有C++コードは3通りにコンパイルされます — arduino-cliがESP32-S3ファームウェアを、モックのArduinoヘッダーを使ったg++がraylibデスクトップエミュレータを、em++がブラウザ向けの同じエミュレータを生成します

エミュレータでは、Arduino.h、Wire.h、SPI.hの小さな代替バージョンが、ハードウェアの代わりとなる構造体に書き込みます。この構造体は、ディスプレイメモリ、タッチコントローラのレジスタ、バッテリーの読み取り値を保持します。raylibグラフィックスライブラリで書かれたエミュレータウィンドウがその構造体を描画し、センサー値を変更できるようにします。

ブラウザ版は、C++をWebAssemblyに変換するEmscriptenでコンパイルされた同じエミュレータプログラムです。これを可能にしたのは3つの変更です。

  1. Web向けraylib。 raylibはEmscriptenでビルドすると、WebGL経由で描画する静的ライブラリになります。
  2. 1回の呼び出しで1フレーム。 デスクトップ版は無限ループで動作していました。そのループは1フレームを描画する関数になりました。デスクトップ版はそれをループ内で呼び出し、ブラウザは画面のリフレッシュごとに1回呼び出します。
  3. 固定ウィンドウサイズ。 ブラウザでは、エミュレータは1380×900のキャンバスに描画し、ページがキャンバスを拡大縮小してフィットさせます。サイズ変更可能なキャンバスは、マウス座標とキャンバス座標の対応を壊してしまいました。

3D筐体はEspressifによるものです。ESP-CollegeはESP-VoCat(EchoEar)のケースをSTLファイルとしてOSHWHubでGPL-3.0ライセンスの下に公開しています。短いPythonスクリプトがSTLファイルを1つの3Dモデルファイルに変換し、ブラウザが素早く読み込めるように詳細度を下げます。エミュレータはライブ画面をケースの丸い窓の中の円盤に描画します。3D画面をクリックすると、エミュレータはその円盤上のクリックされた点を見つけ、対応するピクセルへのタッチに変換します。

自分で作る

VoCat Eyesをビルドするには、ESP-VoCat v1.2ボードと、執筆時点ではESP32用Arduinoコアのバージョン3.3が必要です。VoCat Eyes READMEにライブラリと正確なコマンドが記載されています。要約すると、以下のとおりです。

  1. secrets.example.hをsecrets.hにコピーし、Gemini APIキーを追加します。Wi-Fiネットワークもそこで入力できますし、後からロボット上で設定することもできます。
  2. esp_sr_16パーティションスキームとスケッチフォルダ内のpartitions.csvファイルを使ってスケッチをコンパイルします。このレイアウトではアプリケーションに6 MBのフラッシュが割り当てられます。
  3. ファームウェアを「Hi ESP」ウェイクワード用のモデルファイルsrmodels.binと一緒にアップロードします。
  4. ロボット上で画面を長押しし、時計をタップし、Wi-Fiを選択してネットワークを選び、パスワードを入力します。
  5. 「Hi ESP」と言うか、画面下部の小さなマイクをタップして、話し始めます。

ESP32-S3や他のボード向けのファームウェアを設計しているなら、Velxioでハードウェアに書き込む前にブラウザでシミュレーションできます。


共有:
David Montero

執筆者

David Montero

Creator of Velxio, the open-source circuit and Arduino simulator.

GitHub velxio.dev

関連記事


前の記事
ブラウザで作って動かせるハロウィンプロジェクト7選
次の記事
M5Stack Basic で作る Claude 使用状況ダッシュボード — プランの上限と API の支出を机の上に