Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions doc/basic_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ robo8080さんの[AIスタックチャン](https://github.com/robo8080/AI_StackC
- [2. 設定、ビルド手順](#2-設定ビルド手順)
- [2.1. YAMLによる初期設定](#21-yamlによる初期設定)
- [2.2. ビルド&書き込み](#22-ビルド書き込み)
- [3. パーソナライズ](#3-パーソナライズ)
- [3.1. メモリー(長期記憶)について](#31-メモリー長期記憶について)
- [3. 使い方](#3-使い方)
- [3.1. 会話](#31-会話)
- [3.2. パーソナライズ](#32-パーソナライズ)

## 1. 利用可能なAIサービス
会話に必要な各種AIサービスの対応状況を示します。
Expand Down Expand Up @@ -207,15 +208,23 @@ git clone https://github.com/ronron-gh/AI_StackChan_Ex.git
![](../images/build_and_flash.png)


## 3. パーソナライズ
## 3. 使い方
### 3.1. 会話
M5Coreを起動してアバターが表示された後、アバターの額のあたりをタッチすると録音が開始するので話しかけてください。録音時間は約7秒です。

> AtomS3Rは画面自体が物理ボタンになっているため、画面中央を少し強めに押し込んでください。または、次の動画のようにボディーのどこかをダブルタップすることでも録音開始できます(ダブルタップは初期状態は無効になっています。platformio.iniの[env:m5stack-atoms3r]セクション内の-DENABLE_TAP_DETECTのコメントアウトを解除してビルドすることで有効化できます)。
> ![](../images/double_tap.gif)


### 3.2. パーソナライズ
カスタム指示(いわゆるロール)、及びメモリー(長期記憶)により、AI会話機能をユーザーの属性に合わせてカスタマイズすることができます。

PCやスマートフォンのWebブラウザで http://(スタックチャンのIPアドレス) にアクセスすると次のような設定画面が開きます。(IPアドレスは起動時の画面に表示されます。また、Core2/CoreS3はCボタンまたはLCD右端をタッチするとアクセス用のQRコードが表示されます。)

![](../images/Personalize.png)


### 3.1. メモリー(長期記憶)について
**〇 メモリー(長期記憶)について**
メモリーを有効にするには SDカードの/app/AiStackChanEx/SC_ExConfig.yaml で enableMemory を true に設定してください。

> 現在、メモリーに対応しているLLMは、ChatGPT(Realtime API含む)、Gemini Liveです。
Expand Down
3 changes: 3 additions & 0 deletions firmware/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ build_flags =
-DENABLE_WAKEWORD
-DARDUINOJSON_DEFAULT_NESTING_LIMIT=100
;-DCORE_DEBUG_LEVEL=5 ;Verbose
;-DENABLE_TAP_DETECT ; 加速度センサによるダブルタップの検出

lib_deps =
m5stack/M5Unified @ 0.1.17
Expand Down Expand Up @@ -119,6 +120,7 @@ build_flags =
-DARDUINO_M5STACK_CORES3
-DENABLE_WAKEWORD
-DARDUINOJSON_DEFAULT_NESTING_LIMIT=100
;-DENABLE_TAP_DETECT ; 加速度センサによるダブルタップの検出 (※CoreS3-SEは加速度センサ非搭載)
monitor_speed = 115200
upload_speed = 1500000
lib_deps =
Expand Down Expand Up @@ -180,6 +182,7 @@ build_flags =
;-DENABLE_WAKEWORD ; ウェイクワードは未対応
-DARDUINOJSON_DEFAULT_NESTING_LIMIT=100
;-DCAT_FACE
;-DENABLE_TAP_DETECT ; 加速度センサによるダブルタップの検出

lib_deps =
m5stack/M5Unified @ 0.2.7
Expand Down
177 changes: 177 additions & 0 deletions firmware/src/driver/TapDetect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#if defined(ENABLE_TAP_DETECT)
#include <Arduino.h>
#include <M5Unified.h>
#include "TapDetect.h"


#define ACCEL_DIM (3)
TaskHandle_t taskHandle_doubleTapDetect;
bool doubleTapDetected = false;
bool isDoubleTapDetectionRunning = true;
float firstAcc[ACCEL_DIM] = {0.0, 0.0, 0.0};
float detectedAcc[ACCEL_DIM] = {0.0, 0.0, 0.0};
float firstNorm = 0.0;

// ---------------------------
// 感度等の調整パラメータ
// ---------------------------
// タップを検出する加速度の閾値
const float TAP_DELTA = 0.4f;
// タップの回数(ダブルタップを検出したい場合は2とする)
const float TAP_COUNT = 2;
// ハイパスフィルタが安定するまでのカウント数
const float FIRST_NOISE_COUNT = 10;
// 2回目のタップが同じ方向からのタップかどうかを判定するためのコサイン類似度の閾値
//(1.0から-1.0の間で設定。1.0に近づくほど類似度が高い(判定は厳しくなる)。-1.0に近づくと真逆の方向)
const float COS_SIMILAR = 0.9;


// ---------------------------
// ダブルタップ検出タスク
// ---------------------------
void doubleTapDetectTask(void *arg) {
Serial.println("Double tap detection task created");

// 注意: M5.IMU.getAccel の API は環境により差があるため、
// ビルドエラーが出た場合は M5.Imu.getAccel 等に置き換えてください。

unsigned long first_tap_time = 0;
int tap_count = 0;
float accel[ACCEL_DIM] = {0.0, 0.0, 0.0};
float gravity[ACCEL_DIM] = {0.0, 0.0, 0.0};
float norm = 0.0;
bool ok = false;
int firstNoiseCount = 0;

while(1){
// try common IMU API (adjust if your M5 library uses a different name)
#if defined(M5_IMU)
ok = M5.IMU.getAccel(&ax, &ay, &az);
#else
// Fallback attempt (some versions use Imu)
ok = M5.Imu.getAccel(&accel[0], &accel[1], &accel[2]);
#endif

if (ok) {
unsigned long now = millis();
if(now - first_tap_time > 700){
tap_count = 0;
first_tap_time = 0;
}

// ハイパスフィルタで重力を除去
//
const float ALPHA = 0.8;
// ローパスフィルタで重力値を抽出
gravity[0] = ALPHA * gravity[0] + (1 - ALPHA) * accel[0];
gravity[1] = ALPHA * gravity[1] + (1 - ALPHA) * accel[1];
gravity[2] = ALPHA * gravity[2] + (1 - ALPHA) * accel[2];
// 重力を除去
accel[0] = accel[0] - gravity[0];
accel[1] = accel[1] - gravity[1];
accel[2] = accel[2] - gravity[2];
// ベクトルの大きさを計算
norm = sqrt(accel[0] * accel[0] + accel[1] * accel[1] + accel[2] * accel[2]);

// ハイパスフィルタが安定するまで計算結果を破棄
if(firstNoiseCount < FIRST_NOISE_COUNT){
firstNoiseCount ++;
delay(10); // [ms]
continue;
}

// 定期的に加速度値を出力(デバッグ用)
#if 0
static unsigned long last_print = 0;
if(now - last_print > 100) {
Serial.printf("IMU ax=%.3f ay=%.3f az=%.3f norm=%.3f\n", accel[0], accel[1], accel[2], norm);
last_print = now;
}
#endif

if (norm > TAP_DELTA) {
// タップあり
float cos = 1.0;

if(tap_count == 0){
// 1回目のタップ
for(int i = 0; i < ACCEL_DIM; i++){
firstAcc[i] = accel[i];
firstNorm = norm;
first_tap_time = now;
}
}else{
// 2回目以降は同じ方向のタップのみカウントするためにコサイン類似度を計算
float innPro = firstAcc[0] * accel[0] + firstAcc[1] * accel[1] + firstAcc[2] * accel[2];
cos = innPro / (firstNorm * norm);
}

Serial.printf("Tap detected. ax=%.3f ay=%.3f az=%.3f norm=%.3f cos=%.3f\n", accel[0], accel[1], accel[2], norm, cos);
if(now - first_tap_time <= 700) { // 700ms以内なら連続タップ
if(cos > COS_SIMILAR){ // コサイン類似度で同じ方向かを判定
tap_count ++;
}
Serial.printf("tap_count=%d\n", tap_count);
}
}

if (tap_count >= TAP_COUNT) {
// ダブルタップ検出
Serial.println("Double tap detected");
tap_count = 0;
doubleTapDetected = true;
for(int i = 0; i < ACCEL_DIM; i++){
detectedAcc[i] = firstAcc[i];
}
isDoubleTapDetectionRunning = false;
}
} else {
Serial.println("IMU getAccel() returned false or not available");
}

// loopタスクから再開要求の通知が来るまで待機
if(!isDoubleTapDetectionRunning){
Serial.println("Waiting notify...");
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
isDoubleTapDetectionRunning = true;
firstNoiseCount = 0;
}

delay(10); // [ms]
}
}

// ---------------------------
// ダブルタップ検出タスクの起動
// ---------------------------
void invokeDoubleTapDetectTask(void)
{
xTaskCreate(doubleTapDetectTask, /* Function to implement the task */
"doubleTapDetectTask", /* Name of the task */
3*1024, /* Stack size in words */
NULL, /* Task input parameter */
2, /* Priority of the task */
&taskHandle_doubleTapDetect); /* Task handle. */
}

// ---------------------------
// ダブルタップ検出タスクの停止
// ---------------------------
void stopDoubleTapDetectTask(void)
{
if(isDoubleTapDetectionRunning){
isDoubleTapDetectionRunning = false;
}
}

// ---------------------------
// ダブルタップ検出タスクの再開
// ---------------------------
void resumeDoubleTapDetectTask(void)
{
if(!isDoubleTapDetectionRunning){
xTaskNotifyGive(taskHandle_doubleTapDetect);
}
}

#endif //ENABLE_TAP_DETECT
16 changes: 16 additions & 0 deletions firmware/src/driver/TapDetect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#if defined(ENABLE_TAP_DETECT)

#ifndef _TAP_DETECT_H
#define _TAP_DETECT_H


extern bool doubleTapDetected;
//extern bool isDoubleTapDetectionRunning;
extern float detectedAcc[];

void invokeDoubleTapDetectTask(void);
void stopDoubleTapDetectTask(void);
void resumeDoubleTapDetectTask(void);

#endif //_TAP_DETECT_H
#endif //ENABLE_TAP_DETECT
1 change: 1 addition & 0 deletions firmware/src/llm/LLMBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class LLMBase{
String getOutputText();
int getOutputTextQueueSize();
void setSpeaking(bool _speaking){ speaking = _speaking; };
bool isSpeaking(void){ return speaking; };
int search_delimiter(String& text);
};

Expand Down
23 changes: 22 additions & 1 deletion firmware/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "mod/VolumeSetting/VolumeSettingMod.h"

#include "driver/PlayMP3.h" //lipSync
#include "driver/TapDetect.h"

#include <HTTPClient.h>
#include <WiFiClientSecure.h>
Expand Down Expand Up @@ -168,6 +169,7 @@ void battery_check(void *args) {
}
}


//void Wifi_setup() {
bool Wifi_connection_check() {
unsigned long start_millis = millis();
Expand Down Expand Up @@ -273,7 +275,7 @@ void sw_tone()
{
M5.Mic.end();
M5.Speaker.begin();

delay(300); // AtomS3Rはこのdelayがないと鳴らないときがある
M5.Speaker.tone(1000, 100);
delay(500);

Expand Down Expand Up @@ -445,6 +447,10 @@ void setup()
avatar.set_isSubWindowEnable(true);
#endif

#if defined(ENABLE_TAP_DETECT)
invokeDoubleTapDetectTask();
#endif

//init_watchdog();

//ヒープメモリ残量確認(デバッグ用)
Expand Down Expand Up @@ -518,6 +524,21 @@ void loop()
}
#endif

#if defined(ENABLE_TAP_DETECT)
if(doubleTapDetected){
Serial.println("loop(): Double tap detected");
mod->doubleTapped(detectedAcc[0], detectedAcc[1], detectedAcc[2]);
doubleTapDetected = false;
}

// Modで重い処理をしている場合はダブルタップ検出を停止する
if(mod->isBusy()){
stopDoubleTapDetectTask();
}else{
resumeDoubleTapDetectTask();
}
#endif

if(!isOffline){
web_server_handle_client();
ftpSrv.handleFTP();
Expand Down
9 changes: 9 additions & 0 deletions firmware/src/mod/AiStackChan/AiStackChanMod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ void AiStackChanMod::display_touched(int16_t x, int16_t y)

}

void AiStackChanMod::doubleTapped(float ax, float ay, float az)
{
Serial.printf("Mod double tapped. ax=%.3f ay=%.3f az=%.3f\n", ax, ay, az);
#if defined(ARDUINO_M5STACK_ATOMS3R)
sw_tone();
STT_ChatGPT();
#endif
}

void AiStackChanMod::idle(void)
{

Expand Down
1 change: 1 addition & 0 deletions firmware/src/mod/AiStackChan/AiStackChanMod.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class AiStackChanMod: public ModBase{
void btnB_longPressed(void);
void btnC_pressed(void);
void display_touched(int16_t x, int16_t y);
void doubleTapped(float ax, float ay, float az); // 加速度センサによるダブルタップ検出のコールバック。platformio.iniで-DENABLE_TAP_DETECTを有効にしてください
void idle(void);
};

Expand Down
Loading