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
This commit is contained in:
Claude committed 2026-06-13 07:10:28 +00:00
1 parent afbc0e5e23
commit 76b02b840c
3 files changed
+328 -46

No files matched your search

+94 -46
View File
@@ -1,76 +1,124 @@
# Kidsbox # Kidsbox
This is a battery-powered rfid-tag-controlled audio player meant to be used by kids. A battery-powered, RFID/NFC-tag-controlled audio player for 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. 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| |Front|Side|
|-|-| |-|-|
|![Frontal View](docs/img/front.jpeg)|![Side View](docs/img/side.jpeg)| |![Frontal View](docs/img/front.jpeg)|![Side View](docs/img/side.jpeg)|
## Todo ---
- Volume buttons ## Quick Start
- Playlists
- Pausing
- ... I have a lot of ideas :)
## 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). ```bash
2. Clone this repository and `cd` into the repository directory. ./install.sh
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. This installs **ESP-IDF v5.2** and **ESP-ADF v2.7** into `~/esp/` and writes a `.env` file.
5. Execute `idf.py build` to compile, `idf.py build flash monitor` to flash it to your board. 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/<tag-id>` (plain text, one URL or file path per file).
---
## Web Interface
Once connected to WiFi, open `http://<device-ip>` 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 ## Hardware
### Parts ### Parts
_TBD_
| Part | Links | | Part | Links |
|-|-| |---|---|
| ESP32-LyraT 4.3 | [Mouser](https://www.mouser.de/ProductDetail/Espressif-Systems/ESP32-LyraT?qs=MLItCLRbWsxPzPCja546ZA%3D%3D) | | 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) | — |
### DIP-Switch (LyraT 4.3)
### Hardware Setup | Switch | Position | Purpose |
|---|---|---|
The peripherals must be configured for:
- SD Card
- RFID-module
- Vol+/- buttons
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.
#### DIP-Switch
The switches must be exatly in the positions outlined in the table.
|Number|Setting|Purpose|
|-|-|-|
| 1 | OFF | Disconnects GPIO 12 from SD-D2 | | 1 | OFF | Disconnects GPIO 12 from SD-D2 |
| 2 | OFF | Disconnects GPIO 13 from SD-D3 | | 2 | OFF | Disconnects GPIO 13 from SD-D3 |
| 3 | OFF | Connects GPIO 15 to SD CMD | | 3 | OFF | Connects GPIO 15 to SD CMD |
|4|ON|Connects GPIO 13 to the JTAG header| | 4 | ON | Connects GPIO 13 to JTAG header |
|5|ON|Connects GPIO 12 to the JTAG header| | 5 | ON | Connects GPIO 12 to JTAG header |
| 6 | OFF | Connects GPIO 13 to SD CLK | | 6 | OFF | Connects GPIO 13 to SD CLK |
|7|OFF|Disconnects GPIO 12 from the AUX-in-detection| | 7 | OFF | Disconnects GPIO 12 from AUX-in detection |
|8|OFF| - | | 8 | OFF | — |
#### RFID ### RC522 Wiring
_The MISO/MOSI pins are not inverted on host/chip_ > MISO/MOSI pin names are **not** inverted between host and chip on this board.
Connection Table: | RC522 Pin | LyraT v4.3 Pin | GPIO |
|---|---|---|
|RC522 Pin|ESP32 Lyrat v4.3 Pin|GPIO Number| | 3.3V | 3.3V | — |
|-|-|-| | RST | 3.3V | — |
|3.3v|3.3v|-| | GND | GND | — |
|RST|3.3v|-|
|GND|GND|-|
| SDA | I2C SDA | 18 | | SDA | I2C SDA | 18 |
| SCK | I2C SCL | 23 | | SCK | I2C SCL | 23 |
| MOSI | JTAG MTCK | 13 | | MOSI | JTAG MTCK | 13 |
| MISO | JTAG MTDI | 12 | | MISO | JTAG MTDI | 12 |
### SD Card Layout
```
/sdcard/
www/ ← web interface files (copied by setup.sh)
internal/ ← tag-id → URL/path mapping files (manual)
cache/ ← downloaded audio files (managed automatically)
```
---
## Roadmap
- [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)
Executable
+104
View File
@@ -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" <<EOF
# Generated by install.sh — source this before building:
# source .env
export IDF_PATH="${IDF_PATH:-$IDF_DEFAULT}"
export ADF_PATH="${ADF_PATH:-$ADF_DEFAULT}"
EOF
info "Wrote $ENV_FILE"
# ── done ───────────────────────────────────────────────────────────────────────
heading "Done!"
echo ""
echo " Next steps:"
echo " 1. Source the environment:"
echo " source .env && source \$IDF_PATH/export.sh"
echo " 2. Configure WiFi and build:"
echo " ./setup.sh"
echo ""
Executable
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env bash
# setup.sh — configure, build, and optionally flash/deploy Kidsbox
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DEVICE_DIR="$SCRIPT_DIR/device"
WWW_DIR="$SCRIPT_DIR/www"
BOLD='\033[1m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RED='\033[0;31m'; NC='\033[0m'
info() { echo -e "${GREEN}[setup]${NC} $*"; }
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
die() { echo -e "${RED}[error]${NC} $*" >&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" <<EOF
CONFIG_ESP_WIFI_SSID="${WIFI_SSID}"
CONFIG_ESP_WIFI_PASSWORD="${WIFI_PASSWORD}"
CONFIG_ESP_MAXIMUM_RETRY=5
CONFIG_ESP_ADF_BOARD_LYRAT_V4_3=y
CONFIG_IDF_TARGET="esp32"
EOF
info "Wrote $SDKCONFIG_DEFAULTS"
fi
# ── set target & build ─────────────────────────────────────────────────────────
heading "Building firmware"
cd "$DEVICE_DIR"
idf.py set-target esp32
info "Running idf.py build (this takes a few minutes on first run)"
idf.py build
info "Build complete."
# ── flash ──────────────────────────────────────────────────────────────────────
heading "Flash to device"
prompt "Flash firmware to device now? [y/N] "
read -r DO_FLASH
if [[ "$DO_FLASH" =~ ^[Yy]$ ]]; then
prompt "Serial port (e.g. /dev/ttyUSB0, leave blank to auto-detect): "
read -r SERIAL_PORT
if [[ -n "$SERIAL_PORT" ]]; then
idf.py -p "$SERIAL_PORT" flash monitor
else
idf.py flash monitor
fi
fi
# ── SD card setup ──────────────────────────────────────────────────────────────
heading "SD card setup"
prompt "Mount path of SD card (e.g. /media/\$USER/KIDSBOX, leave blank to skip): "
read -r SD_MOUNT
if [[ -n "$SD_MOUNT" ]]; then
[[ -d "$SD_MOUNT" ]] || die "Mount path not found: $SD_MOUNT"
info "Creating directory structure on SD card"
mkdir -p "$SD_MOUNT/www"
mkdir -p "$SD_MOUNT/internal"
mkdir -p "$SD_MOUNT/cache"
info "Copying web interface"
cp -r "$WWW_DIR/." "$SD_MOUNT/www/"
info "SD card contents:"
ls "$SD_MOUNT/"
echo ""
echo " SD card is ready."
echo " To configure a tag: create a file at $SD_MOUNT/internal/<tag-id>"
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://<device-ip> in your browser."
echo ""