From 76b02b840c9cc84fac7caff4be9010ec8022d175 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 07:10:28 +0000 Subject: [PATCH] Add install.sh, setup.sh, and update README install.sh: install ESP-IDF v5.2 + ESP-ADF v2.7, detect existing env, write .env file with IDF_PATH/ADF_PATH setup.sh: prompt for WiFi creds, write sdkconfig.defaults, build, optionally flash and prepare SD card README: rewrite to reflect current architecture (NFC URL streaming, web UI, no arduino-esp32), add roadmap and quick start https://claude.ai/code/session_01MhAnh68DPNnTjcKqjNYDmk --- README.md | 150 +++++++++++++++++++++++++++++++++++------------------ install.sh | 104 +++++++++++++++++++++++++++++++++++++ setup.sh | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 333 insertions(+), 51 deletions(-) create mode 100755 install.sh create mode 100755 setup.sh diff --git a/README.md b/README.md index 67eb41d..30846c3 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,124 @@ # Kidsbox -This is a battery-powered rfid-tag-controlled audio player meant to be used by kids. -You can bundle audio-files to rfid tags and let your kids choose the music or audiobook of their favour by placing the corresponding tag atop of the box. +A battery-powered, RFID/NFC-tag-controlled audio player for kids. +Place an NFC tag on the box — the mapped audio file or stream plays automatically. +Tags are programmed from a smartphone using any NFC writing app (e.g. NFC Tools). +The box streams from the URL written to the tag, caches it locally for offline play, +and exposes a web interface for playback control and storage management. |Front|Side| |-|-| |![Frontal View](docs/img/front.jpeg)|![Side View](docs/img/side.jpeg)| -## Todo +--- -- Volume buttons -- Playlists -- Pausing -- ... I have a lot of ideas :) +## Quick Start -## Setup +### 1. Install the development environment -1. Install ESP-ADF and ESP-IDF according to the [install guide](https://docs.espressif.com/projects/esp-adf/en/latest/get-started/index.html#installation-step-by-step). -2. Clone this repository and `cd` into the repository directory. -3. `git submodule init --recursive` -3. `cd device` -4. Make sure that you have `idf.py` available in your environment. This is described in the install guide. -5. Execute `idf.py build` to compile, `idf.py build flash monitor` to flash it to your board. +```bash +./install.sh +``` + +This installs **ESP-IDF v5.2** and **ESP-ADF v2.7** into `~/esp/` and writes a `.env` file. +Existing installations are detected automatically (via `$IDF_PATH` / `$ADF_PATH`). + +### 2. Configure and build + +```bash +source .env && source $IDF_PATH/export.sh +./setup.sh +``` + +`setup.sh` will: +- Prompt for your WiFi SSID and password +- Build the firmware (`idf.py build`) +- Optionally flash to the device +- Optionally prepare an SD card (create directories, copy web UI) + +--- + +## How It Works + +1. Write an audio URL to an NFC tag with a smartphone app (e.g. **NFC Tools**). +2. Place the tag on the box → the box reads the URL, streams the audio, and caches it. +3. Subsequent plays of the same tag use the local cache (no internet needed). +4. Removing the tag pauses playback; replacing it resumes. + +Without an NDEF URL on a tag, the box falls back to a local mapping file at +`/sdcard/internal/` (plain text, one URL or file path per file). + +--- + +## Web Interface + +Once connected to WiFi, open `http://` in a browser. + +| Section | What it does | +|---|---| +| Now Playing | Track URI, play/pause/stop, volume slider | +| Device Info | IP address, playback status, cache size, free heap | +| Storage | List and delete cached files | +| Bluetooth | Toggle BT speaker mode (stub — in progress) | + +--- ## Hardware ### Parts -_TBD_ +| Part | Links | +|---|---| +| ESP32-LyraT 4.3 | [Mouser](https://www.mouser.de/ProductDetail/Espressif-Systems/ESP32-LyraT?qs=MLItCLRbWsxPzPCja546ZA%3D%3D) | +| RC522 RFID/NFC module | — | +| SD card (FAT32, any size) | — | -|Part|Links| -|-|-| -|ESP32-LyraT 4.3|[Mouser](https://www.mouser.de/ProductDetail/Espressif-Systems/ESP32-LyraT?qs=MLItCLRbWsxPzPCja546ZA%3D%3D)| +### DIP-Switch (LyraT 4.3) +| Switch | Position | Purpose | +|---|---|---| +| 1 | OFF | Disconnects GPIO 12 from SD-D2 | +| 2 | OFF | Disconnects GPIO 13 from SD-D3 | +| 3 | OFF | Connects GPIO 15 to SD CMD | +| 4 | ON | Connects GPIO 13 to JTAG header | +| 5 | ON | Connects GPIO 12 to JTAG header | +| 6 | OFF | Connects GPIO 13 to SD CLK | +| 7 | OFF | Disconnects GPIO 12 from AUX-in detection | +| 8 | OFF | — | -### Hardware Setup +### RC522 Wiring -The peripherals must be configured for: +> MISO/MOSI pin names are **not** inverted between host and chip on this board. -- SD Card -- RFID-module -- Vol+/- buttons +| RC522 Pin | LyraT v4.3 Pin | GPIO | +|---|---|---| +| 3.3V | 3.3V | — | +| RST | 3.3V | — | +| GND | GND | — | +| SDA | I2C SDA | 18 | +| SCK | I2C SCL | 23 | +| MOSI | JTAG MTCK | 13 | +| MISO | JTAG MTDI | 12 | -The ESP32-LyraT board has only a few exposed GPIO pins. To save pins, the SD-Card reader runs only in 1-Wire mode. I have actually no idea what the limitations and differences are, but I didn't encounter any problems yet. +### SD Card Layout -#### DIP-Switch +``` +/sdcard/ + www/ ← web interface files (copied by setup.sh) + internal/ ← tag-id → URL/path mapping files (manual) + cache/ ← downloaded audio files (managed automatically) +``` -The switches must be exatly in the positions outlined in the table. +--- -|Number|Setting|Purpose| -|-|-|-| -|1|OFF|Disconnects GPIO 12 from SD-D2| -|2|OFF|Disconnects GPIO 13 from SD-D3| -|3|OFF|Connects GPIO 15 to SD CMD| -|4|ON|Connects GPIO 13 to the JTAG header| -|5|ON|Connects GPIO 12 to the JTAG header| -|6|OFF|Connects GPIO 13 to SD CLK| -|7|OFF|Disconnects GPIO 12 from the AUX-in-detection| -|8|OFF| - | +## Roadmap -#### RFID - -_The MISO/MOSI pins are not inverted on host/chip_ - -Connection Table: - -|RC522 Pin|ESP32 Lyrat v4.3 Pin|GPIO Number| -|-|-|-| -|3.3v|3.3v|-| -|RST|3.3v|-| -|GND|GND|-| -|SDA|I2C SDA|18| -|SCK|I2C SCL|23| -|MOSI|JTAG MTCK|13| -|MISO|JTAG MTDI|12| \ No newline at end of file +- [x] RFID/NFC tag detection +- [x] NDEF URL reading from NFC tags +- [x] HTTP audio streaming +- [x] Local file cache +- [x] WiFi (native ESP-IDF, no Arduino dependency) +- [x] Web interface — playback controls, storage, device info +- [ ] Volume buttons (hardware) +- [ ] Playlists +- [ ] Bluetooth speaker mode (A2DP sink) diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..1cb19ff --- /dev/null +++ b/install.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# install.sh — set up the Kidsbox development environment +# Installs ESP-IDF v5.2 + ESP-ADF v2.7, or validates an existing installation. +set -euo pipefail + +BOLD='\033[1m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RED='\033[0;31m'; NC='\033[0m' +info() { echo -e "${GREEN}[install]${NC} $*"; } +warn() { echo -e "${YELLOW}[warn]${NC} $*"; } +die() { echo -e "${RED}[error]${NC} $*" >&2; exit 1; } +heading() { echo -e "\n${BOLD}$*${NC}"; } + +IDF_VERSION="v5.2.3" +ADF_VERSION="v2.7" +IDF_DEFAULT="$HOME/esp/esp-idf" +ADF_DEFAULT="$HOME/esp/esp-adf" + +# ── prerequisites ────────────────────────────────────────────────────────────── + +heading "Checking prerequisites" + +for cmd in git python3 pip3 cmake ninja; do + if command -v "$cmd" &>/dev/null; then + info "$cmd: $(command -v $cmd)" + else + die "$cmd not found. Please install it and re-run." + fi +done + +PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') +python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3,8) else 1)' \ + || die "Python >= 3.8 required (found $PYTHON_VERSION)" +info "Python $PYTHON_VERSION: OK" + +# ── ESP-IDF ──────────────────────────────────────────────────────────────────── + +heading "ESP-IDF ($IDF_VERSION)" + +if [[ -n "${IDF_PATH:-}" && -f "$IDF_PATH/export.sh" ]]; then + INSTALLED_TAG=$(git -C "$IDF_PATH" describe --tags 2>/dev/null || echo "unknown") + info "Found existing ESP-IDF at \$IDF_PATH=$IDF_PATH (tag: $INSTALLED_TAG)" + warn "Skipping IDF install. To use a fresh install, unset \$IDF_PATH." +else + IDF_PATH="${IDF_DEFAULT}" + info "Installing ESP-IDF $IDF_VERSION to $IDF_PATH" + mkdir -p "$(dirname "$IDF_PATH")" + if [[ -d "$IDF_PATH/.git" ]]; then + git -C "$IDF_PATH" fetch --tags + git -C "$IDF_PATH" checkout "$IDF_VERSION" + else + git clone --depth 1 --branch "$IDF_VERSION" \ + https://github.com/espressif/esp-idf.git "$IDF_PATH" + fi + git -C "$IDF_PATH" submodule update --init --recursive + info "Running ESP-IDF install script" + "$IDF_PATH/install.sh" esp32 + info "ESP-IDF installed." +fi + +# ── ESP-ADF ──────────────────────────────────────────────────────────────────── + +heading "ESP-ADF ($ADF_VERSION)" + +if [[ -n "${ADF_PATH:-}" && -d "$ADF_PATH" ]]; then + INSTALLED_TAG=$(git -C "$ADF_PATH" describe --tags 2>/dev/null || echo "unknown") + info "Found existing ESP-ADF at \$ADF_PATH=$ADF_PATH (tag: $INSTALLED_TAG)" + warn "Skipping ADF install. To use a fresh install, unset \$ADF_PATH." +else + ADF_PATH="${ADF_DEFAULT}" + info "Installing ESP-ADF $ADF_VERSION to $ADF_PATH" + mkdir -p "$(dirname "$ADF_PATH")" + if [[ -d "$ADF_PATH/.git" ]]; then + git -C "$ADF_PATH" fetch --tags + git -C "$ADF_PATH" checkout "$ADF_VERSION" + else + git clone --depth 1 --branch "$ADF_VERSION" \ + https://github.com/espressif/esp-adf.git "$ADF_PATH" + fi + git -C "$ADF_PATH" submodule update --init --recursive + info "ESP-ADF installed." +fi + +# ── env file ─────────────────────────────────────────────────────────────────── + +heading "Writing .env" + +ENV_FILE="$(dirname "$0")/.env" +cat > "$ENV_FILE" <&2; exit 1; } +heading() { echo -e "\n${BOLD}$*${NC}"; } +prompt() { echo -e "${YELLOW}$*${NC}"; } + +# ── check environment ────────────────────────────────────────────────────────── + +heading "Checking environment" + +[[ -f "$SCRIPT_DIR/.env" ]] && source "$SCRIPT_DIR/.env" + +[[ -n "${IDF_PATH:-}" ]] || die "IDF_PATH not set. Run: source .env && source \$IDF_PATH/export.sh" +[[ -n "${ADF_PATH:-}" ]] || die "ADF_PATH not set. Run: source .env && source \$IDF_PATH/export.sh" +command -v idf.py &>/dev/null || die "idf.py not found. Run: source \$IDF_PATH/export.sh" + +info "IDF_PATH: $IDF_PATH" +info "ADF_PATH: $ADF_PATH" + +# ── WiFi credentials ─────────────────────────────────────────────────────────── + +heading "WiFi configuration" + +SDKCONFIG_DEFAULTS="$DEVICE_DIR/sdkconfig.defaults" + +if [[ -f "$SDKCONFIG_DEFAULTS" ]] \ + && grep -q "CONFIG_ESP_WIFI_SSID" "$SDKCONFIG_DEFAULTS" 2>/dev/null; then + CURRENT_SSID=$(grep 'CONFIG_ESP_WIFI_SSID' "$SDKCONFIG_DEFAULTS" | cut -d'"' -f2) + info "WiFi already configured (SSID: $CURRENT_SSID)" + prompt "Re-enter credentials? [y/N] " + read -r RECONFIGURE + [[ "$RECONFIGURE" =~ ^[Yy]$ ]] || SKIP_WIFI=1 +fi + +if [[ -z "${SKIP_WIFI:-}" ]]; then + prompt "WiFi SSID: " + read -r WIFI_SSID + prompt "WiFi Password: " + read -rs WIFI_PASSWORD + echo "" + + [[ -n "$WIFI_SSID" ]] || die "SSID cannot be empty" + [[ -n "$WIFI_PASSWORD" ]] || die "Password cannot be empty" + + cat > "$SDKCONFIG_DEFAULTS" <" + echo " containing either:" + echo " - An http:// URL (will be streamed and cached)" + echo " - A local path (e.g. /sdcard/music/song.mp3)" + echo " Tag IDs are shown in the serial monitor when a tag is scanned." +fi + +# ── done ─────────────────────────────────────────────────────────────────────── + +heading "Done!" +echo "" +echo " Once running, open http:// in your browser." +echo ""