07.T-Watch S3 plusでスマホシャッター


07.T-Watch S3 plusでスマホシャッター

・T-Watch S3 plus の画面ボタンをタッチすると、スマホカメラのシャッターが切れます。
・T-Watch S3 plus でテストしていますが、plus 無しの T-Watch S3 でも動作します。
・lvgl 9.5.0で作成しています。
・簡易的に日本語が使えるようにfontは、&lv_font_source_han_sans_sc_16_cjk を使います。
・BLEは省電力のため、出力を最低まで下げています(-12dBm)。5mは届くと思います。
・カウントダウンのbeep音が鳴ります。

操作法

1.起動すると時計には"T-Watch Shutter"と表示されています。
2.スマホ > 設定 > 接続設定 > Bluetooth > 新しいデバイスとペア設定 > T-Watch Shutter > ペア設定する

3.スマホ > カメラを開きます。

4.時計の"T-Watch Shutter"をタッチ > カウントダウンしてカメラのシャッターが切れます。

* BLE接続ができていない場合は未接続を表示します。

各Ver

・(ボード) esp32 by Espressif Systems 3.3.6 (固定)です。3.3.11では動作しませんでした。3.3.7以降はESP32-S3でBLE起動時にクラッシュする既知の不具合があるそうです。
・Arduino IDE 2.3.10 (2026-06-09)
・LilyGoLib 0.2.0 (最新)
・SensorLib 0.3.3
・RadioLib 7.4.0
・TinyGPSPlus 1.1.0
・XPowersLib 0.3.1
・lvgl 9.5.0
・ESP32 BLE Keyboard 0.3.4
・NimBLE-Arduino 2.2.3
・esptool 5.1.0
・FFat 3.3.6
・FS 3.3.6
・Wire 3.3.6
・SPI 3.3.6
・ESP_I2S 3.3.6
・Preferences 3.3.6
* 今回使用していないのも入っています。

スケッチ


// セルフタイマー シャッター
// 画面タッチで 1秒ごとにビープ音 3秒後にスマホのシャッター

// ボード > esp32 by Espressif Systems 3.3.6固定 (arduino-esp32コア)
//  (3.3.7以降はESP32-S3でBLE起動時にクラッシュする既知の不具合がある)
#include <LilyGoLib.h>     // LilyGoLib 0.2.0(最新)を使用
#include <LV_Helper.h>     // lvgl by kisvegabor 9.4.0 出荷プログラムで使用 → 9.5.0
#include <BleKeyboard.h>   // BLEでスマホシャッター使用
#include <NimBLEDevice.h>  // BLE出力パワーを下げる
#include <ESP_I2S.h>       // I2S使用
I2SClass i2s;
BleKeyboard bleKeyboard("T-Watch Shutter", "LilyGO", 100);  // (スマホに表示のデバイス名,機種,100)
#define SPK_BCLK_PIN 48                                     // MAX98357A I2S 3.2W MONO AMP
#define SPK_WCLK_PIN 15                                     // MAX98357A LRCLK
#define SPK_DOUT_PIN 46                                     // MAX98357A DIN
#define SPK_SAMPLE_RATE 16000                               // (Hz) 毎秒RATE回発生
#define BEEP_FREQ 1000                                      // 音の高さ(Hz) 3秒後は別
//参考 NHK:予報440Hz/正報880Hz, NTT電話時報:1kHz
#define DURATION_MS 100  // 音の長さ(mS) 3秒後は別
//参考 NHK予報:正弦波0.1S,無音0.9S,3回繰返す, 正報:正弦波1S一定音量、次の2秒で直線減衰

void speaker_init() {                                             // スピーカ初期設定関数
  i2s.setPins(SPK_BCLK_PIN, SPK_WCLK_PIN, SPK_DOUT_PIN, -1, -1);  // I2Sのピン設定
  // (int8_t bclk,int8_t ws,int8_t dout,int8_t din=-1,int8_t mclk=-1)
  i2s.begin(I2S_MODE_STD, SPK_SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO, -1);  //I2S初期化
}

static void speaker_beep(uint32_t frequency, uint32_t duration_ms) {  // HzとmSでbeep関数
  // if (!spk_tx_handle) return;// 呼出中はこの処理をブロック
  uint32_t num_samples = (SPK_SAMPLE_RATE * duration_ms) / 1000;       // 発生音数
  for (uint32_t i = 0; i < num_samples; i++) {                      // サイン波データを生成しながらI2Sへ書込み
    float sample_f = sin(2.0 * PI * frequency * i / SPK_SAMPLE_RATE);  // sin波の計算 16bit モノラル
    int16_t sample = (int16_t)(sample_f * 15000);                      // 音量は32767まで
    i2s.write((uint8_t *)&sample, sizeof(sample));                     // 1サンプル(16bit=2bytes)を書込み
  }
}

// セルフタイマー
static lv_timer_t *timer_handle = NULL;  //
static int countdown_count = 0;          // 経過秒数(1,2,3)
static lv_obj_t *shutter_label = NULL;   //

static void countdown_timer_cb(lv_timer_t *t) {                       // 3秒カウントダウン後にスマホシャッターを押す関数
  countdown_count++;                                                  // 1カウント(秒)追加
  speaker_beep(BEEP_FREQ, DURATION_MS);                               // fHz を tms鳴らす
  if (countdown_count < 3) {                                       // 3秒以下なら
    lv_label_set_text_fmt(shutter_label, "%d", 3 - countdown_count);  // カウントダウン表示
  } else {                                                            // 3秒経ったら
    lv_obj_align(shutter_label, LV_ALIGN_CENTER, -35, -5);// 位置
    lv_label_set_text(shutter_label, "シャッター完");                       // ラベルに表示
    delay(1000);                                                            // 1秒待つ
    speaker_beep(2000, 300);                                                // 2kHz 0.3秒鳴らす
    if (bleKeyboard.isConnected()) bleKeyboard.write(KEY_MEDIA_VOLUME_UP);  // スマホのシャッターを押す
    lv_timer_del(timer_handle);                                             //
    timer_handle = NULL;                                                    //
    countdown_count = 0;                                                    // カウントダウン変数リセット
  }
}

static void shutter_btn_event_cb(lv_event_t *e) {                                         // 画面シャッターボタンを押した時の関数
  lv_event_code_t code = lv_event_get_code(e);                                            // イベントコードを取得
  if (code != LV_EVENT_CLICKED) return;                                                   // クリック以外は戻る
  if (timer_handle != NULL) return;                                                       // すでにカウントダウン中なら戻る
  if (!bleKeyboard.isConnected()) {                                                       // bleKeyboardに接続してなかったら
    lv_obj_set_style_text_color(shutter_label, lv_color_make(255, 69, 0), LV_PART_MAIN);  // 赤字 (Orangered)
    lv_label_set_text(shutter_label, "  BLE 未接続");                                     // ラベルに表示
    return;                                                                               // 戻る
  }
  countdown_count = 0;                                                               // カウントダウン変数リセット
  lv_obj_center(shutter_label);                                                      // 位置
  lv_obj_set_style_text_color(shutter_label, lv_color_make(0, 0, 0), LV_PART_MAIN);  // 黒字
  lv_label_set_text(shutter_label, "3");                                             // ラベルに表示
  timer_handle = lv_timer_create(countdown_timer_cb, 1000, NULL);                    // 1s周期のタイマーを開始
  // 新しいlv_timer(定期的なコールバック関数,呼出間隔mS,カスタムパラメータ)
}

static void create_ui() {                               // 画面表示の関数
  lv_obj_t *scr = lv_scr_act();                         // 現在の画面
  lv_obj_set_style_bg_color(scr, lv_color_black(), 0);  // 黒地

  lv_obj_t *btn = lv_btn_create(scr);                                      // 画面にボタン作成
  lv_obj_set_size(btn, 238, 120);                                          // サイズ
  lv_obj_center(btn);                                                      // 位置
  lv_obj_set_style_radius(btn, 30, LV_STATE_DEFAULT);                      // 角丸
  lv_obj_set_style_bg_color(btn, lv_color_make(175, 238, 238), 0);         // 水色 Paleturquoise
  lv_obj_add_event_cb(btn, shutter_btn_event_cb, LV_EVENT_CLICKED, NULL);  // タッチしたらcb関数へ

  shutter_label = lv_label_create(btn);                                                // ボタンにラベル作成
  lv_obj_center(shutter_label);                                                        // 位置
  lv_obj_set_style_text_font(shutter_label, &lv_font_source_han_sans_sc_16_cjk, 0);    // 文字の種類
  lv_obj_set_style_transform_zoom(shutter_label, 435, 0);                              // x/256倍に拡大 1.5倍
  lv_obj_align(shutter_label, LV_ALIGN_CENTER, -40, -10);                              // 拡大文字はずれるので補正
  lv_obj_set_style_text_color(shutter_label, lv_color_make(0, 100, 0), LV_PART_MAIN);  // 緑字 (Darkgreen)
  lv_label_set_text(shutter_label, "T-Watch Shutter");                                 // 表示文字(スマホの接続先)
}

void setup() {
  Serial.begin(115200);                                // シリアルモニタの通信速度 Arduino-IDEと合っていないと表示しません
  delay(100);                                          // 0.1秒待つ
  instance.begin();                                    // LilyGoLibを初期化
  bool bleOk = bleKeyboard.begin();                    // BLEキーボードを初期化
  if (bleOk) NimBLEDevice::setPower(ESP_PWR_LVL_N12);  // BLE最小出力 -12dB/m
  // -12dBm:ESP_PWR_LVL_N12(N12),-9dBm:(N9),-6dBm:(N6),
  // -3dBm:(N3),0dBm:(P0),3dBm:(P3),6dBm:(P6),9dBm:(P9)
  beginLvglHelper(instance);    // LVGLを初期化
  instance.setBrightness(100);  // 明るさ1-255,0(消)
  speaker_init();               // スピーカーアンプを初期化
  create_ui();                  // 画面
}
void loop() {
  lv_timer_handler();  //
  delay(5);            // 5mS待つ
}
* flash memory(3.1Mbyte)のうち、スケッチが42%使用。RAM(327kbyte)のうち、global変数が11%使用、local変数で291kbyte使用可能。(1000byte=1kbyteで計算)