mirror of
https://github.com/sweidac/kidsbox.git
synced 2026-10-27 22:52:57 +01:00
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:
3 files changed
+328
-46
No files matched your search
@@ -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|
|
||||
|-|-|
|
||||
|||
|
||||
|
||||
## 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/<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
|
||||
|
||||
### 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) | — |
|
||||
|
||||
### DIP-Switch (LyraT 4.3)
|
||||
|
||||
### Hardware Setup
|
||||
|
||||
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|
|
||||
|-|-|-|
|
||||
| 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 the JTAG header|
|
||||
|5|ON|Connects GPIO 12 to the JTAG header|
|
||||
| 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 the AUX-in-detection|
|
||||
|8|OFF| - |
|
||||
| 7 | OFF | Disconnects GPIO 12 from AUX-in detection |
|
||||
| 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|ESP32 Lyrat v4.3 Pin|GPIO Number|
|
||||
|-|-|-|
|
||||
|3.3v|3.3v|-|
|
||||
|RST|3.3v|-|
|
||||
|GND|GND|-|
|
||||
| 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 |
|
||||
|
||||
### 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
@@ -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 ""
|
||||
@@ -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 ""
|
||||
Reference in new issue
Block a user