Skip to content

starkoka/AIRI

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AIRI

AIRI (Artificial Intelligence Roommate Interface) は,Raspberry Pi Zero 2 Wを使用した「部屋とあなたを1つに繋ぐ,賢い相棒​」です.

AIRI Board セットアップ手順

1. 基板の発注

airi_board/GERBER-AIRI-BOARD.zip をPCBメーカーへ発注してください.JLCPCBで発注・動作を確認しています.

2. 部品の調達

以下の部品が必要になります.はんだ付けするための道具も必要です.

部品名 個数 商品リンク
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接続のみでセットアップを完了させることが出来るため,キーボードやマウスを接続するためのハブ,ディスプレイやマウスは任意となります.

3. 組み立て

シルク及びairi_board/airi.stepを参考に組み立ててください.ボタンについては,1個だけ独立しているのが赤色,短辺方向に縦に並んでる2つが白色です.

4. Raspberry Piに装着

ピンソケットをピンヘッダに差し込むことで,AIRI BoardをRaspberry Piに装着できます.

5. ケースを印刷

airi_board/ケース.STL及びairi_board/蓋.STLを印刷してください.この際,蓋は長辺を下に垂直に立てて,ケースは上下反転させて,それぞれツリー形状のサポートを有効にして印刷すると成功しやすいです. また,録音ボタン側の蓋はサポートを剥がすときに切断しやすいので,慎重に外してください.

6. ケースに組み込む

M2 x 20mm プラネジ,及びM2のプラナットを使用して固定します.底面にナットを埋め込めますが,場合によっては穴が小さすぎる場合があります.その場合は削って拡張してください.

強くネジを締めすぎると,OLEDのディスプレイのケーブルを圧迫してディスプレイが表示不可能になることがあります.

Raspberry Pi Zero 2W セットアップ手順

本システムはハードウェアとソフトウェアが密接に連携するため,OSレベルの設定が必要です.以下の手順に従って環境を構築してください.

1. OSイメージと初期設定

Raspberry Pi OS Lite (64-bit) ベースでの構築を推奨します.SSH接続およびWi-Fi設定を済ませた状態で開始してください.

2. /boot/firmware/config.txt (または /boot/config.txt) の編集

ハードウェアの機能を有効化するために,以下のオーバーレイ設定を追記します.

# 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 が存在することを確認します.

3. システムパッケージのインストール

LIRC,I2Sオーディオ録音用パッケージ,機械学習用の依存関係をインストールします.

sudo apt update
sudo apt install -y lirc v4l-utils alsa-utils libasound2-dev libgomp1 libopenblas-dev swig liblgpio-dev

4. Python仮想環境の構築

プロジェクトの server/ ディレクトリ内で仮想環境を作成し,必要なライブラリをインストールします. 本プロジェクトでは pandasscikit-learnlightgbm などの重量級パッケージを使用します.そのため,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_tmp

5. APIキーと各種トークンの取得・設定

server/.env.example をコピーして server/.env を作成します.

  1. Discord Bot Token:
    • Discord Developer Portal にてBotを作成.
    • Privileged Gateway Intents の「Message Content Intent」を必ず有効にしてください.
    • トークンを .envDISCORD_TOKEN に記載します.
  2. 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は制限モードでローカル操作・簡易提案・タイマーを継続します.
  3. FCM (Firebase Cloud Messaging) Service Account:
    • Androidアプリへのプッシュ通知用です(HTTP v1 API対応).
    • Firebase コンソールの「プロジェクトの設定 > サービスアカウント」から「新しい秘密鍵の生成」をクリックし,JSONファイルをダウンロードします.
    • ダウンロードしたJSONファイルをサーバーの server/firebase-adminsdk.json に配置します.(場所は .envFCM_CREDENTIALS_PATH で変更可能です)
  4. 天気予報連携 (Open-Meteo):
    • 自発提案の精度を上げるため,設置場所の緯度経度を .env に設定します.
    • 例:
      WEATHER_LATITUDE=35.6895
      WEATHER_LONGITUDE=139.6917
      WEATHER_TIMEZONE=Asia/Tokyo
  5. 赤外線送信設定:
    • IR_DEVICE_PATH は通常 /dev/lirc0 です.
    • IR_CARRIER_HZ は通常 38000IR_DUTY_CYCLE は通常 33 を使用します.

6. AndroidアプリとFirebaseの設定

自発的なAIの提案(プッシュ通知)を受信・チャット連携するために,Androidアプリをビルドします.

  1. Firebase プロジェクト作成:
    • Firebase Consoleで新しいプロジェクトを作成し,Androidアプリ(パッケージ名: com.example.airi)を追加します.
  2. google-services.json の配置:
    • ダウンロードした google-services.jsonandroid_app/app/ ディレクトリの直下に配置してください.
  3. Android Studioでのビルド:
    • android_app/ をプロジェクトとして開いてビルドし,実機にインストールします.

7. Cloudflare Tunnel & Zero Trust の設定(外部アクセス・API保護用)

Androidアプリの Retrofit (AiriApi.kt) が FastAPI と通信するために,Cloudflare Tunnel を使用してセキュアに公開し,Zero Trust の Service Token で保護します.

  1. Service Tokenの発行:

    • Cloudflare Zero Trust ダッシュボードの Access > Service Auth > Service Tokens にてトークンを作成します.
    • 発行された Client IDClient Secret をコピーします.
  2. Access Applicationの作成:

    • Access > Applications にて,トンネルのホスト名 (api.yourdomain.com) に対する Self-hosted アプリを作成します.
    • ポリシーとして,上記で作成した Service Token を許可 (Include: Service Token) に設定します.
  3. 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>'
  4. cloudflaredのインストール:

    wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
    sudo dpkg -i cloudflared-linux-arm64.deb
  5. ログインとトンネル作成:

    cloudflared tunnel login
    cloudflared tunnel create airi-board
    cloudflared tunnel route dns airi-board api.yourdomain.com
  6. ルーティング設定とシステムディレクトリへの配置: まず,ディレクトリを作成し,生成された認証ファイルをコピーします.

    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...)に置き換えてください.

  7. サービス登録と起動:

    sudo cloudflared service install
    sudo systemctl enable cloudflared
    sudo systemctl start cloudflared

    ※注意: トンネルのURLはアプリ内 Settings 画面から保存できます.固定値を書き換える必要はありません.

8. システムの起動

設定が完了したら,サーバー側のプロセスを起動します.

cd AIRI/server
source venv/bin/activate
python main.py

正常に起動すると,Discord Botがオンラインになり,FastAPI (ポート8000) がローカルで立ち上がり,実機のOLEDディスプレイに表情が表示されます!

運用時に使う主な機能

Discord

  • /ac: 直接エアコンを操作
  • /timer: タイマーの一覧・追加・削除
  • /config: 提案ON/OFF,提案頻度,IRエンコーダ切り替え
  • /shutdown: Raspberry Piを安全に停止

Androidアプリ

  • 会話: 自然言語チャットと即時ショートカット操作
  • タイマー: 単発/繰り返しタイマーの追加・削除
  • 操作: リモコン風の直接操作
  • 設定: API接続診断,提案頻度,IRエンコーダ,FCM再登録,シャットダウン

FastAPI

  • 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
Loading
  • エッジ推論: LightGBM をSQLite履歴を用いたゼロからの日次学習バッチとリアルタイム推論.
  • 物理IRレイヤ: ir-ctl (LIRC) を叩く Factory パターン実装のカスタムエンコーダプラグイン方式.
  • 対話処理: Discord.py, FastAPI, Jetpack Compose が協調し,OpenAI互換形式での STT+LLM パイプラインを稼働.

systemd による自動起動

Raspberry Pi 起動時に AIRI を自動起動したい場合は,server/venv を直接使う systemd サービスにするのが安全です.source venv/bin/activateExecStart に書く必要はありません.

ただし,Pi の起動直後は Wi-Fi や DNS がまだ不安定なことがあり,その瞬間に起動すると Discord / Cloudflare / LLM / STT / 天気取得が失敗する可能性があります.
そのため,以下の例では:

  • After=Wants=network-online.target でネットワーク初期化後に寄せる
  • ExecStartPre で少し待ちつつ,デフォルトルートが見えるまで待機する
  • 実行本体は venv/bin/python main.py

という形にしています.

1. service ファイルを作成

/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 を使っています.

2. service を有効化して起動

sudo systemctl daemon-reload
sudo systemctl enable airi.service
sudo systemctl start airi.service

3. 状態確認

systemctl status airi.service
journalctl -u airi.service -f

4. よくある注意点

  • server/.envWorkingDirectory=/home/kokastar/AIRI/server で起動する前提です.別ディレクトリで起動すると .env の読込先がずれます.
  • 仮想環境を作り直した場合でも,ExecStart は常に server/venv/bin/python を指している必要があります.
  • ネットワークが完全に使えない状態でも,AIRI はローカル機能を中心に制限モードで動く設計ですが,起動時の外部接続失敗ログを減らしたい場合は ExecStartPre の待機時間を少し長めにしてください.

Raspberry Pi で server/ だけ clone したい場合

最初に Raspberry Pi へ取得するとき,Android 側まで不要なら git sparse-checkout を使うと server/ だけ作業ツリーへ展開できます.
以下は main ブランチの例です.

1. sparse-checkout で clone

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/ だけが展開されます.

2. Python 依存をインストール

cd ~/AIRI/server
source venv/bin/activate
pip install -r requirements.txt

3. サービスを再起動

sudo systemctl restart airi.service
sudo systemctl status airi.service

補足

  • 後から更新するときは通常どおり cd ~/AIRI && git pull で構いません.sparse-checkout 設定は維持されるので,作業ツリーには引き続き server/ だけが展開されます.
  • もし後から Android 側も必要になったら,git sparse-checkout set server android_app のように対象を増やせます.

About

部屋とあなたを1つに繋ぐ,賢い相棒​

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors