AIRI (Artificial Intelligence Roommate Interface) は,Raspberry Pi Zero 2 Wを使用した「部屋とあなたを1つに繋ぐ,賢い相棒」です.
airi_board/GERBER-AIRI-BOARD.zip をPCBメーカーへ発注してください.JLCPCBで発注・動作を確認しています.
以下の部品が必要になります.はんだ付けするための道具も必要です.
| 部品名 | 個数 | 商品リンク |
|---|---|---|
| 5mm赤外線LED OSI5FU5111C-40 | 2 | https://akizukidenshi.com/catalog/g/g103261/ |
| SSD1306 OLED Display | 1 | https://akizukidenshi.com/catalog/g/g112031/ |
| 電解コンデンサー(100μF16V) | 1 | https://akizukidenshi.com/catalog/g/g110271/ |
| タクトスイッチ(黒色) | 1 | https://akizukidenshi.com/catalog/g/g103647/ |
| タクトスイッチ(赤色) | 1 | https://akizukidenshi.com/catalog/g/g103646/ |
| タクトスイッチ(白色) | 2 | https://akizukidenshi.com/catalog/g/g103648/ |
| ピンソケット (メス) 2×20 (40P) | 1 | https://akizukidenshi.com/catalog/g/g100085/ |
| 10Ω抵抗 (1/2W) | 1 | https://akizukidenshi.com/catalog/g/g107795/ |
| 470Ω抵抗 (1/4W) | 1 | https://akizukidenshi.com/catalog/g/g125471/ |
| Raspberry Pi Zero 2 WH | 1 | https://akizukidenshi.com/catalog/g/g129607/ |
| NPNトランジスター 2SC2120-Y | 1 | https://akizukidenshi.com/catalog/g/g113829/ |
| 温湿度センサモジュール SHT31 | 1 | https://www.amazon.co.jp/dp/B0CZHTWLJZ |
| 全指向性マイク INMP441 | 1 | https://www.amazon.co.jp/dp/B0GD6269X4 |
また,別途Raspberry Pi 2W及び動作させるためのSDカード・USB MicroB給電ケーブルが必要です.SSH接続のみでセットアップを完了させることが出来るため,キーボードやマウスを接続するためのハブ,ディスプレイやマウスは任意となります.
シルク及びairi_board/airi.stepを参考に組み立ててください.ボタンについては,1個だけ独立しているのが赤色,短辺方向に縦に並んでる2つが白色です.
ピンソケットをピンヘッダに差し込むことで,AIRI BoardをRaspberry Piに装着できます.
airi_board/ケース.STL及びairi_board/蓋.STLを印刷してください.この際,蓋は長辺を下に垂直に立てて,ケースは上下反転させて,それぞれツリー形状のサポートを有効にして印刷すると成功しやすいです.
また,録音ボタン側の蓋はサポートを剥がすときに切断しやすいので,慎重に外してください.
M2 x 20mm プラネジ,及びM2のプラナットを使用して固定します.底面にナットを埋め込めますが,場合によっては穴が小さすぎる場合があります.その場合は削って拡張してください.
強くネジを締めすぎると,OLEDのディスプレイのケーブルを圧迫してディスプレイが表示不可能になることがあります.
本システムはハードウェアとソフトウェアが密接に連携するため,OSレベルの設定が必要です.以下の手順に従って環境を構築してください.
Raspberry Pi OS Lite (64-bit) ベースでの構築を推奨します.SSH接続およびWi-Fi設定を済ませた状態で開始してください.
ハードウェアの機能を有効化するために,以下のオーバーレイ設定を追記します.
# I2Cの有効化 (SHT31 / OLED用)
dtparam=i2c_arm=on
# I2Sマイク (INMP441等) の有効化
dtparam=i2s=on
dtoverlay=googlevoicehat-soundcard
# (利用するI2Sマイクのドライバーに合わせて追加 例: dtoverlay=rpi-i2s-audio / asoundrc の設定が別途必要)
# 赤外線送信 (LIRC / ir-ctl用: GPIO 22を使用)
dtoverlay=gpio-ir-tx,gpio_pin=22変更後,システムを再起動 (sudo reboot) してください.再起動後,ls /dev/lirc0 が存在することを確認します.
LIRC,I2Sオーディオ録音用パッケージ,機械学習用の依存関係をインストールします.
sudo apt update
sudo apt install -y lirc v4l-utils alsa-utils libasound2-dev libgomp1 libopenblas-dev swig liblgpio-devプロジェクトの server/ ディレクトリ内で仮想環境を作成し,必要なライブラリをインストールします.
本プロジェクトでは pandas や scikit-learn,lightgbm などの重量級パッケージを使用します.そのため,Raspberry Pi Zero(RAM 512MB)などの環境で通常の pip install を実行すると,**メモリ不足による強制終了(Killed)**や,**解凍用一時領域(/tmp)の容量不足(No space left on device)**が発生し,インストールに失敗します.以下の手順で「仮想メモリ(Swap)の拡張」と「一時作業ディレクトリのSDカードへの移動」を行ってからインストールを実行してください.
# 1. 一時的な仮想メモリ(Swap)を2GB追加して,RAMの枯渇(Killed)を回避する
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 2. pipの解凍作業用フォルダをSDカード上に作成し,/tmp(RAM依存)の容量不足を回避する
mkdir ~/pip_tmp
export TMPDIR=~/pip_tmp
# 3. キャッシュを無効化してインストールを実行する
cd AIRI/server
python3 -m venv venv
source venv/bin/activate
pip install --no-cache-dir -r requirements.txt
# 4. インストール完了後,作成した一時環境(Swapとフォルダ)をお掃除して元に戻す
sudo swapoff /swapfile
sudo rm /swapfile
rm -rf ~/pip_tmpserver/.env.example をコピーして server/.env を作成します.
- Discord Bot Token:
- Discord Developer Portal にてBotを作成.
- Privileged Gateway Intents の「Message Content Intent」を必ず有効にしてください.
- トークンを
.envのDISCORD_TOKENに記載します.
- LLM / STT API Key:
AI_API_KEY,AI_BASE_URL(cotomi2-proやwhisper等と互換性のあるエンドポイント) のキーとBase URLを設定します.STT_MODEL_NAME(例:whisper-large-v3-turbo) とLLM_MODEL_NAME(例:cotomi2-pro) でモデルを選択できます.- 文字起こし精度を稼ぐため,STT前処理では音声を
STT_REPEAT_COUNT=10のように複製連結できます. - API未設定時でも,AIRIは制限モードでローカル操作・簡易提案・タイマーを継続します.
- FCM (Firebase Cloud Messaging) Service Account:
- Androidアプリへのプッシュ通知用です(HTTP v1 API対応).
- Firebase コンソールの「プロジェクトの設定 > サービスアカウント」から「新しい秘密鍵の生成」をクリックし,JSONファイルをダウンロードします.
- ダウンロードしたJSONファイルをサーバーの
server/firebase-adminsdk.jsonに配置します.(場所は.envのFCM_CREDENTIALS_PATHで変更可能です)
- 天気予報連携 (Open-Meteo):
- 自発提案の精度を上げるため,設置場所の緯度経度を
.envに設定します. - 例:
WEATHER_LATITUDE=35.6895 WEATHER_LONGITUDE=139.6917 WEATHER_TIMEZONE=Asia/Tokyo
- 自発提案の精度を上げるため,設置場所の緯度経度を
- 赤外線送信設定:
IR_DEVICE_PATHは通常/dev/lirc0です.IR_CARRIER_HZは通常38000,IR_DUTY_CYCLEは通常33を使用します.
自発的なAIの提案(プッシュ通知)を受信・チャット連携するために,Androidアプリをビルドします.
- Firebase プロジェクト作成:
- Firebase Consoleで新しいプロジェクトを作成し,Androidアプリ(パッケージ名:
com.example.airi)を追加します.
- Firebase Consoleで新しいプロジェクトを作成し,Androidアプリ(パッケージ名:
google-services.jsonの配置:- ダウンロードした
google-services.jsonをandroid_app/app/ディレクトリの直下に配置してください.
- ダウンロードした
- Android Studioでのビルド:
android_app/をプロジェクトとして開いてビルドし,実機にインストールします.
Androidアプリの Retrofit (AiriApi.kt) が FastAPI と通信するために,Cloudflare Tunnel を使用してセキュアに公開し,Zero Trust の Service Token で保護します.
-
Service Tokenの発行:
- Cloudflare Zero Trust ダッシュボードの
Access > Service Auth > Service Tokensにてトークンを作成します. - 発行された
Client IDとClient Secretをコピーします.
- Cloudflare Zero Trust ダッシュボードの
-
Access Applicationの作成:
Access > Applicationsにて,トンネルのホスト名 (api.yourdomain.com) に対する Self-hosted アプリを作成します.- ポリシーとして,上記で作成した Service Token を許可 (Include: Service Token) に設定します.
-
Androidアプリの
local.properties設定:- プロジェクトのルートディレクトリに
android_app/local.propertiesを作成(または編集)し,以下を追記します.CF_ACCESS_CLIENT_ID=取得したClient-ID CF_ACCESS_CLIENT_SECRET=取得したClient-Secret
- アプリはこの値を Retrofit の全通信に
CF-Access-Client-Id/CF-Access-Client-Secretとして自動付与します. - 設定後は Android Studio で再ビルドしてください.
Settings画面の接続診断で接続OKが出れば反映できています. Settings画面の URL にはhttps://api.yourdomain.comのようなホストのルートを入力してください./apiの追記は不要です.- 切り分けは PC から次で行えます.これで HTML が返るならアプリではなく Cloudflare Access 設定側の問題です.
curl -i https://api.yourdomain.com/api/health \ -H 'CF-Access-Client-Id: <Client-ID>' \ -H 'CF-Access-Client-Secret: <Client-Secret>'
- プロジェクトのルートディレクトリに
-
cloudflaredのインストール:
wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb sudo dpkg -i cloudflared-linux-arm64.deb
-
ログインとトンネル作成:
cloudflared tunnel login cloudflared tunnel create airi-board cloudflared tunnel route dns airi-board api.yourdomain.com
-
ルーティング設定とシステムディレクトリへの配置: まず,ディレクトリを作成し,生成された認証ファイルをコピーします.
sudo mkdir -p /etc/cloudflared sudo cp ~/.cloudflared/*.json /etc/cloudflared/
次に,設定ファイル
/etc/cloudflared/config.ymlを作成し,以下を記述します.tunnel: <TUNNEL-ID> credentials-file: /etc/cloudflared/<TUNNEL-ID>.json ingress: - hostname: api.example.com service: http://localhost:8000 - service: http_status:404
<TUNNEL-ID>の部分は,cloudflared tunnel listコマンドで確認できる実際のID(例:1a2b3c...)に置き換えてください. -
サービス登録と起動:
sudo cloudflared service install sudo systemctl enable cloudflared sudo systemctl start cloudflared※注意: トンネルのURLはアプリ内
Settings画面から保存できます.固定値を書き換える必要はありません.
設定が完了したら,サーバー側のプロセスを起動します.
cd AIRI/server
source venv/bin/activate
python main.py正常に起動すると,Discord Botがオンラインになり,FastAPI (ポート8000) がローカルで立ち上がり,実機のOLEDディスプレイに表情が表示されます!
/ac: 直接エアコンを操作/timer: タイマーの一覧・追加・削除/config: 提案ON/OFF,提案頻度,IRエンコーダ切り替え/shutdown: Raspberry Piを安全に停止
会話: 自然言語チャットと即時ショートカット操作タイマー: 単発/繰り返しタイマーの追加・削除操作: リモコン風の直接操作設定: API接続診断,提案頻度,IRエンコーダ,FCM再登録,シャットダウン
GET /api/health: 接続診断GET/PUT /api/config: 提案設定とIRエンコーダ設定POST /api/ac/control: 直接操作POST /api/system/shutdown: 安全なシャットダウン
flowchart TB
subgraph Users["ユーザー / 外部IF"]
Discord["Discord"]
Android["Androidアプリ"]
Buttons["物理ボタン"]
Mic["I2Sマイク"]
end
subgraph Pi["Raspberry Pi Zero 2 W"]
Main["server/main.py\n常駐プロセス"]
API["FastAPI\n/api/*"]
Bot["Discord Bot"]
Audio["録音 / STT前処理"]
LLM["LLM / STT Client"]
ML["LightGBM\n推論 / 深夜再学習"]
Weather["天気サービス\n気象庁 + AMeDAS"]
HW["Hardware Manager\nSHT31 / OLED / Buttons"]
IR["IR Controller\nEncoder Plugins"]
DB["SQLite\n履歴 / 設定 / タイマー"]
end
subgraph External["外部サービス"]
CF["Cloudflare Tunnel / Zero Trust"]
OpenAICompat["OpenAI互換 API\nLLM / Whisper"]
Firebase["Firebase FCM"]
JMA["気象庁 API"]
end
subgraph Devices["デバイス"]
OLED["OLEDモニター"]
SHT31["温湿度センサー"]
IRLED["赤外線LED"]
AC["エアコン"]
end
Discord --> Bot
Android --> CF --> API
Buttons --> HW
Mic --> Audio --> LLM
Main --> API
Main --> Bot
Main --> Audio
Main --> ML
Main --> HW
Main --> IR
API <--> DB
Bot <--> DB
ML <--> DB
Main <--> DB
API --> LLM
Bot --> LLM
LLM --> OpenAICompat
Weather --> JMA
API --> Firebase
Bot --> Firebase
HW --> OLED
HW --> SHT31
IR --> IRLED --> AC
Main --> HW
- エッジ推論:
LightGBMをSQLite履歴を用いたゼロからの日次学習バッチとリアルタイム推論. - 物理IRレイヤ:
ir-ctl(LIRC) を叩く Factory パターン実装のカスタムエンコーダプラグイン方式. - 対話処理: Discord.py, FastAPI, Jetpack Compose が協調し,OpenAI互換形式での STT+LLM パイプラインを稼働.
Raspberry Pi 起動時に AIRI を自動起動したい場合は,server/venv を直接使う systemd サービスにするのが安全です.source venv/bin/activate を ExecStart に書く必要はありません.
ただし,Pi の起動直後は Wi-Fi や DNS がまだ不安定なことがあり,その瞬間に起動すると Discord / Cloudflare / LLM / STT / 天気取得が失敗する可能性があります.
そのため,以下の例では:
After=Wants=network-online.targetでネットワーク初期化後に寄せるExecStartPreで少し待ちつつ,デフォルトルートが見えるまで待機する- 実行本体は
venv/bin/python main.py
という形にしています.
/etc/systemd/system/airi.service を作成します.
[Unit]
Description=AIRI Board Service
Wants=network-online.target
After=network-online.target sound.target
[Service]
Type=simple
User=kokastar
WorkingDirectory=/home/kokastar/AIRI/server
Environment=PYTHONUNBUFFERED=1
# 起動直後はWi-Fiがまだ不安定なことがあるので,少し待ってから開始する
ExecStartPre=/bin/bash -lc 'for i in $(seq 1 20); do /usr/bin/ip route | /usr/bin/grep -q "^default" && exit 0; /bin/sleep 2; done; exit 0'
ExecStartPre=/bin/sleep 10
# venv を直接使って起動する
ExecStart=/home/kokastar/AIRI/server/venv/bin/python /home/kokastar/AIRI/server/main.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
User=は実際のユーザー名に置き換えてください.上の例ではkokastarを使っています.
sudo systemctl daemon-reload
sudo systemctl enable airi.service
sudo systemctl start airi.servicesystemctl status airi.service
journalctl -u airi.service -fserver/.envはWorkingDirectory=/home/kokastar/AIRI/serverで起動する前提です.別ディレクトリで起動すると.envの読込先がずれます.- 仮想環境を作り直した場合でも,
ExecStartは常にserver/venv/bin/pythonを指している必要があります. - ネットワークが完全に使えない状態でも,AIRI はローカル機能を中心に制限モードで動く設計ですが,起動時の外部接続失敗ログを減らしたい場合は
ExecStartPreの待機時間を少し長めにしてください.
最初に Raspberry Pi へ取得するとき,Android 側まで不要なら git sparse-checkout を使うと server/ だけ作業ツリーへ展開できます.
以下は main ブランチの例です.
git clone --filter=blob:none --no-checkout https://github.com/starkoka/AIRI.git AIRI
cd AIRI
git sparse-checkout init --cone
git sparse-checkout set server
git checkout masterこれで .git にはリポジトリ情報を持ちつつ,作業ツリーには基本的に server/ だけが展開されます.
cd ~/AIRI/server
source venv/bin/activate
pip install -r requirements.txtsudo systemctl restart airi.service
sudo systemctl status airi.service- 後から更新するときは通常どおり
cd ~/AIRI && git pullで構いません.sparse-checkout 設定は維持されるので,作業ツリーには引き続きserver/だけが展開されます. - もし後から Android 側も必要になったら,
git sparse-checkout set server android_appのように対象を増やせます.