1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ラズパイ+M5 Stack Core2 AWS +Groq API で音声会話

1
Posted at

はじめに

 前回までに、Groq API をラズパイで動かし、音声会話システムを構築しました。
今回は、M5 Stack Core2 AWS に入出力(マイク、スピーカー)の役割をもたせます。

Arduino IDE への追加インストール

まず、PC上の Arduino IDEにM5Stack用の設定を追加します。M5Stackにプログラムを書き込むだけなので、PCはなんでも OK です。

①ボードマネージャーにM5Stack URLを追加
Arduino IDEの設定画面で追加のボードマネージャーURLに  下を追加:

https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json

②ボードマネージャーでM5Stackをインストール
  ツール → ボード → ボードマネージャー で M5Stack を検索してインストール。
  M5Stack by M5Stack official : ver.3.3.7 でした。

③ライブラリをインストール
  ツール → ライブラリを管理 で以下をインストール:
  M5Unified : v.0.2.14 で、インストール済みでした(たまたま?)。
  ArduinoJson : v.7.4.3 で、インストール済みでした(たまたま?)。

ラズパイにFlaskサーバーを設定

ラズパイに Flask をインストールします。

pip install flask --break-system-packages

Flaskサーバー用コードを ~/voice_server.py として保存します。スクリプトの内容は以下です。

from flask import Flask, request, send_file
import subprocess
import os
import json

app = Flask(__name__)

VOICE = "/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice"
DICT = "/var/lib/mecab/dic/open-jtalk/naist-jdic"
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")

@app.route("/talk", methods=["POST"])
def talk():
    print(f"受信データサイズ: {len(request.data)} bytes")
    print(f"Content-Type: {request.content_type}")
    audio_data = request.data
    if len(audio_data) == 0:
        return "音声データが空です", 400
    with open("/tmp/input.wav", "wb") as f:
        f.write(audio_data)

    # Groq音声認識
    result = subprocess.run([
        "curl", "-s",
        "https://api.groq.com/openai/v1/audio/transcriptions",
        "-H", f"Authorization: Bearer {GROQ_API_KEY}",
        "-F", "model=whisper-large-v3",
        "-F", "file=@/tmp/input.wav",
        "-F", "language=ja"
    ], capture_output=True, text=True)
    text = json.loads(result.stdout)["text"]
    print(f"あなた: {text}")

    # Groq LLM
    messages = [
        {"role": "system", "content": "あなたは簡潔に返答するアシスタントです。返答は必ず日本語で1文以内にしてください。"},
        {"role": "user", "content": text}
    ]
    result = subprocess.run([
        "curl", "-s",
        "https://api.groq.com/openai/v1/chat/completions",
        "-H", f"Authorization: Bearer {GROQ_API_KEY}",
        "-H", "Content-Type: application/json",
        "-d", json.dumps({"model": "llama-3.3-70b-versatile", "messages": messages})
    ], capture_output=True, text=True)
    reply = json.loads(result.stdout)["choices"][0]["message"]["content"]
    print(f"AI: {reply}")

    # open_jtalkで音声合成(reply_raw.wavに出力)
    clean_reply = reply.replace("\n", "、")
    subprocess.run(
        f'echo "{clean_reply}" | open_jtalk -x {DICT} -m {VOICE} -ow /tmp/reply.wav',
        shell=True
    )

    # サイズ確認
    size = os.path.getsize("/tmp/reply.wav")
    print(f"送信WAVサイズ: {size} bytes")

    return send_file("/tmp/reply.wav", mimetype="audio/wav")

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

起動方法は以下の通りです。

python3 ~/voice_server.py

これで以下が表示されれば OK です。途中で Warning が出ますが、商用利用不可のメッセージなので私的にホビーとして使用する分には大丈夫です。

Running on http://0.0.0.0:5000
Running on http://ラズパイのIPアドレス:5000

M5Stack 用のArduino IDE スケッチ

以下をコピペして、raspi_flask の名前で保存します。コンパイルしてエラーが出ないことを確認して、M5Stack に書き込みます。SSid と password、 IP address は各自の値を入れます。

#include <M5Unified.h>
#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid = "xxxxxxxxxxxxxxxxxxx";
const char* password = "xxxxxxxxxxxxxxxxxxx";
const char* serverUrl = "http://ラズパイのIPアドレス:5000/talk";

#define RECORD_SECONDS 5
#define SAMPLE_RATE 16000
#define BUFFER_SIZE (SAMPLE_RATE * RECORD_SECONDS)

int16_t recordBuffer[BUFFER_SIZE];

void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.Speaker.setVolume(255);  // 0-255で調整
  M5.Lcd.setTextSize(2);
  M5.Lcd.println("AI Assistant");

  WiFi.begin(ssid, password);
  M5.Lcd.print("WiFi connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.print(".");
  }
  M5.Lcd.println("\nWiFi completed!");
  M5.Lcd.println("Press A button");
}

void loop() {
  M5.update();

  if (M5.BtnA.wasPressed()) {
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("Recording...");

    // 録音
    M5.Mic.begin();
    M5.Mic.record(recordBuffer, BUFFER_SIZE, SAMPLE_RATE);
    M5.Mic.end();

    M5.Lcd.println("Sending...");

    // WAVヘッダー作成
    int dataSize = BUFFER_SIZE * 2;
    int wavSize = dataSize + 44;
    uint8_t* wavBuffer = (uint8_t*)malloc(wavSize);

    memcpy(wavBuffer, "RIFF", 4);
    *(int32_t*)(wavBuffer + 4) = wavSize - 8;
    memcpy(wavBuffer + 8, "WAVE", 4);
    memcpy(wavBuffer + 12, "fmt ", 4);
    *(int32_t*)(wavBuffer + 16) = 16;
    *(int16_t*)(wavBuffer + 20) = 1;
    *(int16_t*)(wavBuffer + 22) = 1;
    *(int32_t*)(wavBuffer + 24) = SAMPLE_RATE;
    *(int32_t*)(wavBuffer + 28) = SAMPLE_RATE * 2;
    *(int16_t*)(wavBuffer + 32) = 2;
    *(int16_t*)(wavBuffer + 34) = 16;
    memcpy(wavBuffer + 36, "data", 4);
    *(int32_t*)(wavBuffer + 40) = dataSize;
    memcpy(wavBuffer + 44, recordBuffer, dataSize);

    // HTTPでラズパイに送信
    HTTPClient http;
    http.begin(serverUrl);
    http.addHeader("Content-Type", "audio/wav");
    int httpCode = http.POST(wavBuffer, wavSize);
    free(wavBuffer);

    if (httpCode == 200) {
      M5.Lcd.println("Playing...");
      WiFiClient* stream = http.getStreamPtr();
      int len = http.getSize();
      uint8_t* replyWav = (uint8_t*)malloc(len);
      stream->readBytes(replyWav, len);
      M5.Speaker.begin();
      M5.Speaker.playWav(replyWav, len);
      while (M5.Speaker.isPlaying()) { delay(10); }
      M5.Speaker.end();
      free(replyWav);
    } else {
      M5.Lcd.println("Error: " + String(httpCode));
    }
    http.end();

    M5.Lcd.println("Press A button");
  }
}

M5 Stack の液晶画面に A の文字が表示されたら、画面左下の薄い〇印を押します。すぐに、M5 Stack 内蔵のマイクで録音が始まるので、話しかけてください。

すると、ラズパイのターミナルに話した内容のテキスト、および、AIの回答テキストが表示されます。それと同時に、M5 Stack のスピーカーからAIの回答テキストが発話されます。

AIの回答を2文以内に指定したところ、ときどき音声サイズオーバーで M5 Stack 液晶画面に -11 が表示される場合があります。そのため、1文以内に制限しています。

おわりに

 わりとスムーズに構築できました。音声サイズオーバー対策が課題です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?