はじめに
今、ESP32ボードを使ったネコ型おしゃべりロボットを開発中で、次にやりたいことは下記。
まずは、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とアクセスしてスケッチファイルを開くと、コード例が記載されている。
最小要件テーブルの設計
まず、ミニマムな要件として設計したテーブルは下記
今後は、このテーブルの各カラムを拡張(例えば、phraseテーブルに時間帯のカラムを追加して、ボタンを押された時刻が朝の時間だったら、朝に相当するフレーズを話すなど)する必要があるが、いきなりやるとややこしくなるので、一旦これで。
やりたいこと
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に質問。回答がこちら
5番の「Arduino IDEの「ツール」メニューから「ESP32 LittleFS Data Upload」を選択」とあるが、Arduino2系だと1-4を実行したとしても表示されない。2系ではまだGUIでのdata uploadがサポートされていないため。
CLI経由でのアップロード方法もあるみたいだが、今回は面倒だが下記の別方法で行った。
Arduino 1.8系を別にインストールして、作成したスケッチファイルを1.8系で起動。
すると、1-4が終わっていれば、ツールメニューから「ESP32 LittleFS Data Upload」を選択できるので、アップロード。
ただし、dataディレクトリの中のファイルが、合計3MBを超えているとESP32の場合は容量オーバーで、「File system is full」エラーが表示されるので、下記2つのいずれかを対策する必要がある。
4番と6番も個人的にハマったポイントで
esp32にアップロードしたいデータ自体は、スケッチファイルと同階層にdataフォルダを用意して、その中に配置する必要があるが、スケッチ中でデータベースを指定する際のパスは「/data/DB名.db」ではなく「/littlefs/DB名.db」にする必要がある。
LittleFSを使ってSqlite3へアクセスしてデータを取ってくる
esp32 sqlite3ライブラリの中にあったlittlefsの記述例を参考に改変と、今回に不要な部分を削除
最終コードがこちら
#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()関数内の
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に移行したい。移行に関する記事はこちら。
コメント