1. Arborescence minimale
Les fichiers utiles aux tuiles Dashboard vivent dans assets/station_s (miroir du firmware). Ici les dossiers pertinents :
assets/station_s/
+- include/app/
¦ +- core/
¦ ¦ +- gui_core.h ? point dentrée LVGL
¦ +- input/
¦ ¦ +- obb_touch_gt911.h ? interface tactile commune
¦ +- ui/
¦ +- obb_tiles_dashboard.h
+- src/app/
+- core/
¦ +- gui_core.cpp ? initialise lécran + injecte la tuile
+- input/
¦ +- obb_touch_gt911.cpp ? lit GT911 et détecte les swipes
+- ui/tiles/
+- obb_tiles_dashboard.cpp ? contenu des 9 tuiles
Ce découpage restera identique lorsque nous pousserons vers backend/OBB_nodes_templates/Station_S.
2. Tuile centrale & navigation 33
Chaque case du Dashboard est stockée dans kTileLabels (ligne 9 du fichier). Le cur de la navigation est la fonction ci-dessous :
void obb_tiles_dashboard_on_swipe(lv_dir_t dir) {
const int8_t delta = (dir == LV_DIR_RIGHT) ? 1 :
(dir == LV_DIR_LEFT) ? -1 :
(dir == LV_DIR_TOP) ? -3 :
(dir == LV_DIR_BOTTOM) ? 3 : 0;
const int8_t next = s_currentTile + delta;
if (delta == 0 || !tile_exists(s_currentTile, next, dir)) {
pulse_edge(dir, lv_palette_main(LV_PALETTE_RED));
return;
}
s_currentTile = next;
refresh_tiles();
pulse_edge(dir, lv_palette_main(LV_PALETTE_GREEN));
}
Ce code se contente dajouter +1/-1/-3/+3 selon les swipes, vérifie que la tuile existe (pas de débordement en bordure) puis :
- refresh_tiles() recolore les cartes pour montrer la tuile active.
- pulse_edge() allume brièvement le bord (vert si déplacement accepté, rouge sinon).
Cette logique correspond exactement à ce qui est décrit dans la page conceptuelle (central tile, bords lumineux).
3. Détection des swipes (GT911)
Le tactile publie un callback générique pour ne pas exposer toute notre pile. Le principe :
static void lv_touch_read(..., lv_indev_data_t *data) {
bool touched = obb_gt911_hw_read(x, y);
if (touched) {
data->state = LV_INDEV_STATE_PRESSED;
if (!s_last_touch) { s_swipe_origin = s_last_point; }
} else {
data->state = LV_INDEV_STATE_RELEASED;
if (s_last_touch && s_swipe_cb) {
if (abs(dx) > abs(dy) && abs(dx) > kSwipeThreshold) ...
}
}
s_last_touch = touched;
}
Les fonctions obb_gt911_hw_init / obb_gt911_hw_read sont volontairement « faibles » (weak). Cela permet de montrer lAPI sans dévoiler nos registres/I²C spécifiques. Le grand public retient juste : « un swipe horizontal change de tuile, un swipe vertical change détage ».
4. Bandeau supérieur & Emergency Panel
La barre vitale expose un petit état temps réel partagé avec le backend :
struct obb_status_bar_t {
int8_t signal_dbm;
uint8_t packet_loss;
uint8_t notif_count;
uint8_t alert_level; // 0=OK 1=Warn 2=Critical
bool emergency_latched;
} g_status;
Louverture du panneau Emergency instancie les 9 boutons (3 rangées 3 colonnes) :
static const char *kEmergencyLabels[3][3] = {
{"SOS HOS", "SOS POL", "SOS FIRE"},
{"Macro A", "Macro B", "Macro C"},
{"XCOM", "Silent", "Life Mode"}
};
lv_obj_t *obb_emergency_panel_create(lv_obj_t *parent) {
lv_obj_t *grid = lv_obj_create(parent);
lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_COLUMN);
for (int row = 0; row < 3; ++row) {
lv_obj_t *row_cont = lv_obj_create(grid);
lv_obj_set_flex_flow(row_cont, LV_FLEX_FLOW_ROW);
for (int col = 0; col < 3; ++col) {
lv_obj_t *btn = lv_btn_create(row_cont);
lv_obj_set_size(btn, 160, 88); // 1/4 largeur paysage
lv_obj_add_event_cb(btn, emergency_btn_cb, LV_EVENT_CLICKED, (void *)(row * 3 + col));
lv_label_set_text(lv_label_create(btn), kEmergencyLabels[row][col]);
}
}
return grid;
}
Les callbacks déclenchent les routines SOS (GPS, SSID, enregistrements audio), macros personnalisées ou le Life Saving Mode (cycle enregistrements ? sommeil prolongé). Les formats de trames restent dans le dépôt privé.
5. Données de la tuile Dashboard (Tile?5)
La tuile centrale consomme un petit snapshot partagé par le backend :
struct obb_tile5_snapshot_t {
char k_region[24];
char k_station[16];
char last_docs[3][24];
char last_msgs[3][24];
bool gps_valid;
float gps_lat;
float gps_lon;
char masked_name[12];
char qr_payload[64];
} g_tile5;
Le rafraîchissement met à jour les labels LVGL et le QR :
static void tile5_refresh(lv_obj_t *tile) {
lv_label_set_text_fmt(s_lbl_network, "NETWORK K:%s %s",
g_tile5.k_station, g_tile5.k_region);
lv_label_set_text_fmt(s_lbl_doc, "Last doc: %s", g_tile5.last_docs[s_doc_idx]);
lv_label_set_text_fmt(s_lbl_msg, "Last msg: %s", g_tile5.last_msgs[s_msg_idx]);
lv_label_set_text_fmt(s_lbl_gps,
g_tile5.gps_valid ? "GPS %.5f / %.5f" : "GPS OFF",
g_tile5.gps_lat, g_tile5.gps_lon);
lv_label_set_text(s_lbl_name, g_tile5.masked_name);
}
Taper sur Next doc/Next msg incrémente simplement les index. Laffichage QR utilise lv_qrcode_create() à la demande pour limiter lempreinte mémoire.
6. Boucle LVGL simplifiée
La partie qui connecte LovyanGFX, LVGL et la tuile :
void gui_core_init(lgfx::LGFX_Device &display) {
s_display = &display;
s_display->init();
lv_init();
lv_disp_draw_buf_init(&s_draw_buf, s_buf1, s_buf2, 480 * 40);
// ... enregistrement du driver LVGL ...
obb_touch_gt911_init();
obb_touch_gt911_attach_lvgl();
obb_tiles_dashboard_init(lv_scr_act());
}
Cela correspond exactement aux étapes décrites dans GUI Core · Stack firmware : initialiser lécran, brancher LVGL, associer le tactile, puis afficher la grille 33.