09.T-Watch S3 Plusにスマホシャッター追加


09.T-Watch S3 Plusにスマホシャッター追加

前回の"08.T-Watch S3 plusで時計まとめ"に"07.T-Watch S3 plusでスマホシャッター"を追加しました。また、begin にオプションを加える事により起動時間の短縮になりました。

ボードのVer変更

espressif > arduino-esp32 > Release list
https://github.com/espressif/arduino-esp32/releases
にて、3.3.7以降は ESP32-S3 で BLE 起動時にクラッシュする既知の不具合があるらしいです。platformio.iniのプラットフォームのVerを1つずつ変更し、.pio を削除後に実行をして確認しました。

ボードマネージャのVer→プラットフォームのVer:結果
3.3.6 → 55.03.36-1 : シャッターは動作する
3.3.7 → 55.03.37 : 連続再起動をして表示しない
3.3.8 → 55.03.38-1 : エラー
3.3.9 → 55.03.39 : 連続再起動をして表示しない
3.3.10 → 欠番
3.3.11 → 55.03.311 : 連続再起動をして表示しない
3.3.12 → 55.03.312 : エラー
4.0.0-alpha1(最新) → Verの表示無し

よって、3.3.7~12は正常動作しないので、現在の所(ボード) esp32 by Espressif Systems のVerは"3.3.6"に固定します。

スマホシャッターの操作法

操作は、"07.T-Watch S3 plusでスマホシャッター"と同じです。
1.時計には"T-Watch Shutter"と表示されています。
2.スマホ > 設定 > 接続設定 > Bluetooth > 新しいデバイスとペア設定 > T-Watch Shutter > ペア設定する
* スマホにより異なります。
3.スマホ > カメラを開きます。
4.時計の"T-Watch Shutter"をタッチ > カウントダウンしてカメラのシャッターが切れます。
* BLE接続ができていない場合は"未接続"を表示します。
* 2分間の未操作で時計の電源がoffします。

起動時間短縮

S3 Plus には S3 に無い GPS チップがあります。S3 と S3 Plus は同じライブラリを使用し、GPS チップがあるかを起動時に判別しています。GPS チップがある場合は判定結果(起動)が早いのですが、無い場合には判定(起動)に数秒かかっているようです。よって S3 の場合は、判定しないようにしました。すると起動時間が6秒→1秒に短縮できました。また、FATファイルシステムも使用しないとしました。src\main.cpp の setup() で以下のように修正しました。

・変更前
instance.begin(); // LilyGoLibを初期化。

・変更後
#if defined(ARDUINO_T_WATCH_S3) // S3の場合
instance.begin(NO_INIT_FATFS | NO_HW_GPS); // FatFsとGPSのチェックはしない
#else // S3 Plusの場合
instance.begin(NO_INIT_FATFS); // GPSのチェックはする
#endif

スケッチ


// 時計 v1.2 スマホシャッター (高速起動)
// 次回検討 翌日アラーム タッチ振動
// deepsleep アラーム時刻10分前と5分前に振動 時報

// 起動時の画面は(12)で、横3x縦2の6画面タイル
// (11) アラーム時刻設定 (12) デジタル時計表示 (13) 明るさの設定
// (21) スマホシャッター (22) 手動で時刻設定  (23) アナログ時計表示

// (ボード) esp32 by Espressif Systems 3.3.6
// 3.3.0(LilyGoが要求)-3.3.6(Bluetoothが動作)
// lv_conf.hでデフォルトfont:中日韓16pxのlv_font_simsun_16_cjk を使用
// 英数字のサイズ変更は、フォントを変更
// 48:D時計の時刻 28:ローラーの選択結果 24:いろいろ 16:A時計の文字盤で小
// 現時刻の年は、2026に固定。2秒ごとに時刻とBT残量を更新します
#include <LilyGoLib.h>   // LilyGoLib 0.2.0
#include <LV_Helper.h> // lvgl by kisvegabor 9.5.0
#include <Preferences.h> // 不揮発性メモリNVSを使用 電源断でもアラーム時刻を覚える
Preferences prefs;                   // Preferencesオブジェクト
#include <freertos/FreeRTOS.h>       // リアルタイムオペレーティングシステム
#include <freertos/task.h>           // タスク管理
#include <math.h>                    // アナログ時計で三角関数を使用
#include <time.h>                    // 時間を使用
#include <BleKeyboard.h>   // BLEでスマホシャッター使用
BleKeyboard bleKeyboard("T-Watch Shutter", "LilyGO", 100);  // (スマホに表示のデバイス名,機種,100)
#include <NimBLEDevice.h>  // BLE出力を下げる
#include <ESP_I2S.h>       // I2S使用
I2SClass i2s;
#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秒で直線減衰
SET_LOOP_TASK_STACK_SIZE(16 * 1024); // スタックサイズを16〜32KBに増やす 標準8kB
// (スワイプ時に大きなスタックサイズが必要 重いタイルがあると超える)

// 変数
int BattPer;                    // バッテリー残量0-100(%)
bool BattCher;                  // 1:稲妻充電マーク, 0:" "
const char *bright_buf = "100"; // 明るさ変数 0(off), 1-255

lv_obj_t *label, *set_label, *cxl_label,
    *time_label; // ラベル,セット,キャンセル,時間表示
lv_obj_t *label11, *label12, *label14, *label15; // デジタル時計用 1行目
lv_obj_t *label21, *label22, *label32, *label42; // デジタル時計用 2-4行目
lv_obj_t *btn, *set_btn, *cxl_btn;               // ボタン,セット,キャンセル
lv_obj_t *day_roller, *hour_roller, *min_roller; // 日,時,分のローラー
lv_obj_t *tv;                                    // タイル

static lv_subject_t day_subject3;    // 日 アラーム(今日 or 明日)
static lv_subject_t hour_subject3;   // 時
static lv_subject_t minute_subject3; // 分
static lv_subject_t time_subject3;   // 上のsubjectが入るグループ

static lv_subject_t
    month_subject; // 月 値が変わると、登録コールバックが呼ばれます
static lv_subject_t day_subject;    // 日 現在時刻
static lv_subject_t hour_subject;   // 時
static lv_subject_t minute_subject; // 分
static lv_subject_t time_subject;   // 上のsubjectが入るグループ

static lv_subject_t *time_group_array_subject3[] = {
    &day_subject3, &hour_subject3,
    &minute_subject3}; // グループにする アラーム時刻
static lv_subject_t *time_group_array_subject4[] = {
    &month_subject, &day_subject, &hour_subject,
    &minute_subject}; // グループにする 現時刻
const char *month_options = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12"; // 月 1-12
const char *day_options02 = "0\n1"; // 日 0:今日, 1:翌日
const char *day_options31 =         // 日 1-31
    "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21"
    "\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31";
const char *hour_options = // 時 0-23
    "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n"
    "21\n22\n23";
const char *minute_options = // 分 0-59
    "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n"
    "21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39"
    "\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n"
    "58\n59";
int32_t month4, day4, hour4, minute4, day3, hour3,
    minute3; // 現在時刻, アラーム時刻の表示用
int32_t nowMonth, nowDay, nowHour, nowMinute,
    setDay; // set_btnで引き渡す現在の月&日&時&分,アラームの日&時&分
int32_t setHour = 0, setMinute = 0; // 復帰後も保持
char youbi[7][5] = {"日0", "月1", "火2", "水3",
                    "木4", "金5", "土6"}; // 曜日配列
static const char *btnm_map[] = {
    "255", "200", "150", "\n", // 1行目 2行3列にする
    "100", "50",  "1",   ""};  // ボタンmap 最後に""が必要

int16_t dispX, dispY;   // 機種ごとのXYサイズ
char buf[64], buf2[64]; // 文字操作用
bool firstDisp = true;  // 画面が起動時状態

// (23.アナログ時計) 変数
static lv_obj_t *tileM23 = nullptr;
static lv_obj_t *clock_scale = nullptr; // 文字盤(分目盛りリング)
static lv_obj_t *hand_hour_main = nullptr;
static lv_obj_t *hand_hour_tail = nullptr;
static lv_obj_t *hand_min_main = nullptr;
static lv_obj_t *hand_min_tail = nullptr;
static lv_obj_t *hand_sec_main = nullptr;
static lv_obj_t *hand_sec_tail = nullptr;
static lv_obj_t *clock_center = nullptr; // 中心のキャップ
static lv_timer_t *clock_timer = nullptr;

static lv_coord_t clock_cx = 0,
                  clock_cy = 0; // 文字盤(clock_scale)ローカル座標での中心
static float clock_radius = 0;

// (21.スマホシャッター) セルフタイマー
static lv_timer_t *timer_handle = NULL;  //
static int countdown_count = 0;          // 経過秒数(1,2,3)
static lv_obj_t *shutter_label = NULL;   //
// 変数設定終わり



// 関数
// ==============================
// キャンセル(戻る)ボタン 各画面でcxl_btnを押したら時計画面へ
// ==============================
static void cxl_clicked_event_cb(lv_event_t *e) {
  lv_tileview_set_tile_by_index(tv, 1, 0,
                                LV_ANIM_ON); // 時計タイルへ戻る 移動アニメon
}

// ==============================
// 11.アラーム設定
// ==============================
// (11.アラーム設定) 3ローラーの値をlabelに表示
static void time_observer_cb3(lv_observer_t *observer, lv_subject_t *subject) {
  day3 = lv_subject_get_int(lv_subject_get_group_element(subject, 0));
  hour3 = lv_subject_get_int(lv_subject_get_group_element(subject, 1));
  minute3 = lv_subject_get_int(lv_subject_get_group_element(subject, 2));
  // lv_subject_get_int(
  // )は、整数Subjectの現在の値を取得(Subjectへのpointer)、戻り値:現在の値
  // lv_subject_get_group_element( )は、
  // Subject
  // Groupのリストから要素を取得(Group型のSubjectへのpointer,取得する要素のインデックス)
  // 戻り値:リスト内のインデックス付Subjectへのpointer
  // インデックスが範囲外の場合はNULL

  lv_obj_t *label = (lv_obj_t *)lv_observer_get_target(observer);
  // Observerのターゲットを取得(Observerへのpointer)、戻り値:保存されたターゲットへのpointer
  lv_label_set_text_fmt(label,
                        "Alam\n  %" LV_PRId32 "+\n%" LV_PRId32 ":%02" LV_PRId32,
                        day3, hour3, minute3); // ローラーの値
}

// (11.アラーム設定) set_btnを押したら呼出される関数
static void set_btn_clicked_event_cb3(lv_event_t *e) {
  // set_btn = lv_event_get_target_obj(e);
  //  イベントの元のターゲットobjを取得(イベント記述子へのpointer)、戻り値:event_codeの元のターゲットへのpointer
  // Serial.printf("Alam_set_btn %1d+ %2d:%02d%n", setDay, setHour, setMinute);
  // シリアルモニタに日時分を表示

  setHour = hour3; // アラーム時
  if (day3 > 0)
    setHour = setHour + 24; // 翌日を考慮
  setMinute = minute3;      // アラーム分

  prefs.begin("myApp", false);      // (開く名前空間, 書込モード)
  prefs.putInt("setHour", setHour); // 値を保存(キーは文字列、値) アラームの時
  prefs.putInt("setMinute",
               setMinute); // 値を保存(キーは文字列、値) アラームの分
  // setHour = prefs.getInt("setHour");
  prefs.end();                                         // NVSを閉じる
  lv_tileview_set_tile_by_index(tv, 1, 0, LV_ANIM_ON); // タイル位置移動
  // アクティブタイル設定(tileviewのポインタ,タイルのカラム,行,アニメLV_ANIM_ON
  // or LV_ANIM
}

// ==============================
// 12.D時計
// ==============================
// (12.時計) 時計画面loop表示
void tileM12_update() {
  struct tm timeinfo; // 現在時刻を取得する構造体 tm
  instance.rtc.getDateTime(&timeinfo); // RTCをtimeinfoに格納

  size_t written = strftime(buf, 64, "'%y/%m/%d(", &timeinfo);// 西暦下2桁/月/日(
  lv_label_set_text(label11, buf);// 年/月/日 表示

  written = strftime(buf2, 64, "%w", &timeinfo);// 曜日番号 0(日)-6(土)
  sprintf(buf, "%s", youbi[atoi(buf2)]);// 曜日番号から曜日文字列を取得
  lv_label_set_text(label12, buf);// 曜日表示

  written = strftime(buf, 64, "%P", &timeinfo); // am/pm
  lv_label_set_text(label21, buf);// am/pm表示

  written = strftime(buf, 64, "%l:%M", &timeinfo); // 時(1-12を空白埋め):分
  lv_label_set_text(label22, buf); // 時:分 表示

  sprintf(buf, "%2d:%02d", setHour, setMinute); // アラーム時:分
  lv_label_set_text(label32, buf); // アラーム時:分 表示

  int32_t ato =
      (setHour * 60 + setMinute) - (timeinfo.tm_hour * 60 + timeinfo.tm_min); // アラームまでの残り分
  if (ato < -999) // 999分以上過ぎていたら
    ato = 0; // 0にする
  sprintf(buf, "%d", ato); // 残りの分
  lv_label_set_text(label42, buf); // 残りの分 表示

  BattCher = instance.pmu.isCharging(); // 充電中かどうかを取得
  lv_label_set_text_fmt(label14, "%s", BattCher ? LV_SYMBOL_CHARGE : "  "); // 充電中なら稲妻マーク, そうでなければ空白

  BattPer = instance.pmu.getBatteryPercent(); // バッテリー残量を取得
  if (BattPer < 0) // 0%未満なら0
    BattPer = 0;
  if (BattPer > 100) // 100%を超えていたら100
    BattPer = 100;
  if (BattPer < 30) { // 30%未満のなら赤色
    lv_obj_set_style_text_color(label15, lv_color_make(255, 0, 0),
                                LV_PART_MAIN);
  } else if (BattPer < 50) { // 50%未満なら黄色
    lv_obj_set_style_text_color(label15, lv_color_make(255, 255, 0),
                                LV_PART_MAIN);
  }

  String s = "   " + String(BattPer); // 文字列にして前にスペースを付ける
  s = s.substring(s.length() - 3); // 後ろ3桁をとる
  lv_label_set_text_fmt(label15, "%s%%", s); // バッテリー残量表示
  lv_obj_set_style_text_color(label15, lv_color_make(0, 255, 255),
                              LV_PART_MAIN); // 水色
}

// (12.D時計) 時計表示を2秒タイマーに登録
void status_update_timer_cb(lv_timer_t *timer) {
  struct tm timeinfo; // 現在時刻を取得する構造体 tm
  instance.rtc.getDateTime(&timeinfo); // RTCをtimeinfoに格納

  size_t written =
      strftime(buf, 64, "'%y/%m/%d(", &timeinfo); // 西暦下2桁/月/日(
  lv_label_set_text(label11, buf); // 年/月/日 表示

  written = strftime(buf2, 64, "%w", &timeinfo); // 曜日番号 0(日)-6(土)
  sprintf(buf, "%s", youbi[atoi(buf2)]);
  lv_label_set_text(label12, buf);

  written = strftime(buf, 64, "%P", &timeinfo); // am/pm
  lv_label_set_text(label21, buf);

  written = strftime(buf, 64, "%l:%M", &timeinfo); // 時 1-12 空白埋め:分
  lv_label_set_text(label22, buf);

  sprintf(buf, "%2d:%02d", setHour, setMinute); // アラーム時:分
  lv_label_set_text(label32, buf);

  int32_t ato =
      (setHour * 60 + setMinute) - (timeinfo.tm_hour * 60 + timeinfo.tm_min);
  if (ato < -999)
    ato = 0;
  sprintf(buf, "%d", ato); // 残りの分
  lv_label_set_text(label42, buf);

  BattCher = instance.pmu.isCharging();
  lv_label_set_text_fmt(label14, "%s", BattCher ? LV_SYMBOL_CHARGE : "  ");

  BattPer = instance.pmu.getBatteryPercent();
  if (BattPer < 0)
    BattPer = 0;
  if (BattPer > 100)
    BattPer = 100;
  if (BattPer < 30) {
    lv_obj_set_style_text_color(label15, lv_color_make(255, 0, 0),
                                LV_PART_MAIN);
  } else if (BattPer < 50) {
    lv_obj_set_style_text_color(label15, lv_color_make(255, 255, 0),
                                LV_PART_MAIN);
  }

  String s = "   " + String(BattPer);
  s = s.substring(s.length() - 3);
  lv_label_set_text_fmt(label15, "%s%%", s);
  lv_obj_set_style_text_color(label15, lv_color_make(0, 255, 255),
                              LV_PART_MAIN);
}

// ==============================
// 13.明るさ設定
// ==============================
// (13.明るさ設定) 明るさ設定btnが押された時
static void event_handler(lv_event_t *e) {
  lv_event_code_t code = lv_event_get_code(e);
  // イベントのコードを取得(イベント記述子へのpointer)
  // 戻り値:イベントコード(例 LV_EVENT_CLICKED, LV_EVENT_FOCUSED等)
  lv_obj_t *obj1 = lv_event_get_target_obj(e);
  // イベントの元のターゲットobjを取得(イベント記述子へのpointer)
  // 戻り値:event_codeの元のターゲットへのpointer
  if (code ==
      LV_EVENT_VALUE_CHANGED) { // 特別イベントウィジェットの値が変更されれば
    uint32_t id = lv_buttonmatrix_get_selected_button(obj1);
    // 最後に押した・離した・フォーカス等アクティブにしたbtnのインデックス
    // (btnマトリックスobjへのpointer)
    // 戻り値:最後に離されたbtnのインデックス
    bright_buf = lv_buttonmatrix_get_button_text(obj1, id);
    // btnのtextを取得(btnのマトリックスobjへのpointer,改行文字を含まないbtnのインデックス)
    // 戻り値:btn_indexボタンのtext btnのtextが明るさの変数になる
    instance.setBrightness(strtol(bright_buf, NULL, 10)); // 明るさ設定 char→int
    // strtol()は、文字列中の数値部分をlongに変換(変換対象の入力文字列を指すポインタ,
    // 変換が完了した位置へのポインタを格納するための引数 不要はNULL,
    // 10進数なら10 16進数なら16)
  }
}

// ==============================
// 21.スマホシャッター
// ==============================
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 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,カスタムパラメータ)
}




// ==============================
// 22.現時刻修正
// ==============================
// (22.現時刻修正) 4ローラーの値をlabelに表示
static void time_observer_cb4(lv_observer_t *observer, lv_subject_t *subject) {
  month4 = lv_subject_get_int(lv_subject_get_group_element(subject, 0)) + 1;
  day4 = lv_subject_get_int(lv_subject_get_group_element(subject, 1)) + 1;
  hour4 = lv_subject_get_int(lv_subject_get_group_element(subject, 2));
  minute4 = lv_subject_get_int(
      lv_subject_get_group_element(subject, 3)); //

  lv_obj_t *label =
      (lv_obj_t *)lv_observer_get_target(observer); //
  lv_label_set_text_fmt(
      label,
      "  %" LV_PRId32 "  /  %" LV_PRId32 "   %" LV_PRId32 " : %02" LV_PRId32,
      month4, day4, hour4,
      minute4); // ローラーの値
  // Serial.printf("2026/%d/%d %d : %02d", month4, day4, hour4, minute4); //
  // パニックエラーになる
}

// (22.現時刻修正) set_btnで呼出される関数
static void set_btn_clicked_event_cb4(lv_event_t *e) {
  nowMonth = month4; //
  nowDay = day4;
  nowHour = hour4;
  nowMinute = minute4;
  // Serial.printf("now_set_btn 2026/%d/%d %2d:%02d%n", nowMonth, nowDay,
  // nowHour, nowMinute);  // シリアルモニタに年/月/日 時:分 を表示
  instance.rtc.setDateTime(2026, nowMonth, nowDay, nowHour, nowMinute,
                           30);                        // RTCセット
  lv_tileview_set_tile_by_index(tv, 1, 0, LV_ANIM_ON); // タイル位置移動
  // アクティブタイル設定(tileviewのポインタ,タイルのカラム,行,アニメLV_ANIM_ON
  // or LV_ANIM
}

// ==============================
// 23.アナログ時計の関数
// ==============================
// (23.AA時計) 角度→座標変換 インデックス配置用
// angle_deg:0→12時 時計回りに増加, len:中心からのpx
// out_x/out_y:計算結果(clock_scaleローカル座標)
// create_analog_clock_tileM23から呼ばれる
static void angle_to_point(float angle_deg, float len, lv_coord_t *out_x,
                           lv_coord_t *out_y) {
  float rad = (angle_deg - 90.0f) * (float)M_PI / 180.0f;
  *out_x = (lv_coord_t)lroundf(clock_cx + len * cosf(rad));
  *out_y = (lv_coord_t)lroundf(clock_cy + len * sinf(rad));
}

// (23.A時計) 針セグメント生成ヘルパー
// 中心(clock_cx, clock_cy)を回転ピボットとする細長い角丸バーを作る。
// is_tail = false : 中心から「先端方向(上)」へ長さlenだけ伸びるセグメント
// is_tail = true  : 中心から「反対方向(尾)」へ長さlenだけ伸びるセグメント
// 回転角(transform_rotation)は0=真上(12時)、時計回りに正の値。
static lv_obj_t *create_hand_segment(lv_obj_t *parent, lv_coord_t width,
                                     lv_coord_t len, bool is_tail,
                                     lv_color_t color, lv_coord_t radius) {
  lv_obj_t *seg = lv_obj_create(parent); // ベースオブジェクト(長方形)を作成
  lv_obj_remove_style_all(seg);          // 全スタイル削除()
  lv_obj_set_size(seg, width, len);      // サイズ(,幅,高さ)
  lv_obj_set_style_bg_opa(seg, LV_OPA_COVER, 0);
  lv_obj_set_style_bg_color(seg, color, 0); // 背景色(,色,セレクタ)
  lv_obj_set_style_radius(seg, radius, 0);  // 角丸(,半径,セレクタ)
  lv_obj_remove_flag(seg,
                     LV_OBJ_FLAG_CLICKABLE); // フラグを削除(,クリックできる)
  lv_obj_remove_flag(seg,
                     LV_OBJ_FLAG_SCROLLABLE); // フラグを削除(,スクロールできる)
  if (!is_tail) { // 下端(高さlenの位置)が中心のピボット???
    lv_obj_set_pos(seg, clock_cx - width / 2, clock_cy - len); // 位置(,x,y)
    lv_obj_set_style_transform_pivot_x(seg, width / 2, 0);
    lv_obj_set_style_transform_pivot_y(seg, len, 0);
  } else { // 上端(高さ0の位置)が中心のピボット???
    lv_obj_set_pos(seg, clock_cx - width / 2, clock_cy);
    lv_obj_set_style_transform_pivot_x(seg, width / 2, 0);
    lv_obj_set_style_transform_pivot_y(seg, 0, 0);
  }
  return seg;
}

// (23.A時計) 針の更新タイマー
// 30ms周期(約33fps)で呼ばれ、マイクロ秒精度で角度を計算するため
// 3針がなめらかに動く
// 各針は transform_rotation の値を更新するだけなので処理は軽い。
static void clock_update_timer_cb(lv_timer_t *timer) {
  LV_UNUSED(timer);

  struct timeval tv;
  gettimeofday(&tv, nullptr);

  struct tm tminfo;
  localtime_r(&tv.tv_sec, &tminfo);

  float sub_sec = tv.tv_usec / 1000000.0f; // 0.0 - 0.999999(同じ瞬間の端数)

  float sec_f = tminfo.tm_sec + sub_sec; // 0.0 - 59.999999
  float min_f =
      tminfo.tm_min + sec_f / 60.0f; // 秒の端数を分に反映→分針も滑らかに
  float hour_f = (tminfo.tm_hour % 12) +
                 min_f / 60.0f; // 分の端数を時に反映→時針も滑らかに

  float sec_angle = sec_f / 60.0f * 360.0f;   // 秒の角度 1周360度60秒
  float min_angle = min_f / 60.0f * 360.0f;   // 分針の角度
  float hour_angle = hour_f / 12.0f * 360.0f; // 時針の角度

  int32_t sec_rot = (int32_t)lroundf(sec_angle * 10.0f); // 0.1度単位
  int32_t min_rot = (int32_t)lroundf(min_angle * 10.0f);
  int32_t hour_rot = (int32_t)lroundf(hour_angle * 10.0f);

  lv_obj_set_style_transform_rotation(hand_sec_main, sec_rot, 0);
  lv_obj_set_style_transform_rotation(hand_sec_tail, sec_rot, 0);
  lv_obj_set_style_transform_rotation(hand_min_main, min_rot, 0);
  lv_obj_set_style_transform_rotation(hand_min_tail, min_rot, 0);
  lv_obj_set_style_transform_rotation(hand_hour_main, hour_rot, 0);
  lv_obj_set_style_transform_rotation(hand_hour_tail, hour_rot, 0);
}

// (23.A時計) tileM23で使用
void create_analog_clock_tileM23(lv_obj_t *tileM23) {
  lv_coord_t tile_w = lv_obj_get_width(tileM23);  // tileの幅
  lv_coord_t tile_h = lv_obj_get_height(tileM23); // tileの高さ
  lv_coord_t outer_d =
      LV_MIN(tile_w, tile_h) - 10; // 小さい方から10小さく(文字盤の半径)

  // タイル全体の背景(円の外側の四隅)
  lv_obj_set_style_bg_color(tileM23, lv_color_hex(0x0B1F3A), 0); // 紺色
  lv_obj_set_style_bg_opa(tileM23, LV_OPA_COVER,
                          0); // 背景(,不透明255,デフォルト状態)

  // 外側ベゼル
  lv_obj_t *face_bg =
      lv_obj_create(tileM23);       // ベースオブジェクト(長方形)を作成
  lv_obj_remove_style_all(face_bg); // その全スタイルを削除
  lv_obj_set_size(face_bg, outer_d, outer_d);            // サイズ設定
  lv_obj_center(face_bg);                                // 親の中心に合わせる
  lv_obj_set_style_radius(face_bg, LV_RADIUS_CIRCLE, 0); // 角丸半径
  lv_obj_set_style_bg_opa(face_bg, LV_OPA_COVER,
                          0); // 背景の不透明度 0:透明 255:不透明
  lv_obj_set_style_bg_color(face_bg, lv_color_hex(0x0A0A0A),
                            0);                  // 内側は黒文字盤
  lv_obj_set_style_border_width(face_bg, 12, 0); // 太いブロンズ枠=ベゼル
  lv_obj_set_style_border_color(face_bg, lv_color_hex(0x8A6A2A), 0);
  lv_obj_remove_flag(
      face_bg,
      LV_OBJ_FLAG_CLICKABLE); // フラグ削除(,入力デバイスによるクリック可能)
  lv_obj_remove_flag(face_bg,
                     LV_OBJ_FLAG_SCROLLABLE); // フラグ削除(,スクロール可能)

  // 外周の分数字リング(5,10,15...60) — 装飾用、回転しない固定表示
  static const char *minute_labels[12] = {"60", "5",  "10", "15", "20", "25",
                                          "30", "35", "40", "45", "50", "55"};

  // 内側アクセントリング(明るいゴールドの細線)
  lv_coord_t accent_d = outer_d - 14;
  lv_obj_t *accent_ring = lv_obj_create(tileM23);

  lv_obj_remove_style_all(accent_ring);

  // 文字盤: 分目盛りリング(61本指定で60本ぶんを均等配置)
  lv_coord_t scale_d = accent_d - 10;
  clock_scale = lv_scale_create(tileM23);
  lv_obj_set_size(clock_scale, scale_d, scale_d);
  lv_obj_center(clock_scale);
  lv_obj_set_style_pad_all(clock_scale, 0, 0);
  lv_obj_remove_flag(clock_scale,
                     LV_OBJ_FLAG_CLICKABLE); // tileview のスワイプを妨げない
  lv_obj_remove_flag(clock_scale, LV_OBJ_FLAG_SCROLLABLE);

  lv_scale_set_mode(clock_scale, LV_SCALE_MODE_ROUND_INNER);
  lv_scale_set_label_show(clock_scale, false);
  lv_scale_set_total_tick_count(clock_scale, 61); // 60分割を均等に
  lv_scale_set_major_tick_every(clock_scale, 5);
  lv_scale_set_range(clock_scale, 0, 60);
  lv_scale_set_angle_range(clock_scale, 360);
  lv_scale_set_rotation(clock_scale, 270); // 0を12時位置に合わせる

  lv_obj_set_style_bg_opa(clock_scale, LV_OPA_TRANSP, LV_PART_MAIN);
  lv_obj_set_style_line_width(clock_scale, 0, LV_PART_MAIN);

  // 分目盛り
  lv_obj_set_style_line_width(clock_scale, 3,
                              LV_PART_ITEMS); // 線幅 1→3 見えないので少し太く
  lv_obj_set_style_length(clock_scale, 6, LV_PART_ITEMS);
  lv_obj_set_style_line_color(clock_scale, lv_color_hex(0x5A4A2A),
                              LV_PART_ITEMS);
  lv_obj_set_style_line_width(clock_scale, 1, LV_PART_INDICATOR);
  lv_obj_set_style_length(clock_scale, 6, LV_PART_INDICATOR);
  lv_obj_set_style_line_color(clock_scale, lv_color_hex(0x5A4A2A),
                              LV_PART_INDICATOR);

  // 針の座標系(clock_scaleローカル座標)
  clock_cx = scale_d / 2;
  clock_cy = scale_d / 2;
  clock_radius = scale_d / 2.0f - 8.0f;

  // アプライド・アワーインデックス(12本のバトン)
  for (int i = 0; i < 12; i++) {
    float ang_deg = i * 30.0f;
    lv_coord_t mw = (i == 0) ? 8 : 5;   // 12時位置だけ太く
    lv_coord_t mh = (i == 0) ? 18 : 14; // 12時位置だけ長く

    lv_coord_t mx, my;
    angle_to_point(ang_deg, clock_radius - (float)mh / 2.0f - 2.0f, &mx, &my);

    lv_obj_t *marker = lv_obj_create(clock_scale);
    lv_obj_remove_style_all(marker);
    lv_obj_set_size(marker, mw, mh);
    lv_obj_set_style_bg_opa(marker, LV_OPA_COVER, 0);
    lv_obj_set_style_bg_color(marker, lv_color_hex(0xD8B25C), 0);
    lv_obj_set_style_radius(marker, 1, 0);
    lv_obj_remove_flag(marker, LV_OBJ_FLAG_CLICKABLE);
    lv_obj_remove_flag(marker, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_set_pos(marker, mx - mw / 2, my - mh / 2);
    lv_obj_set_style_transform_pivot_x(marker, mw / 2, 0);
    lv_obj_set_style_transform_pivot_y(marker, mh / 2, 0);
    lv_obj_set_style_transform_rotation(marker, (int32_t)(ang_deg * 10.0f), 0);
  }

  // 大きい数字(12)を強調表示、他は小さめの数字
  static const char *hour_labels[12] = {"12", "1", "2", "3", "4",  "5",
                                        "6",  "7", "8", "9", "10", "11"};
  for (int i = 0; i < 12; i++) { // i=0-11について
    float ang_deg = i * 30.0f;   // 360/12.0=30度
    // bool is_big = (i == 0 || i == 4 || i == 8);  // 12, 4, 8 のインデックス
    bool is_big = (i == 0); // 12時の時大きくする

    lv_obj_t *num = lv_label_create(clock_scale);
    lv_label_set_text(num, hour_labels[i]);                      // 数字を表示
    lv_obj_set_style_text_color(num, lv_color_hex(0xF5F5F5), 0); // 明るい白
    lv_obj_set_style_text_font(
        num, is_big ? &lv_font_montserrat_24 : &lv_font_montserrat_16,
        0); // 大きい数字は24px、小さい数字は16px
    if (is_big) {
      lv_obj_set_style_text_color(num, lv_color_hex(0xFFFFFF), 0);
      // 太字っぽく見せたい場合はopaやshadowで縁取りも可(任意)
    }

    lv_coord_t lx, ly;
    float label_radius =
        clock_radius - (is_big ? 34.0f : 28.0f); // 大きい数字は内側寄り
    angle_to_point(ang_deg, label_radius, &lx, &ly);

    lv_obj_update_layout(num); // サイズ確定のため先にレイアウト計算
    lv_coord_t lw = lv_obj_get_width(num);
    lv_coord_t lh = lv_obj_get_height(num);
    lv_obj_set_pos(num, lx - lw / 2, ly - lh / 2);
    lv_obj_remove_flag(num, LV_OBJ_FLAG_CLICKABLE);
    lv_obj_remove_flag(num, LV_OBJ_FLAG_SCROLLABLE);
  }

  // 針(ゴールド。時針・分針は先端+尾の2セグメント構成)
  lv_color_t gold_color = lv_color_hex(0xD8B25C); // 金色 時針・分針 いらないかも
  lv_color_t cream_color =
      lv_color_hex(0xF0E6D2); // クリーム色(ほぼ白) 秒針 いらないかも

  // 針(色を画像風に変更:時針=黄色、分針=白、秒針=赤)
  lv_color_t hour_color = lv_color_hex(0xFFC107); // 黄色(時針)
  lv_color_t min_color = lv_color_hex(0xF0F0F0);  // 白/銀(分針)
  lv_color_t sec_color = lv_color_hex(0xE53935);  // 赤(秒針)

  hand_hour_main = create_hand_segment(
      clock_scale, 7, (lv_coord_t)(clock_radius * 0.50f), false, hour_color, 3);
  hand_hour_tail = create_hand_segment(
      clock_scale, 5, (lv_coord_t)(clock_radius * 0.12f), true, hour_color, 2);
  hand_min_main = create_hand_segment(
      clock_scale, 5, (lv_coord_t)(clock_radius * 0.72f), false, min_color, 2);
  hand_min_tail = create_hand_segment(
      clock_scale, 4, (lv_coord_t)(clock_radius * 0.14f), true, min_color, 2);
  hand_sec_main = create_hand_segment(
      clock_scale, 2, (lv_coord_t)(clock_radius * 0.85f), false, sec_color, 1);
  hand_sec_tail = create_hand_segment(clock_scale, 8, 8, true, sec_color, 4);

  // 中心のキャップ
  clock_center = lv_obj_create(clock_scale);
  lv_obj_remove_style_all(clock_center); //
  lv_obj_set_size(clock_center, 11, 11); // サイズ
  lv_obj_set_style_radius(clock_center, LV_RADIUS_CIRCLE, 0);
  lv_obj_set_style_bg_opa(clock_center, LV_OPA_COVER, 0);
  lv_obj_set_style_bg_color(clock_center, sec_color,
                            0); // 中心キャップも秒針と同色に
  lv_obj_set_style_border_width(clock_center, 1, 0); // 枠の幅
  lv_obj_set_style_border_color(clock_center, lv_color_hex(0x0A0A0A),
                                0); // 枠の色 ほぼ黒色
  lv_obj_center(clock_center);

  // 初期表示 + 更新タイマー(30ms=滑らかな動き)
  clock_update_timer_cb(nullptr);
  if (clock_timer == nullptr)
    clock_timer = lv_timer_create(clock_update_timer_cb, 30, nullptr);
}

// ==============================
// 99.電源off
// ==============================
// (99.電源off) 3回点滅後電源off
void powerOffSystem() {
  for (int i = 3; i > 0; i--) {     // 3回繰返す 点滅
    instance.setBrightness(0);      // 明るさ 0(off),1-255
    delay(300);                     // 0.3秒待つ
    instance.setBrightness(i * 33); // 明るさ 0(off),1-255
    delay(300);                     // 0.3秒待つ
  }
  instance.setBrightness(0); // 明るさ 0(off),1-255
  delay(200);                // 0.2秒待つ
  instance.pmu.shutdown();   // PMU(AXP2101)の全電源を遮断して完全シャットダウン
}

// (99.画面on/off) 無操作監視タイマー setup()から呼ばれる
void idle_check_timer_cb(lv_timer_t *timer) {
  uint32_t inactive_ms =
      lv_disp_get_inactive_time(NULL); // 最後の操作から経過したmS
  if (inactive_ms > 2UL * 60UL * 1000UL)
    powerOffSystem(); // 2分より過ぎていれば電源off関数へ
}

// tileview全体に1つだけ登録するコールバック(setup()内、tv作成後に追加)
static void tileview_change_cb(lv_event_t *e) {
  lv_obj_t *tv_obj = (lv_obj_t *)lv_event_get_target(e);
  lv_obj_t *act_tile =
      lv_tileview_get_tile_act(tv_obj); // 現在アクティブなタイル

  // アナログ時計タイルがアクティブな時だけタイマーを動かす
  if (act_tile == tileM23) {
    if (clock_timer)
      lv_timer_resume(clock_timer);
    clock_update_timer_cb(
        nullptr); // 復帰直後に一度即時更新(古い時刻表示を防ぐ)
  } else {
    if (clock_timer)
      lv_timer_pause(clock_timer);
  }
}

// ==============================
// setup(), loop()
// ==============================
void setup() {
#if defined(ARDUINO_T_WATCH_S3) // T-Watch S3,T-Watch S3 Plusの時
  dispX = 240;
  dispY = 240;
#else // T-Watch Ultra, ESP32-S3-Touch-AMOLED-2.06の時
  dispX = 410;// 予定のみ
  dispY = 502;// 予定のみ
#endif
  // dispX = 368;// (非時計)ESP32-S3-Touch-AMOLED-1.8の時はどうするか
  // dispY = 448;

  Serial.begin(115200); // シリアルモニタの通信速度
                        // Arduino-IDEと合っていないと表示しません
  delay(100);                                          // 0.1秒待つ
#if defined(ARDUINO_T_WATCH_S3)
  instance.begin(NO_INIT_FATFS | NO_HW_GPS); // FatFsとGPSのチェックはしない LilyGoLibを初期化
#else
  instance.begin(NO_INIT_FATFS);
#endif

  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)
  // メモリ確保の為、BLEはLVGL初期化より前に
  beginLvglHelper(instance);          // LVGLを初期化
  instance.pmu.enableBattDetection(); // 電源機能の有効化
  instance.setBrightness(100);        // 明るさ 1-255,0(消)
  instance.rtc.hwClockWrite();        // RTC時刻をハードウェアにcopy
  speaker_init();               // スピーカーアンプを初期化
  if (!prefs.begin("myApp", true)) { // (保存領域名, 書込モード(trueは書込不可))
                                     // NVSを開けなかったら
    Serial.println("スペースを開くことができません");
  }
  setHour = prefs.getInt("setHour");     // アラームの時 読出し
  setMinute = prefs.getInt("setMinute"); // アラームの分 読出し
  prefs.end();                           // NVSを閉じる

  lv_display_t *disp =
      lv_display_create(240, 240); // 指定解像度で新しいディスプレイを作成
  tv = lv_tileview_create(lv_screen_active()); // default displayにタイルを作成

  lv_obj_add_event_cb(tv, tileview_change_cb, LV_EVENT_VALUE_CHANGED, nullptr);



  // ======================================================
  // タイル11 アラーム時刻設定 [ ALM ]-D時計-輝度//シャッター-t設定-A時計
  // ======================================================
  lv_obj_t *tileM11 =
      lv_tileview_add_tile(tv, 0, 0, LV_DIR_RIGHT); // 1行1列目 右のみに移動
  // tileviewにtileを追加(tileviewオブジェクトへのpointer,タイルの列,行,次に進む方向)

  lv_subject_init_int(&day_subject3, 0);     // 初期値 0:今日, 1:明日
  lv_subject_init_int(&hour_subject3, 12);   // 初期値 12時
  lv_subject_init_int(&minute_subject3, 30); // 初期値 30分 (初期値今日の12:30)
  // 整数型のSubjectを初期化(Subjectへのpointer,初期値)
  lv_subject_init_group(&time_subject3, time_group_array_subject3, 3);
  // グループ型サブジェクトを初期化(グループ型サブジェクトへのpointer,
  // 他のサブジェクトアドレスのリスト,list[]内の要素数)

  // キャンセルボタン ------------------------------
  cxl_btn = lv_button_create(tileM11); // cxl_btnを作成
  // ボタンobjを作成(objへのpointer 新しいボタンの親)
  // 戻り値:作成されたボタンへのpointer
  lv_obj_set_pos(cxl_btn, 2, 5); // btnの位置
  lv_obj_add_event_cb(cxl_btn, cxl_clicked_event_cb, LV_EVENT_CLICKED,
                      NULL); // cxl_btnを押したらコールバック関数へ

  lv_obj_t *cxl_label = lv_label_create(cxl_btn); // cxl_btnにlabelを作成
  lv_obj_set_style_text_font(cxl_label, &lv_font_montserrat_24,
                             0);          // 英数文字サイズ (8)10-48の偶数
  lv_label_set_text(cxl_label, "Cancel"); // labelのtextを設定

  // セットボタン ------------------------------
  set_btn = lv_button_create(tileM11); // set_btnを作成
  lv_obj_set_pos(set_btn, 120, 5);     // btnの位置
  lv_obj_set_width(set_btn, 115);      // btn幅
  lv_obj_add_event_cb(set_btn, set_btn_clicked_event_cb3, LV_EVENT_CLICKED,
                      NULL); // set_btnを押したらコールバック関数へ

  set_label = lv_label_create(set_btn); // btnにlabelを作成
  lv_obj_set_style_text_font(set_label, &lv_font_montserrat_24,
                             0);           // 英数文字サイズ (8)10-48の偶数
  lv_label_set_text(set_label, "    Set"); // labelのtextを設定

  // ラベル ------------------------------
  lv_obj_t *time_label3 = lv_label_create(tileM11); // time_labelを作成
  lv_obj_set_pos(time_label3, 5, 60);               // ラベルの位置 0,60
  lv_obj_set_width(time_label3, 110);               // 幅設定
  lv_obj_set_style_text_font(time_label3, &lv_font_montserrat_28,
                             0); // 英数文字サイズ (8)10-48の偶数
  lv_subject_add_observer_obj(&time_subject3, time_observer_cb3, time_label3,
                              NULL);
  // ウィジェットのサブジェクトにオブザーバーを追加(サブジェクトへのポインタ,
  // 通知コールバック,ウィジェットへのpointer,オプションのユーザーデータ)
  // 戻り値:新しく作成されたオブザーバーへのpointer
  // ウィジェットが削除されると、オブザーバーはサブジェクトから自動的に登録解除。
  // 注:この方法で作成されたオブザーバーは呼出さない。
  // これらのオブザーバーは、次のいずれかの方法でのみクリーンアップしてください。
  // lv_observer_remove()ウィジェットを削除するか、
  // すべてのオブザーバーを適切に分離して削除するには、lv_subject_deinit()
  // を呼出す。
  lv_obj_set_pos(time_label3, 0, 60); // ラベルの位置 70,20

  // 翌日ローラー ------------------------------
  day_roller = lv_roller_create(tileM11);         // 画面に翌日用ローラーを作成
  lv_obj_set_pos(day_roller, 10, 155);            // rollerの位置 10 105
  lv_obj_set_width(day_roller, 80);               // 幅設定
  lv_roller_set_visible_row_count(day_roller, 2); // 望ましい行数
  lv_obj_set_style_text_font(day_roller, &lv_font_montserrat_24,
                             0); // 英数文字サイズ (8)10-48の偶数
  lv_roller_set_options(day_roller, day_options02,
                        LV_ROLLER_MODE_NORMAL);    // ローラーは最後で停止
  lv_roller_bind_value(day_roller, &day_subject3); // 関連付

  // 時ローラー ------------------------------
  lv_obj_t *hour_roller = lv_roller_create(tileM11); // 画面に時用ローラーを作成
  lv_obj_set_pos(hour_roller, 100, 55);              // rollerの位置 85 105
  lv_obj_set_width(hour_roller, 60);                 // 幅設定
  lv_roller_set_visible_row_count(hour_roller, 5);   // 望ましい行数
  lv_obj_set_style_text_font(hour_roller, &lv_font_montserrat_24,
                             0); // 英数文字サイズ (8)10-48の偶数
  lv_obj_add_flag(hour_roller, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK);
  // 1つ以上のフラグを設定(objへのpointer, 設定する値のOR演算)
  // LV_OBJ_FLAG_FLEX_IN_NEW_TRACK:このアイテムで新しいFlexトラックを開始
  lv_roller_set_options(
      hour_roller, hour_options,
      LV_ROLLER_MODE_INFINITE); // 永久スクロール ...22,23,0,1,...
  // ローラーのオプションを設定(ローラーobjへのpointer,\nで区切られた文字列,LV_ROLER_MODE_・・・)
  // LV_ROLLER_MODE_NORMAL:ローラーはオプションの最後で停止
  // LV_ROLLER_MODE_INFINITE:ローラーは永久にスクロール
  lv_roller_bind_value(hour_roller, &hour_subject3);
  // 整数SubjectをRollerの値に関連付(Rollerへのpointer,Subjectへのpointer)

  // 分ローラー ------------------------------
  min_roller = lv_roller_create(tileM11);         // 画面に分用のローラーを作成
  lv_obj_set_pos(min_roller, 170, 55);            // rollerの位置 160 105
  lv_obj_set_width(min_roller, 60);               // 幅設定
  lv_roller_set_visible_row_count(min_roller, 5); // 望ましい行数?????
  lv_obj_set_style_text_font(min_roller, &lv_font_montserrat_24,
                             0); // 英数文字サイズ (8)10-48の偶数
  lv_roller_set_options(min_roller, minute_options,
                        LV_ROLLER_MODE_INFINITE);     // 永久スクロール 上と同様
  lv_roller_bind_value(min_roller, &minute_subject3); // 関連付け 上と同様

  // ======================================================
  // タイル12 デジタル時計 ALM-[ D時計 ]-輝度//シャッター-t設定-A時計
  // ======================================================
  lv_obj_t *tileM12 = lv_tileview_add_tile(
      tv, 1, 0,
      (lv_dir_t)(LV_DIR_LEFT | LV_DIR_BOTTOM | LV_DIR_RIGHT));   // 左右下へ移動
  lv_obj_set_style_bg_color(tileM12, lv_color_make(0, 0, 0), 0); // rgb 黒地
  lv_obj_set_style_bg_opa(tileM12, LV_OPA_COVER, 0); // 不透明度を設定(必要)
  lv_obj_set_style_text_color(tileM12, lv_color_make(0, 255, 0), 0); // rgb 緑字
  int y01 = 67, y02 = 35, y03 = 25; // 行間,2行目位置,文字

  // 1行目1 年/月/日( 表示はloop内
  label11 = lv_label_create(tileM12); // 画面にラベル11を追加
  lv_obj_set_style_text_font(label11, &lv_font_montserrat_24,
                             LV_PART_MAIN); // 英数文字サイズ (8)10-48の偶数
  lv_obj_align(label11, LV_ALIGN_TOP_LEFT, 0, 0); // 左上からの位置
  lv_obj_set_style_text_color(label11, lv_color_make(255, 255, 255),
                              LV_PART_MAIN); // rgb 白字
  lv_obj_set_style_transform_zoom(label11, 240,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25) 384

  // 1行目2 曜日 表示はloop内 デフォルトfont
  label12 = lv_label_create(tileM12); // 現在の画面にラベル11を追加
  lv_obj_align(label12, LV_ALIGN_TOP_LEFT, 115, 0); // 左上からの位置
  lv_obj_set_style_text_color(label12, lv_color_make(255, 255, 255),
                              LV_PART_MAIN); // rgb 白字
  lv_obj_set_style_transform_zoom(label12, 352,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25)320

  // 1行目3 ")"
  lv_obj_t *label13 = lv_label_create(tileM12); // 現在の画面にラベル11を追加
  lv_obj_set_style_text_font(label13, &lv_font_montserrat_24,
                             LV_PART_MAIN); // 英数文字サイズ (8)10-48の偶数
  lv_obj_align(label13, LV_ALIGN_TOP_LEFT, 156, 0); // 左上からの位置
  lv_obj_set_style_text_color(label13, lv_color_make(255, 255, 255),
                              LV_PART_MAIN); // rgb 白字
  lv_obj_set_style_transform_zoom(label13, 240,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25)
  lv_label_set_text(label13, ")");    // ラベルにセット

  // 1行目4 充電マーク 表示はloop内
  label14 = lv_label_create(tileM12); // 現在の画面にラベル12を追加
  lv_obj_align(label14, LV_ALIGN_TOP_LEFT, 170, 5); // 左上からの位置
  lv_obj_set_style_text_color(label14, lv_color_make(0, 255, 255),
                              LV_PART_MAIN); // rgb 水色字
  lv_obj_set_style_transform_zoom(label14, 240,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25) 384

  // 1行目5 Batt(%) 表示はloop内
  label15 = lv_label_create(tileM12); // 現在の画面にラベル12を追加
  lv_obj_set_style_text_font(label15, &lv_font_montserrat_24,
                             LV_PART_MAIN); // 英数文字サイズ (8)10-48の偶数
  lv_obj_align(label15, LV_ALIGN_TOP_LEFT, 182, 0); // 左上からの位置
  lv_obj_set_style_text_color(label15, lv_color_make(0, 255, 255),
                              LV_PART_MAIN); // rgb 水色字
  lv_obj_set_style_transform_zoom(label15, 240,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25) 384

  // 2行目1 "am" or "pm" 表示はloop内
  label21 = lv_label_create(tileM12); // 現在の画面にラベル21を追加
  lv_obj_set_style_text_font(label21, &lv_font_montserrat_24,
                             LV_PART_MAIN); // 英数文字サイズ (8)10-48の偶数
  lv_obj_align(label21, LV_ALIGN_TOP_LEFT, 0,
               y01 * 0 + y02 + y03); // 左上からの位置

  // 2行目2 現在時刻(12H) 表示はloop内
  label22 = lv_label_create(tileM12); // 現在の画面にラベル22を追加
  lv_obj_set_style_text_font(label22, &lv_font_montserrat_48,
                             LV_PART_MAIN); // 英数文字サイズ (8)10-48の偶数
  lv_obj_align(label22, LV_ALIGN_TOP_LEFT, 60, y01 * 0 + y02); // 左上からの位置
  lv_obj_set_style_transform_zoom(label22, 320,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25)

  // 3行目1 "設定" デフォルトfont
  lv_obj_t *label31 = lv_label_create(tileM12); // 現在の画面にラベル31を追加
  lv_obj_align(label31, LV_ALIGN_TOP_LEFT, 0,
               y01 * 1 + y02 + y03); // 左上からの位置
  lv_obj_set_style_transform_zoom(label31, 448,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25)320
  lv_label_set_text(label31, "設定"); // テキスト

  // 3行目2 設定時刻(24H)
  label32 = lv_label_create(tileM12); // 現在の画面にラベル32を追加
  lv_obj_set_style_text_font(label32, &lv_font_montserrat_48,
                             LV_PART_MAIN); // 英数文字サイズ (8)10-48の偶数
  lv_obj_align(label32, LV_ALIGN_TOP_LEFT, 60, y01 * 1 + y02); // 左上からの位置
  lv_obj_set_style_transform_zoom(label32, 320, 0); // ズーム(=/256)倍

  // 4行目1 "あと" デフォルトfont
  lv_obj_t *label41 = lv_label_create(tileM12); // 現在の画面にラベル41を追加
  lv_obj_align(label41, LV_ALIGN_TOP_LEFT, 0,
               y01 * 2 + y02 + y03); // 左上からの位置
  lv_obj_set_style_transform_zoom(label41, 448,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25)320
  lv_label_set_text(label41, "あと"); // テキスト

  // 4行目2 残りの分数
  label42 = lv_label_create(tileM12); // 現在の画面にラベル42を追加
  lv_obj_set_style_text_font(label42, &lv_font_montserrat_48,
                             LV_PART_MAIN); // 英数文字サイズ (8)10-48の偶数
  lv_obj_align(label42, LV_ALIGN_TOP_RIGHT, -65,
               y01 * 2 + y02);                      // 右上からの位置-60
  lv_obj_set_style_transform_zoom(label42, 320, 0); // ズーム4(=/256)倍

  // 4行目3 "分" デフォルトfont
  lv_obj_t *label43 = lv_label_create(tileM12); // 現在の画面にラベル43を追加
  lv_obj_align(label43, LV_ALIGN_TOP_LEFT, 200,
               y01 * 2 + y02 + y03); // 左上からの位置
  lv_obj_set_style_transform_zoom(label43, 448,
                                  0); // ズーム(=/256)倍 384(1.5) 320(1.25)
  lv_label_set_text(label43, "分");   // ラベル1のテキスト 現在時刻

  // ======================================================
  // タイル13 明るさ設定 ALM-D時計-[ 輝度 ]//シャッター-t設定-A時計
  // ======================================================
  lv_obj_t *tileM13 =
      lv_tileview_add_tile(tv, 2, 0, LV_DIR_LEFT); // 1行3列目 左のみに移動

  // タイトル ------------------------------
  label = lv_label_create(tileM13); // 画面にlabel追加
  lv_obj_align(label, LV_ALIGN_TOP_LEFT, 20, 20);
  // objの配置を変更し新座標を設定(配置objへのpointer,配置の種類,配置後のxオフセット,y)
  lv_label_set_text(label, "明るさ設定"); // タイトル表示

  // バック(キャンセル)ボタン ------------------------------
  cxl_btn = lv_button_create(tileM13); // cxl_btnを作成
  lv_obj_set_pos(cxl_btn, 120, 5);     // btnの位置
  lv_obj_set_width(cxl_btn, 115);      // btn幅
  lv_obj_add_event_cb(cxl_btn, cxl_clicked_event_cb, LV_EVENT_CLICKED,
                      NULL); // cxl_btnを押したらコールバック関数へ

  cxl_label = lv_label_create(cxl_btn); // cxl_btnにlabelを作成
  lv_obj_set_style_text_font(cxl_label, &lv_font_montserrat_24,
                             0);          // 英数文字サイズ (8)10-48の偶数
  lv_label_set_text(cxl_label, "  back"); // labelのtextを設定

  // 明るさのボタン6個 ------------------------------
  lv_obj_t *btnm1 = lv_buttonmatrix_create(tileM13); // 画面にボタンmatrix追加
  lv_obj_set_pos(btnm1, 5, 60);                      // 開始位置
  lv_obj_set_size(btnm1, 230, 170);                  // サイズ
  lv_obj_set_style_text_font(btnm1, &lv_font_montserrat_24,
                             LV_PART_MAIN); // 英数文字サイズ (8)10-48の偶数
  lv_buttonmatrix_set_map(btnm1, btnm_map); // ボタンmapのセット
  lv_obj_add_event_cb(btnm1, event_handler, LV_EVENT_ALL,
                      NULL); // 押したら上記cb関数へ
  // objにイベントハンドラ関数を追加(objへのpointer,新イベント関数,
  // イベントを呼出すコード,その関数でカスタムデータを使用できます)

  // ======================================================
  // タイル21 スマホシャッター ALM-D時計-輝度//[ シャッター ]-t設定-A時計
  // ======================================================
  lv_obj_t *tileM21 =
      lv_tileview_add_tile(tv, 0, 1, LV_DIR_RIGHT); // 2行1列目 右のみに移動
  /*
  btn = lv_button_create(tileM21);                  // 画面にボタン追加
  label = lv_label_create(btn);                     // ボタンにラベル追加
  lv_label_set_text(label, "wifiで時刻設定 右へ");  // ラベルの文字設定
  lv_obj_set_size(btn, LV_SIZE_CONTENT, LV_SIZE_CONTENT); // サイズは自動調整
  lv_obj_center(btn);                                     // 親の中央に配置
  */

 // lv_obj_t *scr = lv_scr_act();                         // 現在の画面
  //lv_obj_set_style_bg_color(tileM21, lv_color_black(), 0);  // 黒地
  lv_obj_set_style_bg_color(tileM12, lv_color_make(0, 0, 0), 0); // rgb 黒地

  // タイトル ------------------------------
  label = lv_label_create(tileM21); // 画面にlabel追加
  lv_obj_align(label, LV_ALIGN_TOP_LEFT, 20, 20);
  // objの配置を変更し新座標を設定(配置objへのpointer,配置の種類,配置後のxオフセット,y)
  lv_label_set_text(label, "シャッター"); // タイトル表示

  // バック(キャンセル)ボタン ------------------------------
  cxl_btn = lv_button_create(tileM21); // cxl_btnを作成
  lv_obj_set_pos(cxl_btn, 120, 5);     // btnの位置
  lv_obj_set_width(cxl_btn, 115);      // btn幅
  lv_obj_add_event_cb(cxl_btn, cxl_clicked_event_cb, LV_EVENT_CLICKED,
                      NULL); // cxl_btnを押したらコールバック関数へ

  cxl_label = lv_label_create(cxl_btn); // cxl_btnにlabelを作成
  lv_obj_set_style_text_font(cxl_label, &lv_font_montserrat_24,
                             0);          // 英数文字サイズ (8)10-48の偶数
  lv_label_set_text(cxl_label, "Cancel"); // labelのtextを設定

  btn = lv_btn_create(tileM21);                                      // 画面にボタン作成
  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");                                 // 表示文字(スマホの接続先)


  // ======================================================
  // タイル22 手動で時刻設定 ALM-D時計-輝度//シャッター-[ t設定 ]-A時計
  // ======================================================
  lv_obj_t *tileM22 =
      lv_tileview_add_tile(tv, 1, 1, LV_DIR_ALL); // 全方向に移動
  lv_subject_init_int(&month_subject,
                      2); // 初期値 3月(1からスタートなので-1する)
  lv_subject_init_int(&day_subject,
                      14); // 初期値 15日(1からスタートなので-1する)
  lv_subject_init_int(&hour_subject, 12);   // 初期値 12時
  lv_subject_init_int(&minute_subject, 30); // 初期値 30分
  // 整数型のSubjectを初期化(Subjectへのpointer,初期値)
  lv_subject_init_group(&time_subject, time_group_array_subject4, 4);
  // グループ型サブジェクトを初期化(グループ型サブジェクトへのpointer,
  // 他のサブジェクトアドレスのリスト,list[]内の要素数)

  // キャンセルボタン ------------------------------
  cxl_btn = lv_button_create(tileM22); // cxl_btnを作成
  lv_obj_set_pos(cxl_btn, 2, 5);       // btnの位置
  lv_obj_add_event_cb(cxl_btn, cxl_clicked_event_cb, LV_EVENT_CLICKED,
                      NULL); // cxl_btnを押したらコールバック関数へ

  cxl_label = lv_label_create(cxl_btn); // cxl_btnにlabelを作成
  lv_obj_set_style_text_font(cxl_label, &lv_font_montserrat_24,
                             0);          // 英数文字サイズ (8)10-48の偶数
  lv_label_set_text(cxl_label, "Cancel"); // labelのtextを設定

  // セットボタン ------------------------------
  set_btn = lv_button_create(tileM22); // set_btnを作成
  lv_obj_set_pos(set_btn, 120, 5);     // btnの位置
  lv_obj_set_width(set_btn, 115);      // btn幅
  lv_obj_add_event_cb(set_btn, set_btn_clicked_event_cb4, LV_EVENT_CLICKED,
                      NULL); // set_btnを押したらコールバック関数へ

  set_label = lv_label_create(set_btn); // btnにlabelを作成
  lv_obj_set_style_text_font(set_label, &lv_font_montserrat_24,
                             0);           // 英数文字サイズ (8)10-48の偶数
  lv_label_set_text(set_label, "    Set"); // labelのtextを設定

  // ラベル ------------------------------
  time_label = lv_label_create(tileM22); // time_labelを作成 設定時刻を表示
  lv_obj_set_style_text_font(time_label, &lv_font_montserrat_28,
                             0); // 英数文字サイズ (8)10-48の偶数
  lv_subject_add_observer_obj(&time_subject, time_observer_cb4, time_label,
                              NULL);
  // ウィジェットのサブジェクトにオブザーバーを追加(サブジェクトへのポインタ,
  // 通知コールバック,ウィジェットへのpointer,オプションのユーザーデータ)
  // 戻り値:新しく作成されたオブザーバーへのpointer
  // ウィジェットが削除されると、オブザーバーはサブジェクトから自動的に登録解除。
  // 注:この方法で作成されたオブザーバーは呼出さない。
  // これらのオブザーバーは、次のいずれかの方法でのみクリーンアップしてください。
  // lv_observer_remove()ウィジェットを削除するか、
  // すべてのオブザーバーを適切に分離して削除するには、lv_subject_deinit()
  // を呼出す。
  lv_obj_set_pos(time_label, 0, 60); // ラベルの位置 70,20

  // 月ローラー ------------------------------
  lv_obj_t *month_roller =
      lv_roller_create(tileM22); // 画面に翌日用ローラーを作成
  lv_roller_set_options(month_roller, month_options,
                        LV_ROLLER_MODE_INFINITE);     // 永久スクロール
  lv_roller_bind_value(month_roller, &month_subject); // 関連付
  lv_obj_set_pos(month_roller, 0, 100);               // rollerの位置
  lv_obj_set_style_text_font(month_roller, &lv_font_montserrat_24,
                             0);      // 英数文字サイズ (8)10-48の偶数
  lv_obj_set_width(month_roller, 60); // 幅

  // 日ローラー ------------------------------
  day_roller = lv_roller_create(tileM22); // 画面に翌日用ローラーを作成
  lv_roller_set_options(day_roller, day_options31,
                        LV_ROLLER_MODE_INFINITE); // 永久スクロール
  lv_roller_bind_value(day_roller, &day_subject); // 関連付
  lv_obj_set_pos(day_roller, 60, 100);            // rollerの位置
  lv_obj_set_style_text_font(day_roller, &lv_font_montserrat_24,
                             0);    // 英数文字サイズ (8)10-48の偶数
  lv_obj_set_width(day_roller, 60); // 幅

  // 時ローラー ------------------------------
  hour_roller = lv_roller_create(tileM22); // 画面に時用ローラーを作成
  lv_obj_add_flag(hour_roller, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK);
  // 1つ以上のフラグを設定(objへのpointer, 設定する値のOR演算)
  // LV_OBJ_FLAG_FLEX_IN_NEW_TRACK:このアイテムで新しいFlexトラックを開始
  lv_roller_set_options(
      hour_roller, hour_options,
      LV_ROLLER_MODE_INFINITE); // 永久スクロール ...22,23,0,1,...
  // ローラーのオプションを設定(ローラーobjへのpointer,\nで区切られた文字列,
  // or LV_ROLER_MODE_NORMAL LV_ROLLER_MODE_INFINITE)
  // LV_ROLLER_MODE_NORMAL:ローラーはオプションの最後で停止
  // LV_ROLLER_MODE_INFINITE:ローラーは永久にスクロール
  lv_roller_bind_value(hour_roller, &hour_subject);
  // 整数SubjectをRollerの値に関連付(Rollerへのpointer,Subjectへのpointer)
  // 戻り値:新しく作成されたObserverへのpointer
  lv_obj_set_pos(hour_roller, 120, 100); // rollerの位置
  lv_obj_set_style_text_font(hour_roller, &lv_font_montserrat_24,
                             0);     // 英数文字サイズ (8)10-48の偶数
  lv_obj_set_width(hour_roller, 60); // 幅

  // 分ローラー ------------------------------
  min_roller = lv_roller_create(tileM22); // 画面に分用のローラーを作成
  lv_roller_set_options(min_roller, minute_options,
                        LV_ROLLER_MODE_INFINITE);    // 永久スクロール 上と同様
  lv_roller_bind_value(min_roller, &minute_subject); // 関連付け 上と同様
  lv_obj_set_pos(min_roller, 180, 100);              // rollerの位置
  lv_obj_set_style_text_font(min_roller, &lv_font_montserrat_24,
                             0); // 英数文字サイズ (8)10-48の偶数
  lv_obj_set_width(min_roller, 60);

  // ======================================================
  // タイル23 アナログ時計表示 ALM-D時計-輝度//シャッター-t設定-[ A時計 ]
  // ======================================================
  // lv_obj_t *tileM23 = lv_tileview_add_tile(tv, 2, 1, LV_DIR_LEFT);  //
  // 2行3列目 左のみに移動
  tileM23 =
      lv_tileview_add_tile(tv, 2, 1, LV_DIR_LEFT); // 2行3列目 左のみに移動
  create_analog_clock_tileM23(tileM23);            //

  // ======================================================
  lv_tileview_set_tile_by_index(tv, 1, 0, LV_ANIM_OFF); // 起動時のタイル位置
  // アクティブタイル設定(tileviewのポインタ,タイルのカラム,行,アニメLV_ANIM_ON
  // or LV_ANIM_OFF)
  instance.setBrightness(100); // 明るさ 0-255
  if (firstDisp) {             // 起動時なら
    tileM12_update();          // 時計画面のloop表示
    firstDisp = false;         // 起動時の表示の後
  }
  lv_timer_create(status_update_timer_cb, 2000,
                  NULL); // タイマー登録 2秒ごとに時計画面更新
  lv_timer_create(idle_check_timer_cb, 1000,
                  NULL); // 無操作監視タイマーを1秒ごとに登録
}

void loop() {
  lv_timer_handler(); //
  lv_task_handler();  // lvglタスク処理は、ループ内に書く
  instance.loop();    // LVGLの内部状態を更新
  delay(5);           // 5mS待つ
}
*/

スケッチの状態

PLATFORM: Espressif 32 (55.3.36) > LilyGo T-Watch S3 (16M Flash 8M OPI PSRAM )
HARDWARE: ESP32S3 240MHz, 320KB RAM, 16MB Flash
- contrib-piohome @ 3.4.4
- framework-arduinoespressif32 @ 3.3.6
- framework-arduinoespressif32-libs @ 5.5.0+sha.f56bea3d1f
- tool-esp-rom-elfs @ 2024.10.11
- tool-esptoolpy @ 5.1.0
- toolchain-xtensa-esp-elf @ 14.2.0+20251107
|-- LilyGoLib @ 0.2.1+sha.9301b6d
|-- lvgl @ 9.5.0
|-- RadioLib @ 7.4.0
|-- XPowersLib @ 0.3.1
|-- SensorLib @ 0.3.3
|-- TinyGPSPlus @ 1.0.3
|-- ESP32 BLE Keyboard Fork @ 0.3.4+sha.20c8b9f
|-- ESP32 BLE Mouse @ 0.3.1+sha.61bcd32
|-- ESP8266Audio @ 2.0.0
|-- NimBLE-Arduino @ 2.2.3
|-- IRremoteESP8266 @ 2.8.6
|-- ESP_I2S @ 3.3.6
|-- Preferences @ 3.3.6
RAM: [= ] 11.1% (used 36384 bytes from 327680 bytes)
Flash: [==== ] 35.6% (used 1493715 bytes from 4194304 bytes)
esptool v5.1.0
Chip type: ESP32-S3 (QFN56) (revision v0.2)
Features: Wi-Fi, BT 5 (LE), Dual Core + LP Core, 240MHz, Embedded PSRAM 8MB (AP_3v3)
Crystal frequency: 40MHz
USB mode: USB-Serial/JTAG
Changing baud rate to 921600...