2317 文字
12 分
自宅サーバーをスマートスピーカー化したメモ

現在、自宅サーバーにはスマートホームを実現するためのFOSS、HomeAssistantが導入されている。しかし、私はスマートスピーカーを持っていない。

そこで、自宅サーバーにスピーカーフォンを接続して、スマートスピーカーのようにHomeAssistantを操作できるようにした。

その作業メモです。

動機#

自宅にメイドさんが欲しい。期待する用途は以下の通り。

  • 音声指示でデスクライト等のスマートデバイスを操作する
  • 各種HomeAssistantから利用できる値を元に、特定のシチュエーションで通知する
    • 二酸化炭素モニタの値を元に、CO2濃度が高まった時に音声で警告する
    • 天気が怪しくなったら、音声で警告する
  • 生活をメイドさんに管理してもらいたい
    • モーニングコールや就寝時刻など

必然的に、使用する音声はAlexaやGoogle Assistantのようなものではなく、可愛らしい声が望ましい。

背景#

HomeAssistant(HAOS)はNoelleというホスト名のVM上で動作している。

下調べ#

あと数日待てばブラックフライデーのセールでAmazon Echoとかが安く買えそうではあった。しかし調べた感じ、確かにHomeAssistantへの接続はできるものの、機能をフルに使うことはできないようだった。

それに、大企業の束縛から逃れるというのがHomelabをやっている理由のひとつなのに、Amazonの製品に頼るというのは哲学に反する。

AmazonやGoogleの経済圏に束縛されないスマートスピーカーや自作キットを使って実現する方法もあるようだが、なかなかいいお値段がする。

以下の動画はラズパイを使って実現したものだが、かなり自分のやりたいことに近い。

しかし、ラスパイもまた、なかなかいいお値段がする。既存の道具を有効活用して実現したい。

そこで、「自宅サーバーにマイクとスピーカーを接続し、ネットワークスピーカーとして機能するVM(LXC)を用意する」というアイデアを考えた。

作業メモ#

以下、Geminiさんに作業記録から書き起こしてもらったもの。


構成概要#

今回のゴールは、Proxmox上のLXCコンテナを「マイクとスピーカーのインターフェース(サテライト)」として機能させ、頭脳であるHome Assistant(VM)と連携させることだ。

  • Hardware:
    • Server: 自宅サーバー (Proxmox VE)
    • Audio: Jabra Speak 410 (USB接続のスピーカーフォン)
  • Software:
    • Host: Proxmox VE 8.x
    • Satellite: LXC Container (Debian 12) + Wyoming Satellite
    • Brain: Home Assistant OS (VM)
      • STT: Whisper
      • TTS: VOICEVOX
      • Wake Word: openWakeWord
      • LLM: Google Gemini

1. ProxmoxホストとLXCの準備#

まず、Proxmoxホストに接続したUSBスピーカーフォン(Jabra)を、LXCコンテナから認識させる必要がある。 セキュリティを考慮し、非特権コンテナ (Unprivileged Container) のままデバイスをパススルーすることに拘った。これが最初の難関だった。

1.1. デバイスの確認#

Proxmoxホスト側でデバイスを確認。

Terminal window
ls -l /dev/snd/

Jabraを接続すると controlC1pcmC1D0p などが増える。しかし、非特権コンテナではホストのデバイス権限(通常 root:audio)にアクセスできない。

1.2. udevルールによる権限の自動書き換え#

「コンテナ内のroot」は「ホスト側のUID 100000」にマッピングされている。そこで、サウンドデバイスが接続されたら自動的にグループを 100000 に変更するudevルールを作成した。

/etc/udev/rules.d/99-lxc-audio.rules を作成:

Terminal window
SUBSYSTEM=="sound", GROUP="100000"

ルールを適用:

Terminal window
udevadm control --reload-rules
udevadm trigger

これで /dev/snd/* の所有グループが 100000 になり、非特権コンテナから見える準備が整った。

1.3. LXCコンテナの設定#

LXCの設定ファイル (/etc/pve/lxc/105.conf 等) に、デバイスのパススルー設定を追記。 サウンドデバイスのメジャー番号は 116 だったので、以下のように記述。

lxc.cgroup2.devices.allow: c 116:* rwm
lxc.mount.entry: /dev/snd dev/snd none bind,optional,create=dir

コンテナを再起動し、コンテナ内で aplay -l を実行して Jabra (Card 1) が見えていれば成功。

2. Wyoming Satelliteの構築 (LXC)#

サテライト側のソフトウェアには、Home Assistant公式が推進する Wyoming Satellite を使用する。

2.1. インストール#

公式のセットアップスクリプトを使用するのが最も確実だった。

Terminal window
git clone https://github.com/rhasspy/wyoming-satellite.git
cd wyoming-satellite
script/setup

2.2. サービス化 (systemd)#

ここが最大のハマりポイントだった。 最初はパイプを使った複雑なコマンドを書いていたが、最終的には公式スクリプト (script/run) を素直に使うのが正解だった。 また、サーバーのCPU (Core i3-7100) がAVX命令セットの関係でローカルでのウェイクワード検出ライブラリと相性が悪かったため、**「音声ストリームを垂れ流して、サーバー(HA)側でウェイクワードを検出する」**構成を採用した。

/etc/systemd/system/wyoming-satellite.service:

[Unit]
Description=Wyoming Satellite for Home Assistant
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
# Jabraは plughw:1,0 として認識されていたため明示的に指定
ExecStart=/root/wyoming-satellite/script/run \
--name "Jabra Speaker" \
--uri "tcp://0.0.0.0:10700" \
--mic-command "arecord -D plughw:1,0 -r 16000 -c 1 -f S16_LE -t raw" \
--snd-command "aplay -D plughw:1,0 -r 22050 -c 1 -f S16_LE -t raw" \
--snd-volume-multiplier 1.0
WorkingDirectory=/root/wyoming-satellite
User=root
Restart=always
RestartSec=1
[Install]
WantedBy=multi-user.target

3. Home Assistant側の設定#

HA側で「耳」「口」「頭脳」を組み立てていく。

注: ここまでで自宅サーバーの仮想Wyoming Satellite化が完了しているので、あとは基本的に上述の動画と同じようにやれば良い。

3.1. アドオンの導入#

以下の公式/コミュニティアドオンを導入した。

  • Whisper: 音声認識 (STT)。モデルは base-int8、言語は ja
  • VOICEVOX: 音声合成 (TTS)。HACSで統合を入れ、別途Docker(アドオン)でエンジンを立てる構成。ずんだもんや四国めたんなどが使える。
  • openWakeWord: ウェイクワード検出。サテライトから送られてくる音声ストリームから「Okay Nabu」などを検知する。

※ 構築中にHACSのサーバーダウンに遭遇するという不運もあったが、復旧を待って解決した。

3.2. Wyoming Protocol 統合#

「設定」>「デバイスとサービス」から Wyoming Protocol を追加。 LXCコンテナのIPアドレスとポート(10700)を指定すると、Jabra Speaker が認識される。

3.3. アシストパイプラインの作成#

「設定」>「Voice assistants」で新しいパイプラインを作成。

  • 言語: 日本語
  • 会話エージェント: Google Generative AI Conversation (Gemini)
  • STT: Whisper
  • TTS: VOICEVOX TTS
  • ウェイクワード: openWakeWord

「Proxmox Speaker」デバイスの設定で、このパイプラインを使用し、ウェイクワード検出を**「Home Assistantで処理」**に設定することで、常時ストリーミング構成が完成する。

4. LLMによる「人格」の形成#

会話エージェントに、Google Gemini (API) を使用した。 標準のHome Assistantエージェントだと「電気をつけて」等の命令しか聞かないが、LLMなら雑談も可能になる。

システムプロンプト(Instructions)に以下のような設定を入れ、キャラ付けを行った。

あなたは「原神」の「ノエル」として振る舞ってください。
常に献身的で丁寧な言葉遣いを心がけてください。

5. モーニングコールの自動化#

仕上げに、毎朝決まった時間に挨拶をするオートメーションを作成。 Geminiにセンサー値(天気やCO2濃度)を渡して「一言コメント」を生成させ、それをVOICEVOXで喋らせるというハイブリッド構成にした。

オートメーションの例 (Action部分):

  1. Conversation: Process
    • Geminiに「天気とCO2濃度に基づいた一言コメント」を考えさせ、変数 noelle_comment に格納。
  2. Assist Satellite: Announce
    • ターゲット: Jabra Speaker
    • メッセージ:
      おはようございます、栄誉騎士様。
      現在の気温は {{ state_attr('weather.forecast_home', 'temperature') }} 度。
      お部屋の二酸化炭素濃度は {{ states('sensor.co2_sensor') }} ピーピーエムです。
      {{ noelle_comment.response.speech.plain.speech }}

以上、Geminiさん書き起こし終わり。

Future work#

とりあえず期待していたものは作れたが、以下の点は改善したい。

ローカルLLMの採用#

脱Google、脱Amazonの観点から言うと、ローカルLLMの採用が望ましい。サーバーにGPUが積んであればLlamaあたりを動かしたかった。普段使いのPC上でLLMサーバーを用意するのも考えたが、システムが複雑になりすぎるし、普段使いPCの電源を落とせなくなってしまう。

ウェイクワード検出の改善#

現状は「OK,Nabu」で起動するが、HomeAssistantをノエルさんに見立てている以上、「ノエル」をウェイクワードとするべき。

これは一番上の動画を見る限りすぐに実現できそうな雰囲気。

音声の改善#

同じ理由で、ノエルさんの声で話すようにしたい。技術的には実現できそうだが、お金、応答速度、倫理的な問題が生じる。

とはいえVOICEVOXの声も十分かわいいので、このままでも良いかも。

(VOICEVOX 中国うさぎ(おどろき)を採用。)

もっとスマートデバイスを増やす#

現状、HomeAssistantに接続されているデバイスはCO2濃度計(温度・湿度も一応測れる)とデスクライトくらい。もっと増やしたい。

もっと大きな家に住む#

小さな部屋で自動化してもありがたみが薄い。

まとめ#

LXCの権限周りや、Home Assistantのパイプラインの仕組み(特にウェイクワードの検出場所)の理解に苦労したが、結果として非常に満足のいく「自作スマートスピーカー」が完成した。

既存のリソースを活用し、プライバシーを重視しつつ、いい感じの声と人格で生活をサポートしてくれる環境をほぼゼロ円で構築できた。

封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00