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
|
# 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|
|
||||||
|-|-|
|
|-|-|
|
||||||
|||
|
|||
|
||||||
|
|
||||||
## 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
@@ -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