From cc1654a28f59be67c2350b1e733a58cb7253b8a5 Mon Sep 17 00:00:00 2001 From: SimeonOnSecurity <4913771+simeononsecurity@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:01:14 -0500 Subject: [PATCH 1/2] feat: add M5Atom Echo/Lite, Atom VoiceS3R (ESP32-S3) board support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hardware support added - M5Atom Echo (USE_M5ATOM_ECHO): NeoPixel LED + GPIO25 buzzer - M5Atom Lite (USE_M5ATOM_LITE): NeoPixel LED, no buzzer - Atom VoiceS3R (USE_M5ATOM_VOICES3R): ESP32-S3-PICO-1-N8R8, ES8311 I2S speaker via M5Unified, no RGB LED, button on GPIO41 platformio.ini - [env:m5atom-echo]: Adafruit NeoPixel, usbserial port, 1.5Mbaud upload - [env:m5atom-lite]: same board, no buzzer flag - [env:m5atom-voices3r]: espressif32@6.7.0, esp32-s3-devkitc-1, qio_opi PSRAM, ARDUINO_USB_CDC_ON_BOOT, M5Unified@^0.2.2 main.cpp - TESTING_MODE guarded with #ifndef so build flag can override it - Adafruit NeoPixel include guarded for Echo/Lite only (no NeoPixel on S3R) - M5Unified speaker: M5.Speaker.tone() used for all audio on VoiceS3R - Startup: SMB World 1-1 overworld riff (E E _E_ C E G) - Detection chirp: two ascending tones (2000→2800 Hz) - Heartbeat beep: two monotone 1500 Hz pulses - Serial1 UART mirror skipped on VoiceS3R (USB-CDC only) - setup(): M5.begin() + Speaker.setVolume(200) for VoiceS3R New scripts - flash_atom.sh: auto-detects /dev/tty.usbserial-*, venv auto-load - flash_voices3r.sh: targets /dev/tty.usbmodem* (ESP32-S3 native USB-CDC), kills port-holding processes, uses brew esptool with --before usb-reset - Both scripts activate venv automatically (searches .venv/venv/env/api/venv and ~/.platformio/penv in priority order) - partitions_4mb.csv: custom partition table for all supported boards Add -DTESTING_MODE=1 to any env's build_flags to alert on all WiFi frames. --- flash_atom.sh | 101 +++++++++++++++++++++++++++++++++ flash_voices3r.sh | 126 ++++++++++++++++++++++++++++++++++++++++++ main.cpp | 135 ++++++++++++++++++++++++++++++++++++++------- partitions_4mb.csv | 5 ++ platformio.ini | 77 ++++++++++++++++++++++++-- 5 files changed, 420 insertions(+), 24 deletions(-) create mode 100755 flash_atom.sh create mode 100755 flash_voices3r.sh create mode 100644 partitions_4mb.csv diff --git a/flash_atom.sh b/flash_atom.sh new file mode 100755 index 0000000..3fdf666 --- /dev/null +++ b/flash_atom.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# Auto-detect and flash M5Atom Lite with Flock-You firmware + +set -e # Exit on error + +# ── Virtual Environment Auto-Load ──────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +activate_venv() { + local venv_path="$1" + if [ -f "$venv_path/bin/activate" ]; then + echo "🐍 Activating venv: $venv_path" + # shellcheck disable=SC1091 + source "$venv_path/bin/activate" + return 0 + fi + return 1 +} + +if [ -z "$VIRTUAL_ENV" ]; then + # Search order: project-local venvs → PlatformIO default venv + VENV_FOUND=false + for candidate in \ + "$SCRIPT_DIR/.venv" \ + "$SCRIPT_DIR/venv" \ + "$SCRIPT_DIR/env" \ + "$SCRIPT_DIR/api/.venv" \ + "$SCRIPT_DIR/api/venv" \ + "$HOME/.platformio/penv" + do + if activate_venv "$candidate"; then + VENV_FOUND=true + break + fi + done + + if [ "$VENV_FOUND" = false ]; then + echo "⚠️ No venv found — using system Python/pio (if available)" + fi +else + echo "🐍 Using active venv: $VIRTUAL_ENV" +fi +# ───────────────────────────────────────────────────────────────────────────── + +echo "🔍 Looking for M5Atom Lite..." + +# Find USB serial port +PORTS=$(ls /dev/tty.usbserial-* 2>/dev/null || true) + +if [ -z "$PORTS" ]; then + echo "❌ ERROR: No M5Atom Lite found!" + echo " Please connect the device via USB-C" + exit 1 +fi + +# Count how many ports found +PORT_COUNT=$(echo "$PORTS" | wc -l | tr -d ' ') + +if [ "$PORT_COUNT" -gt 1 ]; then + echo "⚠️ Multiple devices found:" + echo "$PORTS" + echo "" + echo "Please specify which one to flash:" + select PORT in $PORTS; do + if [ -n "$PORT" ]; then + break + fi + done +else + PORT="$PORTS" +fi + +echo "✅ Found M5Atom Lite at: $PORT" +echo "" + +# Change to project directory +cd "$(dirname "$0")" + +echo "🔨 Compiling firmware..." +pio run --environment m5atom-lite + +echo "" +echo "⬆️ Uploading firmware to $PORT..." +pio run --target upload --upload-port "$PORT" --environment m5atom-lite + +echo "" +echo "✅ Flash complete!" +echo "" + +# Ask if user wants to monitor +read -p "📡 Start serial monitor? (y/n) " -n 1 -r +echo "" + +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Starting monitor (Ctrl+C to exit)..." + echo "────────────────────────────────────" + pio device monitor --port "$PORT" --baud 115200 +fi + +echo "" +echo "✅ Done!" diff --git a/flash_voices3r.sh b/flash_voices3r.sh new file mode 100755 index 0000000..50eae13 --- /dev/null +++ b/flash_voices3r.sh @@ -0,0 +1,126 @@ +#!/bin/bash +# Flash Atom VoiceS3R with Flock-You firmware (TESTING_MODE=1) +# ───────────────────────────────────────────────────────────── +# Before running: put device in DOWNLOAD MODE +# Hold the reset button ~2 sec until the internal green LED lights, then release. + +set -e # Exit on error + +# ── Virtual Environment Auto-Load ──────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +activate_venv() { + local venv_path="$1" + if [ -f "$venv_path/bin/activate" ]; then + echo "🐍 Activating venv: $venv_path" + # shellcheck disable=SC1091 + source "$venv_path/bin/activate" + return 0 + fi + return 1 +} + +if [ -z "$VIRTUAL_ENV" ]; then + VENV_FOUND=false + for candidate in \ + "$SCRIPT_DIR/.venv" \ + "$SCRIPT_DIR/venv" \ + "$SCRIPT_DIR/env" \ + "$SCRIPT_DIR/api/.venv" \ + "$SCRIPT_DIR/api/venv" \ + "$HOME/.platformio/penv" + do + if activate_venv "$candidate"; then + VENV_FOUND=true + break + fi + done + + if [ "$VENV_FOUND" = false ]; then + echo "⚠️ No venv found — using system Python/pio (if available)" + fi +else + echo "🐍 Using active venv: $VIRTUAL_ENV" +fi +# ───────────────────────────────────────────────────────────────────────────── + +echo "🔍 Looking for Atom VoiceS3R (USB-CDC)..." + +# ESP32-S3 native USB shows up as /dev/tty.usbmodem* on macOS +PORTS=$(ls /dev/tty.usbmodem* 2>/dev/null || true) + +if [ -z "$PORTS" ]; then + echo "" + echo "❌ ERROR: No Atom VoiceS3R found!" + echo "" + echo " → Enter DOWNLOAD MODE first:" + echo " Hold the reset button ~2 sec until the internal green LED lights," + echo " then release. The port will appear as /dev/tty.usbmodem*" + echo "" + exit 1 +fi + +PORT_COUNT=$(echo "$PORTS" | wc -l | tr -d ' ') + +if [ "$PORT_COUNT" -gt 1 ]; then + echo "⚠️ Multiple USB-CDC devices found:" + echo "$PORTS" + echo "" + echo "Please select which one to flash:" + select PORT in $PORTS; do + if [ -n "$PORT" ]; then + break + fi + done +else + PORT="$PORTS" +fi + +echo "✅ Found Atom VoiceS3R at: $PORT" +echo "" + +# Release any process holding the CDC port (e.g. leftover serial monitors) +PORT_HOLDER=$(lsof -t "$PORT" 2>/dev/null || true) +if [ -n "$PORT_HOLDER" ]; then + echo "🔓 Releasing port held by PID $PORT_HOLDER..." + kill "$PORT_HOLDER" 2>/dev/null || true + sleep 1 +fi + +# Change to project directory +cd "$(dirname "$0")" + +echo "🔨 Compiling firmware (TESTING_MODE=1)..." +pio run --environment m5atom-voices3r + +echo "" +echo "⬆️ Uploading to $PORT..." +# Use brew esptool directly — handles ESP32-S3 native USB CDC reset reliably +esptool --chip esp32s3 \ + --port "$PORT" \ + --before usb-reset \ + --after hard-reset \ + write-flash \ + 0x0 .pio/build/m5atom-voices3r/bootloader.bin \ + 0x8000 .pio/build/m5atom-voices3r/partitions.bin \ + 0xe000 "$HOME/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin" \ + 0x10000 .pio/build/m5atom-voices3r/firmware.bin + +echo "" +echo "✅ Flash complete!" +echo " The device will reboot automatically." +echo " Any WiFi packet it hears will trigger a detection line on the serial monitor." +echo "" + +# Ask if user wants to monitor +read -p "📡 Start serial monitor? (y/n) " -n 1 -r +echo "" + +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Starting monitor (Ctrl+C to exit)..." + echo "────────────────────────────────────" + pio device monitor --port "$PORT" --baud 115200 --environment m5atom-voices3r +fi + +echo "" +echo "✅ Done!" diff --git a/main.cpp b/main.cpp index 4621777..2be1872 100644 --- a/main.cpp +++ b/main.cpp @@ -4,23 +4,72 @@ #include #include #include +// NeoPixel only needed for Echo/Lite variants +#if defined(USE_M5ATOM_ECHO) || defined(USE_M5ATOM_LITE) +#include +#endif + +// M5Atom support - using NeoPixel for LED only (NO M5Atom library - it's buggy!) +#if defined(USE_M5ATOM_ECHO) || defined(USE_M5ATOM_LITE) + #define USE_M5ATOM 1 + #define LED_PIN 27 + #define NUM_LEDS 1 // Atom Lite has 1 LED (Matrix has 25) + #define BUTTON_PIN 39 // M5Atom button on GPIO39 + Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800); +#endif + +// Atom VoiceS3R (ESP32-S3-PICO-1-N8R8): native USB-CDC, no user RGB LED. +// Speaker: ES8311 codec + NS4150B amp, driven via M5Unified over I2S. +// Button: GPIO41. Amp enable: GPIO18 (NS4150_CTR — M5Unified handles it). +#if defined(USE_M5ATOM_VOICES3R) + #include + #define BUTTON_PIN 41 +#endif // ============================================================ // CONFIG // ============================================================ -#define BUZZER_PIN 3 -#define USE_BUZZER 1 +// TESTING MODE: Set to 1 to blink/alert on ANY WiFi traffic (for testing) +// Set to 0 for normal operation (Flock OUI detection only) +// Can be overridden at build time via -DTESTING_MODE=1 in platformio.ini +#ifndef TESTING_MODE + #define TESTING_MODE 0 +#endif -// Onboard user LED on Seeed XIAO ESP32-S3 is GPIO21 and is ACTIVE LOW -// (driving the pin LOW lights the LED). -#define LED_PIN 21 -#define USE_LED 1 -#define LED_ACTIVE_HIGH 0 -#define LED_FLASH_MS 120 +#if defined(USE_M5ATOM_ECHO) + // M5Atom Echo: Built-in speaker + 5x5 LED matrix + #define BUZZER_PIN 25 + #define USE_BUZZER 1 + #define USE_M5_SPEAKER 1 + #define USE_LED 1 + #define USE_LED_MATRIX 1 + #define LED_FLASH_MS 120 +#elif defined(USE_M5ATOM_LITE) + // M5Atom Lite: 5x5 LED matrix only (no speaker) + #define BUZZER_PIN 25 + #define USE_BUZZER 0 + #define USE_LED 1 + #define USE_LED_MATRIX 1 + #define LED_FLASH_MS 120 +#elif defined(USE_M5ATOM_VOICES3R) + // Atom VoiceS3R: ES8311 I2S speaker via M5Unified. No RGB LED. + #define USE_BUZZER 0 + #define USE_M5_SPEAKER 1 // M5.Speaker.tone() — requires M5Unified + #define USE_LED 0 + #define LED_FLASH_MS 0 +#else + // Standard ESP32 DevKit + #define BUZZER_PIN 25 + #define USE_BUZZER 1 + #define LED_PIN 2 + #define USE_LED 1 + #define LED_ACTIVE_HIGH 1 + #define LED_FLASH_MS 120 +#endif #define MIRROR_SERIAL 1 -#define MIRROR_TX_PIN 43 +#define MIRROR_TX_PIN 17 #define MIRROR_BAUD 115200 #define CHANNEL_MODE_FULL_HOP 0 @@ -241,7 +290,7 @@ static void dualPrintf(const char* fmt, ...) { va_end(args); if (n > 0) { Serial.write(_dualBuf, n); -#if MIRROR_SERIAL +#if MIRROR_SERIAL && !defined(USE_M5ATOM_VOICES3R) Serial1.write(_dualBuf, n); #endif } @@ -249,14 +298,22 @@ static void dualPrintf(const char* fmt, ...) { static void dualPrintln(const char* str) { Serial.println(str); -#if MIRROR_SERIAL +#if MIRROR_SERIAL && !defined(USE_M5ATOM_VOICES3R) Serial1.println(str); #endif } static inline void ledSet(bool on) { #if USE_LED -#if LED_ACTIVE_HIGH +#if defined(USE_LED_MATRIX) + // M5Atom: Use NeoPixel library for LED control + if (on) { + strip.setPixelColor(0, strip.Color(0, 0, 255)); // Blue + } else { + strip.setPixelColor(0, strip.Color(0, 0, 0)); // Off + } + strip.show(); +#elif LED_ACTIVE_HIGH digitalWrite(LED_PIN, on ? HIGH : LOW); #else digitalWrite(LED_PIN, on ? LOW : HIGH); @@ -293,6 +350,10 @@ static void newDetectChirp() { tone(BUZZER_PIN, NEW_CHIRP_LO_HZ); delay(NEW_CHIRP_NOTE_MS); noTone(BUZZER_PIN); delay(NEW_CHIRP_GAP_MS); tone(BUZZER_PIN, NEW_CHIRP_HI_HZ); delay(NEW_CHIRP_NOTE_MS); noTone(BUZZER_PIN); +#elif defined(USE_M5_SPEAKER) && USE_M5_SPEAKER + M5.Speaker.tone(NEW_CHIRP_LO_HZ, NEW_CHIRP_NOTE_MS); delay(NEW_CHIRP_NOTE_MS + NEW_CHIRP_GAP_MS); + M5.Speaker.tone(NEW_CHIRP_HI_HZ, NEW_CHIRP_NOTE_MS); delay(NEW_CHIRP_NOTE_MS); + M5.Speaker.stop(); #endif } @@ -303,6 +364,10 @@ static void heartbeatBeep() { tone(BUZZER_PIN, HB_BEEP_HZ); delay(HB_BEEP_NOTE_MS); noTone(BUZZER_PIN); delay(HB_BEEP_GAP_MS); tone(BUZZER_PIN, HB_BEEP_HZ); delay(HB_BEEP_NOTE_MS); noTone(BUZZER_PIN); +#elif defined(USE_M5_SPEAKER) && USE_M5_SPEAKER + M5.Speaker.tone(HB_BEEP_HZ, HB_BEEP_NOTE_MS); delay(HB_BEEP_NOTE_MS + HB_BEEP_GAP_MS); + M5.Speaker.tone(HB_BEEP_HZ, HB_BEEP_NOTE_MS); delay(HB_BEEP_NOTE_MS); + M5.Speaker.stop(); #endif } static void startupBeep() { @@ -316,6 +381,18 @@ static void startupBeep() { noTone(BUZZER_PIN); if (i < 5) delay(22); } +#elif defined(USE_M5_SPEAKER) && USE_M5_SPEAKER + // SMB World 1-1 overworld opening riff: E E _E_ C E G (low G) + // Frequencies: E5=659 C5=523 G5=784 G4=392 + static const uint16_t notes[] = { 659, 659, 659, 523, 659, 784, 392 }; + static const uint16_t durs[] = { 100, 100, 100, 100, 100, 300, 300 }; + static const uint8_t gaps[] = { 80, 80, 80, 0, 0, 80, 0 }; + for (size_t i = 0; i < sizeof(notes)/sizeof(notes[0]); i++) { + M5.Speaker.tone(notes[i], durs[i]); + delay(durs[i]); + if (gaps[i]) { M5.Speaker.stop(); delay(gaps[i]); } + } + M5.Speaker.stop(); #endif } @@ -846,6 +923,12 @@ static void IRAM_ATTR wifiSniffer(void* buf, wifi_promiscuous_pkt_type_t type) { uint8_t ch = (uint8_t)pkt->rx_ctrl.channel; // actual rx channel from driver +#if TESTING_MODE + // TESTING MODE: Alert on ANY WiFi traffic for verification + enqueueAlert(ALERT_OUI_ADDR2, hdr->addr2, rssi, ch, nullptr, "test"); + return; // Skip normal OUI checking in test mode +#endif + // --- OUI check: addr2 (transmitter/source) --- // // For mgmt Probe Requests (type=0 subtype=4) from a matched OUI, tighten @@ -1044,21 +1127,35 @@ static void heartbeatTick() { void setup() { Serial.begin(115200); - // Crucial for USB-optional operation: without this, Serial.write() will - // block indefinitely on an ESP32-S3 USB-CDC port when no host is attached. - Serial.setTxTimeoutMs(0); delay(300); -#if MIRROR_SERIAL - Serial1.begin(MIRROR_BAUD, SERIAL_8N1, -1, MIRROR_TX_PIN); // TX-only on GPIO43 +#if defined(USE_M5ATOM) + // Initialize NeoPixel LED (NO M5Atom library!) + strip.begin(); + strip.show(); // Initialize all pixels to 'off' + // Initialize button (GPIO39 on Echo/Lite) + pinMode(BUTTON_PIN, INPUT_PULLUP); #endif -#if USE_BUZZER +#if defined(USE_M5ATOM_VOICES3R) + // M5Unified init: configures ES8311 codec, NS4150B amp (GPIO18), and button. + auto m5cfg = M5.config(); + M5.begin(m5cfg); + M5.Speaker.setVolume(200); // 0–255; ~78% is loud but not distorted +#endif + +// Serial1 UART mirror — skip on M5Atom (NeoPixel owns that UART) and on +// VoiceS3R (USB-CDC is the main port; no spare UART mapped to GPIO17). +#if MIRROR_SERIAL && !defined(USE_M5ATOM) && !defined(USE_M5ATOM_VOICES3R) + Serial1.begin(MIRROR_BAUD, SERIAL_8N1, -1, MIRROR_TX_PIN); +#endif + +#if USE_BUZZER && !defined(USE_M5ATOM) pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); #endif -#if USE_LED +#if USE_LED && !defined(USE_LED_MATRIX) pinMode(LED_PIN, OUTPUT); ledSet(false); #endif diff --git a/partitions_4mb.csv b/partitions_4mb.csv new file mode 100644 index 0000000..a0943b0 --- /dev/null +++ b/partitions_4mb.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x2E0000, +spiffs, data, spiffs, 0x2F0000, 0x100000, diff --git a/platformio.ini b/platformio.ini index 2b3da17..df3be1c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,20 +1,87 @@ [platformio] src_dir = . -[env:xiao_esp32s3] +[env:esp32dev] platform = espressif32@^6.3.0 -board = seeed_xiao_esp32s3 +board = esp32dev framework = arduino monitor_speed = 115200 upload_speed = 921600 build_flags = -DCORE_DEBUG_LEVEL=0 - -DARDUINO_USB_CDC_ON_BOOT=1 - -DBOARD_HAS_PSRAM build_src_filter = + +board_build.partitions = partitions_4mb.csv +board_build.filesystem = spiffs + +[env:m5atom-echo] +platform = espressif32@^6.3.0 +board = m5stack-atom +framework = arduino +monitor_speed = 115200 +upload_speed = 1500000 +upload_port = /dev/tty.usbserial-* + +lib_deps = + adafruit/Adafruit NeoPixel@^1.15.1 + +build_flags = + -DCORE_DEBUG_LEVEL=0 + -DUSE_M5ATOM_ECHO + +build_src_filter = + + +board_build.partitions = partitions_4mb.csv +board_build.filesystem = spiffs + +[env:m5atom-lite] +platform = espressif32@^6.3.0 +board = m5stack-atom +framework = arduino +monitor_speed = 115200 +upload_speed = 1500000 +upload_port = /dev/tty.usbserial-* + +lib_deps = + adafruit/Adafruit NeoPixel@^1.15.1 + +build_flags = + -DCORE_DEBUG_LEVEL=0 + -DUSE_M5ATOM_LITE + +build_src_filter = + + +board_build.partitions = partitions_4mb.csv +board_build.filesystem = spiffs + +; ── Atom VoiceS3R (ESP32-S3-PICO-1-N8R8, 8MB flash, 8MB PSRAM) ────────────── +; USB CDC port: /dev/tty.usbmodem* (not usbserial — it's native USB) +; Speaker: ES8311 codec via I2S — driven through M5Unified M5.Speaker.tone() +; Normal mode: Flock OUI detection only. Add -DTESTING_MODE=1 to alert on all WiFi. +[env:m5atom-voices3r] +platform = espressif32@6.7.0 +board = esp32-s3-devkitc-1 +framework = arduino +monitor_speed = 115200 +upload_port = /dev/tty.usbmodem* + board_build.arduino.memory_type = qio_opi -board_build.partitions = partitions.csv + +lib_deps = + m5stack/M5Unified @ ^0.2.2 + +build_flags = + -DESP32S3 + -DBOARD_HAS_PSRAM + -mfix-esp32-psram-cache-issue + -DCORE_DEBUG_LEVEL=0 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 + -DUSE_M5ATOM_VOICES3R + +build_src_filter = + + +board_build.partitions = partitions_4mb.csv board_build.filesystem = spiffs From 98a28f636ced143b517fdf90e5dd4d4d667f36cd Mon Sep 17 00:00:00 2001 From: SimeonOnSecurity <4913771+simeononsecurity@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:12:56 -0500 Subject: [PATCH 2/2] docs: add multi-board support table, per-board flash commands, testing mode docs - Restored [env:xiao_esp32s3] (Seeed XIAO ESP32-S3, original upstream board) - Added Supported Hardware table: xiao_esp32s3 / esp32dev / m5atom-echo / m5atom-lite / m5atom-voices3r with port types, audio/LED notes - Replaced generic 'pio run' section with board-specific flash + monitor commands for all five environments - Added VoiceS3R section: kill-port-holder one-liner + esptool fallback for ESP32-S3 native USB-CDC (fixes 'port busy' upload errors) - Added Testing mode section: -DTESTING_MODE=1 build flag instructions - Pin maps for XIAO ESP32-S3 (original) and Atom VoiceS3R - Removed flash_atom.sh and flash_voices3r.sh (commands in README instead) --- README.md | 115 +++++++++++++++++++++++++++++++++++++++--- flash_atom.sh | 101 ------------------------------------- flash_voices3r.sh | 126 ---------------------------------------------- platformio.ini | 20 ++++++++ 4 files changed, 127 insertions(+), 235 deletions(-) delete mode 100755 flash_atom.sh delete mode 100755 flash_voices3r.sh diff --git a/README.md b/README.md index 288b459..486bcd5 100644 --- a/README.md +++ b/README.md @@ -187,9 +187,19 @@ Open `http://localhost:5000`, pick your serial port from the UI, detections star --- -## Hardware +## Supported Hardware -**Board:** Seeed Studio XIAO ESP32-S3 +Five environments are included in `platformio.ini`. Pick the one that matches your board; everything else is automatic. + +| Environment | Board | Port type | Speaker / LED | Notes | +|---|---|---|---|---| +| `xiao_esp32s3` | Seeed XIAO ESP32-S3 | `/dev/tty.usbmodem*` | GPIO3 piezo + GPIO21 LED | **Original upstream board** | +| `esp32dev` | Generic ESP32 DevKit | `/dev/tty.usbserial-*` | GPIO25 piezo + GPIO2 LED | Bare dev board | +| `m5atom-echo` | M5Stack Atom Echo | `/dev/tty.usbserial-*` | NeoPixel (G27) + GPIO25 buzzer | Compact with speaker | +| `m5atom-lite` | M5Stack Atom Lite | `/dev/tty.usbserial-*` | NeoPixel (G27), no speaker | Ultra-compact | +| `m5atom-voices3r` | M5Stack Atom VoiceS3R | `/dev/tty.usbmodem*` | ES8311 I²S speaker via M5Unified | ESP32-S3, 8MB PSRAM | + +### Pin map — XIAO ESP32-S3 (original) | Pin | Function | |-----|----------| @@ -197,21 +207,110 @@ Open `http://localhost:5000`, pick your serial port from the UI, detections star | GPIO 21 | Onboard user LED (active low) | | GPIO 43 | Serial1 TX mirror (115200 baud) | -Boot sound: first 6 notes of Super Mario Bros. World 1-2 (underground). +### Pin map — Atom VoiceS3R + +| Pin | Function | +|-----|----------| +| GPIO 41 | User button | +| GPIO 18 | Speaker amp enable (NS4150_CTR, handled by M5Unified) | +| I²S (G45/G0/G48/G4/G3/G17/G11) | ES8311 codec — M5Unified drives this automatically | + +Boot sound: **Super Mario Bros. World 1-1 overworld opening riff** (E E _E_ C E G) on VoiceS3R / Atom Echo; first 6 notes of World 1-2 underground on original buzzer boards. --- ## Build and flash -Requires [PlatformIO](https://platformio.org/). +Requires [PlatformIO](https://platformio.org/) (`pip install platformio` or install the VS Code extension). + +### Quick start — any board + +```bash +# Build for your board (replace with one of the environment names above) +pio run -e + +# Build + flash +pio run -e -t upload + +# Serial monitor +pio device monitor -e +``` + +### Board-specific commands + +#### Seeed XIAO ESP32-S3 (original upstream board) +```bash +pio run -e xiao_esp32s3 -t upload +pio device monitor -e xiao_esp32s3 + +# Serial monitor (direct): +screen $(ls /dev/tty.usbmodem* | head -1) 115200 +``` + +#### Generic ESP32 DevKit +```bash +pio run -e esp32dev -t upload +pio device monitor -e esp32dev + +# Serial monitor (direct): +screen $(ls /dev/tty.usbserial-* | head -1) 115200 +``` + +#### M5Stack Atom Echo +```bash +pio run -e m5atom-echo -t upload +pio device monitor -e m5atom-echo + +# Serial monitor (direct): +screen $(ls /dev/tty.usbserial-* | head -1) 115200 +``` + +#### M5Stack Atom Lite +```bash +pio run -e m5atom-lite -t upload +pio device monitor -e m5atom-lite + +# Serial monitor (direct): +screen $(ls /dev/tty.usbserial-* | head -1) 115200 +``` + +#### M5Stack Atom VoiceS3R (ESP32-S3, native USB-CDC) +The VoiceS3R uses native USB — `pio run -t upload` works when the port is free. +If the port is busy (e.g. a serial monitor is open), kill it first: ```bash -pio run # build -pio run -t upload # flash -pio device monitor # serial output +# Kill anything holding the CDC port, then flash: +lsof -t /dev/tty.usbmodem* | xargs kill 2>/dev/null; sleep 0.5 +pio run -e m5atom-voices3r -t upload + +# Or use brew esptool directly (most reliable for ESP32-S3 native USB): +esptool --chip esp32s3 --port /dev/tty.usbmodem101 \ + --before usb-reset --after hard-reset write-flash \ + 0x0 .pio/build/m5atom-voices3r/bootloader.bin \ + 0x8000 .pio/build/m5atom-voices3r/partitions.bin \ + 0xe000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin \ + 0x10000 .pio/build/m5atom-voices3r/firmware.bin + +# Serial monitor: +screen /dev/tty.usbmodem101 115200 +# (replace 101 with whatever number the port shows — run: ls /dev/tty.usbmodem*) ``` -`platformio.ini` and `partitions.csv` are at the root (1.9 MB SPIFFS partition, 6 MB app). No extra libraries needed beyond the Arduino-ESP32 core that ships with the espressif32 platform. +> **Tip:** After flashing the VoiceS3R, unplug and replug the USB cable once to let the firmware re-enumerate the CDC port. + +### Testing mode + +To alert on **all** WiFi frames (useful for range/hardware testing before deployment), add `-DTESTING_MODE=1` to any env's `build_flags`: + +```ini +build_flags = + ... + -DTESTING_MODE=1 +``` + +Remove it to return to normal Flock OUI–only detection. + +`platformio.ini` and `partitions_4mb.csv` are at the root. The XIAO env uses `partitions.csv` (original upstream layout); all other envs use `partitions_4mb.csv`. --- diff --git a/flash_atom.sh b/flash_atom.sh deleted file mode 100755 index 3fdf666..0000000 --- a/flash_atom.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -# Auto-detect and flash M5Atom Lite with Flock-You firmware - -set -e # Exit on error - -# ── Virtual Environment Auto-Load ──────────────────────────────────────────── -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -activate_venv() { - local venv_path="$1" - if [ -f "$venv_path/bin/activate" ]; then - echo "🐍 Activating venv: $venv_path" - # shellcheck disable=SC1091 - source "$venv_path/bin/activate" - return 0 - fi - return 1 -} - -if [ -z "$VIRTUAL_ENV" ]; then - # Search order: project-local venvs → PlatformIO default venv - VENV_FOUND=false - for candidate in \ - "$SCRIPT_DIR/.venv" \ - "$SCRIPT_DIR/venv" \ - "$SCRIPT_DIR/env" \ - "$SCRIPT_DIR/api/.venv" \ - "$SCRIPT_DIR/api/venv" \ - "$HOME/.platformio/penv" - do - if activate_venv "$candidate"; then - VENV_FOUND=true - break - fi - done - - if [ "$VENV_FOUND" = false ]; then - echo "⚠️ No venv found — using system Python/pio (if available)" - fi -else - echo "🐍 Using active venv: $VIRTUAL_ENV" -fi -# ───────────────────────────────────────────────────────────────────────────── - -echo "🔍 Looking for M5Atom Lite..." - -# Find USB serial port -PORTS=$(ls /dev/tty.usbserial-* 2>/dev/null || true) - -if [ -z "$PORTS" ]; then - echo "❌ ERROR: No M5Atom Lite found!" - echo " Please connect the device via USB-C" - exit 1 -fi - -# Count how many ports found -PORT_COUNT=$(echo "$PORTS" | wc -l | tr -d ' ') - -if [ "$PORT_COUNT" -gt 1 ]; then - echo "⚠️ Multiple devices found:" - echo "$PORTS" - echo "" - echo "Please specify which one to flash:" - select PORT in $PORTS; do - if [ -n "$PORT" ]; then - break - fi - done -else - PORT="$PORTS" -fi - -echo "✅ Found M5Atom Lite at: $PORT" -echo "" - -# Change to project directory -cd "$(dirname "$0")" - -echo "🔨 Compiling firmware..." -pio run --environment m5atom-lite - -echo "" -echo "⬆️ Uploading firmware to $PORT..." -pio run --target upload --upload-port "$PORT" --environment m5atom-lite - -echo "" -echo "✅ Flash complete!" -echo "" - -# Ask if user wants to monitor -read -p "📡 Start serial monitor? (y/n) " -n 1 -r -echo "" - -if [[ $REPLY =~ ^[Yy]$ ]]; then - echo "Starting monitor (Ctrl+C to exit)..." - echo "────────────────────────────────────" - pio device monitor --port "$PORT" --baud 115200 -fi - -echo "" -echo "✅ Done!" diff --git a/flash_voices3r.sh b/flash_voices3r.sh deleted file mode 100755 index 50eae13..0000000 --- a/flash_voices3r.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/bash -# Flash Atom VoiceS3R with Flock-You firmware (TESTING_MODE=1) -# ───────────────────────────────────────────────────────────── -# Before running: put device in DOWNLOAD MODE -# Hold the reset button ~2 sec until the internal green LED lights, then release. - -set -e # Exit on error - -# ── Virtual Environment Auto-Load ──────────────────────────────────────────── -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -activate_venv() { - local venv_path="$1" - if [ -f "$venv_path/bin/activate" ]; then - echo "🐍 Activating venv: $venv_path" - # shellcheck disable=SC1091 - source "$venv_path/bin/activate" - return 0 - fi - return 1 -} - -if [ -z "$VIRTUAL_ENV" ]; then - VENV_FOUND=false - for candidate in \ - "$SCRIPT_DIR/.venv" \ - "$SCRIPT_DIR/venv" \ - "$SCRIPT_DIR/env" \ - "$SCRIPT_DIR/api/.venv" \ - "$SCRIPT_DIR/api/venv" \ - "$HOME/.platformio/penv" - do - if activate_venv "$candidate"; then - VENV_FOUND=true - break - fi - done - - if [ "$VENV_FOUND" = false ]; then - echo "⚠️ No venv found — using system Python/pio (if available)" - fi -else - echo "🐍 Using active venv: $VIRTUAL_ENV" -fi -# ───────────────────────────────────────────────────────────────────────────── - -echo "🔍 Looking for Atom VoiceS3R (USB-CDC)..." - -# ESP32-S3 native USB shows up as /dev/tty.usbmodem* on macOS -PORTS=$(ls /dev/tty.usbmodem* 2>/dev/null || true) - -if [ -z "$PORTS" ]; then - echo "" - echo "❌ ERROR: No Atom VoiceS3R found!" - echo "" - echo " → Enter DOWNLOAD MODE first:" - echo " Hold the reset button ~2 sec until the internal green LED lights," - echo " then release. The port will appear as /dev/tty.usbmodem*" - echo "" - exit 1 -fi - -PORT_COUNT=$(echo "$PORTS" | wc -l | tr -d ' ') - -if [ "$PORT_COUNT" -gt 1 ]; then - echo "⚠️ Multiple USB-CDC devices found:" - echo "$PORTS" - echo "" - echo "Please select which one to flash:" - select PORT in $PORTS; do - if [ -n "$PORT" ]; then - break - fi - done -else - PORT="$PORTS" -fi - -echo "✅ Found Atom VoiceS3R at: $PORT" -echo "" - -# Release any process holding the CDC port (e.g. leftover serial monitors) -PORT_HOLDER=$(lsof -t "$PORT" 2>/dev/null || true) -if [ -n "$PORT_HOLDER" ]; then - echo "🔓 Releasing port held by PID $PORT_HOLDER..." - kill "$PORT_HOLDER" 2>/dev/null || true - sleep 1 -fi - -# Change to project directory -cd "$(dirname "$0")" - -echo "🔨 Compiling firmware (TESTING_MODE=1)..." -pio run --environment m5atom-voices3r - -echo "" -echo "⬆️ Uploading to $PORT..." -# Use brew esptool directly — handles ESP32-S3 native USB CDC reset reliably -esptool --chip esp32s3 \ - --port "$PORT" \ - --before usb-reset \ - --after hard-reset \ - write-flash \ - 0x0 .pio/build/m5atom-voices3r/bootloader.bin \ - 0x8000 .pio/build/m5atom-voices3r/partitions.bin \ - 0xe000 "$HOME/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin" \ - 0x10000 .pio/build/m5atom-voices3r/firmware.bin - -echo "" -echo "✅ Flash complete!" -echo " The device will reboot automatically." -echo " Any WiFi packet it hears will trigger a detection line on the serial monitor." -echo "" - -# Ask if user wants to monitor -read -p "📡 Start serial monitor? (y/n) " -n 1 -r -echo "" - -if [[ $REPLY =~ ^[Yy]$ ]]; then - echo "Starting monitor (Ctrl+C to exit)..." - echo "────────────────────────────────────" - pio device monitor --port "$PORT" --baud 115200 --environment m5atom-voices3r -fi - -echo "" -echo "✅ Done!" diff --git a/platformio.ini b/platformio.ini index df3be1c..04883d3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,6 +1,26 @@ [platformio] src_dir = . +; ── Original board (upstream baseline) ────────────────────────────────────── +[env:xiao_esp32s3] +platform = espressif32@^6.3.0 +board = seeed_xiao_esp32s3 +framework = arduino +monitor_speed = 115200 +upload_speed = 921600 + +build_flags = + -DCORE_DEBUG_LEVEL=0 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DBOARD_HAS_PSRAM + +build_src_filter = + + +board_build.arduino.memory_type = qio_opi +board_build.partitions = partitions.csv +board_build.filesystem = spiffs + +; ── Standard ESP32 DevKit ───────────────────────────────────────────────────── [env:esp32dev] platform = espressif32@^6.3.0 board = esp32dev