M5Stack用のジェスチャーユニット(PAJ7620U2)を利用して、iPadの書籍アプリ(Kindle、i文庫HDなど)を操作してみました。
iPadの起動からアプリの立ち上げ、ページめくり、画面キャプチャーまで、iPadに触れずに全ての操作が可能です。
m5StickCとiPadはBLEで接続しています。最初にペアリングするだけで、あとは特に何も設定は入りません。
BLEとの接続とコマンド送信についてはLang-shipさんを参考にさせていただきました。
記事中ではESP32-BLE-Keyboardライブラリが不安定でiOSで使えない。とありましたが、今は無事アップデートされて問題なく接続できているようです。
ソースコード
#include <M5StickC.h> // M5StickC用のライブラリ
#include <BleKeyboard.h> // Bluetoothキーボード用のライブラリ
#include <DFRobot_PAJ7620U2.h> // ジェスチャーセンサ用のライブラリ
BleKeyboard bleKeyboard("M5StickC BLE Gesture ctrl for iPad");
// Bluetoothキーボードのインスタンスを生成
DFRobot_PAJ7620U2 sensor;
// ジェスチャーセンサのインスタンスを生成
unsigned long nextVbatCheck = 0;
// バッテリー残量の更新時間
const int BATTERY_UPDATE_INTERVAL = 60000;
// バッテリー残量の更新間隔
// 画面表示関数
void displayState(int textSize, int x, int y, String description) {
M5.Lcd.setTextSize(textSize);
M5.Lcd.setCursor(x, y);
M5.Lcd.print(description);
}
void setDisplay() {
// M5StickCの画面設定
M5.Axp.ScreenBreath(8);
setCpuFrequencyMhz(80);
M5.Lcd.setRotation(3);
M5.Lcd.fillScreen(BLACK);
displayState(2, 0, 30, "flipGesture"); //画面表示
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 16);
}
// バッテリー残量を取得する関数
int getBatteryLevel() {
float voltage = M5.Axp.GetBatVoltage();
int level = (voltage - 3.2) / 0.8 * 100;
level = max(0, min(100, level));
return level;
}
// ワンショットキー関数
void pressKey(int key) {
bleKeyboard.press(key);
delay(10);
bleKeyboard.releaseAll();
}
// コマンドキーと文字キーを同時に押す関数
void pressCommandAndKey(int commandKey, char key) {
bleKeyboard.press(commandKey);
delay(50);
bleKeyboard.press(key);
delay(50);
bleKeyboard.releaseAll();
delay(50);
}
// コマンドキーとShiftキーと文字キーを同時に押す関数
void pressCommandShiftKey(int commandKey, char key) {
bleKeyboard.press(commandKey);
delay(50);
bleKeyboard.press(KEY_LEFT_SHIFT);
delay(10);
bleKeyboard.press(key);
delay(50);
bleKeyboard.releaseAll();
delay(50);
}
// ブックアプリを呼び出す関数
void callApp(String book) {
pressCommandAndKey(KEY_LEFT_GUI, 'h'); // ->command (Cmd + h)ホームボタン
pressCommandAndKey(KEY_LEFT_GUI, ' '); // ->command (Cmd + Space)検索窓
bleKeyboard.write(KEY_BACKSPACE);
delay(50);
bleKeyboard.print(book);
delay(900);
pressKey(KEY_RETURN);
delay(100);
pressKey(KEY_RETURN);
}
void setup() {
M5.begin();
Serial.begin(115200); // シリアルポートの初期化
setDisplay(); // 画面の初期化
while (sensor.begin() != 0) {
Serial.println("initial PAJ7620U2 failure!");
delay(500);
}
sensor.setGestureHighRate(true); // ジェスチャーの高速モードに設定
bleKeyboard.setBatteryLevel(getBatteryLevel()); // Bluetoothキーボードのバッテリー残量を設定
bleKeyboard.begin(); // Bluetoothキーボードを開始
}
void loop() {
M5.update();
/*
* eGestureNone eGestureRight eGestureLeft eGestureUp eGestureDown
* eGestureForward eGestureBackward eGestureClockwise eGestureAntiClockwise
* eGestureWave eGestureWaveSlowlyDisorder eGestureWaveSlowlyLeftRight
* eGestureWaveSlowlyUpDown eGestureWaveSlowlyForwardBackward
*/
DFRobot_PAJ7620U2::eGesture_t gesture = sensor.getGesture(); // ジェスチャーを取得
if (gesture != sensor.eGestureNone) {
/*
* "None","Right","Left", "Up", "Down", "Forward", "Backward",
* "Clockwise", "Anti-Clockwise", "Wave", "WaveSlowlyDisorder",
* "WaveSlowlyLeftRight", "WaveSlowlyUpDown",
* "WaveSlowlyForwardBackward"
*/
String description = sensor.gestureDescription(gesture);
Serial.println("Gesture = " + description);
// ジェスチャーに応じて処理を実行
if (description == "Right" ) {
M5.Lcd.fillScreen(RED);
displayState(4, 0, 16, description); //画面表示
pressKey(KEY_LEFT_ARROW);
}
else if (description == "Left" ) {
M5.Lcd.fillScreen(BLUE);
displayState(4, 0, 16, description); //画面表示
pressKey(KEY_RIGHT_ARROW);
}
else if (description == "Clockwise" ) {
M5.Lcd.fillScreen(WHITE);
displayState(3, 0, 20, "Capture"); //画面表示
// ScreenShot(COMMAND + SHIFT + 3)
pressCommandShiftKey(KEY_LEFT_GUI, '3');
}
else if (description == "Forward" ) {
M5.Lcd.fillScreen(YELLOW);
displayState(3, 0, 16, "App"); //画面表示
// ScreenShot(COMMAND + h)
pressCommandAndKey(KEY_LEFT_GUI, 'h');
unsigned long startTime = millis(); // 現在時刻を取得する
while (millis() - startTime < 5000) {
DFRobot_PAJ7620U2::eGesture_t gesture = sensor.getGesture();
String description = sensor.gestureDescription(gesture);
// 5秒間ループするコード
if (description == "Left" ) {
displayState(3,20, 42, "ibunko"); //画面表示
callApp("ibunko"); //i文庫を立ち上げる
break; // whileループを抜ける
}
else if (description == "Right" ) {
displayState(2,20, 42, "ComicShare"); //画面表示
callApp("ComicShare"); //ComicShareを立ち上げる
break; // whileループを抜ける
}
else if (description == "Up" ) {
displayState(3,20, 42, "Kindle"); //画面表示
callApp("kindle"); //Kindleを立ち上げる
break; // whileループを抜ける
}
}
M5.Lcd.fillScreen(BLACK);
}
}
// Bluetoothキーボードが接続されている場合
if (bleKeyboard.isConnected()) {
// ボタンAが押された場合、スクリーンショットを撮影する
if ( M5.BtnA.wasPressed() ) {
// ScreenShot(COMMAND + SHIFT + 3)
pressCommandShiftKey(KEY_LEFT_GUI, '3');
}
// ボタンBが押された場合、スクリーンショットを撮影する
else if ( M5.BtnB.wasPressed() ) {
// ScreenShot(COMMAND + SHIFT + 3)
pressCommandShiftKey(KEY_LEFT_GUI, '3');
}
}
// バッテリーレベルを更新する
if (nextVbatCheck < millis()) {
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(112, 0);
M5.Lcd.printf("%3d%%", getBatteryLevel());
nextVbatCheck = millis() + BATTERY_UPDATE_INTERVAL;
}
// 1ミリ秒待つ
delay(1);
}
スクリプトの
callApp("ComicShare")
を所有しているアプリ名に置き換えるだけです。
使い方
- ページめくりは、右・左スワイプで可能です。
- アプリの立ち上げは、手を上から被せるように下ろし、5秒以内に上右左どちらかにスワイプ。
- キャプチャーは時計回りにくるくるします。
おすすめの設置場所
右手のトラックパッドの上あたりにジェスチャーユニットを配置すると、右手を大きく動かさずに、左側に縦向けに設置されたiPadを動かせるので、すこぶる快適です。
また、iPadがスリープでブラックアウトしていても、ジェスチャーで起動することができるので、実質立ち上げから読書に至るまで一切iPadに触れることなくできるので便利です。
書類を見ながら、パソコンで原稿を書くことが多い人はとても便利だと思います。