方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

【ESP32】LittleFSとSqlite3を使用してデータを呼び出す

ファイルシステムを介したデータのアップロード
この記事は約13分で読めます。

はじめに

今、ESP32ボードを使ったネコ型おしゃべりロボットを開発中で、次にやりたいことは下記。

1)ボタンを押したら、元々データベースに保存されていたフレーズからランダムに1つピックアップして猫型ロボットに話をしてほしい

2)表情(LCDディスプレイを使った目のアニメーションと、サーボモータを使った口の動き)に合わせた音声データを再生したい(例:楽しそうなフレーズなら楽しそうな表情)

3)1日の時間帯に応じて話すフレーズを変えたい(寝る前ならおやすみなさいなど)。また祝日や誕生日を祝うなど

4)天気を読み取って、ユーザーがアプリで設定した時刻にお知らせした欲しい(例:午前8時に「今日は午後から雨が降る予定だから折り畳み傘を忘れずに」など)

まずは、1と2から取り組む
いきなり全部は難しいので、ミニマムにタスクを分解

  • DBとファイルシステムの選定
  • 最小要件テーブルの設計
  • DBへのデータ格納
  • ボタンを押したら、DBにアクセスしてデータを取ってくる部分の実装

DBとファイルシステムの選定

今は、プロトタイプ段階でESP32ボードで開発中であり

  • DB:sqlite3
  • ファイルシステム:LittleFS

を使うことに決定。

ファイルシステムについて

今まではソフトウェアのプログラムを主にやっていたので、そもそもファイルシステムって何ぞや!?という感じだったのだが、こちらのサイトがわかりやすかった。

https://www.ei.tohoku.ac.jp/xkozima/lab/espTutorial5.html

ファイルシステムを介したデータのアップロード
ファイルシステムを介したデータのアップロード

Arduinoでは、ソフトウェアで言うところのプログラミングコードは「スケッチ」と呼ばれており、フラッシュメモリの中の一部がコード部分として割り当てられている。

その他のファイルに関しては別でファイルシステム領域が割り当てられており、一旦ファイルシステム領域にデータ(ログデータや画像データ,HTML/CSS/JS などのウェブデータなど)をアップロードすることで初めて、Arduinoに記載したコードからデータにアクセスすることができる(データにアクセスするためのコードを記載する必要があることはもちろん)。

今回は、ファイルシステムをESP32のflashメモリにデータ(DBと目の画像データ、音声データ)を格納する用として使う。

  • まずSDカードを使うか使わないかで、大きく分かれるが、今回はSDカードは使わずにESP32の内部フラッシュメモリのみを使いたかったので、そうなると選択肢はSPIFFSを使うかLittleFSを使うかになる。
  • 現在は、SPIFFSは非推奨で将来削除される可能性があるので、LittleFSを使うことに。LittleFSの方がSPIFFSと比較してデータの読み書きの速度も速い
  • LittleFSは単体のLittleFS_esp32というライブラリとして開発されていたが、現在はArduino esp32の本体のライブラリに取り込まれているので、Arduino core for the esp32ライブラリをインストールすればOK

https://github.com/lorol/LITTLEFS

Arduino core for the esp32

https://github.com/espressif/arduino-esp32

 

DB:Sqlite3

他にもDBの選択肢はあると思うが、現段階では、軽量なDBで素早く、ボタンを押したら表情と話す内容が連動することを検証したかったので、sqlite3を選定。
ライブラリはこちら。

https://github.com/siara-cc/esp32_arduino_sqlite3_lib

このライブラリでは、LittleFSもサポートされていて、examples→sqlite3_littlefs→sqlite3_littlefs.inoとアクセスしてスケッチファイルを開くと、コード例が記載されている。

https://github.com/siara-cc/esp32_arduino_sqlite3_lib/blob/master/examples/sqlite3_littlefs/sqlite3_littlefs.ino

最小要件テーブルの設計

まず、ミニマムな要件として設計したテーブルは下記

・表情パターンを格納するテーブル(例:id:1, name:happy、id:2, name:sad)
・表情パターンのidを外部キーとして持つ、音声ファイルテーブル(phrase)と目の画像テーブル(eye_display)

今後は、このテーブルの各カラムを拡張(例えば、phraseテーブルに時間帯のカラムを追加して、ボタンを押された時刻が朝の時間だったら、朝に相当するフレーズを話すなど)する必要があるが、いきなりやるとややこしくなるので、一旦これで。

Sqlite3テーブル

やりたいこと

1)ボタンを押した時に、sqlite3データベースにアクセスして、expressionテーブルからランダムにidを取得する

2)取得したidをprimaryキーとして、phraseカラムとeye_displayカラムから、それぞれ対応するvoice_path, image_pathを取ってくる(この段階で、表情にマッチした音声データと目の画像データを取得)

3)DBから取得してきた音声データと目の画像データを表示

この中で、まずは1をできれば良いので、「sqlite3データベースにアクセスして、expressionテーブルからランダムにidを取得」を目標にして次に進める。

DBの作成とデータ格納

sqlite3のDB作成と、データ格納に関しては、ターミナルからCLI(コマンドラインインターフェース)でやる方法とGUIでやる方法があるが、お好きな方で

CLI:https://rakuishi.com/archives/4535/

GUI:DB Browser for SQLiteなどがある
https://qiita.com/hiesiea/items/0f65536c23fcd248127e

データのアップロード

ここは、ハマってしまったので、Chat-gptに質問。回答がこちら

Arduino IDEでは、特定のプラグインを使用してESP32のLittleFSにファイルをアップロードすることができます。lorolという作者による**arduino-esp32fs-plugin**というプラグインがその一例です。このプラグインを使用すると、スケッチの「ツール」メニューから「ESP32 LittleFS Data Upload」オプションを選択することでデータをESP32にアップロードすることができます。
具体的な手順は以下の通りです:

1. **arduino-esp32fs-plugin**をダウンロードします。
2. ダウンロードしたZIPファイルを解凍し、得られたフォルダを Arduino/tools/ESP32FS/tool/esp32fs.jar というパスに配置します。ここで、**Arduino**はArduino IDEがインストールされているディレクトリを指します。
3. Arduino IDEを再起動します。
4. スケッチの階層に data という名前のフォルダを作成し、その中に clocky.db ファイルを置きます。
5. Arduino IDEの「ツール」メニューから「ESP32 LittleFS Data Upload」を選択します。
6. スケッチをESP32にアップロードします。

これにより、clocky.db ファイルはESP32のLittleFSにアップロードされ、スケッチからアクセス可能となります。あなたのコードで db_open(“/clocky.db”, &db1) を呼び出すと、正常にデータベースが開けるはずです。
ただし、スケッチ中でデータベースを指定する際のパスは、**”/clocky.db”ではなく”/littlefs/clocky.db”とする必要があります。これは、LittleFSプラグインがアップロードしたデータを/littlefs**ディレクトリ以下に配置するためです。

5番の「Arduino IDEの「ツール」メニューから「ESP32 LittleFS Data Upload」を選択」とあるが、Arduino2系だと1-4を実行したとしても表示されない。2系ではまだGUIでのdata uploadがサポートされていないため。
CLI経由でのアップロード方法もあるみたいだが、今回は面倒だが下記の別方法で行った。

Ardiuno2系だと、『ESP32 LittleFS Data Upload』が表示されない

 

Arduino 1.8系を別にインストールして、作成したスケッチファイルを1.8系で起動。
すると、1-4が終わっていれば、ツールメニューから「ESP32 LittleFS Data Upload」を選択できるので、アップロード。

1.8系を別途インストールして起動時に選択
Ardiuno1.8系だと、『ESP32 LittleFS Data Upload』が表示される

ただし、dataディレクトリの中のファイルが、合計3MBを超えているとESP32の場合は容量オーバーで、「File system is full」エラーが表示されるので、下記2つのいずれかを対策する必要がある。

1. データのサイズを減らす: データディレクトリの不要なファイルを削除したり、ファイルをより効率的な形式に変換(たとえば、画像をより効率的な形式に変換)したりして、データの全体的なサイズを減らすことができます。
2. ファイルシステムの領域を増やす: Arduino IDEの「ツール」メニューから、Flash Sizeという設定を見つけることができます。これを変更して、LittleFS領域にもっとスペースを割り当てることができます。ただし、これは全体的なアプリケーションのサイズに影響を与えます。

4番と6番も個人的にハマったポイントで

esp32にアップロードしたいデータ自体は、スケッチファイルと同階層にdataフォルダを用意して、その中に配置する必要があるが、スケッチ中でデータベースを指定する際のパスは「/data/DB名.db」ではなく「/littlefs/DB名.db」にする必要がある。

dataフォルダの中に、作成したdbファイルを格納

 

LittleFSを使ってSqlite3へアクセスしてデータを取ってくる

esp32 sqlite3ライブラリの中にあったlittlefsの記述例を参考に改変と、今回に不要な部分を削除

https://github.com/siara-cc/esp32_arduino_sqlite3_lib/blob/master/examples/sqlite3_littlefs/sqlite3_littlefs.ino

最終コードがこちら

C++
#include <sqlite3.h>
#include <LittleFS.h>
#define FORMAT_LITTLEFS_IF_FAILED true
int last_id = 0;
static int callback(void *NotUsed, int argc, char **argv, char **azColName) {
   for(int i = 0; i < argc; i++) {
      if(strcmp(azColName[i], "id") == 0) {
         last_id = atoi(argv[i]);
      }
   }
   return 0;
}
int db_open(const char *filename, sqlite3 **db) {
   int rc = sqlite3_open(filename, db);
   if (rc) {
       Serial.printf("Can't open database: %s\n", sqlite3_errmsg(*db));
       return rc;
   } else {
       Serial.printf("Opened database successfully\n");
   }
   return rc;
}
char *zErrMsg = 0;
int db_exec(sqlite3 *db, const char *sql) {
   Serial.println(sql);
   long start = micros();
   int rc = sqlite3_exec(db, sql, callback, NULL, &zErrMsg);
   if (rc != SQLITE_OK) {
       Serial.printf("SQL error: %s\n", zErrMsg);
       sqlite3_free(zErrMsg);
   } else {
       Serial.printf("Operation done successfully\n");
   }
   Serial.print(F("Time taken:"));
   Serial.println(micros()-start);
   return rc;
}
const int buttonPin = 14;     // ボタンが接続されているデジタルピンの番号
int buttonState = 0;         // ボタンの現在の状態を保存する変数
int lastButtonState = HIGH;     // ボタンの前回の状態を保存する変数
void setup() {
   Serial.begin(115200);
   sqlite3 *db;
   int rc;
   pinMode(buttonPin, INPUT_PULLUP);  // ボタンピンを入力として設定
   if (!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)) {
       Serial.println("Failed to mount file system");
       return;
   }
   sqlite3_initialize();
}
void loop() {
   buttonState = digitalRead(buttonPin); // ボタンの状態を読み取る
   // ボタンが押された瞬間だけを検出
   if (lastButtonState == HIGH && buttonState == LOW) {
    sqlite3 *db;
    if (db_open("/littlefs/clocky.db", &db))
        return;
    int rc = db_exec(db, "SELECT id FROM expression ORDER BY RANDOM() LIMIT 1;");
    if (rc == SQLITE_OK) {
        Serial.printf("Random expression id: %d\n", last_id);
    }
    sqlite3_close(db);
  }
   lastButtonState = buttonState; // 現在のボタンの状態を保存
}

ここでハマったポイントは、void loop()関数内の

C++
int rc = db_exec(db, "SELECT id FROM expression ORDER BY RANDOM() LIMIT 1;");

この部分で、sqlite3 dbから、表情パターンのIDをランダムに取得してrc変数に格納しているので、この変数rcをSerial.println(rc); で出力すればSerial Monitorに出力されるのではと思ったのだが、そうではなかった。

ここもChat-gptに質問して、出てきた回答がこちら

完成!

これで、ボタンを押すたびに、Serialモニターにランダムなid値が表示されるようになりました。
右側のSerialモニターに、ボタンを押すたびに『Random expression id : 数字』という形で、ランダムな数値が表示されているのが分かります。

次は、この取得したidをもとに、音声カラムと目の画像カラムからそれぞれ該当するvoice_path, image_pathを取得して、表情パターンにマッチした目の動きと音声フレーズの再生をできるようにしたいと思う。

また、ArdinoからPlatformIOに移行したい。移行に関する記事はこちら。

 

コメント

タイトルとURLをコピーしました