docs(plan): plan Phase 1 socle ESP-IDF
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
# Phase 1 — Socle ESP-IDF : Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Obtenir un projet ESP-IDF natif minimal qui build, flashe et boote sur l'ESP32-A1S (ESP32 classique), avec logs série, Bluetooth Classic activé dans la config, et NVS initialisé — sans aucun driver métier encore.
|
||||
|
||||
**Architecture :** Nouveau projet ESP-IDF à la racine du dépôt (`CMakeLists.txt`, `main/`, `components/`), coexistant avec le code Arduino existant (`src/`, `platformio.ini`) que le build IDF ignore. La cible est `esp32`. Le `sdkconfig` est généré depuis un `sdkconfig.defaults` versionné qui active Bluedroid BR/EDR + HFP-HF (chemin data HCI). NVS est initialisé au boot via un composant `config_store` embryonnaire.
|
||||
|
||||
**Tech Stack :** ESP-IDF v5.4 (`/Users/electron/esp/esp-idf`), CMake, Bluedroid (Bluetooth Classic), NVS, esp_log.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- ESP-IDF v5.4 — activer l'environnement dans CHAQUE shell avant tout `idf.py` : `. /Users/electron/esp/esp-idf/export.sh`
|
||||
- Cible unique : `esp32` (classique, module A1S) — PAS de S3.
|
||||
- Port série par défaut : `/dev/cu.usbserial-0001` (peut varier ; vérifier avec `ls /dev/cu.*`).
|
||||
- Aucune dépendance Arduino dans le projet IDF.
|
||||
- Logs via `ESP_LOGx` uniquement (jamais `Serial`/`printf` brut).
|
||||
- Le `build/` et le `sdkconfig` généré ne sont PAS versionnés ; seul `sdkconfig.defaults` l'est.
|
||||
- Bluetooth : Bluedroid, mode **BR/EDR only** (pas de BLE — économie RAM), **HFP-HF** activé, chemin audio **HCI** (`CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI`).
|
||||
- Flash 4 MB (cf. module A1S) ; partitions sans OTA.
|
||||
- PSRAM : ne PAS activer aveuglément (variante A1S incertaine) — détecter puis décider (Task 4).
|
||||
|
||||
---
|
||||
|
||||
### Task 1 : Squelette projet IDF qui boote
|
||||
|
||||
**Files:**
|
||||
- Create: `CMakeLists.txt` (racine)
|
||||
- Create: `main/CMakeLists.txt`
|
||||
- Create: `main/app_main.c`
|
||||
- Modify: `.gitignore` (ajouter `build/`, `sdkconfig`, `sdkconfig.old`, `dependencies.lock`, `managed_components/`)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: rien (point de départ).
|
||||
- Produces: une fonction d'entrée `void app_main(void)` qui log une bannière de boot. Les phases suivantes y câbleront leurs `*_init()`.
|
||||
|
||||
- [ ] **Step 1 : Créer le `CMakeLists.txt` racine**
|
||||
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(rtc_bl_phone)
|
||||
```
|
||||
|
||||
- [ ] **Step 2 : Créer `main/CMakeLists.txt`**
|
||||
|
||||
```cmake
|
||||
idf_component_register(
|
||||
SRCS "app_main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES nvs_flash
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 3 : Créer `main/app_main.c`**
|
||||
|
||||
```c
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "rtc_phone";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "RTC BL PHONE — socle ESP-IDF v5.4 (esp32 classique)");
|
||||
ESP_LOGI(TAG, "boot OK — aucun driver chargé (Phase 1)");
|
||||
while (true) {
|
||||
ESP_LOGI(TAG, "heartbeat");
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4 : Mettre à jour `.gitignore`**
|
||||
|
||||
Ajouter ces lignes à `.gitignore` (les laisser groupées sous un commentaire) :
|
||||
|
||||
```
|
||||
# ESP-IDF
|
||||
/build/
|
||||
/sdkconfig
|
||||
/sdkconfig.old
|
||||
/dependencies.lock
|
||||
/managed_components/
|
||||
```
|
||||
|
||||
- [ ] **Step 5 : Activer l'environnement et fixer la cible**
|
||||
|
||||
Run (dans le shell, une fois) :
|
||||
```bash
|
||||
. /Users/electron/esp/esp-idf/export.sh
|
||||
cd /Users/electron/RTC_BL_PHONE
|
||||
idf.py set-target esp32
|
||||
```
|
||||
Expected : `Building ESP-IDF components for target esp32` puis génération du `sdkconfig`, se termine sans erreur. Un fichier `sdkconfig` apparaît.
|
||||
|
||||
- [ ] **Step 6 : Builder**
|
||||
|
||||
Run : `idf.py build`
|
||||
Expected : se termine par `Project build complete.` et affiche la taille binaire (`rtc_bl_phone.bin`). Aucune erreur de compilation.
|
||||
|
||||
- [ ] **Step 7 : Flasher et observer le boot**
|
||||
|
||||
Run : `idf.py -p /dev/cu.usbserial-0001 flash monitor`
|
||||
Expected : après le reset, les lignes de log apparaissent :
|
||||
```
|
||||
I (xxx) rtc_phone: RTC BL PHONE — socle ESP-IDF v5.4 (esp32 classique)
|
||||
I (xxx) rtc_phone: boot OK — aucun driver chargé (Phase 1)
|
||||
I (xxx) rtc_phone: heartbeat
|
||||
```
|
||||
La ligne `heartbeat` se répète toutes les 5 s. Quitter le monitor avec `Ctrl+]`.
|
||||
(Si pas de matériel branché : valider au minimum que `idf.py build` réussit — le flash/monitor sera fait par l'utilisateur.)
|
||||
|
||||
- [ ] **Step 8 : Commit**
|
||||
|
||||
```bash
|
||||
git add CMakeLists.txt main/CMakeLists.txt main/app_main.c .gitignore
|
||||
git commit -m "feat(idf): socle projet ESP-IDF qui boote"
|
||||
```
|
||||
(Sujet ≤ 50 car., sans attribution IA — un hook local le vérifie.)
|
||||
|
||||
---
|
||||
|
||||
### Task 2 : Config Bluetooth Classic + table de partitions
|
||||
|
||||
**Files:**
|
||||
- Create: `sdkconfig.defaults`
|
||||
- Create: `partitions.csv`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: le projet de Task 1.
|
||||
- Produces: un `sdkconfig` régénéré où `CONFIG_BT_ENABLED`, `CONFIG_BT_CLASSIC_ENABLED`, `CONFIG_BT_HFP_CLIENT_ENABLE` et `CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI` valent `y`. Phase 4 (`bt_hfp`) en dépend.
|
||||
|
||||
- [ ] **Step 1 : Créer `partitions.csv` (4 MB, sans OTA)**
|
||||
|
||||
```csv
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 0x2F0000,
|
||||
```
|
||||
|
||||
- [ ] **Step 2 : Créer `sdkconfig.defaults`**
|
||||
|
||||
```
|
||||
# Cible
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
|
||||
# Partitions
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
|
||||
# Bluetooth : Bluedroid, BR/EDR only (pas de BLE), HFP-HF, data path HCI
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BT_HFP_ENABLE=y
|
||||
CONFIG_BT_HFP_CLIENT_ENABLE=y
|
||||
CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI=y
|
||||
|
||||
# Confort logs
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
|
||||
# Watchdog : tolérer la boucle de heartbeat
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
```
|
||||
|
||||
- [ ] **Step 3 : Régénérer la config et builder**
|
||||
|
||||
Run :
|
||||
```bash
|
||||
rm -f sdkconfig
|
||||
idf.py build
|
||||
```
|
||||
Expected : build OK. Note : la première reconfig peut afficher des avertissements de dépendances Kconfig BT — acceptable tant que le build se termine.
|
||||
|
||||
- [ ] **Step 4 : Vérifier que les clés BT sont bien appliquées**
|
||||
|
||||
Run :
|
||||
```bash
|
||||
grep -E "CONFIG_BT_CLASSIC_ENABLED=|CONFIG_BT_HFP_CLIENT_ENABLE=|CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI=|CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=" sdkconfig
|
||||
```
|
||||
Expected (4 lignes, toutes `=y`) :
|
||||
```
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BT_HFP_CLIENT_ENABLE=y
|
||||
CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI=y
|
||||
```
|
||||
Si une clé manque ou vaut `n` : ouvrir `idf.py menuconfig` → `Component config → Bluetooth` pour repérer le nom exact dans cette build d'IDF, corriger `sdkconfig.defaults`, puis reprendre au Step 3.
|
||||
|
||||
- [ ] **Step 5 : Commit**
|
||||
|
||||
```bash
|
||||
git add sdkconfig.defaults partitions.csv
|
||||
git commit -m "feat(idf): config Bluetooth Classic HFP-HF + partitions"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3 : Composant `config_store` (init NVS)
|
||||
|
||||
**Files:**
|
||||
- Create: `components/config_store/CMakeLists.txt`
|
||||
- Create: `components/config_store/include/config_store.h`
|
||||
- Create: `components/config_store/config_store.c`
|
||||
- Modify: `main/CMakeLists.txt` (ajouter `config_store` aux `REQUIRES`)
|
||||
- Modify: `main/app_main.c` (appeler `config_store_init()`)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: NVS (`nvs_flash`), le squelette de Task 1.
|
||||
- Produces:
|
||||
- `esp_err_t config_store_init(void);` — initialise NVS (gère `ESP_ERR_NVS_NO_FREE_PAGES` / `ESP_ERR_NVS_NEW_VERSION_FOUND` par effacement+réinit), retourne `ESP_OK` si la partition NVS est prête. Les phases ultérieures stockeront leur config via ce composant.
|
||||
|
||||
- [ ] **Step 1 : Créer `components/config_store/CMakeLists.txt`**
|
||||
|
||||
```cmake
|
||||
idf_component_register(
|
||||
SRCS "config_store.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES nvs_flash
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2 : Créer `components/config_store/include/config_store.h`**
|
||||
|
||||
```c
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialise la partition NVS (effacement+réinit si version incompatible).
|
||||
* @return ESP_OK si NVS est prêt, sinon le code d'erreur esp_err.
|
||||
*/
|
||||
esp_err_t config_store_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
- [ ] **Step 3 : Créer `components/config_store/config_store.c`**
|
||||
|
||||
```c
|
||||
#include "config_store.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "config_store";
|
||||
|
||||
esp_err_t config_store_init(void)
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_LOGW(TAG, "NVS illisible (%s) — effacement et réinit", esp_err_to_name(err));
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "NVS prêt");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "échec init NVS : %s", esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4 : Référencer le composant dans `main/CMakeLists.txt`**
|
||||
|
||||
Remplacer le contenu par :
|
||||
```cmake
|
||||
idf_component_register(
|
||||
SRCS "app_main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES nvs_flash config_store
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 5 : Appeler `config_store_init()` dans `main/app_main.c`**
|
||||
|
||||
Insérer l'include en tête et l'appel au début de `app_main`, avant la boucle :
|
||||
```c
|
||||
#include "config_store.h"
|
||||
```
|
||||
et, juste après la première ligne de log de bannière :
|
||||
```c
|
||||
ESP_ERROR_CHECK(config_store_init());
|
||||
```
|
||||
|
||||
- [ ] **Step 6 : Builder**
|
||||
|
||||
Run : `idf.py build`
|
||||
Expected : `Project build complete.` sans erreur.
|
||||
|
||||
- [ ] **Step 7 : Flasher et vérifier le log NVS**
|
||||
|
||||
Run : `idf.py -p /dev/cu.usbserial-0001 flash monitor`
|
||||
Expected : présence de la ligne :
|
||||
```
|
||||
I (xxx) config_store: NVS prêt
|
||||
```
|
||||
avant les `heartbeat`. (Sans matériel : valider que `idf.py build` réussit.)
|
||||
|
||||
- [ ] **Step 8 : Commit**
|
||||
|
||||
```bash
|
||||
git add components/config_store main/CMakeLists.txt main/app_main.c
|
||||
git commit -m "feat(idf): composant config_store init NVS"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4 : Détection PSRAM et décision d'activation
|
||||
|
||||
**Files:**
|
||||
- Modify: `sdkconfig.defaults` (uniquement si PSRAM détectée)
|
||||
- Create: `docs/superpowers/notes/2026-06-19-psram-a1s.md` (consigner le résultat)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: le binaire bootable des tâches précédentes.
|
||||
- Produces: une décision tracée (PSRAM présente → activée ; absente → laissée off avec justification). Phase 4 s'appuiera dessus pour dimensionner les buffers BT/audio.
|
||||
|
||||
- [ ] **Step 1 : Identifier la variante de flash/PSRAM de la carte**
|
||||
|
||||
Run : `esptool.py --port /dev/cu.usbserial-0001 flash_id`
|
||||
Expected : affiche `Detected flash size` (ex. `4MB`). Noter la valeur.
|
||||
Pour la PSRAM, observer le log de boot complet : au démarrage l'IDF logue la présence/absence de SPIRAM. Run `idf.py -p /dev/cu.usbserial-0001 monitor` et chercher une ligne `spiram` / `SPI RAM` pendant le boot du second-stage bootloader.
|
||||
|
||||
- [ ] **Step 2 : Consigner le résultat**
|
||||
|
||||
Créer `docs/superpowers/notes/2026-06-19-psram-a1s.md` avec :
|
||||
```markdown
|
||||
# Variante carte A1S — flash & PSRAM (2026-06-19)
|
||||
|
||||
- Flash détectée : <valeur de flash_id>
|
||||
- PSRAM : <présente | absente> (preuve : <ligne de log boot>)
|
||||
- Décision : <PSRAM activée dans sdkconfig.defaults | laissée désactivée>
|
||||
- Raison : <justification>
|
||||
```
|
||||
|
||||
- [ ] **Step 3 : (Conditionnel) Activer la PSRAM si présente**
|
||||
|
||||
UNIQUEMENT si le Step 1 confirme la présence de PSRAM, ajouter à `sdkconfig.defaults` :
|
||||
```
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_OCT=n
|
||||
```
|
||||
puis `rm -f sdkconfig && idf.py build` et vérifier `grep CONFIG_SPIRAM= sdkconfig` → `CONFIG_SPIRAM=y`, et que le boot ne régresse pas (`idf.py flash monitor` → toujours `NVS prêt` + `heartbeat`).
|
||||
Si PSRAM absente : ne rien changer au `sdkconfig.defaults` (laisser off).
|
||||
|
||||
- [ ] **Step 4 : Commit**
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/notes/2026-06-19-psram-a1s.md sdkconfig.defaults
|
||||
git commit -m "chore(idf): trace decision PSRAM carte A1S"
|
||||
```
|
||||
(`sdkconfig.defaults` n'est ré-ajouté que s'il a changé au Step 3.)
|
||||
|
||||
---
|
||||
|
||||
## Critère de sortie de Phase 1
|
||||
|
||||
Le firmware ESP-IDF natif **build, flashe et boote** sur l'ESP32-A1S, logue une bannière + `NVS prêt` + `heartbeat` en série, avec Bluetooth Classic / HFP-HF activé dans la config (vérifié par `grep` sur `sdkconfig`) et la décision PSRAM tracée. Aucun driver métier n'est encore présent — c'est l'objet des Phases 2-5.
|
||||
|
||||
## Notes sur les tests
|
||||
|
||||
La Phase 1 est du scaffolding : la « vérification » est le **succès du build** + l'**observation du log de boot**, pas des tests unitaires. Les tests unitaires (Unity host) arrivent en Phases 2-3 sur les composants de logique pure (DTMF Goertzel, décodeur d'impulsion, machine d'état SLIC, générateur de tonalités), conformément au spec.
|
||||
Reference in New Issue
Block a user