diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50f349d --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# ESP-IDF build artifacts +/build/ +sdkconfig +sdkconfig.old + +# Managed components are fetched by the component manager +/managed_components/ +**/managed_components/ +dependencies.lock + +# Editor / OS +.vscode/ +.idea/ +*.swp +.DS_Store + +# Python venv occasionally created by tooling +.venv/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..60a8828 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +# Lisael Box — top-level project CMake for ESP-IDF 5.4/5.5 +# Target: ESP32-S3-BOX-3 +cmake_minimum_required(VERSION 3.16) + +# --- ESP-ADF integration ----------------------------------------------------- +# ESP-ADF is NOT a managed component: it is installed separately and exposed via +# the ADF_PATH environment variable (see README "Installer ESP-ADF"). +# We add its component tree to EXTRA_COMPONENT_DIRS so that audio_pipeline, +# http_stream, esp_decoder, fatfs_stream, i2s_stream etc. become available. +# +# If ADF_PATH is unset the project still configures, but the audio modules that +# need ADF will fail to link. We surface a clear message instead of a cryptic +# "component not found" error. +if(DEFINED ENV{ADF_PATH}) + set(ADF_COMPONENTS "$ENV{ADF_PATH}/components") + if(EXISTS "${ADF_COMPONENTS}") + list(APPEND EXTRA_COMPONENT_DIRS "${ADF_COMPONENTS}") + message(STATUS "Lisael Box: ESP-ADF components from ${ADF_COMPONENTS}") + else() + message(WARNING "Lisael Box: ADF_PATH set but ${ADF_COMPONENTS} not found") + endif() +else() + message(WARNING + "Lisael Box: ADF_PATH not set. Audio streaming (webradio / SD playback) " + "will not link. Run '. $ADF_PATH/export.sh' or export ADF_PATH. " + "See README.") +endif() + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(lisael_box) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..27f4840 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,62 @@ +# Lisael Box — main component. +# +# ESP-ADF components (audio_pipeline, esp-adf-libs, audio_stream, esp_decoder, +# audio_board ...) are resolved at configure time from ADF_PATH via +# EXTRA_COMPONENT_DIRS (top-level CMakeLists.txt). They are listed in REQUIRES +# so the linker pulls them in; if ADF_PATH is missing the build fails loudly +# in the audio sources only. +# +# The audio REQUIRES are guarded: when ADF is absent we still let the UI / net +# layers build for inspection. Set LISAEL_WITH_ADF=OFF to compile without audio +# (UI/clock/breathing only) for quick syntax checks on a machine without ADF. + +set(srcs + "app_main.c" + "net/wifi.c" + "net/sntp_time.c" + "net/weather.c" + "ui/ui.c" + "ui/fr_date.c" + "ui/icons.c" + "modes/home.c" + "modes/radio.c" + "modes/soundbox.c" + "modes/calm.c" + "modes/clock.c" + "modes/games.c" + "audio/audio_player.c" + "audio/radio_pipeline.c" + "audio/sd_player.c" +) + +set(reqs + nvs_flash + esp_wifi + esp_event + esp_netif + esp-tls + esp_http_client + mbedtls # esp_crt_bundle_attach (CA bundle for HTTPS) + esp-box-3 # BSP: display, touch, codec, sdcard, sensors + json # IDF built-in cJSON component (provides cJSON.h) + fatfs + esp_driver_i2c + esp_driver_tsens # driver/temperature_sensor.h (internal die temp) +) + +# ESP-ADF audio components. Names follow the ADF master component tree. +# Guarded so a no-ADF inspection build still configures. +if(DEFINED ENV{ADF_PATH}) + list(APPEND reqs + audio_pipeline + audio_stream + esp-adf-libs + audio_sal + ) +endif() + +idf_component_register( + SRCS ${srcs} + INCLUDE_DIRS "." + REQUIRES ${reqs} +) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..99b99ee --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,66 @@ +menu "Lisael Box configuration" + + menu "Wi-Fi" + config LISAEL_WIFI_SSID + string "Wi-Fi SSID" + default "myssid" + help + SSID (network name) the box connects to at boot. Stored as a + build-time default; the runtime value can also be provisioned + into NVS later (see net/wifi.c). + + config LISAEL_WIFI_PASSWORD + string "Wi-Fi password" + default "mypassword" + help + WPA2 passphrase for the SSID above. Leave the defaults and + override via NVS provisioning if you do not want creds in the + binary. + endmenu + + menu "Weather (open-meteo.com)" + config LISAEL_WEATHER_LAT + string "Default latitude" + default "48.8566" + help + Latitude used for the open-meteo forecast when none is stored + in NVS. Default is Paris, France. + + config LISAEL_WEATHER_LON + string "Default longitude" + default "2.3522" + help + Longitude used for the open-meteo forecast when none is stored + in NVS. Default is Paris, France. + + config LISAEL_WEATHER_CITY + string "City label" + default "Paris" + help + Human-readable city name shown on the home screen next to the + temperature. + + config LISAEL_WEATHER_REFRESH_MIN + int "Refresh interval (minutes)" + range 5 180 + default 30 + help + How often the weather task re-fetches the forecast. + endmenu + + menu "Audio" + config LISAEL_VOLUME_MAX_PERCENT + int "Maximum volume (child cap, percent)" + range 10 100 + default 60 + help + Hard cap on output volume. The volume UI never lets the value + exceed this, to protect a child's hearing. + + config LISAEL_VOLUME_DEFAULT_PERCENT + int "Default volume at boot (percent)" + range 5 100 + default 35 + endmenu + +endmenu diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..5444411 --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,24 @@ +# Managed component dependencies for Lisael Box. +# Fetched automatically into managed_components/ on `idf.py build`. +# +# NOTE: ESP-ADF is NOT listed here — it is not distributed via the component +# registry. It is installed separately and pulled in through ADF_PATH / +# EXTRA_COMPONENT_DIRS (see top-level CMakeLists.txt and README). +# +# Versions below reflect the esp-box-3 BSP v3.2.x line, which itself depends on +# LVGL 9.x, esp_lvgl_port ^2 and esp_codec_dev ~1.5. We let the BSP pin the +# exact transitive versions; we only state the top-level ones we use directly. +# Per repo supply-chain policy these should eventually be mirrored/pinned by +# hash against the audited baseline — TODO before any production build. +dependencies: + idf: + version: ">=5.4" + + # Board support package for the ESP32-S3-BOX-3: display, touch, ES8311 codec, + # ES7210 mic, microSD, IMU, buttons. Pulls esp_lvgl_port, lvgl, esp_codec_dev, + # esp_lcd_ili9341, button, icm42670, aht30 transitively. + espressif/esp-box-3: "^3.2.0" + + # JSON parsing (manifest.json, open-meteo replies) uses the cJSON shipped with + # ESP-IDF as the built-in "json" component — no managed dependency needed. + # (REQUIRES json in main/CMakeLists.txt.) diff --git a/main/lisael_config.h b/main/lisael_config.h new file mode 100644 index 0000000..c903e89 --- /dev/null +++ b/main/lisael_config.h @@ -0,0 +1,28 @@ +// Lisael Box — shared compile-time configuration and small helpers. +#pragma once + +#include "sdkconfig.h" + +// --- Timezone (Europe/Paris with EU DST rules) ------------------------------ +// CET-1CEST,M3.5.0,M10.5.0/3 : last Sunday of March 02:00 -> +1, last Sunday +// of October 03:00 -> back. Set with setenv("TZ", ...) + tzset() in sntp_time.c. +#define LISAEL_TZ "CET-1CEST,M3.5.0,M10.5.0/3" + +// --- NVS namespace / keys ---------------------------------------------------- +#define LISAEL_NVS_NS "lisael" +#define LISAEL_NVS_WIFI_SSID "wifi_ssid" +#define LISAEL_NVS_WIFI_PASS "wifi_pass" +#define LISAEL_NVS_LAT "lat" +#define LISAEL_NVS_LON "lon" +#define LISAEL_NVS_VOLUME "volume" + +// --- Audio volume (child-safe) ---------------------------------------------- +#define LISAEL_VOLUME_MAX CONFIG_LISAEL_VOLUME_MAX_PERCENT +#define LISAEL_VOLUME_DEFAULT CONFIG_LISAEL_VOLUME_DEFAULT_PERCENT + +// --- Weather ----------------------------------------------------------------- +#define LISAEL_WEATHER_REFRESH_MIN CONFIG_LISAEL_WEATHER_REFRESH_MIN + +// --- Display geometry (ESP32-S3-BOX-3 LCD) ---------------------------------- +#define LISAEL_SCREEN_W 320 +#define LISAEL_SCREEN_H 240 diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 0000000..c9ec8d5 --- /dev/null +++ b/partitions.csv @@ -0,0 +1,18 @@ +# Lisael Box partition table — ESP32-S3-BOX-3 (16 MB flash) +# +# Single factory app, deliberately large (4 MB) because the firmware links +# ESP-ADF (audio pipeline + decoders), LVGL 9 and the esp-box BSP, which is a +# big binary. No OTA partitions for now (kept simple); switch to a dual-OTA +# layout later if remote updates are needed — there is plenty of headroom. +# +# The "sounds" / audio assets live on the physical microSD card mounted by the +# BSP (FATFS), NOT in flash — so no large data partition is needed here. +# +# All offsets/sizes aligned to 0x1000 (4 KB). Bootloader + partition table sit +# below 0x10000. +# +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x400000, +storage, data, fat, 0x410000, 0x100000, diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..84d693f --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,28 @@ +# Lisael Box — committed defaults (target-independent). +# Target-specific overrides live in sdkconfig.defaults.esp32s3. +# Regenerate sdkconfig with: idf.py set-target esp32s3 && idf.py reconfigure + +# --- Partition table --------------------------------------------------------- +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + +# --- Logging / build --------------------------------------------------------- +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_COMPILER_OPTIMIZATION_PERF=y + +# --- FreeRTOS ---------------------------------------------------------------- +# 1 kHz tick gives smoother LVGL animations (bubble breathing, transitions). +CONFIG_FREERTOS_HZ=1000 + +# --- mbedTLS / TLS ----------------------------------------------------------- +# open-meteo.com is HTTPS; webradio streams may be HTTPS too. Use the global +# CA store bundle so we don't have to embed individual certs. +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y + +# --- HTTP client ------------------------------------------------------------- +CONFIG_ESP_TLS_INSECURE=n + +# --- esp-box BSP ------------------------------------------------------------- +# SD card mount point used throughout the firmware (see audio/sd_player.c). +CONFIG_BSP_SD_MOUNT_POINT="/sdcard" diff --git a/sdkconfig.defaults.esp32s3 b/sdkconfig.defaults.esp32s3 new file mode 100644 index 0000000..3fded34 --- /dev/null +++ b/sdkconfig.defaults.esp32s3 @@ -0,0 +1,34 @@ +# Lisael Box — ESP32-S3-BOX-3 specific defaults. +# Applied automatically on top of sdkconfig.defaults when target == esp32s3. + +# --- Flash: 16 MB on the BOX-3 ---------------------------------------------- +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y + +# --- PSRAM: 8 MB octal SPI on the BOX-3 ------------------------------------- +# Required for the LVGL framebuffer(s) and ESP-ADF ring buffers. +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +# Let malloc() fall back to PSRAM for large allocations (LVGL buffers, decoders). +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y + +# --- CPU --------------------------------------------------------------------- +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y + +# --- LVGL (esp_lvgl_port / lvgl 9) ------------------------------------------ +# Use PSRAM for the LVGL draw buffers so the 320x240x16bpp framebuffer fits. +CONFIG_LV_MEM_CUSTOM=y +CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y + +# --- Main task --------------------------------------------------------------- +# app_main runs BSP + LVGL bring-up; give it a comfortable stack. +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 + +# --- Audio: ESP-ADF decoders need a decent heap; keep DMA in internal RAM --- +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096