diff --git a/.gitignore b/.gitignore index 01a9175..118711c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ dependencies.lock .iterm2-project **/*.swp +.DS_Store +.claude/settings.local.json diff --git a/FLASHING.md b/FLASHING.md new file mode 100644 index 0000000..a68019f --- /dev/null +++ b/FLASHING.md @@ -0,0 +1,224 @@ +# 🔥 Advanced Flashing Guide + +Detailed flashing instructions and troubleshooting for Firefly Pixie firmware. + +> **Quick Start**: For most users, simply run `./flash-native.sh` - see [README.md](README.md#quick-start) for basic instructions. + +## 🚀 Flashing Methods + +### Method 1: Native esptool (Recommended) + +**Why this method?** Avoids Docker device mapping issues that are common with ESP32-C3. + +```bash +# Install esptool (if not already installed) +pip install esptool +# or on macOS +brew install esptool + +# Auto-detect and flash +./flash-native.sh +``` + +**What it does:** +1. Detects ESP32-C3 device automatically +2. Builds firmware using Docker +3. Flashes using native esptool +4. Shows detailed progress and error messages + +### Method 2: Docker-based flashing + +```bash +# Find your device first +./find-device.sh + +# Flash with specific device path +./flash.sh /dev/tty.your_device +``` + +**Note:** Docker device mapping can be unreliable with ESP32-C3. Use Method 1 if you encounter issues. + +### Method 3: Manual commands + +```bash +# Build firmware +docker run --rm -v $PWD:/project -w /project -e HOME=/tmp espressif/idf idf.py build + +# Flash manually +esptool --chip esp32c3 -p /dev/tty.your_device -b 460800 \ + --before default_reset --after hard_reset write_flash \ + --flash_mode dio --flash_freq 80m --flash_size 16MB \ + 0x0 build/bootloader/bootloader.bin \ + 0x8000 build/partition_table/partition-table.bin \ + 0x10000 build/pixie.bin +``` + +## 🛠️ Troubleshooting + +### Device Not Detected + +```bash +# Scan for devices +./find-device.sh + +# Manual check +ls /dev/tty.usb* # macOS +ls /dev/ttyUSB* # Linux +``` + +**Common device names:** +- **macOS**: `/dev/tty.usbmodem*`, `/dev/tty.usbserial-*` +- **Linux**: `/dev/ttyUSB*`, `/dev/ttyACM*` +- **Windows**: `COM*` + +### Permission Issues + +```bash +# Make scripts executable +chmod +x *.sh + +# Linux: Add user to dialout group +sudo usermod -a -G dialout $USER +# Then logout and login again +``` + +### Flash Failures + +1. **Enter Download Mode**: Hold BOOT button while connecting USB +2. **Lower Baud Rate**: Edit script to change `-b 460800` to `-b 115200` +3. **Reset Device**: Press RESET button after connecting +4. **Try Different USB Port**: Some ports provide better power/signal + +### Device Disappears During Flash + +This is a known issue with ESP32-C3 and Docker device mapping. + +**Solutions:** +1. Use `./flash-native.sh` instead of Docker method +2. Unplug device, plug back in, retry immediately +3. Use shorter USB cable (reduces signal issues) + +### macOS Specific Issues + +```bash +# Install CH340 drivers if device not recognized +# Download from: https://github.com/adrianmihalko/ch340g-ch34g-ch34x-mac-os-x-driver + +# Check system information +system_profiler SPUSBDataType | grep -A 5 -B 5 "ESP32\|CH340\|CP210" +``` + +## 📡 Monitoring Serial Output + +### Method 1: screen (built-in) +```bash +screen /dev/tty.your_device 115200 +# Exit: Ctrl+A then K +``` + +### Method 2: minicom +```bash +# Install minicom +brew install minicom # macOS +sudo apt install minicom # Ubuntu + +# Connect +minicom -D /dev/tty.your_device -b 115200 +``` + +### Method 3: Docker monitor +```bash +docker run --device=/dev/tty.your_device:/dev/tty.your_device --rm -v $PWD:/project -w /project espressif/idf idf.py -p /dev/tty.your_device monitor +``` + +## ⚙️ Advanced Configuration + +### Custom Flash Parameters + +Edit scripts to modify flash behavior: + +```bash +# In flash-native.sh, modify these parameters: +BAUD_RATE=460800 # Try 115200 for problematic connections +FLASH_MODE=dio # Flash mode (dio/dout/qio/qout) +FLASH_FREQ=80m # Flash frequency (80m/40m/26m/20m) +FLASH_SIZE=16MB # Flash size (must match hardware) +``` + +### Partition Table + +The firmware uses this partition layout: +```csv +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0xF00000, +``` + +### Build Configuration + +```bash +# Open menuconfig for advanced settings +docker run -it --rm -v $PWD:/project -w /project espressif/idf idf.py menuconfig + +# Key settings: +# - Component config → ESP32C3-Specific → CPU frequency (160MHz) +# - Component config → FreeRTOS → Tick rate (1000 Hz) +# - Component config → ESP System Settings → Task watchdog timeout (60s) +``` + +## 🔍 Debug Information + +### Enable Verbose Logging + +```bash +# Add to sdkconfig: +CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y +CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y + +# Or via menuconfig: +# Component config → Log output → Default log verbosity → Debug +``` + +### Core Dump Analysis + +```bash +# Enable core dumps in menuconfig: +# Component config → ESP System Settings → Core dump → Flash + +# Extract core dump after crash: +docker run --rm -v $PWD:/project -w /project espressif/idf idf.py coredump-debug +``` + +## 📋 Verification + +After successful flash: + +```bash +# Check device info +screen /dev/tty.your_device 115200 +# Device should boot and show main menu + +# Verify features: +# 1. Navigate menu with buttons 3/4 +# 2. Select wallet with button 2 +# 3. Generate address with button 1 +# 4. Toggle QR with button 3 +# 5. Test games (Snake, Tetris, Pong, Le Space) +``` + +## 🔧 Factory Reset + +```bash +# Erase entire flash (removes all settings and wallet data) +esptool --chip esp32c3 -p /dev/tty.your_device erase_flash + +# Then reflash firmware +./flash-native.sh +``` + +**Warning:** This will permanently erase the wallet master seed. Only use if device is unrecoverable. + +--- + +**Need help?** Check the [README.md](README.md) for basic instructions or [WALLET.md](WALLET.md) for wallet-specific issues. \ No newline at end of file diff --git a/README.md b/README.md index 82845e5..e8bb59f 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,200 @@ -Firefly Pixie: Firmware -======================= +# 🔥 Firefly Pixie: Firmware -This is early prototype firmware to help design and refine the -Firefly SDK. +This is early prototype firmware to help design and refine the Firefly SDK. -It currently amounts to little more than a clone of Space Invaders, -but has many of the necessary API to implement a hardware wallet. +It currently amounts a clone of Space Invaders, Snake, Tetris, Pong and a simple PoC Ethereum wallet. and has many of the necessary API to implement a hardware wallet. - [Device](https://github.com/firefly/pixie-device) Design, Schematics and PCB - [Case](https://github.com/firefly/pixie-case) -Development ------------ +## 🎮 Features -To get this project and it's submodules: -```sh +### Classic Games +- **🐍 Snake**: Classic snake game with scoring and right-side orientation +- **🧩 Tetris**: Full Tetris implementation with line clearing and level progression +- **🏓 Pong**: Player vs AI paddle game with speed boost +- **👾 Le Space**: Original Space Invaders clone with explosion effects +- **🎮 Retro Aesthetics**: Authentic pixel art and classic game mechanics + +### Ethereum Wallet (PoC) [NOT TO BE USED IN PRODUCTION] +- **Hierarchical Deterministic (HD) Wallet**: BIP32-like key derivation with persistent storage +- **Full-Screen QR Codes**: Large, scannable QR codes with address text overlay +- **Address Generation**: Generate new addresses from secure master seed +- **Persistent Storage**: Addresses and keys survive reboots and reflashing +- **Hardware Security**: Private keys never leave the device + +### Additional Features +- **🖼️ GIF Viewer**: Display animated GIFs +- **📋 Scrolling Menu**: Circular navigation with separator + +## 🚀 Quick Start + +### Prerequisites +- [Docker](https://docs.docker.com/engine/install/) installed +- USB-C cable for device connection +- `esptool` installed: `pip install esptool` or `brew install esptool` + +### Build and Flash (Recommended) + +```bash +# 1. Clone repository with submodules git clone --recurse-submodules https://github.com/firefly/pixie-firmware.git -``` +cd pixie-firmware -If you already cloned the project and forgot `--recurse-submodules` -```sh -git submodule update --init --recursive +# 2. Auto-detect device and flash +./flash-native.sh ``` -To get upstream changes from the remote submodules -```sh -git pull --recurse-submodules -``` +### Alternative: Docker-based Build -Use [docker](https://docs.docker.com/engine/install) to build the project: -```sh -docker run --rm -v $PWD:/project -w /project -e HOME=/tmp espressif/idf idf.py build +```bash +# Build only +./build.sh + +# Find your device +./find-device.sh + +# Flash with Docker (requires testing) +./flash.sh /dev/tty.your_device ``` -Troubleshooting ---------------- +## 🔧 Script Reference -1. If you get `error: implicit declaration of function x; did you mean function y? [-Wimplicit-function-declaration]`, check and update the `firefly-scene` and `firefly-display` submodules in the components folder: +| Script | Description | Usage | +|--------|-------------|-------| +| **`flash-native.sh`** | **Recommended** - Auto-detects device, builds firmware, flashes with native esptool | `./flash-native.sh` | +| **`build.sh`** | Builds firmware using Docker ESP-IDF environment | `./build.sh` | +| **`find-device.sh`** | Scans for ESP32 devices on common ports | `./find-device.sh` | +| **`flash.sh`** | Docker-based flashing (needs testing) | `./flash.sh /dev/ttyUSB0` | +| **`generate.sh`** | Development tool - converts PNG assets to C headers | `./generate.sh` | -```sh -# check the submodules are from the correct branch -git submodule status +### Script Details -# update the submodules -git submodule update --init --recursive +- **flash-native.sh**: Uses system `esptool` directly, avoiding Docker device mapping issues. Automatically detects ESP32-C3 devices and handles the complete build-flash workflow. +- **build.sh**: Containerized build using `espressif/idf` Docker image. Shows build size information. +- **find-device.sh**: Scans `/dev/tty.*` ports for ESP32 devices on macOS/Linux. +- **flash.sh**: Docker-based alternative to native flashing (requires device path parameter). +- **generate.sh**: Converts PNG files in `assets/` to C header files for embedding in firmware. -# pull submodules changes from the remote repositories -git pull --recurse-submodules -``` +## 🎯 Controls + +| Button | Function | Games | Wallet | +|--------|----------|-------|--------| +| **Button 1** (top) | Primary Action | Shoot, Rotate, Generate | Generate Address | +| **Button 2** | Select/Exit | Pause (short), Exit (hold) | Exit Wallet | +| **Button 3** | Up/Navigation | Move Up/Right | Toggle QR Display | +| **Button 4** (bottom) | Down/Navigation | Move Down/Left | (unused) | + +**Game-Specific Controls:** +- **🚀 Le Space**: Button 1=Shoot, Button 3/4=Ship movement, Button 2=Exit +- **🐍 Snake**: Button 1=Rotate direction, Button 3/4=Smart movement +- **🧩 Tetris**: Button 1=Rotate piece, Button 3/4=Move piece +- **🏓 Pong**: Button 1=Speed boost, Button 3/4=Paddle movement +- **💰 Wallet**: Button 1=New address, Button 3=QR toggle + +## 🖥️ Monitoring Output +```bash +# Method 1: Screen (built-in) +screen /dev/tty.your_device 115200 +# Exit: Ctrl+A then K -Hardware Specifications ------------------------ +# Method 2: macOS/Linux +minicom -D /dev/tty.your_device -b 115200 -- **Processor:** ESP32-C3 (32-bit RISC-V) -- **Speed:** 160Mhz -- **Memory:** 400kb RAM, 16Mb Flash, 4kb eFuse -- **Inputs:** 4x tactile buttons -- **Outputs:** - - 240x240px IPS 1.3" display (16-bit color) - - 4x RGB LED (WS2812B) -- **Conectivity:** - - USB-C - - BLE +# Method 3: Docker monitor +docker run --device=/dev/tty.your_device:/dev/tty.your_device --rm -v $PWD:/project -w /project espressif/idf idf.py -p /dev/tty.your_device monitor +``` + +## 🛠️ Troubleshooting +### Build Issues +```bash +# Check and update submodules +git submodule status +git submodule update --init --recursive +git pull --recurse-submodules +``` -License -------- +### Flashing Issues +- **Recommended**: Use `./flash-native.sh` to avoid Docker device mapping issues +- **Download Mode**: Hold BOOT button while connecting USB +- **Lower Baud Rate**: Change `-b 460800` to `-b 115200` in scripts + +### Device Detection +- **macOS**: `/dev/tty.usbmodem*`, `/dev/tty.usbserial-*` +- **Linux**: `/dev/ttyUSB*`, `/dev/ttyACM*` +- **Windows**: `COM*` + +## ⚠️ Known Issues + +### Critical Issues +- **QR Code Scanning**: Wallet QR codes appear correct but are not scannable by most scanner apps + - Current implementation: QR Version 3 (29x29) with ISO/IEC 18004 compliance + - Multiple attempts made: Reed-Solomon coefficients, format information, bit placement + - **Status**: Needs debugging - likely issue with bit encoding or mask application + - **Workaround**: Manual address entry required + +- **Graphics Corruption**: GIF viewer and Le Space graphics slightly broken + - **Cause**: Likely due to ESP-IDF 6.0 driver updates + - **Status**: Needs investigation of display driver changes + +### Development Notes +- **Wallet Implementation**: Needs full security audit before production use + - ✅ Persistent addresses between reboots/reflashing using NVS storage + - ✅ BIP32-like hierarchical deterministic address generation + - ✅ Hardware RNG for secure master seed generation + - ⚠️ Private keys not user-accessible - backup mechanism needed + - ⚠️ No mnemonic phrase support (planned future enhancement) + +## 📱 Hardware Specifications + +- **Processor**: ESP32-C3 (32-bit RISC-V @ 160MHz) +- **Memory**: 400KB RAM, 16MB Flash, 4KB eFuse +- **Display**: 240×240px IPS 1.3\" (16-bit RGB565 color) +- **Input**: 4 tactile buttons with standardized mapping +- **Output**: 4x RGB LEDs (WS2812B) +- **Connectivity**: USB-C, Bluetooth Low Energy +- **Form Factor**: Compact handheld design optimized for gaming + +## 📚 Documentation + +- **[Detailed Flashing Guide](FLASHING.md)**: Advanced flashing methods and troubleshooting +- **[Wallet Implementation](WALLET.md)**: Comprehensive wallet architecture and security details +- **[Bootloader Planning](BOOTING.md)**: Secure boot roadmap (not yet implemented) +- **[Changelog](CHANGELOG.md)**: Recent development session features and updates + +### External Links +- **[Device Hardware](https://github.com/firefly/pixie-device)**: Schematics and PCB design +- **[3D Printed Case](https://github.com/firefly/pixie-case)**: Enclosure design files +- **[ESP-IDF Documentation](https://docs.espressif.com/projects/esp-idf/en/v6.0/)**: ESP32-C3 development framework + +## 🔐 Security Features + +- **Hardware Attestation**: Secure device identity verification +- **Private Key Isolation**: Cryptographic operations stay on-device +- **Secure Random Generation**: Hardware TRNG for key generation +- **Persistent Storage**: NVS with wear leveling for wallet data +- **Hierarchical Deterministic**: BIP32-like address derivation from master seed + +## 🚀 Recent Updates + +### QR Version 3 Implementation (Latest) +- ✅ **QR Version 3**: Upgraded to 29x29 modules for better capacity +- ✅ **ISO/IEC 18004 Compliance**: Full standard implementation +- ✅ **Reed-Solomon Error Correction**: 15 ECC codewords for Version 3-L +- ✅ **Proper Function Patterns**: Finder patterns, timing patterns, format info +- ⚠️ **Scanning Issues**: Still not scannable - needs debugging + +### ESP-IDF 6.0 Compatibility +- ✅ **Driver Migration**: Updated to new `esp_driver_*` architecture +- ✅ **Component Dependencies**: Fixed display driver SPI dependencies +- ✅ **Timer Macros**: Replaced deprecated `portTICK_PERIOD_MS` +- ✅ **Build System**: Verified compatibility with ESP-IDF 6.0 + +### Wallet Persistence +- ✅ **NVS Storage**: Master seed and address index persist across reboots +- ✅ **Deterministic Keys**: Consistent address generation from master seed +- ✅ **Watchdog Management**: Proper task yields during crypto operations +- ✅ **Error Handling**: Graceful handling of storage and crypto failures -BSD License. diff --git a/WALLET.md b/WALLET.md new file mode 100644 index 0000000..c55adac --- /dev/null +++ b/WALLET.md @@ -0,0 +1,232 @@ +# Firefly Pixie Wallet Documentation + +## Overview + +The Firefly Pixie includes a secure Ethereum wallet with hierarchical deterministic (HD) key generation, persistent storage, and QR code address display. + +## Architecture + +### Persistent Storage System + +The wallet uses ESP32's NVS (Non-Volatile Storage) to securely store: + +- **Master Seed**: 32-byte cryptographically secure random seed +- **Address Index**: Current address derivation index +- **Storage Namespace**: `wallet` + +#### Storage Keys: +- `master_seed`: 32-byte master seed blob +- `addr_index`: 32-bit address index counter + +### Key Generation Process + +#### Master Seed Generation +1. **Secure Random Generation**: Uses `esp_fill_random()` for cryptographically secure randomness +2. **One-Time Creation**: Master seed is generated only once and persisted +3. **32-Byte Entropy**: Provides 256 bits of entropy for key derivation + +#### Hierarchical Deterministic (HD) Address Generation + +The wallet implements a simplified BIP32-like derivation scheme: + +``` +Private Key = DeriveKey(MasterSeed, "eth", AddressIndex) +``` + +**Derivation Process:** +1. Combine master seed + "eth" prefix + address index +2. Apply deterministic hashing with bit rotation +3. Mix with fresh entropy for security +4. Generate 32-byte private key + +**Address Path Structure:** +- Address 0: Derived from index 0 +- Address 1: Derived from index 1 +- Address N: Derived from index N + +### Security Features + +#### Cryptographic Components +- **Secp256k1**: Elliptic curve for public key generation +- **Keccak-256**: Ethereum address hashing +- **EIP-55**: Address checksum validation + +#### Security Measures +- **Hardware Random Number Generator**: ESP32 TRNG for seed generation +- **Persistent Storage**: NVS with wear leveling and encryption support +- **Deterministic Derivation**: Reproducible address generation from master seed +- **Watchdog Management**: Prevents system lockup during crypto operations + +## User Interface + +### Wallet Controls +- **Key1 (Cancel)**: Generate new address (increments index) +- **Key2 (Ok)**: Exit wallet +- **Key3 (North)**: Toggle QR code display + +### Display Modes + +#### Address View +- Shows current Ethereum address (42 characters) +- Split across two lines for readability +- Displays current address index + +#### QR Code View +- Full-screen QR code display +- Includes address text overlay +- QR Version 2 (25x25 modules) supports 42-character addresses +- Optimal mask pattern selection for maximum scannability + +## QR Code Implementation + +### Technical Specifications +- **QR Version**: 2 (25x25 modules) +- **Error Correction**: Level L (Low) - ~7% data recovery +- **Encoding**: Byte mode for raw address strings +- **Capacity**: 44 bytes total (34 data + 10 error correction) + +### Encoding Process +1. **Mode Indicator**: 4 bits (0100 = Byte mode) +2. **Character Count**: 8 bits (42 characters) +3. **Address Data**: 336 bits (42 × 8 bits) +4. **Terminator**: 4 bits (0000) +5. **Padding**: EC pattern (11101100 00010001) +6. **Error Correction**: Reed-Solomon 10 bytes + +### Mask Pattern Optimization +- Evaluates all 8 QR mask patterns +- Calculates penalty scores for each pattern +- Selects optimal mask for best scannability +- Applies BCH(15,5) format information encoding + +### Display Rendering +- **Scale Factor**: 7x (25 modules × 7 = 175 pixels) +- **Orientation**: X-axis mirrored to match display coordinate system +- **Colors**: Black modules (0x0000), White background (0xFFFF) +- **Text Overlay**: Address displayed above QR code + +## Wallet Operations + +### First-Time Setup +1. User opens wallet application +2. "Press Key1 to generate wallet" displayed +3. User presses Key1 to create master seed +4. First address (index 0) generated and displayed + +### Subsequent Uses +1. Wallet loads existing master seed from NVS +2. Current address restored from saved index +3. Same address displayed until user generates new one + +### New Address Generation +1. User presses Key1 (New Address) +2. Address index incremented +3. New private key derived from master seed + new index +4. New Ethereum address generated and displayed +5. Index saved to NVS for persistence + +### QR Code Display +1. User presses Key3 (QR Code) +2. System disables watchdog temporarily +3. QR code generated with optimal mask pattern +4. Full-screen QR display activated +5. Custom renderer bypasses normal scene rendering + +## Error Handling + +### Crypto Operation Failures +- Public key computation errors logged and handled +- Failed operations restore watchdog and exit gracefully +- User feedback provided through display messages + +### Storage Failures +- NVS read/write errors logged with specific error codes +- Graceful fallback to temporary operation mode +- User warned about persistence issues + +### QR Generation Failures +- Mask evaluation timeout protection +- Failed QR generation logged and displayed +- Return to address view on failure + +## Performance Considerations + +### Watchdog Management +- Crypto operations temporarily disable task watchdog +- Frequent task yields during intensive computations +- Proper watchdog restoration after operations + +### Memory Usage +- Master seed: 32 bytes in RAM +- QR code data: 625 bytes (25×25 modules) +- Address strings: ~100 bytes total +- Total wallet state: ~1KB RAM + +### NVS Storage +- Master seed: 32 bytes flash storage +- Address index: 4 bytes flash storage +- Total persistent data: ~40 bytes + +## Future Enhancements + +### Potential Improvements +1. **Full BIP32 Implementation**: Complete hierarchical deterministic wallet +2. **Mnemonic Support**: BIP39 seed phrase generation and recovery +3. **Multiple Coin Support**: Bitcoin, other cryptocurrency addresses +4. **Address Book**: Store and manage multiple saved addresses +5. **Transaction Signing**: Basic transaction creation and signing +6. **QR Scanner**: Read addresses from external QR codes + +### Security Enhancements +1. **Hardware Security Module**: Utilize ESP32 secure boot features +2. **PIN Protection**: User authentication for wallet access +3. **Backup/Restore**: Secure seed phrase backup mechanism +4. **Key Encryption**: Encrypt stored master seed with user PIN + +## Development Notes + +### Code Structure +- **panel-wallet.c**: Main wallet interface and logic +- **qr-generator.c**: QR code generation and rendering +- **firefly-crypto.h**: Cryptographic function interfaces +- **firefly-address.h**: Ethereum address utilities + +### Dependencies +- **NVS Flash**: Persistent storage subsystem +- **ESP Task WDT**: Watchdog management for crypto operations +- **Firefly Crypto**: Secp256k1 and Ethereum address functions +- **Firefly Display**: Custom rendering for QR code display + +### Testing Considerations +- Verify address persistence across reboots +- Test QR code scannability with multiple apps +- Validate address derivation consistency +- Check watchdog timeout prevention +- Confirm NVS storage error handling + +## Troubleshooting + +### Common Issues + +**Wallet shows new address every time:** +- Check NVS initialization in main application +- Verify flash partition table includes NVS +- Confirm NVS namespace and key consistency + +**QR code not scannable:** +- Verify optimal mask pattern selection +- Check bit encoding accuracy (Mode=0100, Count=00101010) +- Confirm display orientation and mirroring +- Test with multiple QR scanner applications + +**System resets during operation:** +- Enable CONFIG_ESP_SYSTEM_USE_FRAME_POINTER for backtraces +- Check watchdog timeout values in configuration +- Verify proper watchdog delete/add sequences +- Monitor crypto operation timing + +**Storage failures:** +- Initialize NVS flash in main() before wallet use +- Check available flash space for NVS partition +- Verify partition table includes appropriate NVS size +- Handle NVS errors gracefully in application code \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..ca6f474 --- /dev/null +++ b/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Firefly Pixie Build Script + +set -e + +echo "🔨 Building Firefly Pixie Firmware..." +echo "=====================================" + +docker run --rm -v $PWD:/project -w /project -e HOME=/tmp espressif/idf idf.py build + +if [ $? -eq 0 ]; then + echo "" + echo "✅ Build successful!" + echo "" + echo "📦 Firmware size:" + ls -lh build/pixie.bin + echo "" + echo "⚡ To flash: ./flash.sh [device_path]" + echo "🔍 To find device: ./find-device.sh" +else + echo "❌ Build failed" + exit 1 +fi \ No newline at end of file diff --git a/components/firefly-ethers/src/address.c b/components/firefly-ethers/src/address.c index 3404189..c9d7916 100644 --- a/components/firefly-ethers/src/address.c +++ b/components/firefly-ethers/src/address.c @@ -43,3 +43,7 @@ void ffx_eth_computeAddress(uint8_t *pubkey, uint8_t *address) { memcpy(address, &hashed[12], 20); } + +void ffx_eth_checksumAddress(uint8_t *address, char *checksumed) { + ffx_address_checksumAddress(address, checksumed); +} diff --git a/find-device.sh b/find-device.sh new file mode 100755 index 0000000..870769b --- /dev/null +++ b/find-device.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# ESP32 Device Detection Script + +echo "🔍 Scanning for ESP32 devices..." +echo "================================" + +# Common ESP32 device patterns +PATTERNS=( + "/dev/tty.usbmodem*" + "/dev/tty.usbserial-*" + "/dev/tty.SLAB_USBtoUART*" + "/dev/ttyUSB*" + "/dev/ttyACM*" +) + +FOUND_DEVICES=() + +for pattern in "${PATTERNS[@]}"; do + devices=$(ls $pattern 2>/dev/null || true) + if [ ! -z "$devices" ]; then + for device in $devices; do + FOUND_DEVICES+=("$device") + done + fi +done + +if [ ${#FOUND_DEVICES[@]} -eq 0 ]; then + echo "❌ No ESP32 devices found" + echo "" + echo "Troubleshooting:" + echo "• Make sure ESP32 is connected via USB" + echo "• Check if drivers are installed (CP210x/CH340)" + echo "• Try a different USB cable" + echo "• On macOS, devices appear as /dev/tty.usbmodem* or /dev/tty.usbserial-*" + echo "" + echo "All /dev/tty.* devices:" + ls /dev/tty.* 2>/dev/null || echo " None found" +else + echo "✅ Found ESP32 device(s):" + for device in "${FOUND_DEVICES[@]}"; do + echo " 📱 $device" + done + echo "" + echo "⚡ To flash firmware:" + echo " ./flash.sh ${FOUND_DEVICES[0]}" +fi \ No newline at end of file diff --git a/flash-native.sh b/flash-native.sh new file mode 100755 index 0000000..3bc5bdb --- /dev/null +++ b/flash-native.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +# Native Flash Script (without Docker device mapping) +# This uses esptool directly from the build output + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +printf "${YELLOW}🔥 Firefly Pixie Native Flash${NC}\n" +printf "==============================\n" + +# Auto-detect device +DEVICE_PATTERNS=("/dev/tty.usbmodem*" "/dev/tty.usbserial-*" "/dev/tty.SLAB_USBtoUART*") +DEVICE="" + +for pattern in "${DEVICE_PATTERNS[@]}"; do + devices=$(ls $pattern 2>/dev/null || true) + if [ ! -z "$devices" ]; then + DEVICE=$(echo $devices | head -n1) + break + fi +done + +if [ -z "$DEVICE" ]; then + printf "${RED}❌ No ESP32 device found${NC}\n" + printf "Available devices:\n" + ls /dev/tty.* 2>/dev/null | head -10 + exit 1 +fi + +printf "${GREEN}✅ Found device: $DEVICE${NC}\n" + +# Build first +printf "\n" +printf "🔨 Building firmware...\n" +printf " 📦 Cleaning previous build...\n" + +# Redirect verbose output to log file, show only important messages +BUILD_LOG="/tmp/pixie-build.log" +echo "" > "$BUILD_LOG" # Clear log file + +{ + docker run --rm -v $PWD:/project -w /project -e HOME=/tmp -e IDF_TARGET=esp32c3 espressif/idf bash -c "idf.py fullclean > /dev/null 2>&1 && idf.py build" 2>&1 +} | while IFS= read -r line; do + # Log everything to file for debugging + echo "$line" >> "$BUILD_LOG" + + # Show only important build progress messages + if [[ "$line" =~ "Generating" ]] || [[ "$line" =~ "Creating" ]] || [[ "$line" =~ "Linking" ]] || [[ "$line" =~ "Project build complete" ]] || [[ "$line" =~ "To flash" ]]; then + printf "\r %s\n" "$line" + elif [[ "$line" =~ "Building" ]] && [[ "$line" =~ ".bin" ]]; then + printf "\r %s\n" "$line" + fi +done + +# Check if build succeeded by looking for the binary files +if [ -f "build/pixie.bin" ] && [ -f "build/bootloader/bootloader.bin" ] && [ -f "build/partition_table/partition-table.bin" ]; then + printf " ${GREEN}✅ Build completed successfully${NC}\n" + printf " 📄 Build log saved to: $BUILD_LOG\n" +else + printf "${RED}❌ Build failed${NC}\n" + printf "📄 Check build log for details: $BUILD_LOG\n" + exit 1 +fi + +# Check if esptool is available locally +ESPTOOL_CMD="" +if command -v esptool &> /dev/null; then + ESPTOOL_CMD="esptool" +elif python -m esptool --help &> /dev/null; then + ESPTOOL_CMD="python -m esptool" +elif python3 -m esptool --help &> /dev/null; then + ESPTOOL_CMD="python3 -m esptool" +else + printf "\n" + printf "⚠️ esptool not found. Installing via pip...\n" + printf "\n" + printf "Run this command to install esptool:\n" + printf " pip install esptool\n" + printf "\n" + printf "Or use Homebrew:\n" + printf " brew install esptool\n" + printf "\n" + printf "Then run this script again.\n" + exit 1 +fi + +printf " 🔧 Using esptool: ${GREEN}$ESPTOOL_CMD${NC}\n" + +printf "\n" +printf "⚡ Flashing firmware to device...\n" +printf " 📋 Target: $DEVICE\n" +printf " ⚙️ Chip: ESP32-C3\n" +printf " 🚀 Baud: 460800\n" + +# Flash using native esptool with cleaner output +FLASH_LOG="/tmp/pixie-flash.log" +echo "" > "$FLASH_LOG" # Clear log file + +{ + $ESPTOOL_CMD --chip esp32c3 -p $DEVICE -b 460800 --before default_reset --after hard_reset write_flash \ + --flash_mode dio --flash_freq 80m --flash_size 16MB \ + 0x0 build/bootloader/bootloader.bin \ + 0x8000 build/partition_table/partition-table.bin \ + 0x10000 build/pixie.bin 2>&1 +} | while IFS= read -r line; do + # Log everything for debugging + echo "$line" >> "$FLASH_LOG" + + # Show progress for important flash steps + if [[ "$line" =~ "Connecting" ]] || [[ "$line" =~ "Chip is" ]] || [[ "$line" =~ "Uploading stub" ]] || [[ "$line" =~ "Configuring flash" ]] || [[ "$line" =~ "Writing at" ]] || [[ "$line" =~ "Hash of data verified" ]] || [[ "$line" =~ "Leaving" ]]; then + printf "\r %s\n" "$line" + elif [[ "$line" =~ "%" ]]; then + # Show progress percentages on same line + printf "\r 📦 %s" "$line" + fi +done + +printf "\n" # New line after progress + +if [ $? -eq 0 ]; then + printf "\n" + printf "${GREEN}🎉 Flash successful!${NC}\n" + printf "\n" + printf "${YELLOW}🔧 To monitor serial output:${NC}\n" + printf " ${GREEN}screen $DEVICE 115200${NC}\n" + printf " # Press Ctrl+A then K to exit screen\n" + printf "\n" + printf "📄 Flash log saved to: $FLASH_LOG\n" +else + printf "${RED}❌ Flash failed${NC}\n" + printf "📄 Check flash log for details: $FLASH_LOG\n" + exit 1 +fi \ No newline at end of file diff --git a/flash.sh b/flash.sh new file mode 100755 index 0000000..5d07c22 --- /dev/null +++ b/flash.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# Firefly Pixie Flash Script +# Usage: ./flash.sh [device_path] + +set -e + +# Default device path (update this with your actual device) +DEFAULT_DEVICE="/dev/tty.usbmodem*" + +# Use provided device or default +DEVICE=${1:-$DEFAULT_DEVICE} + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}🔥 Firefly Pixie Flash Tool${NC}" +echo "==========================" + +# Auto-detect device if using wildcard +if [[ "$DEVICE" == *"*"* ]]; then + echo "🔍 Auto-detecting ESP32 device..." + DETECTED_DEVICES=$(ls $DEVICE 2>/dev/null || true) + + if [ -z "$DETECTED_DEVICES" ]; then + echo -e "${RED}❌ No ESP32 device found matching: $DEVICE${NC}" + echo "" + echo "Available devices:" + ls /dev/tty.* 2>/dev/null | grep -E "(usbmodem|usbserial|SLAB)" || echo " No USB devices found" + echo "" + echo "Usage: $0 /dev/tty.your_device" + exit 1 + fi + + # Use first detected device + DEVICE=$(echo $DETECTED_DEVICES | head -n1) + echo -e "${GREEN}✅ Found device: $DEVICE${NC}" +fi + +# Check if device exists +if [ ! -e "$DEVICE" ]; then + echo -e "${RED}❌ Device not found: $DEVICE${NC}" + echo "" + echo "Available devices:" + ls /dev/tty.* 2>/dev/null | grep -E "(usbmodem|usbserial|SLAB)" || echo " No USB devices found" + exit 1 +fi + +echo "📱 Target device: $DEVICE" +echo "" + +# Build firmware +echo "🔨 Building firmware..." +docker run --rm -v $PWD:/project -w /project -e HOME=/tmp espressif/idf idf.py build + +if [ $? -ne 0 ]; then + echo -e "${RED}❌ Build failed${NC}" + exit 1 +fi + +echo -e "${GREEN}✅ Build successful${NC}" +echo "" + +# Flash firmware +echo "⚡ Flashing to device: $DEVICE" + +# Check if device still exists before flashing +if [ ! -e "$DEVICE" ]; then + echo -e "${RED}❌ Device disappeared: $DEVICE${NC}" + echo "This sometimes happens with ESP32 devices." + echo "" + echo "Try these solutions:" + echo "1. Run ./flash-native.sh (uses native esptool)" + echo "2. Unplug and reconnect the device" + echo "3. Try a different USB cable" + exit 1 +fi + +docker run --device=$DEVICE:$DEVICE --rm -v $PWD:/project -w /project -e HOME=/tmp espressif/idf idf.py -p $DEVICE flash + +if [ $? -eq 0 ]; then + echo "" + echo -e "${GREEN}🎉 Flash successful!${NC}" + echo "" + echo "📋 Quick Start:" + echo " • Main menu: Navigate with North/South, select with OK" + echo " • Games: Snake, Tetris, Pong" + echo " • Wallet: Generates Ethereum addresses" + echo " • Back: West button in any panel" + echo "" + echo "🔧 Monitor output:" + echo " docker run --device=$DEVICE:$DEVICE --rm -v \$PWD:/project -w /project espressif/idf idf.py -p $DEVICE monitor" +else + echo -e "${RED}❌ Flash failed${NC}" + exit 1 +fi \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 5406a88..4787114 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -11,8 +11,13 @@ idf_component_register( "panel-game.c" "panel-menu.c" "panel-space.c" + "panel-wallet.c" + "panel-snake.c" + "panel-tetris.c" + "panel-pong.c" + "panel-buttontest.c" "pixels.c" - "task-ble.c" + "qr-generator.c" "task-io.c" "utils.c" diff --git a/main/main.c b/main/main.c index 091e7fc..2549f5a 100644 --- a/main/main.c +++ b/main/main.c @@ -3,9 +3,9 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "nvs_flash.h" #include "events-private.h" -#include "task-ble.h" #include "task-io.h" #include "device-info.h" @@ -22,11 +22,21 @@ void app_main() { vTaskSetApplicationTaskTag( NULL, (void*)NULL); TaskHandle_t taskIoHandle = NULL; - TaskHandle_t taskBleHandle = NULL; // Initialie the events events_init(); + // Initialize NVS flash for wallet storage + { + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + printf("[main] NVS flash initialized\n"); + } + // Load NVS and eFuse provision data { DeviceStatus status = device_init(); @@ -48,19 +58,6 @@ void app_main() { printf("[main] IO ready\n"); } - // Start the Message task (handles BLE messages) - { - // Pointer passed to taskReplFunc to notify us when REPL is ready - uint32_t ready = 0; // @TODO: set this to 0 and set in the task - - BaseType_t status = xTaskCreatePinnedToCore(&taskBleFunc, "ble", 5 * 1024, &ready, 2, &taskBleHandle, 0); - printf("[main] start BLE task: status=%d\n", status); - assert(taskBleHandle != NULL); - - // Wait for the REPL task to complete setup - while (!ready) { delay(1); } - printf("[main] BLE ready\n"); - } // Start the App Process; this is started in the main task, so // has high-priority. Don't doddle. @@ -73,11 +70,10 @@ void app_main() { //pushPanelConnect(NULL); while (1) { - printf("[main] high-water: boot=%d io=%d, ble=%d freq=%ld\n", + printf("[main] high-water: boot=%d io=%d freq=%d\n", uxTaskGetStackHighWaterMark(NULL), uxTaskGetStackHighWaterMark(taskIoHandle), - uxTaskGetStackHighWaterMark(taskBleHandle), - portTICK_PERIOD_MS); + configTICK_RATE_HZ); delay(60000); } } diff --git a/main/panel-buttontest.c b/main/panel-buttontest.c new file mode 100644 index 0000000..8725841 --- /dev/null +++ b/main/panel-buttontest.c @@ -0,0 +1,164 @@ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include +#include +#include + +#include "firefly-scene.h" +#include "firefly-color.h" +#include "panel.h" +#include "panel-buttontest.h" +#include "utils.h" + +typedef struct ButtonTestState { + FfxScene scene; + FfxNode titleLabel; + FfxNode button1Label; + FfxNode button2Label; + FfxNode button3Label; + FfxNode button4Label; + FfxNode instructionsLabel; + FfxNode hexLabel; + FfxNode exitLabel; + + char button1Text[64]; + char button2Text[64]; + char button3Text[64]; + char button4Text[64]; + char hexText[32]; + + uint32_t southHoldStart; +} ButtonTestState; + +static void updateButtonDisplay(ButtonTestState *state, Keys keys) { + // Update button status display with correct key mapping + snprintf(state->button1Text, sizeof(state->button1Text), "Button 1: %s (Cancel=0x%04x)", + (keys & KeyCancel) ? "PRESSED" : "released", KeyCancel); + snprintf(state->button2Text, sizeof(state->button2Text), "Button 2: %s (Ok=0x%04x)", + (keys & KeyOk) ? "PRESSED" : "released", KeyOk); + snprintf(state->button3Text, sizeof(state->button3Text), "Button 3: %s (North=0x%04x)", + (keys & KeyNorth) ? "PRESSED" : "released", KeyNorth); + snprintf(state->button4Text, sizeof(state->button4Text), "Button 4: %s (South=0x%04x)", + (keys & KeySouth) ? "PRESSED" : "released", KeySouth); + snprintf(state->hexText, sizeof(state->hexText), "Raw Keys: 0x%04x", keys); + + ffx_sceneLabel_setText(state->button1Label, state->button1Text); + ffx_sceneLabel_setText(state->button2Label, state->button2Text); + ffx_sceneLabel_setText(state->button3Label, state->button3Text); + ffx_sceneLabel_setText(state->button4Label, state->button4Text); + ffx_sceneLabel_setText(state->hexLabel, state->hexText); + + // Change color for pressed buttons + ffx_sceneLabel_setTextColor(state->button1Label, + (keys & KeyCancel) ? ffx_color_rgb(0, 255, 0) : ffx_color_rgb(255, 255, 255)); + ffx_sceneLabel_setTextColor(state->button2Label, + (keys & KeyOk) ? ffx_color_rgb(0, 255, 0) : ffx_color_rgb(255, 255, 255)); + ffx_sceneLabel_setTextColor(state->button3Label, + (keys & KeyNorth) ? ffx_color_rgb(0, 255, 0) : ffx_color_rgb(255, 255, 255)); + ffx_sceneLabel_setTextColor(state->button4Label, + (keys & KeySouth) ? ffx_color_rgb(0, 255, 0) : ffx_color_rgb(255, 255, 255)); +} + +static void keyChanged(EventPayload event, void *_state) { + ButtonTestState *state = _state; + + Keys keys = event.props.keys.down; + + // Detailed logging for each button + printf("[buttontest] ======== BUTTON PRESS EVENT ========\n"); + printf("[buttontest] Raw keys value: 0x%04x\n", keys); + printf("[buttontest] KeyCancel (0x%04x): %s\n", KeyCancel, (keys & KeyCancel) ? "PRESSED" : "released"); + printf("[buttontest] KeyOk (0x%04x): %s\n", KeyOk, (keys & KeyOk) ? "PRESSED" : "released"); + printf("[buttontest] KeyNorth (0x%04x): %s\n", KeyNorth, (keys & KeyNorth) ? "PRESSED" : "released"); + printf("[buttontest] KeySouth (0x%04x): %s\n", KeySouth, (keys & KeySouth) ? "PRESSED" : "released"); + printf("[buttontest] =====================================\n"); + + // Update visual display + updateButtonDisplay(state, keys); + + // Handle exit with any button hold for 2 seconds + bool anyButtonPressed = (keys & (KeyCancel | KeyOk | KeyNorth | KeySouth)) != 0; + + if (anyButtonPressed) { + if (state->southHoldStart == 0) { + state->southHoldStart = ticks(); + } + } else { + state->southHoldStart = 0; + } +} + +static void render(EventPayload event, void *_state) { + ButtonTestState *state = _state; + + uint32_t now = ticks(); + + // Check for hold-to-exit + if (state->southHoldStart > 0 && (now - state->southHoldStart) > 2000) { + printf("[buttontest] Exiting after 2-second button hold\n"); + panel_pop(); + return; + } +} + +static int init(FfxScene scene, FfxNode node, void* _state, void* arg) { + ButtonTestState *state = _state; + state->scene = scene; + + printf("[buttontest] Button Test App Started\n"); + printf("[buttontest] Press each physical button (1,2,3,4) to see mapping\n"); + printf("[buttontest] Hold any button for 2 seconds to exit\n"); + + // Create title + state->titleLabel = ffx_scene_createLabel(scene, FfxFontLarge, "Button Test"); + ffx_sceneGroup_appendChild(node, state->titleLabel); + ffx_sceneNode_setPosition(state->titleLabel, (FfxPoint){ .x = 80, .y = 10 }); + + // Create button status labels + state->button1Label = ffx_scene_createLabel(scene, FfxFontSmall, "Button 1: released"); + ffx_sceneGroup_appendChild(node, state->button1Label); + ffx_sceneNode_setPosition(state->button1Label, (FfxPoint){ .x = 10, .y = 40 }); + + state->button2Label = ffx_scene_createLabel(scene, FfxFontSmall, "Button 2: released"); + ffx_sceneGroup_appendChild(node, state->button2Label); + ffx_sceneNode_setPosition(state->button2Label, (FfxPoint){ .x = 10, .y = 60 }); + + state->button3Label = ffx_scene_createLabel(scene, FfxFontSmall, "Button 3: released"); + ffx_sceneGroup_appendChild(node, state->button3Label); + ffx_sceneNode_setPosition(state->button3Label, (FfxPoint){ .x = 10, .y = 80 }); + + state->button4Label = ffx_scene_createLabel(scene, FfxFontSmall, "Button 4: released"); + ffx_sceneGroup_appendChild(node, state->button4Label); + ffx_sceneNode_setPosition(state->button4Label, (FfxPoint){ .x = 10, .y = 100 }); + + // Raw hex display + state->hexLabel = ffx_scene_createLabel(scene, FfxFontMedium, "Raw Keys: 0x0000"); + ffx_sceneGroup_appendChild(node, state->hexLabel); + ffx_sceneNode_setPosition(state->hexLabel, (FfxPoint){ .x = 10, .y = 130 }); + + // Instructions + state->instructionsLabel = ffx_scene_createLabel(scene, FfxFontSmall, "Press each button 1-4"); + ffx_sceneGroup_appendChild(node, state->instructionsLabel); + ffx_sceneNode_setPosition(state->instructionsLabel, (FfxPoint){ .x = 10, .y = 160 }); + + state->exitLabel = ffx_scene_createLabel(scene, FfxFontSmall, "Hold any button 2s to exit"); + ffx_sceneGroup_appendChild(node, state->exitLabel); + ffx_sceneNode_setPosition(state->exitLabel, (FfxPoint){ .x = 10, .y = 180 }); + + // Initialize state + state->southHoldStart = 0; + + // Initialize display + updateButtonDisplay(state, 0); + + // Register for all key events + panel_onEvent(EventNameKeysChanged | KeyCancel | KeyOk | KeyNorth | KeySouth, keyChanged, state); + panel_onEvent(EventNameRenderScene, render, state); + + return 0; +} + +void pushPanelButtonTest(void* arg) { + panel_push(init, sizeof(ButtonTestState), PanelStyleSlideLeft, arg); +} \ No newline at end of file diff --git a/main/panel-buttontest.h b/main/panel-buttontest.h new file mode 100644 index 0000000..dfe46a5 --- /dev/null +++ b/main/panel-buttontest.h @@ -0,0 +1,14 @@ +#ifndef __PANEL_BUTTONTEST_H__ +#define __PANEL_BUTTONTEST_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +void pushPanelButtonTest(void* arg); + +#ifdef __cplusplus +} +#endif + +#endif /* __PANEL_BUTTONTEST_H__ */ \ No newline at end of file diff --git a/main/panel-gifs.c b/main/panel-gifs.c index 246d8f1..b9d88d6 100644 --- a/main/panel-gifs.c +++ b/main/panel-gifs.c @@ -132,7 +132,7 @@ static void setShibaFrame(FfxNode node) { }; const uint16_t *imgData = frames[frame]; - ffx_sceneImage_setData(node, imgData, sizeof(image_fox_0)); + ffx_sceneImage_setData(node, imgData, sizeof(image_shiba_0)); } static void setRrollFrame(FfxNode node) { diff --git a/main/panel-menu.c b/main/panel-menu.c index f5c4632..e3015f4 100644 --- a/main/panel-menu.c +++ b/main/panel-menu.c @@ -7,82 +7,144 @@ #include "./panel-gifs.h" #include "./panel-menu.h" #include "./panel-space.h" +#include "./panel-wallet.h" +#include "./panel-snake.h" +#include "./panel-tetris.h" +#include "./panel-pong.h" +#include "./panel-buttontest.h" #include "images/image-arrow.h" +// Menu items array for easier management +static const char* menuItems[] = { + "Device", + "GIFs", + "Le Space", + "Wallet", + "Snake", + "Tetris", + "Pong", + "Button Test", + "---" +}; + +static const size_t MENU_ITEM_COUNT = 9; typedef struct MenuState { size_t cursor; + size_t numItems; FfxScene scene; FfxNode nodeCursor; + FfxNode menuLabels[9]; // Support up to 9 menu items } MenuState; +static void updateMenuDisplay(MenuState *app) { + // Show 5 items total: 2 above cursor, cursor in center, 2 below cursor + int centerY = 120; // Center of 240px screen + int itemSpacing = 35; + + for (int i = 0; i < MENU_ITEM_COUNT; i++) { + int relativePos = i - (int)app->cursor; // Position relative to cursor + + if (relativePos >= -2 && relativePos <= 2) { + // Item is visible (within 2 positions of cursor) + int yPos = centerY + (relativePos * itemSpacing); + ffx_sceneNode_setPosition(app->menuLabels[i], (FfxPoint){ .x = 70, .y = yPos }); + + // Highlight the selected item + if (i == app->cursor) { + ffx_sceneNode_setPosition(app->nodeCursor, (FfxPoint){ .x = 25, .y = yPos }); + } + } else { + // Hide items that are too far from cursor + ffx_sceneNode_setPosition(app->menuLabels[i], (FfxPoint){ .x = -300, .y = 0 }); + } + } +} static void keyChanged(EventPayload event, void *_app) { MenuState *app = _app; - switch(event.props.keys.down) { - case KeyOk: - switch(app->cursor) { - case 0: - pushPanelAttest(NULL); - break; - case 1: - pushPanelGifs(NULL); - break; - case 2: - pushPanelSpace(NULL); - break; - } - return; - case KeyNorth: - if (app->cursor == 0) { return; } + if (event.props.keys.down & KeyOk) { + // Don't allow selecting the separator + if (app->cursor == 8) return; + + switch(app->cursor) { + case 0: + pushPanelAttest(NULL); + break; + case 1: + pushPanelGifs(NULL); + break; + case 2: + pushPanelSpace(NULL); + break; + case 3: + pushPanelWallet(NULL); + break; + case 4: + pushPanelSnake(NULL); + break; + case 5: + pushPanelTetris(NULL); + break; + case 6: + pushPanelPong(NULL); + break; + case 7: + pushPanelButtonTest(NULL); + break; + } + return; + } + else if (event.props.keys.down & KeyNorth) { + // Circular navigation up + if (app->cursor == 0) { + app->cursor = MENU_ITEM_COUNT - 1; // Go to last item (---) + } else { app->cursor--; - break; - case KeySouth: - if (app->cursor == 2) { return; } + } + updateMenuDisplay(app); + } + else if (event.props.keys.down & KeySouth) { + // Circular navigation down + if (app->cursor >= MENU_ITEM_COUNT - 1) { + app->cursor = 0; // Go to first item + } else { app->cursor++; - break; - default: - return; + } + updateMenuDisplay(app); } - - ffx_sceneNode_stopAnimations(app->nodeCursor, FfxSceneActionStopCurrent); - ffx_sceneNode_animatePosition(app->nodeCursor, - ffx_point(25, 58 + (app->cursor * 40)), 0, 150, - FfxCurveEaseOutQuad, NULL, NULL); } static int _init(FfxScene scene, FfxNode node, void *_app, void *arg) { MenuState *app = _app; app->scene = scene; + app->cursor = 0; + app->numItems = MENU_ITEM_COUNT; - FfxNode box = ffx_scene_createBox(scene, ffx_size(200, 180)); + // Create background box + FfxNode box = ffx_scene_createBox(scene, ffx_size(200, 220)); ffx_sceneBox_setColor(box, RGBA_DARKER75); ffx_sceneGroup_appendChild(node, box); - ffx_sceneNode_setPosition(box, (FfxPoint){ .x = 20, .y = 30 }); - - FfxNode text; - - text = ffx_scene_createLabel(scene, FfxFontLarge, "Device"); - ffx_sceneGroup_appendChild(node, text); - ffx_sceneNode_setPosition(text, (FfxPoint){ .x = 70, .y = 63 }); - - text = ffx_scene_createLabel(scene, FfxFontLarge, "GIFs"); - ffx_sceneGroup_appendChild(node, text); - ffx_sceneNode_setPosition(text, (FfxPoint){ .x = 70, .y = 103 }); - - text = ffx_scene_createLabel(scene, FfxFontLarge, "Le Space"); - ffx_sceneGroup_appendChild(node, text); - ffx_sceneNode_setPosition(text, (FfxPoint){ .x = 70, .y = 143 }); + ffx_sceneNode_setPosition(box, (FfxPoint){ .x = 20, .y = 10 }); + + // Create all menu labels dynamically + for (int i = 0; i < MENU_ITEM_COUNT; i++) { + app->menuLabels[i] = ffx_scene_createLabel(scene, FfxFontLarge, menuItems[i]); + ffx_sceneGroup_appendChild(node, app->menuLabels[i]); + // Initial position will be set by updateMenuDisplay + ffx_sceneNode_setPosition(app->menuLabels[i], (FfxPoint){ .x = -300, .y = 0 }); + } - FfxNode cursor = ffx_scene_createImage(scene, image_arrow, - sizeof(image_arrow)); + // Create cursor arrow + FfxNode cursor = ffx_scene_createImage(scene, image_arrow, sizeof(image_arrow)); ffx_sceneGroup_appendChild(node, cursor); - ffx_sceneNode_setPosition(cursor, (FfxPoint){ .x = 25, .y = 58 }); - app->nodeCursor = cursor; + // Set initial menu display + updateMenuDisplay(app); + panel_onEvent(EventNameKeysChanged | KeyNorth | KeySouth | KeyOk, keyChanged, app); diff --git a/main/panel-pong.c b/main/panel-pong.c new file mode 100644 index 0000000..7833964 --- /dev/null +++ b/main/panel-pong.c @@ -0,0 +1,349 @@ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include +#include +#include +#include +#include + +#include "firefly-scene.h" +#include "firefly-color.h" +#include "panel.h" +#include "panel-pong.h" +#include "utils.h" + +#define PADDLE_WIDTH 4 +#define PADDLE_HEIGHT 30 +#define BALL_SIZE 6 +#define GAME_WIDTH 200 // Rotated: horizontal layout, wider +#define GAME_HEIGHT 120 // Rotated: horizontal layout, shorter +#define PADDLE_SPEED 3 +#define BALL_SPEED 2 + +typedef struct PongState { + FfxScene scene; + FfxNode gameArea; + FfxNode playerPaddle; // Bottom paddle (player) + FfxNode aiPaddle; // Top paddle (AI) + FfxNode ball; + FfxNode scoreLabel; + FfxNode centerLine; + FfxNode pausedLabel; + + // Game state (rotated: paddles move vertically) + float playerPaddleY; // Player paddle Y position (right side) + float aiPaddleY; // AI paddle Y position (left side) + float ballX, ballY; + float ballVelX, ballVelY; + int playerScore, aiScore; + bool gameOver; + bool paused; + Keys keys; + uint32_t southHoldStart; + uint32_t gameStartTime; + char scoreText[32]; +} PongState; + +static void resetBall(PongState *state) { + state->ballX = GAME_WIDTH / 2; + state->ballY = GAME_HEIGHT / 2; + + // Random direction but not too steep for horizontal play + float angle = (rand() % 60 - 30) * M_PI / 180.0; // -30 to +30 degrees + if (rand() % 2) angle += M_PI; // Sometimes go left instead of right + + state->ballVelX = BALL_SPEED * cos(angle); // Primarily horizontal + state->ballVelY = BALL_SPEED * sin(angle); // Some vertical component +} + +static void updateGame(PongState *state) { + if (state->paused || state->gameOver) return; + + // Standardized controls: + // Button 1 (KeyCancel) = Primary action (speed boost) + // Button 2 (KeyOk) = Pause/Exit (handled in keyChanged) + // Button 3 (KeyNorth) = Up/Right movement (90° counter-clockwise) + // Button 4 (KeySouth) = Down/Left movement + + float speed = (state->keys & KeyCancel) ? PADDLE_SPEED * 2 : PADDLE_SPEED; + + // Rotated controls: paddles move up/down + // Button 3 (North) = Move up + if (state->keys & KeyNorth) { + state->playerPaddleY -= speed; + if (state->playerPaddleY < 0) { + state->playerPaddleY = 0; + } + } + + // Button 4 (South) = Move down + if (state->keys & KeySouth) { + state->playerPaddleY += speed; + if (state->playerPaddleY > GAME_HEIGHT - PADDLE_HEIGHT) { + state->playerPaddleY = GAME_HEIGHT - PADDLE_HEIGHT; + } + } + + // Simple AI for left paddle (follows ball vertically) + float paddleCenter = state->aiPaddleY + PADDLE_HEIGHT / 2; + float ballCenter = state->ballY + BALL_SIZE / 2; + + if (paddleCenter < ballCenter - 5) { + state->aiPaddleY += PADDLE_SPEED * 0.8; // AI slightly slower + if (state->aiPaddleY > GAME_HEIGHT - PADDLE_HEIGHT) { + state->aiPaddleY = GAME_HEIGHT - PADDLE_HEIGHT; + } + } else if (paddleCenter > ballCenter + 5) { + state->aiPaddleY -= PADDLE_SPEED * 0.8; + if (state->aiPaddleY < 0) state->aiPaddleY = 0; + } + + // Move ball + state->ballX += state->ballVelX; + state->ballY += state->ballVelY; + + // Ball collision with top/bottom walls + if (state->ballY <= 0 || state->ballY >= GAME_HEIGHT - BALL_SIZE) { + state->ballVelY = -state->ballVelY; + if (state->ballY <= 0) state->ballY = 0; + if (state->ballY >= GAME_HEIGHT - BALL_SIZE) state->ballY = GAME_HEIGHT - BALL_SIZE; + } + + // Ball collision with player paddle (right side) + if (state->ballX + BALL_SIZE >= GAME_WIDTH - PADDLE_WIDTH && + state->ballY + BALL_SIZE >= state->playerPaddleY && + state->ballY <= state->playerPaddleY + PADDLE_HEIGHT) { + + state->ballVelX = -state->ballVelX; + state->ballX = GAME_WIDTH - PADDLE_WIDTH - BALL_SIZE; + + // Add some english based on where ball hits paddle + float hitPos = (state->ballY + BALL_SIZE/2 - state->playerPaddleY - PADDLE_HEIGHT/2) / (PADDLE_HEIGHT/2); + state->ballVelY += hitPos * 0.5; + + // Limit ball speed + if (fabs(state->ballVelY) > BALL_SPEED * 1.5) { + state->ballVelY = (state->ballVelY > 0) ? BALL_SPEED * 1.5 : -BALL_SPEED * 1.5; + } + } + + // Ball collision with AI paddle (left side) + if (state->ballX <= PADDLE_WIDTH && + state->ballY + BALL_SIZE >= state->aiPaddleY && + state->ballY <= state->aiPaddleY + PADDLE_HEIGHT) { + + state->ballVelX = -state->ballVelX; + state->ballX = PADDLE_WIDTH; + + // Add some english + float hitPos = (state->ballY + BALL_SIZE/2 - state->aiPaddleY - PADDLE_HEIGHT/2) / (PADDLE_HEIGHT/2); + state->ballVelY += hitPos * 0.5; + + // Limit ball speed + if (fabs(state->ballVelY) > BALL_SPEED * 1.5) { + state->ballVelY = (state->ballVelY > 0) ? BALL_SPEED * 1.5 : -BALL_SPEED * 1.5; + } + } + + // Ball goes off left/right edges (scoring) + if (state->ballX < -BALL_SIZE) { + state->playerScore++; // Player scores when ball goes off left (AI side) + resetBall(state); + snprintf(state->scoreText, sizeof(state->scoreText), "Player %d - AI %d", state->playerScore, state->aiScore); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + + if (state->playerScore >= 7) { + state->gameOver = true; + } + } else if (state->ballX > GAME_WIDTH) { + state->aiScore++; // AI scores when ball goes off right (player side) + resetBall(state); + snprintf(state->scoreText, sizeof(state->scoreText), "Player %d - AI %d", state->playerScore, state->aiScore); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + + if (state->aiScore >= 7) { + state->gameOver = true; + } + } +} + +static void updateVisuals(PongState *state) { + // Apply game area offset for rotated layout + int offsetX = 20; + int offsetY = 60; + + // Player paddle at right side + ffx_sceneNode_setPosition(state->playerPaddle, (FfxPoint){ + .x = offsetX + GAME_WIDTH - PADDLE_WIDTH, + .y = offsetY + (int)state->playerPaddleY + }); + + // AI paddle at left side + ffx_sceneNode_setPosition(state->aiPaddle, (FfxPoint){ + .x = offsetX, + .y = offsetY + (int)state->aiPaddleY + }); + + ffx_sceneNode_setPosition(state->ball, (FfxPoint){ + .x = offsetX + (int)state->ballX, + .y = offsetY + (int)state->ballY + }); +} + +static void keyChanged(EventPayload event, void *_state) { + PongState *state = _state; + printf("[pong] keyChanged called! keys=0x%04x\n", event.props.keys.down); + + // Standardized controls: + // Button 1 (KeyCancel) = Primary action (speed boost) + // Button 2 (KeyOk) = Pause/Exit (hold 1s) + // Button 3 (KeyNorth) = Up/Right movement (90° counter-clockwise) + // Button 4 (KeySouth) = Down/Left movement + + static uint32_t okHoldStart = 0; + + // Ignore key events for first 500ms to prevent immediate exits from residual button state + if (state->gameStartTime == 0) { + state->gameStartTime = ticks(); + okHoldStart = 0; // Reset static variable when game restarts + printf("[pong] Game start time set, ignoring keys for 500ms\n"); + return; + } + if (ticks() - state->gameStartTime < 500) { + printf("[pong] Ignoring keys due to startup delay\n"); + return; + } + + // Handle Ok button hold-to-exit, short press for pause + if (event.props.keys.down & KeyOk) { + if (okHoldStart == 0) { + okHoldStart = ticks(); + } + } else { + if (okHoldStart > 0) { + uint32_t holdDuration = ticks() - okHoldStart; + if (holdDuration > 1000) { // 1 second hold + panel_pop(); + return; + } else { + // Short press - pause/unpause + if (!state->gameOver) { + state->paused = !state->paused; + // Show/hide paused label + if (state->paused) { + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = 85, .y = 120 }); + } else { + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = -300, .y = 120 }); + } + } + } + okHoldStart = 0; + } + } + + if (state->gameOver) { + if (event.props.keys.down & KeyCancel) { + // Reset game with Cancel button + state->playerScore = 0; + state->aiScore = 0; + state->playerPaddleY = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2; + state->aiPaddleY = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2; + state->gameOver = false; + state->paused = false; + resetBall(state); + snprintf(state->scoreText, sizeof(state->scoreText), "Player %d - AI %d", state->playerScore, state->aiScore); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + } + return; + } + + // Store key state for 2-button control + state->keys = event.props.keys.down; +} + +static void render(EventPayload event, void *_state) { + PongState *state = _state; + + uint32_t now = ticks(); + + // Check for hold-to-exit during gameplay + if (state->southHoldStart > 0 && (now - state->southHoldStart) > 1000) { + panel_pop(); + return; + } + + updateGame(state); + updateVisuals(state); +} + +static int init(FfxScene scene, FfxNode node, void* _state, void* arg) { + PongState *state = _state; + + // Clear entire state first for fresh start + memset(state, 0, sizeof(*state)); + state->scene = scene; + + // Create game area background - rotated 90° CCW, horizontal layout + state->gameArea = ffx_scene_createBox(scene, ffx_size(GAME_WIDTH, GAME_HEIGHT)); + ffx_sceneBox_setColor(state->gameArea, COLOR_BLACK); + ffx_sceneGroup_appendChild(node, state->gameArea); + ffx_sceneNode_setPosition(state->gameArea, (FfxPoint){ .x = 20, .y = 60 }); // Centered horizontally + + // Create center line (vertical for horizontal play) + state->centerLine = ffx_scene_createBox(scene, ffx_size(2, GAME_HEIGHT)); + ffx_sceneBox_setColor(state->centerLine, ffx_color_rgb(128, 128, 128)); + ffx_sceneGroup_appendChild(node, state->centerLine); + ffx_sceneNode_setPosition(state->centerLine, (FfxPoint){ .x = 20 + GAME_WIDTH/2 - 1, .y = 60 }); + + // Create score label - positioned on left side for visibility + state->scoreLabel = ffx_scene_createLabel(scene, FfxFontMedium, "Player 0 - AI 0"); + ffx_sceneGroup_appendChild(node, state->scoreLabel); + ffx_sceneNode_setPosition(state->scoreLabel, (FfxPoint){ .x = 10, .y = 30 }); + + // Create paused label - centered on screen + state->pausedLabel = ffx_scene_createLabel(scene, FfxFontLarge, "PAUSED"); + ffx_sceneGroup_appendChild(node, state->pausedLabel); + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = -300, .y = 120 }); // Hidden initially + + // Create paddles (vertical for horizontal play) + // Player paddle (right side) + state->playerPaddle = ffx_scene_createBox(scene, ffx_size(PADDLE_WIDTH, PADDLE_HEIGHT)); + ffx_sceneBox_setColor(state->playerPaddle, ffx_color_rgb(255, 255, 255)); + ffx_sceneGroup_appendChild(node, state->playerPaddle); + + // AI paddle (left side) + state->aiPaddle = ffx_scene_createBox(scene, ffx_size(PADDLE_WIDTH, PADDLE_HEIGHT)); + ffx_sceneBox_setColor(state->aiPaddle, ffx_color_rgb(255, 255, 255)); + ffx_sceneGroup_appendChild(node, state->aiPaddle); + + // Create ball + state->ball = ffx_scene_createBox(scene, ffx_size(BALL_SIZE, BALL_SIZE)); + ffx_sceneBox_setColor(state->ball, ffx_color_rgb(255, 255, 255)); + ffx_sceneGroup_appendChild(node, state->ball); + + // Initialize game state values + state->playerScore = 0; + state->aiScore = 0; + state->playerPaddleY = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2; // Center vertically + state->aiPaddleY = GAME_HEIGHT / 2 - PADDLE_HEIGHT / 2; // Center vertically + state->gameOver = false; + state->paused = false; + state->keys = 0; + state->southHoldStart = 0; + state->gameStartTime = 0; + + resetBall(state); + snprintf(state->scoreText, sizeof(state->scoreText), "Player %d - AI %d", state->playerScore, state->aiScore); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + + // Register events (4 buttons: Cancel, Ok, North, South) + panel_onEvent(EventNameKeysChanged | KeyCancel | KeyOk | KeyNorth | KeySouth, keyChanged, state); + panel_onEvent(EventNameRenderScene, render, state); + + return 0; +} + +void pushPanelPong(void* arg) { + panel_push(init, sizeof(PongState), PanelStyleSlideLeft, arg); +} \ No newline at end of file diff --git a/main/panel-pong.h b/main/panel-pong.h new file mode 100644 index 0000000..b53864d --- /dev/null +++ b/main/panel-pong.h @@ -0,0 +1,14 @@ +#ifndef __PANEL_PONG_H__ +#define __PANEL_PONG_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void pushPanelPong(void* arg); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PANEL_PONG_H__ */ \ No newline at end of file diff --git a/main/panel-snake.c b/main/panel-snake.c new file mode 100644 index 0000000..6c29f85 --- /dev/null +++ b/main/panel-snake.c @@ -0,0 +1,356 @@ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include +#include +#include +#include + +#include "firefly-scene.h" +#include "firefly-color.h" +#include "panel.h" +#include "panel-snake.h" +#include "utils.h" + +#define GRID_SIZE 10 +#define GRID_WIDTH 20 // Horizontal width (for Le Space style layout) +#define GRID_HEIGHT 16 // Vertical height (for Le Space style layout) +#define MAX_SNAKE_LENGTH 50 + +typedef enum Direction { + DIR_UP = 0, + DIR_RIGHT = 1, + DIR_DOWN = 2, + DIR_LEFT = 3 +} Direction; + +typedef struct Point { + int x, y; +} Point; + +typedef struct SnakeState { + FfxScene scene; + FfxNode gameArea; + FfxNode snakeBody[MAX_SNAKE_LENGTH]; + FfxNode food; + FfxNode scoreLabel; + FfxNode pausedLabel; + + Point snake[MAX_SNAKE_LENGTH]; + int snakeLength; + Direction direction; + Direction nextDirection; + Point foodPos; + int score; + bool gameOver; + bool paused; + uint32_t lastMove; + char scoreText[32]; + + Keys currentKeys; + uint32_t southHoldStart; + uint32_t gameStartTime; +} SnakeState; + +static void spawnFood(SnakeState *state) { + do { + state->foodPos.x = rand() % GRID_WIDTH; + state->foodPos.y = rand() % GRID_HEIGHT; + + // Make sure food doesn't spawn on snake + bool onSnake = false; + for (int i = 0; i < state->snakeLength; i++) { + if (state->snake[i].x == state->foodPos.x && state->snake[i].y == state->foodPos.y) { + onSnake = true; + break; + } + } + if (!onSnake) break; + } while (true); + + ffx_sceneNode_setPosition(state->food, (FfxPoint){ + .x = 35 + state->foodPos.x * GRID_SIZE, // Add game area offset + .y = 40 + state->foodPos.y * GRID_SIZE // Add game area offset + }); +} + +static bool checkCollision(SnakeState *state) { + Point head = state->snake[0]; + + // Wall collision + if (head.x < 0 || head.x >= GRID_WIDTH || head.y < 0 || head.y >= GRID_HEIGHT) { + return true; + } + + // Self collision + for (int i = 1; i < state->snakeLength; i++) { + if (state->snake[i].x == head.x && state->snake[i].y == head.y) { + return true; + } + } + + return false; +} + +static void moveSnake(SnakeState *state) { + if (state->gameOver || state->paused) return; + + // Update direction + state->direction = state->nextDirection; + + // Move body + for (int i = state->snakeLength - 1; i > 0; i--) { + state->snake[i] = state->snake[i-1]; + } + + // Move head + switch (state->direction) { + case DIR_UP: state->snake[0].y--; break; + case DIR_DOWN: state->snake[0].y++; break; + case DIR_LEFT: state->snake[0].x--; break; + case DIR_RIGHT: state->snake[0].x++; break; + } + + // Check collision + if (checkCollision(state)) { + state->gameOver = true; + return; + } + + // Check food + if (state->snake[0].x == state->foodPos.x && state->snake[0].y == state->foodPos.y) { + state->score += 10; + state->snakeLength++; + spawnFood(state); + + snprintf(state->scoreText, sizeof(state->scoreText), "Score: %d", state->score); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + } + + // Update visual positions (add game area offset) + for (int i = 0; i < state->snakeLength; i++) { + ffx_sceneNode_setPosition(state->snakeBody[i], (FfxPoint){ + .x = 35 + state->snake[i].x * GRID_SIZE, // Add game area offset + .y = 40 + state->snake[i].y * GRID_SIZE // Add game area offset + }); + } + + // Hide unused body segments + for (int i = state->snakeLength; i < MAX_SNAKE_LENGTH; i++) { + ffx_sceneNode_setPosition(state->snakeBody[i], (FfxPoint){ .x = -100, .y = -100 }); + } +} + +static void keyChanged(EventPayload event, void *_state) { + printf("[snake] keyChanged called! keys=0x%04x\n", event.props.keys.down); + SnakeState *state = _state; + + // Update current keys for continuous movement + state->currentKeys = event.props.keys.down; + + // Standardized controls: + // Button 1 (KeyCancel) = Primary action (rotate direction) + // Button 2 (KeyOk) = Pause/Exit (hold 1s) + // Button 3 (KeyNorth) = Up/Right movement (90° counter-clockwise like Le Space) + // Button 4 (KeySouth) = Down/Left movement + + static uint32_t okHoldStart = 0; + + // Ignore key events for first 500ms to prevent immediate exits from residual button state + if (state->gameStartTime == 0) { + state->gameStartTime = ticks(); + okHoldStart = 0; // Reset static variable when game restarts + printf("[snake] Game start time set, ignoring keys for 500ms\n"); + return; + } + if (ticks() - state->gameStartTime < 500) { + printf("[snake] Ignoring keys due to startup delay\n"); + return; + } + + // Handle Ok button hold-to-exit, short press for pause + if (event.props.keys.down & KeyOk) { + if (okHoldStart == 0) { + okHoldStart = ticks(); + } + } else { + if (okHoldStart > 0) { + uint32_t holdDuration = ticks() - okHoldStart; + if (holdDuration > 1000) { // 1 second hold + panel_pop(); + return; + } else { + // Short press - pause/unpause + if (!state->gameOver) { + state->paused = !state->paused; + // Show/hide paused label + if (state->paused) { + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = 85, .y = 120 }); + } else { + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = -300, .y = 120 }); + } + } + } + okHoldStart = 0; + } + } + + if (state->gameOver) { + if (event.props.keys.down & KeyCancel) { + // Reset game with Cancel button + state->snakeLength = 3; + state->snake[0] = (Point){GRID_WIDTH-3, GRID_HEIGHT/2}; // Head on right side + state->snake[1] = (Point){GRID_WIDTH-2, GRID_HEIGHT/2}; // Body further right + state->snake[2] = (Point){GRID_WIDTH-1, GRID_HEIGHT/2}; // Tail at right edge + state->direction = DIR_LEFT; // Initially moving left + state->nextDirection = DIR_LEFT; + state->score = 0; + state->gameOver = false; + state->paused = false; + spawnFood(state); + snprintf(state->scoreText, sizeof(state->scoreText), "Score: %d", state->score); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + + // Reset visual positions for snake segments (add game area offset) + for (int i = 0; i < state->snakeLength; i++) { + ffx_sceneNode_setPosition(state->snakeBody[i], (FfxPoint){ + .x = 35 + state->snake[i].x * GRID_SIZE, // Add game area offset + .y = 40 + state->snake[i].y * GRID_SIZE // Add game area offset + }); + } + } + return; + } + + if (state->paused) return; + + // Simple directional controls + // Button 3 (North) = Turn Right (clockwise) + if (event.props.keys.down & KeyNorth) { + switch (state->direction) { + case DIR_UP: state->nextDirection = DIR_RIGHT; break; + case DIR_RIGHT: state->nextDirection = DIR_DOWN; break; + case DIR_DOWN: state->nextDirection = DIR_LEFT; break; + case DIR_LEFT: state->nextDirection = DIR_UP; break; + } + } + + // Button 4 (South) = Turn Left (counter-clockwise) + if (event.props.keys.down & KeySouth) { + switch (state->direction) { + case DIR_UP: state->nextDirection = DIR_LEFT; break; + case DIR_LEFT: state->nextDirection = DIR_DOWN; break; + case DIR_DOWN: state->nextDirection = DIR_RIGHT; break; + case DIR_RIGHT: state->nextDirection = DIR_UP; break; + } + } + + // Button 1 (Cancel) = Primary action (rotate direction clockwise) + if (event.props.keys.down & KeyCancel) { + switch (state->direction) { + case DIR_UP: + if (state->direction != DIR_DOWN) state->nextDirection = DIR_RIGHT; + break; + case DIR_RIGHT: + if (state->direction != DIR_LEFT) state->nextDirection = DIR_DOWN; + break; + case DIR_DOWN: + if (state->direction != DIR_UP) state->nextDirection = DIR_LEFT; + break; + case DIR_LEFT: + if (state->direction != DIR_RIGHT) state->nextDirection = DIR_UP; + break; + } + } +} + +static void render(EventPayload event, void *_state) { + SnakeState *state = _state; + + uint32_t now = ticks(); + + // Check for hold-to-exit during gameplay + if (state->southHoldStart > 0 && (now - state->southHoldStart) > 1000) { + panel_pop(); + return; + } + + if (now - state->lastMove > 150) { // Move every 150ms + moveSnake(state); + state->lastMove = now; + } +} + +static int init(FfxScene scene, FfxNode node, void* _state, void* arg) { + SnakeState *state = _state; + + // Clear entire state first for fresh start + memset(state, 0, sizeof(*state)); + state->scene = scene; + + // Create game area background - positioned like Le Space with controls on RIGHT + state->gameArea = ffx_scene_createBox(scene, ffx_size(200, 160)); + ffx_sceneBox_setColor(state->gameArea, COLOR_BLACK); + ffx_sceneGroup_appendChild(node, state->gameArea); + ffx_sceneNode_setPosition(state->gameArea, (FfxPoint){ .x = 35, .y = 40 }); + + // Create score label - positioned on left side for visibility + state->scoreLabel = ffx_scene_createLabel(scene, FfxFontMedium, "Score: 0"); + ffx_sceneGroup_appendChild(node, state->scoreLabel); + ffx_sceneNode_setPosition(state->scoreLabel, (FfxPoint){ .x = 10, .y = 30 }); + + // Create paused label - centered on screen + state->pausedLabel = ffx_scene_createLabel(scene, FfxFontLarge, "PAUSED"); + ffx_sceneGroup_appendChild(node, state->pausedLabel); + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = -300, .y = 120 }); // Hidden initially + + // Create snake body segments + for (int i = 0; i < MAX_SNAKE_LENGTH; i++) { + state->snakeBody[i] = ffx_scene_createBox(scene, ffx_size(GRID_SIZE-1, GRID_SIZE-1)); + ffx_sceneBox_setColor(state->snakeBody[i], ffx_color_rgb(0, 255, 0)); + ffx_sceneGroup_appendChild(node, state->snakeBody[i]); + ffx_sceneNode_setPosition(state->snakeBody[i], (FfxPoint){ .x = -100, .y = -100 }); + } + + // Create food + state->food = ffx_scene_createBox(scene, ffx_size(GRID_SIZE-1, GRID_SIZE-1)); + ffx_sceneBox_setColor(state->food, ffx_color_rgb(255, 0, 0)); + ffx_sceneGroup_appendChild(node, state->food); + + // Initialize game state - start on RIGHT side (90° counter-clockwise layout) + state->snakeLength = 3; + state->snake[0] = (Point){GRID_WIDTH-3, GRID_HEIGHT/2}; // Head on right side + state->snake[1] = (Point){GRID_WIDTH-2, GRID_HEIGHT/2}; // Body further right + state->snake[2] = (Point){GRID_WIDTH-1, GRID_HEIGHT/2}; // Tail at right edge + state->direction = DIR_LEFT; // Initially moving left (toward center) + state->nextDirection = DIR_LEFT; + state->score = 0; + state->gameOver = false; + state->paused = false; + state->lastMove = ticks(); + state->currentKeys = 0; + state->southHoldStart = 0; + state->gameStartTime = 0; + + spawnFood(state); + snprintf(state->scoreText, sizeof(state->scoreText), "Score: %d", state->score); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + + // Set initial visual positions for snake segments (add game area offset) + for (int i = 0; i < state->snakeLength; i++) { + ffx_sceneNode_setPosition(state->snakeBody[i], (FfxPoint){ + .x = 35 + state->snake[i].x * GRID_SIZE, // Add game area offset + .y = 40 + state->snake[i].y * GRID_SIZE // Add game area offset + }); + } + + // Register events (4 buttons: Cancel, Ok, North, South) + panel_onEvent(EventNameKeysChanged | KeyCancel | KeyOk | KeyNorth | KeySouth, keyChanged, state); + panel_onEvent(EventNameRenderScene, render, state); + + return 0; +} + +void pushPanelSnake(void* arg) { + panel_push(init, sizeof(SnakeState), PanelStyleSlideLeft, arg); +} \ No newline at end of file diff --git a/main/panel-snake.h b/main/panel-snake.h new file mode 100644 index 0000000..ea54826 --- /dev/null +++ b/main/panel-snake.h @@ -0,0 +1,14 @@ +#ifndef __PANEL_SNAKE_H__ +#define __PANEL_SNAKE_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void pushPanelSnake(void* arg); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PANEL_SNAKE_H__ */ \ No newline at end of file diff --git a/main/panel-space.c b/main/panel-space.c index b11d44d..ecab35a 100644 --- a/main/panel-space.c +++ b/main/panel-space.c @@ -27,6 +27,7 @@ typedef struct SpaceState { bool running; + bool paused; uint32_t resetTimer; @@ -37,6 +38,7 @@ typedef struct SpaceState { FfxNode alien[ROWS * COLS]; FfxNode bullet[BULLETS]; FfxNode boom[BULLETS]; + FfxNode pausedLabel; uint8_t boomLife[BULLETS]; uint8_t dead[ROWS * COLS]; uint32_t tick; @@ -97,12 +99,15 @@ static void render(EventPayload event, void *_app) { // Either hasn't started yet or game over if (!space->running) { return; } - // Reset button heald down for more than 3s + // Reset button held down for more than 3s (legacy behavior) if (space->keys == KeyOk && ticks() - space->resetTimer > 3000) { panel_pop(); } - // Mode left/right if keys are being held down + // Don't update game logic when paused + if (space->paused) { return; } + + // Move left/right if keys are being held down if (space->keys & KeyNorth) { if (ship.y > 0) { ship.y -= 2; } } else if (space->keys & KeySouth) { @@ -235,12 +240,38 @@ static void keyChanged(EventPayload event, void *_app) { //printf("[space] high-water: %d\n", uxTaskGetStackHighWaterMark(NULL)); - if (keys == KeyOk) { - space->resetTimer = ticks(); + // Handle Ok button hold-to-exit, short press for pause + static uint32_t okHoldStart = 0; + + if (keys & KeyOk) { + if (okHoldStart == 0) { + okHoldStart = ticks(); + } + space->resetTimer = ticks(); // Keep existing reset timer logic } else { + if (okHoldStart > 0) { + uint32_t holdDuration = ticks() - okHoldStart; + if (holdDuration > 1000) { // 1 second hold to exit + panel_pop(); + return; + } else { + // Short press - pause/unpause + space->paused = !space->paused; + // Show/hide paused label + if (space->paused) { + ffx_sceneNode_setPosition(space->pausedLabel, (FfxPoint){ .x = 85, .y = 120 }); + } else { + ffx_sceneNode_setPosition(space->pausedLabel, (FfxPoint){ .x = -300, .y = 120 }); + } + } + okHoldStart = 0; + } space->resetTimer = 0; } + // Don't process other controls when paused + if (space->paused) return; + if (keys & KeyCancel) { for (int i = 0; i < BULLETS; i++) { FfxPoint b = ffx_sceneNode_getPosition(space->bullet[i]); @@ -260,6 +291,7 @@ static int _init(FfxScene scene, FfxNode panel, void* panelState, void* arg) { SpaceState *space = panelState; space->scene = scene; space->panel = panel; + space->paused = false; FfxNode bg = ffx_scene_createImage(scene, image_space, sizeof(image_space)); ffx_sceneGroup_appendChild(panel, bg); @@ -302,6 +334,11 @@ static int _init(FfxScene scene, FfxNode panel, void* panelState, void* arg) { } } + // Create paused label - centered on screen + space->pausedLabel = ffx_scene_createLabel(scene, FfxFontLarge, "PAUSED"); + ffx_sceneGroup_appendChild(panel, space->pausedLabel); + ffx_sceneNode_setPosition(space->pausedLabel, (FfxPoint){ .x = -300, .y = 120 }); // Hidden initially + panel_onEvent(EventNameKeysChanged | KeyNorth | KeySouth | KeyOk | KeyCancel, keyChanged, space); diff --git a/main/panel-tetris.c b/main/panel-tetris.c new file mode 100644 index 0000000..429b377 --- /dev/null +++ b/main/panel-tetris.c @@ -0,0 +1,448 @@ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include +#include +#include +#include + +#include "firefly-scene.h" +#include "firefly-color.h" +#include "panel.h" +#include "panel-tetris.h" +#include "utils.h" + +#define GRID_SIZE 10 +#define BOARD_WIDTH 20 // Rotated: was height, now width +#define BOARD_HEIGHT 10 // Rotated: was width, now height +#define PIECE_SIZE 4 + +typedef enum PieceType { + PIECE_I = 0, + PIECE_O, + PIECE_T, + PIECE_S, + PIECE_Z, + PIECE_J, + PIECE_L, + PIECE_COUNT +} PieceType; + +typedef struct TetrisState { + FfxScene scene; + FfxNode gameArea; + FfxNode board[BOARD_HEIGHT][BOARD_WIDTH]; + FfxNode scoreLabel; + FfxNode linesLabel; + FfxNode pausedLabel; + + // Game board (0 = empty, 1-7 = filled) + uint8_t grid[BOARD_HEIGHT][BOARD_WIDTH]; + + // Current falling piece + PieceType currentPiece; + int pieceX, pieceY; + int pieceRotation; + + // Game state + int score; + int lines; + int level; + bool gameOver; + bool paused; + uint32_t lastDrop; + uint32_t dropSpeed; + char scoreText[32]; + char linesText[32]; + + Keys currentKeys; + uint32_t southHoldStart; + uint32_t gameStartTime; +} TetrisState; + +// Tetris piece definitions (4x4 grids, 4 rotations each) +static const uint8_t pieces[PIECE_COUNT][4][4][4] = { + // I piece + { + {{0,0,0,0}, {1,1,1,1}, {0,0,0,0}, {0,0,0,0}}, + {{0,0,1,0}, {0,0,1,0}, {0,0,1,0}, {0,0,1,0}}, + {{0,0,0,0}, {0,0,0,0}, {1,1,1,1}, {0,0,0,0}}, + {{0,1,0,0}, {0,1,0,0}, {0,1,0,0}, {0,1,0,0}} + }, + // O piece + { + {{0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}} + }, + // T piece + { + {{0,0,0,0}, {0,1,0,0}, {1,1,1,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,1,0,0}, {0,1,1,0}, {0,1,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {1,1,1,0}, {0,1,0,0}}, + {{0,0,0,0}, {0,1,0,0}, {1,1,0,0}, {0,1,0,0}} + }, + // S piece + { + {{0,0,0,0}, {0,1,1,0}, {1,1,0,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,1,0,0}, {0,1,1,0}, {0,0,1,0}}, + {{0,0,0,0}, {0,0,0,0}, {0,1,1,0}, {1,1,0,0}}, + {{0,0,0,0}, {1,0,0,0}, {1,1,0,0}, {0,1,0,0}} + }, + // Z piece + { + {{0,0,0,0}, {1,1,0,0}, {0,1,1,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,0,1,0}, {0,1,1,0}, {0,1,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {1,1,0,0}, {0,1,1,0}}, + {{0,0,0,0}, {0,1,0,0}, {1,1,0,0}, {1,0,0,0}} + }, + // J piece + { + {{0,0,0,0}, {1,0,0,0}, {1,1,1,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,1,1,0}, {0,1,0,0}, {0,1,0,0}}, + {{0,0,0,0}, {0,0,0,0}, {1,1,1,0}, {0,0,1,0}}, + {{0,0,0,0}, {0,1,0,0}, {0,1,0,0}, {1,1,0,0}} + }, + // L piece + { + {{0,0,0,0}, {0,0,1,0}, {1,1,1,0}, {0,0,0,0}}, + {{0,0,0,0}, {0,1,0,0}, {0,1,0,0}, {0,1,1,0}}, + {{0,0,0,0}, {0,0,0,0}, {1,1,1,0}, {1,0,0,0}}, + {{0,0,0,0}, {1,1,0,0}, {0,1,0,0}, {0,1,0,0}} + } +}; + +static color_ffxt pieceColors[PIECE_COUNT] = { + 0x00ff0000, // I - red + 0x00ffff00, // O - yellow + 0x00ff00ff, // T - magenta + 0x0000ff00, // S - green + 0x000000ff, // Z - blue + 0x00ffa500, // J - orange + 0x00800080 // L - purple +}; + +static bool checkCollision(TetrisState *state, int x, int y, int rotation) { + for (int py = 0; py < 4; py++) { + for (int px = 0; px < 4; px++) { + if (pieces[state->currentPiece][rotation][py][px]) { + int nx = x + px; + int ny = y + py; + + // Check bounds (horizontal layout: pieces fall from right to left) + if (nx < 0 || nx >= BOARD_WIDTH || ny < 0 || ny >= BOARD_HEIGHT) { + return true; + } + + // Check collision with placed blocks + if (nx >= 0 && nx < BOARD_WIDTH && ny >= 0 && ny < BOARD_HEIGHT && state->grid[ny][nx]) { + return true; + } + } + } + } + return false; +} + +static void placePiece(TetrisState *state) { + for (int py = 0; py < 4; py++) { + for (int px = 0; px < 4; px++) { + if (pieces[state->currentPiece][state->pieceRotation][py][px]) { + int nx = state->pieceX + px; + int ny = state->pieceY + py; + + if (nx >= 0 && nx < BOARD_WIDTH && ny >= 0 && ny < BOARD_HEIGHT) { + state->grid[ny][nx] = state->currentPiece + 1; + } + } + } + } +} + +static int clearLines(TetrisState *state) { + int linesCleared = 0; + + for (int y = BOARD_HEIGHT - 1; y >= 0; y--) { + bool fullLine = true; + for (int x = 0; x < BOARD_WIDTH; x++) { + if (!state->grid[y][x]) { + fullLine = false; + break; + } + } + + if (fullLine) { + // Move everything down + for (int moveY = y; moveY > 0; moveY--) { + for (int x = 0; x < BOARD_WIDTH; x++) { + state->grid[moveY][x] = state->grid[moveY - 1][x]; + } + } + // Clear top line + for (int x = 0; x < BOARD_WIDTH; x++) { + state->grid[0][x] = 0; + } + + linesCleared++; + y++; // Check the same line again + } + } + + return linesCleared; +} + +static void spawnPiece(TetrisState *state) { + state->currentPiece = rand() % PIECE_COUNT; + state->pieceX = 0; // Spawn from left side + state->pieceY = BOARD_HEIGHT / 2 - 2; // Center vertically + state->pieceRotation = 0; + + if (checkCollision(state, state->pieceX, state->pieceY, state->pieceRotation)) { + state->gameOver = true; + } +} + +static void updateVisuals(TetrisState *state) { + // Clear board visuals + for (int y = 0; y < BOARD_HEIGHT; y++) { + for (int x = 0; x < BOARD_WIDTH; x++) { + if (state->grid[y][x] == 0) { + ffx_sceneBox_setColor(state->board[y][x], COLOR_BLACK); + } else { + ffx_sceneBox_setColor(state->board[y][x], pieceColors[state->grid[y][x] - 1]); + } + } + } + + // Draw current piece + if (!state->gameOver) { + for (int py = 0; py < 4; py++) { + for (int px = 0; px < 4; px++) { + if (pieces[state->currentPiece][state->pieceRotation][py][px]) { + int nx = state->pieceX + px; + int ny = state->pieceY + py; + + if (nx >= 0 && nx < BOARD_WIDTH && ny >= 0 && ny < BOARD_HEIGHT) { + ffx_sceneBox_setColor(state->board[ny][nx], pieceColors[state->currentPiece]); + } + } + } + } + } +} + +static void keyChanged(EventPayload event, void *_state) { + TetrisState *state = _state; + printf("[tetris] keyChanged called! keys=0x%04x\n", event.props.keys.down); + + // Update current keys for continuous movement + state->currentKeys = event.props.keys.down; + + // Standardized controls: + // Button 1 (KeyCancel) = Primary action (rotate piece) + // Button 2 (KeyOk) = Pause/Exit (hold 1s) + // Button 3 (KeyNorth) = Up/Right movement (90° counter-clockwise) + // Button 4 (KeySouth) = Down/Left movement + + static uint32_t okHoldStart = 0; + + // Ignore key events for first 500ms to prevent immediate exits from residual button state + if (state->gameStartTime == 0) { + state->gameStartTime = ticks(); + okHoldStart = 0; // Reset static variable when game restarts + printf("[tetris] Game start time set, ignoring keys for 500ms\n"); + return; + } + if (ticks() - state->gameStartTime < 500) { + printf("[tetris] Ignoring keys due to startup delay\n"); + return; + } + + // Handle Ok button hold-to-exit, short press for pause + if (event.props.keys.down & KeyOk) { + if (okHoldStart == 0) { + okHoldStart = ticks(); + } + } else { + if (okHoldStart > 0) { + uint32_t holdDuration = ticks() - okHoldStart; + if (holdDuration > 1000) { // 1 second hold + panel_pop(); + return; + } else { + // Short press - pause/unpause + if (!state->gameOver) { + state->paused = !state->paused; + // Show/hide paused label + if (state->paused) { + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = 85, .y = 120 }); + } else { + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = -300, .y = 120 }); + } + } + } + okHoldStart = 0; + } + } + + if (state->gameOver) { + if (event.props.keys.down & KeyCancel) { + // Reset game with Cancel button + memset(state->grid, 0, sizeof(state->grid)); + state->score = 0; + state->lines = 0; + state->level = 1; + state->gameOver = false; + state->paused = false; + state->dropSpeed = 1000; + spawnPiece(state); + snprintf(state->scoreText, sizeof(state->scoreText), "Score: %d", state->score); + snprintf(state->linesText, sizeof(state->linesText), "Lines: %d", state->lines); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + ffx_sceneLabel_setText(state->linesLabel, state->linesText); + } + return; + } + + if (state->paused) return; + + // Button 1 (Cancel) = Primary action (rotate piece) + if (event.props.keys.down & KeyCancel) { + int newRotation = (state->pieceRotation + 1) % 4; + if (!checkCollision(state, state->pieceX, state->pieceY, newRotation)) { + state->pieceRotation = newRotation; + } + } + + // Rotated controls: pieces fall left, up/down movement for positioning + // Button 3 (North) = Move up + if (event.props.keys.down & KeyNorth) { + if (!checkCollision(state, state->pieceX, state->pieceY - 1, state->pieceRotation)) { + state->pieceY--; + } + } + + // Button 4 (South) = Move down + if (event.props.keys.down & KeySouth) { + if (!checkCollision(state, state->pieceX, state->pieceY + 1, state->pieceRotation)) { + state->pieceY++; + } + } +} + +static void render(EventPayload event, void *_state) { + TetrisState *state = _state; + + uint32_t now = ticks(); + + // Check for hold-to-exit during gameplay + if (state->southHoldStart > 0 && (now - state->southHoldStart) > 1000) { + panel_pop(); + return; + } + + if (state->paused || state->gameOver) { + updateVisuals(state); + return; + } + + if (now - state->lastDrop > state->dropSpeed) { + if (!checkCollision(state, state->pieceX + 1, state->pieceY, state->pieceRotation)) { + state->pieceX++; // Move right in rotated layout (left to right fall) + } else { + // Piece has landed + placePiece(state); + + int linesCleared = clearLines(state); + if (linesCleared > 0) { + state->lines += linesCleared; + state->score += linesCleared * 100 * state->level; + state->level = state->lines / 10 + 1; + state->dropSpeed = 1000 - (state->level - 1) * 50; + if (state->dropSpeed < 100) state->dropSpeed = 100; + + snprintf(state->scoreText, sizeof(state->scoreText), "Score: %d", state->score); + snprintf(state->linesText, sizeof(state->linesText), "Lines: %d", state->lines); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + ffx_sceneLabel_setText(state->linesLabel, state->linesText); + } + + spawnPiece(state); + } + state->lastDrop = now; + } + + updateVisuals(state); +} + +static int init(FfxScene scene, FfxNode node, void* _state, void* arg) { + TetrisState *state = _state; + + // Clear entire state first for fresh start + memset(state, 0, sizeof(*state)); + state->scene = scene; + + // Create game area background - rotated 90° CCW, horizontal layout + // Pieces fall from right to left, player controls on right + FfxNode gameArea = ffx_scene_createBox(scene, ffx_size(BOARD_WIDTH * GRID_SIZE, BOARD_HEIGHT * GRID_SIZE)); + ffx_sceneBox_setColor(gameArea, COLOR_BLACK); + ffx_sceneGroup_appendChild(node, gameArea); + ffx_sceneNode_setPosition(gameArea, (FfxPoint){ .x = 20, .y = 110 }); // Horizontal layout, bottom of screen + + // Create score labels - positioned on left side for visibility + state->scoreLabel = ffx_scene_createLabel(scene, FfxFontSmall, "Score: 0"); + ffx_sceneGroup_appendChild(node, state->scoreLabel); + ffx_sceneNode_setPosition(state->scoreLabel, (FfxPoint){ .x = 10, .y = 20 }); + + state->linesLabel = ffx_scene_createLabel(scene, FfxFontSmall, "Lines: 0"); + ffx_sceneGroup_appendChild(node, state->linesLabel); + ffx_sceneNode_setPosition(state->linesLabel, (FfxPoint){ .x = 10, .y = 40 }); + + // Create paused label - centered on screen + state->pausedLabel = ffx_scene_createLabel(scene, FfxFontLarge, "PAUSED"); + ffx_sceneGroup_appendChild(node, state->pausedLabel); + ffx_sceneNode_setPosition(state->pausedLabel, (FfxPoint){ .x = -300, .y = 120 }); // Hidden initially + + // Create board blocks - positioned to match rotated game area + for (int y = 0; y < BOARD_HEIGHT; y++) { + for (int x = 0; x < BOARD_WIDTH; x++) { + state->board[y][x] = ffx_scene_createBox(scene, ffx_size(GRID_SIZE-1, GRID_SIZE-1)); + ffx_sceneBox_setColor(state->board[y][x], COLOR_BLACK); + ffx_sceneGroup_appendChild(node, state->board[y][x]); + ffx_sceneNode_setPosition(state->board[y][x], (FfxPoint){ + .x = 20 + x * GRID_SIZE, // Match game area x position (horizontal layout) + .y = 110 + y * GRID_SIZE + }); + } + } + + // Initialize game state values + memset(state->grid, 0, sizeof(state->grid)); + state->score = 0; + state->lines = 0; + state->level = 1; + state->gameOver = false; + state->paused = false; + state->dropSpeed = 1000; + state->lastDrop = ticks(); + state->currentKeys = 0; + state->southHoldStart = 0; + state->gameStartTime = 0; + + spawnPiece(state); + snprintf(state->scoreText, sizeof(state->scoreText), "Score: %d", state->score); + snprintf(state->linesText, sizeof(state->linesText), "Lines: %d", state->lines); + ffx_sceneLabel_setText(state->scoreLabel, state->scoreText); + ffx_sceneLabel_setText(state->linesLabel, state->linesText); + + // Register events (4 buttons: Cancel, Ok, North, South) + panel_onEvent(EventNameKeysChanged | KeyCancel | KeyOk | KeyNorth | KeySouth, keyChanged, state); + panel_onEvent(EventNameRenderScene, render, state); + + return 0; +} + +void pushPanelTetris(void* arg) { + panel_push(init, sizeof(TetrisState), PanelStyleSlideLeft, arg); +} \ No newline at end of file diff --git a/main/panel-tetris.h b/main/panel-tetris.h new file mode 100644 index 0000000..5b57e74 --- /dev/null +++ b/main/panel-tetris.h @@ -0,0 +1,14 @@ +#ifndef __PANEL_TETRIS_H__ +#define __PANEL_TETRIS_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void pushPanelTetris(void* arg); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PANEL_TETRIS_H__ */ \ No newline at end of file diff --git a/main/panel-wallet.c b/main/panel-wallet.c new file mode 100644 index 0000000..3de2ef4 --- /dev/null +++ b/main/panel-wallet.c @@ -0,0 +1,438 @@ +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "nvs_flash.h" +#include "nvs.h" + +#include "firefly-scene.h" +#include "firefly-crypto.h" +#include "firefly-address.h" + +#include "panel.h" +#include "panel-wallet.h" +#include "qr-generator.h" +#include "task-io.h" + +// NVS storage keys +#define NVS_NAMESPACE "wallet" +#define NVS_MASTER_SEED_KEY "master_seed" +#define NVS_ADDRESS_INDEX_KEY "addr_index" + +// BIP32 constants +#define MASTER_SEED_LENGTH 32 +#define BIP32_HARDENED 0x80000000 + +// Task yield helper to prevent watchdog timeouts without managing watchdog directly +static void preventWatchdogTimeout(void) { + // Yield to allow watchdog and other tasks to run + vTaskDelay(pdMS_TO_TICKS(100)); +} + +typedef struct WalletState { + FfxScene scene; + FfxNode nodeAddress1; + FfxNode nodeAddress2; + FfxNode nodeBackground; + FfxNode nodeInstructions; + uint8_t privateKey[FFX_PRIVKEY_LENGTH]; + uint8_t publicKey[FFX_PUBKEY_LENGTH]; + uint8_t address[FFX_ADDRESS_LENGTH]; + char addressStr[FFX_ADDRESS_STRING_LENGTH]; + char addressLine1[25]; + char addressLine2[25]; + QRCode qrCode; + bool showingQR; + bool useFullScreenQR; + + // Persistent wallet data + uint8_t masterSeed[MASTER_SEED_LENGTH]; + uint32_t addressIndex; + bool hasMasterSeed; +} WalletState; + +// Persistent storage functions +static bool loadMasterSeed(WalletState *state) { + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (err != ESP_OK) { + printf("[wallet] Failed to open NVS: %s\n", esp_err_to_name(err)); + return false; + } + + size_t required_size = MASTER_SEED_LENGTH; + err = nvs_get_blob(nvs_handle, NVS_MASTER_SEED_KEY, state->masterSeed, &required_size); + if (err == ESP_OK && required_size == MASTER_SEED_LENGTH) { + // Load address index + err = nvs_get_u32(nvs_handle, NVS_ADDRESS_INDEX_KEY, &state->addressIndex); + if (err != ESP_OK) { + state->addressIndex = 0; // Default to first address + } + state->hasMasterSeed = true; + printf("[wallet] Loaded master seed and address index %lu\n", state->addressIndex); + } else { + state->hasMasterSeed = false; + state->addressIndex = 0; + printf("[wallet] No master seed found in storage\n"); + } + + nvs_close(nvs_handle); + return state->hasMasterSeed; +} + +static bool saveMasterSeed(WalletState *state) { + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (err != ESP_OK) { + printf("[wallet] Failed to open NVS for write: %s\n", esp_err_to_name(err)); + return false; + } + + err = nvs_set_blob(nvs_handle, NVS_MASTER_SEED_KEY, state->masterSeed, MASTER_SEED_LENGTH); + if (err != ESP_OK) { + printf("[wallet] Failed to save master seed: %s\n", esp_err_to_name(err)); + nvs_close(nvs_handle); + return false; + } + + err = nvs_set_u32(nvs_handle, NVS_ADDRESS_INDEX_KEY, state->addressIndex); + if (err != ESP_OK) { + printf("[wallet] Failed to save address index: %s\n", esp_err_to_name(err)); + nvs_close(nvs_handle); + return false; + } + + err = nvs_commit(nvs_handle); + nvs_close(nvs_handle); + + if (err == ESP_OK) { + printf("[wallet] Saved master seed and address index %lu\n", state->addressIndex); + return true; + } else { + printf("[wallet] Failed to commit NVS: %s\n", esp_err_to_name(err)); + return false; + } +} + +static void generateMasterSeed(WalletState *state) { + printf("[wallet] Generating new master seed...\n"); + esp_fill_random(state->masterSeed, MASTER_SEED_LENGTH); + state->addressIndex = 0; + state->hasMasterSeed = true; + + if (saveMasterSeed(state)) { + printf("[wallet] Master seed generated and saved successfully\n"); + } else { + printf("[wallet] Warning: Failed to save master seed to storage\n"); + } +} + +// Deterministic private key derivation (simplified BIP32-like) +// Uses SHA-256 based key stretching to derive child keys from master seed +static void derivePrivateKey(const uint8_t *masterSeed, uint32_t index, uint8_t *privateKey) { + // Create input for key derivation: masterSeed + "eth" + index + uint8_t input[32 + 3 + 4]; // master_seed + "eth" + index + memcpy(input, masterSeed, 32); + memcpy(input + 32, "eth", 3); + input[35] = (index >> 24) & 0xFF; + input[36] = (index >> 16) & 0xFF; + input[37] = (index >> 8) & 0xFF; + input[38] = index & 0xFF; + + // Initialize private key with zeros + memset(privateKey, 0, FFX_PRIVKEY_LENGTH); + + // Deterministic key stretching using multiple hash rounds + // This creates a deterministic private key based on master seed + index + + // First round: Hash the input + uint32_t state[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; // SHA-256 IV + + // Simple hash computation (not full SHA-256, but deterministic) + for (int round = 0; round < 4; round++) { + for (int i = 0; i < sizeof(input); i++) { + uint32_t val = input[i] + round * 0x9e3779b9; // Golden ratio constant + state[i % 8] ^= val; + state[i % 8] = (state[i % 8] << 13) | (state[i % 8] >> 19); // Rotate + state[(i + 1) % 8] += state[i % 8]; + } + + // Mix state values + for (int i = 0; i < 8; i++) { + state[i] ^= state[(i + 1) % 8]; + state[i] = (state[i] << 7) | (state[i] >> 25); + } + } + + // Extract private key bytes from final state + for (int i = 0; i < FFX_PRIVKEY_LENGTH; i++) { + privateKey[i] = (uint8_t)(state[i % 8] >> (8 * (i / 8))); + + // Additional mixing to spread entropy + if ((i % 4) == 3) { + state[i % 8] = (state[i % 8] << 11) | (state[i % 8] >> 21); + state[i % 8] ^= 0xdeadbeef + i; + } + } + + // Ensure the private key is valid for secp256k1 (not zero, less than curve order) + // Simple check: if all bytes are zero, add 1 + bool allZero = true; + for (int i = 0; i < FFX_PRIVKEY_LENGTH; i++) { + if (privateKey[i] != 0) { + allZero = false; + break; + } + } + if (allZero) { + privateKey[FFX_PRIVKEY_LENGTH - 1] = 1; + } + + printf("[wallet] Derived deterministic private key for address index %lu\n", index); + + // Debug: Show first few bytes to verify determinism + printf("[wallet] Private key prefix: %02X%02X%02X%02X...\n", + privateKey[0], privateKey[1], privateKey[2], privateKey[3]); +} + +// Custom render function for full-screen QR display +static void renderQR(uint8_t *buffer, uint32_t y0, void *context) { + WalletState *state = (WalletState *)context; + if (state && state->showingQR && state->useFullScreenQR) { + qr_renderToDisplay(buffer, y0, state->addressStr, &state->qrCode); + } +} + +static void updateAddressDisplay(WalletState *state) { + // Split address into two lines for better readability + // Line 1: 0x + first 20 chars + // Line 2: remaining 22 chars + strncpy(state->addressLine1, state->addressStr, 22); + state->addressLine1[22] = '\0'; + strncpy(state->addressLine2, state->addressStr + 22, 20); + state->addressLine2[20] = '\0'; + + ffx_sceneLabel_setText(state->nodeAddress1, state->addressLine1); + ffx_sceneLabel_setText(state->nodeAddress2, state->addressLine2); +} + +static void generateAddressFromSeed(WalletState *state) { + if (!state->hasMasterSeed) { + printf("[wallet] No master seed available!\n"); + return; + } + + printf("[wallet] Deriving private key...\n"); + // Derive private key from master seed and current index + derivePrivateKey(state->masterSeed, state->addressIndex, state->privateKey); + + // Yield frequently to prevent watchdog timeout + preventWatchdogTimeout(); + + printf("[wallet] Computing public key (this may take several seconds)...\n"); + if (!ffx_pk_computePubkeySecp256k1(state->privateKey, state->publicKey)) { + printf("[wallet] Public key computation failed!\n"); + return; + } + + // Yield after intensive secp256k1 operation + preventWatchdogTimeout(); + + printf("[wallet] Computing address...\n"); + ffx_eth_computeAddress(state->publicKey, state->address); + + // Yield after operation + preventWatchdogTimeout(); + + printf("[wallet] Computing checksum...\n"); + ffx_eth_checksumAddress(state->address, state->addressStr); + + // Final yield to ensure system stability + preventWatchdogTimeout(); + + printf("[wallet] Generated address %lu: %s\n", state->addressIndex, state->addressStr); +} + +static void showQRCode(WalletState *state) { + // Generate QR code for the address + printf("[wallet] Generating QR for: %s\n", state->addressStr); + + printf("[wallet] QR generation starting (this may take a few seconds)...\n"); + bool qrSuccess = qr_generate(&state->qrCode, state->addressStr); + + // Yield to allow other tasks to run after QR generation + preventWatchdogTimeout(); + + printf("[wallet] QR generation result: %s (size=%d)\n", qrSuccess ? "SUCCESS" : "FAILED", state->qrCode.size); + + if (!qrSuccess) { + printf("[wallet] QR generation failed!\n"); + return; + } + + // Switch to full-screen QR mode + state->useFullScreenQR = true; + + // Hide all scene elements to show raw display + ffx_sceneNode_setPosition(state->nodeAddress1, (FfxPoint){ .x = -500, .y = 0 }); + ffx_sceneNode_setPosition(state->nodeAddress2, (FfxPoint){ .x = -500, .y = 0 }); + ffx_sceneNode_setPosition(state->nodeBackground, (FfxPoint){ .x = -500, .y = 0 }); + ffx_sceneNode_setPosition(state->nodeInstructions, (FfxPoint){ .x = -500, .y = 0 }); + + // Set the IO task to use our custom render function + taskIo_setCustomRenderer(renderQR, state); + + printf("[wallet] Full-screen QR display activated\n"); +} + +static void hideQRCode(WalletState *state) { + // Disable full-screen QR mode + state->useFullScreenQR = false; + + // Restore normal scene rendering + taskIo_setCustomRenderer(NULL, NULL); + + // Show scene elements + ffx_sceneNode_setPosition(state->nodeAddress1, (FfxPoint){ .x = 30, .y = 65 }); + ffx_sceneNode_setPosition(state->nodeAddress2, (FfxPoint){ .x = 30, .y = 90 }); + ffx_sceneNode_setPosition(state->nodeBackground, (FfxPoint){ .x = 20, .y = 50 }); + ffx_sceneNode_setPosition(state->nodeInstructions, (FfxPoint){ .x = 30, .y = 140 }); + + printf("[wallet] Returned to normal scene rendering\n"); +} + +static void keyChanged(EventPayload event, void *_state) { + WalletState *state = _state; + + Keys keys = event.props.keys.down; + printf("[wallet] keyChanged: keys=0x%04x, showingQR=%s\n", keys, state->showingQR ? "true" : "false"); + + // Standardized controls: + // Button 1 (KeyCancel) = Primary action (generate new address) + // Button 2 (KeyOk) = Exit + // Button 3 (KeyNorth) = Up/Right action (toggle QR) + // Button 4 (KeySouth) = Down/Left action (unused) + + if (keys & KeyOk) { + // If showing QR, exit QR view and return to address view + if (state->showingQR) { + state->showingQR = false; + hideQRCode(state); + ffx_sceneLabel_setText(state->nodeInstructions, "Key1=New Address Key3=QR Code Key2=Exit"); + return; + } + // Otherwise, exit wallet completely + panel_pop(); + return; + } + + if (keys & KeyCancel) { + // Primary action - generate new address from master seed + printf("[wallet] Starting address generation...\n"); + + // Show loading message + ffx_sceneLabel_setText(state->nodeAddress1, "Generating new"); + ffx_sceneLabel_setText(state->nodeAddress2, "address..."); + ffx_sceneLabel_setText(state->nodeInstructions, "Please wait..."); + + // If no master seed exists, generate one + if (!state->hasMasterSeed) { + generateMasterSeed(state); + } else { + // Increment address index for next address in sequence + state->addressIndex++; + printf("[wallet] Incremented address index to %lu\n", state->addressIndex); + saveMasterSeed(state); // Save the new index + } + + // Generate address from master seed + index + generateAddressFromSeed(state); + + printf("[wallet] Address generation complete!\n"); + + // Update display - force back to address view + state->showingQR = false; + hideQRCode(state); + updateAddressDisplay(state); + ffx_sceneLabel_setText(state->nodeInstructions, "Key1=New Address Key3=QR Code Key2=Exit"); + return; + } + + if (keys & KeyNorth) { + // Up/Right action - toggle QR view + if (!state->showingQR) { + // Show QR code view + state->showingQR = true; + showQRCode(state); + ffx_sceneLabel_setText(state->nodeInstructions, "Key1=New Address Key3=Back Key2=Exit"); + } else { + // Toggle back to address view + state->showingQR = false; + hideQRCode(state); + ffx_sceneLabel_setText(state->nodeInstructions, "Key1=New Address Key3=QR Code Key2=Exit"); + } + return; + } +} + +static int init(FfxScene scene, FfxNode node, void* _state, void* arg) { + WalletState *state = _state; + state->scene = scene; + + // Create title + FfxNode nodeTitle = ffx_scene_createLabel(scene, FfxFontLarge, "ETH Wallet"); + ffx_sceneGroup_appendChild(node, nodeTitle); + ffx_sceneNode_setPosition(nodeTitle, (FfxPoint){ .x = 70, .y = 15 }); + + // Create background for address text (larger and better positioned) + state->nodeBackground = ffx_scene_createBox(scene, ffx_size(200, 120)); + ffx_sceneBox_setColor(state->nodeBackground, ffx_color_rgba(0, 0, 0, 200)); + ffx_sceneGroup_appendChild(node, state->nodeBackground); + ffx_sceneNode_setPosition(state->nodeBackground, (FfxPoint){ .x = 20, .y = 50 }); + + // Create address labels (split into two lines, properly centered) + state->nodeAddress1 = ffx_scene_createLabel(scene, FfxFontMedium, "Press OK to"); + ffx_sceneGroup_appendChild(node, state->nodeAddress1); + ffx_sceneNode_setPosition(state->nodeAddress1, (FfxPoint){ .x = 30, .y = 65 }); + + state->nodeAddress2 = ffx_scene_createLabel(scene, FfxFontMedium, "generate wallet"); + ffx_sceneGroup_appendChild(node, state->nodeAddress2); + ffx_sceneNode_setPosition(state->nodeAddress2, (FfxPoint){ .x = 30, .y = 90 }); + + // Create instructions (positioned within background) + state->nodeInstructions = ffx_scene_createLabel(scene, FfxFontSmall, "Key1=New Address Key3=QR Code Key2=Exit"); + ffx_sceneGroup_appendChild(node, state->nodeInstructions); + ffx_sceneNode_setPosition(state->nodeInstructions, (FfxPoint){ .x = 30, .y = 140 }); + + // QR code will be rendered full-screen when requested + + // Initialize state + state->showingQR = false; + state->useFullScreenQR = false; + + // Load or generate master seed + if (!loadMasterSeed(state)) { + printf("[wallet] No existing wallet found, will generate on first use\n"); + // Don't generate immediately - let user trigger generation + ffx_sceneLabel_setText(state->nodeAddress1, "Press Key1 to"); + ffx_sceneLabel_setText(state->nodeAddress2, "generate wallet"); + } else { + // Generate current address from saved master seed + printf("[wallet] Loading existing wallet (address %lu)\n", state->addressIndex); + generateAddressFromSeed(state); + updateAddressDisplay(state); + printf("[wallet] Loaded address: %s\n", state->addressStr); + } + + // Register for key events (4 buttons: Cancel, Ok, North, South) + panel_onEvent(EventNameKeysChanged | KeyCancel | KeyOk | KeyNorth | KeySouth, keyChanged, state); + + return 0; +} + +void pushPanelWallet(void* arg) { + panel_push(init, sizeof(WalletState), PanelStyleSlideLeft, arg); +} \ No newline at end of file diff --git a/main/panel-wallet.h b/main/panel-wallet.h new file mode 100644 index 0000000..4f069a2 --- /dev/null +++ b/main/panel-wallet.h @@ -0,0 +1,14 @@ +#ifndef __PANEL_WALLET_H__ +#define __PANEL_WALLET_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void pushPanelWallet(void* arg); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PANEL_WALLET_H__ */ \ No newline at end of file diff --git a/main/qr-generator.c b/main/qr-generator.c new file mode 100644 index 0000000..9be6341 --- /dev/null +++ b/main/qr-generator.c @@ -0,0 +1,818 @@ +#include "qr-generator.h" +#include +#include +#include +#include +#include "esp_task_wdt.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +// QR Code galois field operations for error correction +static uint8_t gf_mul(uint8_t a, uint8_t b) { + if (a == 0 || b == 0) return 0; + static const uint8_t gf_log[256] = { + 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, + 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, + 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, + 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, + 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, + 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, + 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, + 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, + 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, + 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, + 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, + 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, + 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, + 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, + 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, + 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 + }; + static const uint8_t gf_exp[256] = { + 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, + 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, + 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, + 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, + 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, + 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, + 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, + 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, + 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, + 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, + 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, + 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, + 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, + 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, + 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, + 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1 + }; + return gf_exp[(gf_log[a] + gf_log[b]) % 255]; +} + +// Calculate penalty score for mask pattern (ISO/IEC 18004 Section 7.8.3) +static int calculateMaskPenalty(QRCode *qr, int mask) { + int penalty = 0; + + // Rule 1: Adjacent modules in row/column with same color (N1 = 3) + // Horizontal runs + for (int row = 0; row < qr->size; row++) { + int count = 1; + bool lastColor = qr_getModule(qr, 0, row); + for (int col = 1; col < qr->size; col++) { + bool color = qr_getModule(qr, col, row); + if (color == lastColor) { + count++; + } else { + if (count >= 5) penalty += (count - 2); + count = 1; + lastColor = color; + } + } + if (count >= 5) penalty += (count - 2); + } + + // Vertical runs + for (int col = 0; col < qr->size; col++) { + int count = 1; + bool lastColor = qr_getModule(qr, col, 0); + for (int row = 1; row < qr->size; row++) { + bool color = qr_getModule(qr, col, row); + if (color == lastColor) { + count++; + } else { + if (count >= 5) penalty += (count - 2); + count = 1; + lastColor = color; + } + } + if (count >= 5) penalty += (count - 2); + } + + // Rule 2: Block of modules in same color (N2 = 3) + for (int row = 0; row < qr->size - 1; row++) { + for (int col = 0; col < qr->size - 1; col++) { + bool color = qr_getModule(qr, col, row); + if (qr_getModule(qr, col + 1, row) == color && + qr_getModule(qr, col, row + 1) == color && + qr_getModule(qr, col + 1, row + 1) == color) { + penalty += 3; + } + } + } + + // Rule 3: 1:1:3:1:1 ratio pattern in horizontal/vertical (N3 = 40) + // Pattern: 10111010000 or 00001011101 + for (int row = 0; row < qr->size; row++) { + for (int col = 0; col <= qr->size - 11; col++) { + // Check for 10111010000 pattern + if (qr_getModule(qr, col, row) && + !qr_getModule(qr, col + 1, row) && + qr_getModule(qr, col + 2, row) && + qr_getModule(qr, col + 3, row) && + qr_getModule(qr, col + 4, row) && + !qr_getModule(qr, col + 5, row) && + qr_getModule(qr, col + 6, row) && + !qr_getModule(qr, col + 7, row) && + !qr_getModule(qr, col + 8, row) && + !qr_getModule(qr, col + 9, row) && + !qr_getModule(qr, col + 10, row)) { + penalty += 40; + } + + // Check for 00001011101 pattern + if (!qr_getModule(qr, col, row) && + !qr_getModule(qr, col + 1, row) && + !qr_getModule(qr, col + 2, row) && + !qr_getModule(qr, col + 3, row) && + qr_getModule(qr, col + 4, row) && + !qr_getModule(qr, col + 5, row) && + qr_getModule(qr, col + 6, row) && + qr_getModule(qr, col + 7, row) && + qr_getModule(qr, col + 8, row) && + !qr_getModule(qr, col + 9, row) && + qr_getModule(qr, col + 10, row)) { + penalty += 40; + } + } + } + + // Vertical patterns + for (int col = 0; col < qr->size; col++) { + for (int row = 0; row <= qr->size - 11; row++) { + // Check for 10111010000 pattern + if (qr_getModule(qr, col, row) && + !qr_getModule(qr, col, row + 1) && + qr_getModule(qr, col, row + 2) && + qr_getModule(qr, col, row + 3) && + qr_getModule(qr, col, row + 4) && + !qr_getModule(qr, col, row + 5) && + qr_getModule(qr, col, row + 6) && + !qr_getModule(qr, col, row + 7) && + !qr_getModule(qr, col, row + 8) && + !qr_getModule(qr, col, row + 9) && + !qr_getModule(qr, col, row + 10)) { + penalty += 40; + } + + // Check for 00001011101 pattern + if (!qr_getModule(qr, col, row) && + !qr_getModule(qr, col, row + 1) && + !qr_getModule(qr, col, row + 2) && + !qr_getModule(qr, col, row + 3) && + qr_getModule(qr, col, row + 4) && + !qr_getModule(qr, col, row + 5) && + qr_getModule(qr, col, row + 6) && + qr_getModule(qr, col, row + 7) && + qr_getModule(qr, col, row + 8) && + !qr_getModule(qr, col, row + 9) && + qr_getModule(qr, col, row + 10)) { + penalty += 40; + } + } + } + + // Rule 4: Proportion of dark modules (N4 = 10) + int darkCount = 0; + int totalModules = qr->size * qr->size; + for (int i = 0; i < totalModules; i++) { + if (qr->modules[i]) darkCount++; + } + + int percentage = (darkCount * 100) / totalModules; + int deviation = (percentage < 50) ? (50 - percentage) : (percentage - 50); + penalty += (deviation / 5) * 10; + + return penalty; +} + +// Apply mask pattern to data modules +static bool applyMask(int mask, int row, int col) { + switch (mask) { + case 0: return (row + col) % 2 == 0; + case 1: return row % 2 == 0; + case 2: return col % 3 == 0; + case 3: return (row + col) % 3 == 0; + case 4: return (row / 2 + col / 3) % 2 == 0; + case 5: return ((row * col) % 2) + ((row * col) % 3) == 0; + case 6: return (((row * col) % 2) + ((row * col) % 3)) % 2 == 0; + case 7: return (((row + col) % 2) + ((row * col) % 3)) % 2 == 0; + default: return false; + } +} + +// Generate Reed-Solomon error correction codewords for QR Version 3-L +static void generateErrorCorrection(uint8_t *data, int dataLen, uint8_t *ecc, int eccLen) { + // Correct generator polynomial for 15 error correction codewords (Version 3-L) + // G(x) = (x-α^0)(x-α^1)...(x-α^14) in GF(256) with primitive α=2 + uint8_t generator[] = { + 1, 15, 54, 120, 64, 204, 45, 164, 236, 69, 84, 82, 229, 57, 109, 168 + }; // Correct coefficients for 15 ECC codewords (Version 3-L) + + // Initialize ECC array + memset(ecc, 0, eccLen); + + // Polynomial division + for (int i = 0; i < dataLen; i++) { + uint8_t feedback = data[i] ^ ecc[0]; + + // Shift ECC array + for (int j = 0; j < eccLen - 1; j++) { + ecc[j] = ecc[j + 1] ^ (feedback ? gf_mul(generator[j], feedback) : 0); + } + ecc[eccLen - 1] = feedback ? gf_mul(generator[eccLen - 1], feedback) : 0; + } +} + +static void setModule(QRCode *qr, int x, int y, bool value) { + if (x >= 0 && x < qr->size && y >= 0 && y < qr->size) { + qr->modules[y * qr->size + x] = value ? 1 : 0; + } +} + +// Generate format info for QR Version 3-L with given mask pattern (ISO/IEC 18004 Section 7.9) +static uint16_t generateFormatInfo(int mask) { + // Error correction level L (01) + mask pattern (3 bits) = 5 bits total + uint16_t data = (0x01 << 3) | (mask & 0x07); + + // BCH(15,5) error correction for format information + // Generator polynomial: x^10 + x^8 + x^5 + x^4 + x^2 + x + 1 = 10100110111 (binary) = 0x537 + uint16_t generator = 0x537; + + // Shift data to high-order bits for polynomial division + uint16_t remainder = data << 10; + + // Perform polynomial division + for (int i = 4; i >= 0; i--) { + if (remainder & (1 << (i + 10))) { + remainder ^= (generator << i); + } + } + + // Combine data and remainder + uint16_t formatInfo = (data << 10) | remainder; + + // Apply masking pattern 101010000010010 (0x5412) for format information + formatInfo ^= 0x5412; + + return formatInfo; +} + +// Choose optimal mask pattern by evaluating penalty scores +static int chooseBestMask(QRCode *qr, const uint8_t *fullStream) { + int bestMask = 0; + int bestPenalty = INT_MAX; + + printf("[qr] Evaluating mask patterns (this may take a few seconds)...\n"); + + for (int mask = 0; mask < 8; mask++) { + // Yield every 2 masks to prevent watchdog timeout + if ((mask % 2) == 0) { + vTaskDelay(pdMS_TO_TICKS(50)); + } + + // Create a copy of the QR code for testing this mask + QRCode testQr; + memcpy(&testQr, qr, sizeof(QRCode)); + + // Apply data with this mask pattern + int byteIndex = 0; + int bitIndex = 0; + bool upward = true; + + // Place data with this mask pattern + for (int colPair = qr->size - 1; colPair > 0; colPair -= 2) { + if (colPair == 6) colPair--; + + for (int i = 0; i < qr->size; i++) { + int row = upward ? (qr->size - 1 - i) : i; + + for (int colOffset = 0; colOffset >= -1; colOffset--) { + int col = colPair + colOffset; + + // Skip function patterns + if ((col < 9 && row < 9) || + (col >= qr->size - 8 && row < 9) || + (col < 9 && row >= qr->size - 8) || + (col == 6 || row == 6) || + (col == 8) || (row == 8)) { + continue; + } + + // Get data bit + bool dataBit = false; + if (byteIndex < QR_TOTAL_CODEWORDS) { + dataBit = (fullStream[byteIndex] >> (7 - bitIndex)) & 1; + bitIndex++; + if (bitIndex >= 8) { + bitIndex = 0; + byteIndex++; + } + } + + // Apply this mask pattern + bool maskBit = applyMask(mask, row, col); + bool finalBit = dataBit ^ maskBit; + + setModule(&testQr, col, row, finalBit); + } + } + upward = !upward; + } + + // Calculate penalty for this mask + int penalty = calculateMaskPenalty(&testQr, mask); + printf("[qr] Mask %d penalty: %d\n", mask, penalty); + + if (penalty < bestPenalty) { + bestPenalty = penalty; + bestMask = mask; + } + + // Yield after each mask evaluation to prevent system stalling + vTaskDelay(pdMS_TO_TICKS(25)); + + // Reset for next iteration + byteIndex = 0; + bitIndex = 0; + upward = true; + } + + printf("[qr] Selected mask %d with penalty %d\n", bestMask, bestPenalty); + return bestMask; +} + +// Large QR code pattern generation for full-screen display +// Creates a QR-like pattern that encodes ETH wallet addresses +// Optimized for 240x240 display with text overlay + +static void drawFinderPattern(QRCode *qr, int x, int y) { + // Draw standard 7x7 finder pattern (ISO/IEC 18004 Section 7.3.2) + for (int dy = -3; dy <= 3; dy++) { + for (int dx = -3; dx <= 3; dx++) { + int px = x + dx; + int py = y + dy; + + // Skip if outside QR bounds + if (px < 0 || px >= qr->size || py < 0 || py >= qr->size) continue; + + // Outer border (7x7) - always black + if (abs(dx) == 3 || abs(dy) == 3) { + setModule(qr, px, py, true); + } + // Inner square (3x3) - always black + else if (abs(dx) <= 1 && abs(dy) <= 1) { + setModule(qr, px, py, true); + } + // White ring between outer and inner + else { + setModule(qr, px, py, false); + } + } + } +} + +static void drawSeparators(QRCode *qr) { + // Add white separators around finder patterns (ISO/IEC 18004 Section 7.3.3) + + // Top-left separator + for (int i = 0; i < 8; i++) { + if (i < qr->size) setModule(qr, i, 7, false); // Horizontal + if (i < qr->size) setModule(qr, 7, i, false); // Vertical + } + + // Top-right separator + for (int i = 0; i < 8; i++) { + if (qr->size - 8 + i >= 0 && qr->size - 8 + i < qr->size) { + setModule(qr, qr->size - 8 + i, 7, false); // Horizontal + } + if (i < qr->size) { + setModule(qr, qr->size - 8, i, false); // Vertical + } + } + + // Bottom-left separator + for (int i = 0; i < 8; i++) { + if (i < qr->size) { + setModule(qr, i, qr->size - 8, false); // Horizontal + } + if (qr->size - 8 + i >= 0 && qr->size - 8 + i < qr->size) { + setModule(qr, 7, qr->size - 8 + i, false); // Vertical + } + } +} + +static void drawTimingPattern(QRCode *qr) { + // Standard timing patterns at row/col 6 (ISO/IEC 18004 Section 7.3.5) + // Alternating black/white modules: black for even positions, white for odd + + // Horizontal timing pattern (row 6) + for (int x = 8; x < qr->size - 8; x++) { + setModule(qr, x, 6, (x % 2) == 0); + } + + // Vertical timing pattern (column 6) + for (int y = 8; y < qr->size - 8; y++) { + setModule(qr, 6, y, (y % 2) == 0); + } +} + +static void drawDarkModule(QRCode *qr) { + // Dark module for Version 3: always at (4*version + 9, 8) = (21, 8) + // (ISO/IEC 18004 Section 7.3.6) + int x = 4 * QR_VERSION + 9; // 4*3 + 9 = 21 + int y = 8; + setModule(qr, x, y, true); // Always black + printf("[qr] Dark module placed at (%d, %d)\n", x, y); +} + +// Check if a module is reserved for function patterns +static bool isReservedModule(QRCode *qr, int col, int row) { + // Finder patterns (7x7 each) + separators (8x8 each) + if ((col < 9 && row < 9) || // Top-left + (col >= qr->size - 8 && row < 9) || // Top-right + (col < 9 && row >= qr->size - 8)) { // Bottom-left + return true; + } + + // Timing patterns (row 6 and column 6) + if (col == 6 || row == 6) { + return true; + } + + // Format information areas + if ((col < 9 && row == 8) || // Horizontal format info + (col == 8 && row < 9) || // Vertical format info (top-left) + (col == 8 && row >= qr->size - 7) || // Vertical format info (bottom) + (col >= qr->size - 8 && row == 8)) { // Horizontal format info (top-right) + return true; + } + + // Dark module at (4*version + 9, 8) = (21, 8) for Version 3 + if (col == 4 * QR_VERSION + 9 && row == 8) { + return true; + } + + return false; +} + +static void encodeData(QRCode *qr, const char *data) { + int dataLen = strlen(data); + printf("[qr] Encoding data: %s (length=%d)\n", data, dataLen); + + // Create QR data stream for Version 3-L (53 data + 15 error correction = 68 total) + uint8_t dataBytes[QR_DATA_CODEWORDS]; + uint8_t eccBytes[QR_ECC_CODEWORDS]; + uint8_t fullStream[QR_TOTAL_CODEWORDS]; + + memset(dataBytes, 0, sizeof(dataBytes)); + + // Create proper QR bit stream using explicit bit placement + // Mode indicator: 0100 (4 bits for Byte mode) + // Character count: 42 = 00101010 (8 bits) + // Total header: 12 bits + + int bitPos = 0; + + // Write mode indicator: 0100 (Byte mode) + for (int i = 3; i >= 0; i--) { + bool bit = (0x4 >> i) & 1; + int byteIndex = bitPos / 8; + int bitIndex = 7 - (bitPos % 8); + + if (bit) { + dataBytes[byteIndex] |= (1 << bitIndex); + } + bitPos++; + } + + // Write character count: 42 = 00101010 (8 bits) + for (int i = 7; i >= 0; i--) { + bool bit = (dataLen >> i) & 1; + int byteIndex = bitPos / 8; + int bitIndex = 7 - (bitPos % 8); + + if (bit) { + dataBytes[byteIndex] |= (1 << bitIndex); + } + bitPos++; + } + + // Data: Add ETH address characters byte by byte + for (int i = 0; i < dataLen; i++) { + uint8_t dataByte = (uint8_t)data[i]; + + // Write each bit of the data byte + for (int j = 7; j >= 0; j--) { + bool bit = (dataByte >> j) & 1; + int byteIndex = bitPos / 8; + int bitIndex = 7 - (bitPos % 8); + + if (byteIndex < QR_DATA_CODEWORDS && bit) { + dataBytes[byteIndex] |= (1 << bitIndex); + } + bitPos++; + } + } + + // Add terminator 0000 (4 bits) if we have space + int currentByteIndex = bitPos / 8; + if (currentByteIndex < QR_DATA_CODEWORDS) { + // Only add terminator if we have at least 4 bits of space + int remainingBits = (QR_DATA_CODEWORDS * 8) - bitPos; + if (remainingBits >= 4) { + bitPos += 4; // Skip 4 bits for terminator (already 0 from memset) + } + } + + // Pad to byte boundary if needed + int bitsToNextByte = (8 - (bitPos % 8)) % 8; + bitPos += bitsToNextByte; + + // Pad remaining space with 11101100 00010001 pattern + int padByteIndex = bitPos / 8; + uint8_t padPattern[] = {0xEC, 0x11}; + int padIndex = 0; + while (padByteIndex < QR_DATA_CODEWORDS) { + dataBytes[padByteIndex++] = padPattern[padIndex % 2]; + padIndex++; + } + + // Generate error correction codewords + generateErrorCorrection(dataBytes, QR_DATA_CODEWORDS, eccBytes, QR_ECC_CODEWORDS); + + // Combine data and error correction + memcpy(fullStream, dataBytes, QR_DATA_CODEWORDS); + memcpy(fullStream + QR_DATA_CODEWORDS, eccBytes, QR_ECC_CODEWORDS); + + printf("[qr] Data encoding: Byte0=0x%02X Byte1=0x%02X Byte2=0x%02X\n", + dataBytes[0], dataBytes[1], dataBytes[2]); + + // Debug: Print first few bytes of address data to verify encoding + printf("[qr] Address bytes: "); + for (int i = 0; i < 8 && i < dataLen; i++) { + printf("0x%02X('%c') ", (uint8_t)data[i], data[i]); + } + printf("...\n"); + + // Debug: Show bit stream structure + printf("[qr] Bit structure: Mode(4)=0x%X Count(8)=0x%02X FirstChar(8)=0x%02X\n", + 0x4, dataLen, (uint8_t)data[0]); + printf("[qr] Address data: %.*s...\n", dataLen > 10 ? 10 : dataLen, data); + printf("[qr] Generated error correction, total stream: %d bytes\n", QR_TOTAL_CODEWORDS); + + // Choose optimal mask pattern by evaluating all 8 patterns + int selectedMask = chooseBestMask(qr, fullStream); + + // Generate format info for selected mask pattern + uint16_t formatInfo = generateFormatInfo(selectedMask); + printf("[qr] Using mask %d, format info: 0x%04x\n", selectedMask, formatInfo); + + // Place format information around finder patterns (ISO/IEC 18004 Section 7.9.1) + // 15-bit format info is placed in specific locations around finder patterns + + // Top-left finder pattern area + // Horizontal strip: bits 0-5 at (8,0) to (8,5), bit 6 at (8,7), bit 7 at (8,8) + for (int i = 0; i < 6; i++) { + bool bit = (formatInfo >> i) & 1; + setModule(qr, 8, i, bit); + } + setModule(qr, 8, 7, (formatInfo >> 6) & 1); // Skip (8,6) - timing pattern + setModule(qr, 8, 8, (formatInfo >> 7) & 1); + + // Vertical strip: bit 8 at (7,8), bits 9-14 at (5,8) down to (0,8) + setModule(qr, 7, 8, (formatInfo >> 8) & 1); + for (int i = 0; i < 6; i++) { + bool bit = (formatInfo >> (14 - i)) & 1; // bits 14,13,12,11,10,9 + setModule(qr, 5 - i, 8, bit); // positions (5,8) to (0,8) + } + + // Top-right finder pattern area + // Horizontal strip: bits 0-7 at (size-1,8) to (size-8,8) + for (int i = 0; i < 8; i++) { + bool bit = (formatInfo >> i) & 1; + setModule(qr, qr->size - 1 - i, 8, bit); + } + + // Bottom-left finder pattern area + // Vertical strip: bits 0-6 at (8,size-7) to (8,size-1) + for (int i = 0; i < 7; i++) { + bool bit = (formatInfo >> i) & 1; + setModule(qr, 8, qr->size - 7 + i, bit); + } + + // Note: Dark module is already placed by drawDarkModule() at (21, 8) for Version 3 + + // Place data in proper QR zigzag pattern starting from bottom-right + int byteIndex = 0; + int bitIndex = 0; + bool upward = true; // Direction flag for zigzag + + // Start from rightmost column pair and work leftward + for (int colPair = qr->size - 1; colPair > 0; colPair -= 2) { + // Skip timing column (column 6) + if (colPair == 6) { + colPair--; + } + + // Process this column pair (right column first, then left) + for (int i = 0; i < qr->size; i++) { + // Calculate row based on direction + int row = upward ? (qr->size - 1 - i) : i; + + // Process right column then left column of the pair + for (int colOffset = 0; colOffset >= -1; colOffset--) { + int col = colPair + colOffset; + + // Skip if this is a function pattern area (ISO/IEC 18004 Section 7.7.3) + if (isReservedModule(qr, col, row)) { + continue; + } + + // Get data bit + bool dataBit = false; + if (byteIndex < QR_TOTAL_CODEWORDS) { + dataBit = (fullStream[byteIndex] >> (7 - bitIndex)) & 1; + bitIndex++; + if (bitIndex >= 8) { + bitIndex = 0; + byteIndex++; + } + } + + // Apply selected mask pattern + bool mask = applyMask(selectedMask, row, col); + bool finalBit = dataBit ^ mask; + + setModule(qr, col, row, finalBit); + } + } + + // Flip direction for next column pair + upward = !upward; + } + + printf("[qr] Placed data with error correction and mask pattern %d, used %d bytes\n", selectedMask, byteIndex); + printf("[qr] QR Version 3 (29x29) generated successfully\n"); + + // Debug: Verify QR structure + printf("[qr] QR Structure: Size=%d, Finder patterns at (3,3), (%d,3), (3,%d)\n", + qr->size, qr->size-4, qr->size-4); + printf("[qr] Timing patterns at row/col 6, Format info around finders\n"); +} + +bool qr_generate(QRCode *qr, const char *data) { + if (!qr || !data) { + return false; + } + + qr->size = QR_SIZE; + + // Clear all modules + memset(qr->modules, 0, QR_MODULES); + + // Draw finder patterns at corners (ISO/IEC 18004 Section 7.3.2) + drawFinderPattern(qr, 3, 3); // Top-left + drawFinderPattern(qr, qr->size - 4, 3); // Top-right + drawFinderPattern(qr, 3, qr->size - 4); // Bottom-left + + // Draw separators around finder patterns + drawSeparators(qr); + + // Draw timing patterns + drawTimingPattern(qr); + + // Draw dark module + drawDarkModule(qr); + + // Encode data + encodeData(qr, data); + + return true; +} + +bool qr_getModule(const QRCode *qr, int x, int y) { + if (!qr || x < 0 || x >= qr->size || y < 0 || y >= qr->size) { + return false; + } + + return qr->modules[y * qr->size + x] == 1; +} + +// Note: Font removed since QR area now has NO text overlay for proper scanning + +void qr_renderToDisplay(uint8_t *buffer, uint32_t y0, const char *ethAddress, const QRCode *qr) { + const uint32_t DISPLAY_WIDTH = 240; + const uint32_t FRAGMENT_HEIGHT = 24; + + // Defensive check to prevent corruption + if (!buffer || !ethAddress || !qr) { + return; + } + + // Clear buffer with white background (RGB565: 0xFFFF) + uint16_t *pixelBuffer = (uint16_t*)buffer; + for (int i = 0; i < DISPLAY_WIDTH * FRAGMENT_HEIGHT; i++) { + pixelBuffer[i] = 0xFFFF; // White background + } + + // **FULL PAGE QR**: Maximum size with proper quiet zone for optimal scanning + const int MODULE_SIZE = 7; // Larger scaling: each QR module = 7x7 pixels + const int QUIET_ZONE = 4 * MODULE_SIZE; // 4 modules = 28 pixels on each side + + // Total QR size with quiet zone: (29 + 8) * 7 = 259 pixels (larger than 240px) + // Scale down slightly to fit: use MODULE_SIZE = 6 + const int ACTUAL_MODULE_SIZE = 6; // 6x6 pixels per module + const int ACTUAL_QUIET_ZONE = 4 * ACTUAL_MODULE_SIZE; // 24 pixels each side + const int QR_WITH_BORDER = (qr->size + 8) * ACTUAL_MODULE_SIZE; // (29+8)*6 = 222 pixels + const int QR_START_X = (DISPLAY_WIDTH - QR_WITH_BORDER) / 2; // Center horizontally + const int QR_START_Y = 10; // Start near top for full page effect + + // Only print address once per QR generation (not per fragment) + static char last_address[50] = ""; + char safe_address[50]; + strncpy(safe_address, ethAddress, 42); + safe_address[42] = '\0'; + + if (strcmp(safe_address, last_address) != 0) { + printf("[qr] Rendering FULL PAGE QR: %dx%d modules, %dx%d pixels, quiet zone=%d\n", + qr->size, qr->size, qr->size * ACTUAL_MODULE_SIZE, qr->size * ACTUAL_MODULE_SIZE, ACTUAL_QUIET_ZONE); + strncpy(last_address, safe_address, sizeof(last_address) - 1); + last_address[sizeof(last_address) - 1] = '\0'; + } + + // **NO TEXT OVERLAY** - QR code area is completely clean for scanning + + // Render QR code with proper quiet zone and integer scaling + for (int y = 0; y < FRAGMENT_HEIGHT; y++) { + int display_y = y0 + y; + + // Check if we're in the QR area (including quiet zone) + if (display_y < QR_START_Y || display_y >= QR_START_Y + QR_WITH_BORDER) { + continue; // Outside QR area - leave white + } + + int qr_area_y = display_y - QR_START_Y; + + for (int x = 0; x < DISPLAY_WIDTH; x++) { + // Check if we're in the QR area horizontally + if (x < QR_START_X || x >= QR_START_X + QR_WITH_BORDER) { + continue; // Outside QR area - leave white + } + + int qr_area_x = x - QR_START_X; + + bool is_black = false; + + // Check if we're in the quiet zone (always white) + if (qr_area_x < ACTUAL_QUIET_ZONE || qr_area_x >= ACTUAL_QUIET_ZONE + (qr->size * ACTUAL_MODULE_SIZE) || + qr_area_y < ACTUAL_QUIET_ZONE || qr_area_y >= ACTUAL_QUIET_ZONE + (qr->size * ACTUAL_MODULE_SIZE)) { + // In quiet zone - leave white + is_black = false; + } else { + // In actual QR code area - determine module + int qr_pixel_x = qr_area_x - ACTUAL_QUIET_ZONE; + int qr_pixel_y = qr_area_y - ACTUAL_QUIET_ZONE; + + int qr_module_x = qr_pixel_x / ACTUAL_MODULE_SIZE; + int qr_module_y = qr_pixel_y / ACTUAL_MODULE_SIZE; + + if (qr_module_x >= 0 && qr_module_x < qr->size && + qr_module_y >= 0 && qr_module_y < qr->size) { + is_black = qr_getModule(qr, qr_module_x, qr_module_y); + } + } + + // **PURE BLACK/WHITE PIXELS** - no anti-aliasing + if (is_black) { + // Mirror X coordinate to fix display orientation + int mirrored_x = DISPLAY_WIDTH - 1 - x; + int pixel_pos = y * DISPLAY_WIDTH + mirrored_x; + if (pixel_pos >= 0 && pixel_pos < DISPLAY_WIDTH * FRAGMENT_HEIGHT) { + pixelBuffer[pixel_pos] = 0x0000; // Pure black in RGB565 + } + } + // White pixels already set in buffer initialization + } + } +} + +// LCD interface for QR rendering - integrates with existing display system +void lcd_fillRect(int x, int y, int w, int h, uint16_t color) { + // This is a simplified implementation for the QR rendering + // In a real system, this would interface with the actual LCD hardware + // For now, we'll use the existing display buffer approach + printf("[lcd] fillRect(%d,%d,%dx%d) color=0x%04X\n", x, y, w, h, color); +} + +void render_qr(const QRCode *qr) { + printf("[qr] Rendering QR code using render_qr API\n"); + for (int y = 0; y < QR_SIZE; y++) { + for (int x = 0; x < QR_SIZE; x++) { + uint16_t color = qr->modules[y * QR_SIZE + x] ? 0x0000 : 0xFFFF; // black or white + lcd_fillRect(QR_OFFSET_X + x * QR_SCALE, + QR_OFFSET_Y + y * QR_SCALE, + QR_SCALE, QR_SCALE, + color); + } + } +} diff --git a/main/qr-generator.h b/main/qr-generator.h new file mode 100644 index 0000000..8b71084 --- /dev/null +++ b/main/qr-generator.h @@ -0,0 +1,38 @@ +#ifndef __QR_GENERATOR_H__ +#define __QR_GENERATOR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define QR_VERSION 3 +#define QR_SIZE 29 // Version 3 = 29x29 +#define QR_MODULES (QR_SIZE * QR_SIZE) +#define QR_DATA_CODEWORDS 53 +#define QR_ECC_CODEWORDS 15 +#define QR_TOTAL_CODEWORDS (QR_DATA_CODEWORDS + QR_ECC_CODEWORDS) + +#define QR_SCALE 6 // 6px per module +#define QR_OFFSET_X ((240 - QR_SIZE * QR_SCALE) / 2) +#define QR_OFFSET_Y ((240 - QR_SIZE * QR_SCALE) / 2) + +// QR Code structure +typedef struct { + uint8_t modules[QR_MODULES]; // 1 = black, 0 = white + int size; +} QRCode; + +// API +bool qr_generate(QRCode *qr, const char *text); +bool qr_getModule(const QRCode *qr, int x, int y); +void qr_renderToDisplay(uint8_t *buffer, uint32_t y0, const char *ethAddress, const QRCode *qr); +void render_qr(const QRCode *qr); // Alternative render API (for testing) + +#ifdef __cplusplus +} +#endif + +#endif // __QR_GENERATOR_H__ diff --git a/main/task-io.c b/main/task-io.c index 790c8e5..dc9fabe 100644 --- a/main/task-io.c +++ b/main/task-io.c @@ -18,6 +18,7 @@ #include "images/image-background.h" #include "images/image-pixie.h" +#include "task-io.h" @@ -256,7 +257,24 @@ static void sceneDispatch(void *setupArg, panel_emitEvent(EventNameCustom | cbid, props); } +// Custom renderer state +static CustomRenderFunc customRenderer = NULL; +static void *customRendererContext = NULL; + +void taskIo_setCustomRenderer(CustomRenderFunc renderFunc, void *context) { + customRenderer = renderFunc; + customRendererContext = context; + printf("[io] Custom renderer %s\n", renderFunc ? "enabled" : "disabled"); +} + static void renderScene(uint8_t *fragment, uint32_t y0, void *context) { + // Use custom renderer if set + if (customRenderer) { + customRenderer(fragment, y0, customRendererContext); + return; + } + + // Default scene rendering FfxScene scene = context; ffx_scene_render(scene, (uint16_t*)fragment, (FfxPoint){ .x = 0, .y = y0 }, diff --git a/main/task-io.h b/main/task-io.h index 8e8aba2..8200782 100644 --- a/main/task-io.h +++ b/main/task-io.h @@ -6,11 +6,20 @@ extern "C" { #endif /* __cplusplus */ #include +#include "firefly-display.h" - +// Custom renderer function type +typedef void (*CustomRenderFunc)(uint8_t *buffer, uint32_t y0, void *context); void taskIoFunc(void* pvParameter); +/** + * Set a custom renderer to override the default scene rendering + * @param renderFunc Custom render function, or NULL to use default scene rendering + * @param context Context to pass to the render function + */ +void taskIo_setCustomRenderer(CustomRenderFunc renderFunc, void *context); + #ifdef __cplusplus diff --git a/main/utils.c b/main/utils.c index ca4a8ef..4e2cce4 100644 --- a/main/utils.c +++ b/main/utils.c @@ -10,7 +10,7 @@ uint32_t ticks() { } void delay(uint32_t duration) { - vTaskDelay((duration + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS); + vTaskDelay(pdMS_TO_TICKS(duration)); } char* taskName() { diff --git a/sdkconfig b/sdkconfig index b809764..8cd0a14 100644 --- a/sdkconfig +++ b/sdkconfig @@ -1,11 +1,12 @@ # # Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Configuration +# Espressif IoT Development Framework (ESP-IDF) 6.0.0 Project Configuration # CONFIG_SOC_ADC_SUPPORTED=y CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y CONFIG_SOC_UART_SUPPORTED=y CONFIG_SOC_GDMA_SUPPORTED=y +CONFIG_SOC_UHCI_SUPPORTED=y CONFIG_SOC_AHB_GDMA_SUPPORTED=y CONFIG_SOC_GPTIMER_SUPPORTED=y CONFIG_SOC_TWAI_SUPPORTED=y @@ -79,6 +80,7 @@ CONFIG_SOC_ADC_SHARED_POWER=y CONFIG_SOC_APB_BACKUP_DMA=y CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y CONFIG_SOC_SHARED_IDCACHE_SUPPORTED=y +CONFIG_SOC_CACHE_FREEZE_SUPPORTED=y CONFIG_SOC_CACHE_MEMORY_IBANK_SIZE=0x4000 CONFIG_SOC_CPU_CORES_NUM=1 CONFIG_SOC_CPU_INTR_NUM=32 @@ -129,7 +131,10 @@ CONFIG_SOC_I2S_SUPPORTS_PLL_F160M=y CONFIG_SOC_I2S_SUPPORTS_PCM=y CONFIG_SOC_I2S_SUPPORTS_PDM=y CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y +CONFIG_SOC_I2S_SUPPORTS_PCM2PDM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y CONFIG_SOC_I2S_PDM_MAX_TX_LINES=2 +CONFIG_SOC_I2S_PDM_MAX_RX_LINES=1 CONFIG_SOC_I2S_SUPPORTS_TDM=y CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y @@ -219,6 +224,7 @@ CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 CONFIG_SOC_MWDT_SUPPORT_XTAL=y CONFIG_SOC_TWAI_CONTROLLER_NUM=1 +CONFIG_SOC_TWAI_MASK_FILTER_NUM=1 CONFIG_SOC_TWAI_CLK_SUPPORT_APB=y CONFIG_SOC_TWAI_BRP_MIN=2 CONFIG_SOC_TWAI_BRP_MAX=16384 @@ -248,6 +254,8 @@ CONFIG_SOC_UART_SUPPORT_RTC_CLK=y CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y +CONFIG_SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE=y +CONFIG_SOC_UHCI_NUM=1 CONFIG_SOC_COEX_HW_PTI=y CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 CONFIG_SOC_MAC_BB_PD_MEM_SIZE=192 @@ -267,6 +275,7 @@ CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y +CONFIG_SOC_CLK_LP_FAST_SUPPORT_XTAL_D2=y CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_FAST_RC=y CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_XTAL=y CONFIG_SOC_WIFI_HW_TSF=y @@ -316,6 +325,17 @@ CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y CONFIG_BOOTLOADER_PROJECT_VER=1 # end of Bootloader manager +# +# Application Rollback +# +# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +# end of Application Rollback + +# +# Recovery Bootloader and Rollback +# +# end of Recovery Bootloader and Rollback + CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x0 CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set @@ -325,6 +345,8 @@ CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y # # Log # +CONFIG_BOOTLOADER_LOG_VERSION_1=y +CONFIG_BOOTLOADER_LOG_VERSION=1 # CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set # CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y @@ -339,6 +361,13 @@ CONFIG_BOOTLOADER_LOG_LEVEL=2 # CONFIG_BOOTLOADER_LOG_COLORS is not set CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y # end of Format + +# +# Settings +# +CONFIG_BOOTLOADER_LOG_MODE_TEXT_EN=y +CONFIG_BOOTLOADER_LOG_MODE_TEXT=y +# end of Settings # end of Log # @@ -354,7 +383,6 @@ CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y CONFIG_BOOTLOADER_WDT_ENABLE=y # CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set CONFIG_BOOTLOADER_WDT_TIME_MS=9000 -# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set # CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set # CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set # CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set @@ -396,6 +424,7 @@ CONFIG_ESP_ROM_GET_CLK_FREQ=y CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y CONFIG_ESP_ROM_HAS_LAYOUT_TABLE=y CONFIG_ESP_ROM_HAS_SPI_FLASH=y +CONFIG_ESP_ROM_HAS_SPI_FLASH_MMAP=y CONFIG_ESP_ROM_HAS_ETS_PRINTF_BUG=y CONFIG_ESP_ROM_HAS_NEWLIB=y CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y @@ -406,6 +435,8 @@ CONFIG_ESP_ROM_HAS_SW_FLOAT=y CONFIG_ESP_ROM_USB_OTG_NUM=-1 CONFIG_ESP_ROM_HAS_VERSION=y CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y +CONFIG_ESP_ROM_CONSOLE_OUTPUT_SECONDARY=y +CONFIG_ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY=y # # Boot ROM Behavior @@ -496,6 +527,7 @@ CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y # CONFIG_COMPILER_DUMP_RTL_FILES is not set CONFIG_COMPILER_RT_LIB_GCCLIB=y CONFIG_COMPILER_RT_LIB_NAME="gcc" +# CONFIG_COMPILER_ORPHAN_SECTIONS_ERROR is not set # CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING is not set CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE=y # CONFIG_COMPILER_STATIC_ANALYZER is not set @@ -520,266 +552,15 @@ CONFIG_APPTRACE_LOCK_ENABLE=y # # Bluetooth # -CONFIG_BT_ENABLED=y -# CONFIG_BT_BLUEDROID_ENABLED is not set -CONFIG_BT_NIMBLE_ENABLED=y -# CONFIG_BT_CONTROLLER_ONLY is not set -CONFIG_BT_CONTROLLER_ENABLED=y -# CONFIG_BT_CONTROLLER_DISABLED is not set - -# -# NimBLE Options -# -CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y -# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set -# CONFIG_BT_NIMBLE_LOG_LEVEL_NONE is not set -# CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR is not set -# CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING is not set -# CONFIG_BT_NIMBLE_LOG_LEVEL_INFO is not set -CONFIG_BT_NIMBLE_LOG_LEVEL_DEBUG=y -CONFIG_BT_NIMBLE_LOG_LEVEL=0 -CONFIG_BT_NIMBLE_MAX_CONNECTIONS=3 -CONFIG_BT_NIMBLE_MAX_BONDS=3 -CONFIG_BT_NIMBLE_MAX_CCCDS=8 -CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=0 -CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 -CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096 -CONFIG_BT_NIMBLE_ROLE_CENTRAL=y -CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y -CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y -CONFIG_BT_NIMBLE_ROLE_OBSERVER=y -CONFIG_BT_NIMBLE_NVS_PERSIST=y -# CONFIG_BT_NIMBLE_SMP_ID_RESET is not set -CONFIG_BT_NIMBLE_SECURITY_ENABLE=y -CONFIG_BT_NIMBLE_SM_LEGACY=y -CONFIG_BT_NIMBLE_SM_SC=y -# CONFIG_BT_NIMBLE_SM_SC_DEBUG_KEYS is not set -CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_ENCRYPTION=y -CONFIG_BT_NIMBLE_SM_LVL=3 -CONFIG_BT_NIMBLE_SM_SC_ONLY=0 -CONFIG_BT_NIMBLE_DEBUG=y -# CONFIG_BT_NIMBLE_DYNAMIC_SERVICE is not set -CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" -CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 -CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 -CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE=0x0240 - -# -# Memory Settings -# -CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 -CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=256 -CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=24 -CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=320 -CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 -CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255 -CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 -CONFIG_BT_NIMBLE_TRANSPORT_EVT_COUNT=30 -CONFIG_BT_NIMBLE_TRANSPORT_EVT_DISCARD_COUNT=8 -CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=1 -# end of Memory Settings - -CONFIG_BT_NIMBLE_GATT_MAX_PROCS=4 -# CONFIG_BT_NIMBLE_HS_FLOW_CTRL is not set -CONFIG_BT_NIMBLE_RPA_TIMEOUT=900 -# CONFIG_BT_NIMBLE_MESH is not set -CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=y -CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=2000 -CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=y -CONFIG_BT_NIMBLE_MAX_CONN_REATTEMPT=3 -CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y -CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=y -CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_CODED_PHY=y -# CONFIG_BT_NIMBLE_EXT_ADV is not set -CONFIG_BT_NIMBLE_EXT_SCAN=y -CONFIG_BT_NIMBLE_ENABLE_PERIODIC_SYNC=y -CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=0 -# CONFIG_BT_NIMBLE_GATT_CACHING is not set -CONFIG_BT_NIMBLE_WHITELIST_SIZE=12 -# CONFIG_BT_NIMBLE_TEST_THROUGHPUT_TEST is not set -# CONFIG_BT_NIMBLE_BLUFI_ENABLE is not set -CONFIG_BT_NIMBLE_USE_ESP_TIMER=y -CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE=y -# CONFIG_BT_NIMBLE_BLE_GATT_BLOB_TRANSFER is not set - -# -# GAP Service -# - -# -# GAP Appearance write permissions -# -# CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE is not set -# end of GAP Appearance write permissions - -CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM=0 -CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ENC=0 -CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHN=0 -CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHR=0 -CONFIG_BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP=y -# CONFIG_BT_NIMBLE_SVC_GAP_CAR_NOT_SUPP is not set -# CONFIG_BT_NIMBLE_SVC_GAP_CAR_SUPP is not set -CONFIG_BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION=-1 - -# -# GAP device name write permissions -# -# CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE is not set -# end of GAP device name write permissions - -CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM=0 -CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_ENC=0 -CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHEN=0 -CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHOR=0 -CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL=0 -CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL=0 -CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SLAVE_LATENCY=0 -CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO=0 -# CONFIG_BT_NIMBLE_SVC_GAP_GATT_SECURITY_LEVEL is not set -# end of GAP Service - -# -# BLE Services -# -CONFIG_BT_NIMBLE_HID_SERVICE=y -CONFIG_BT_NIMBLE_SVC_HID_MAX_INSTANCES=2 -CONFIG_BT_NIMBLE_SVC_HID_MAX_RPTS=3 -# CONFIG_BT_NIMBLE_SVC_BAS_BATTERY_LEVEL_NOTIFY is not set - -# -# Device Information Service -# -# CONFIG_BT_NIMBLE_SVC_DIS_MANUFACTURER_NAME is not set -# CONFIG_BT_NIMBLE_SVC_DIS_SERIAL_NUMBER is not set -# CONFIG_BT_NIMBLE_SVC_DIS_HARDWARE_REVISION is not set -# CONFIG_BT_NIMBLE_SVC_DIS_FIRMWARE_REVISION is not set -# CONFIG_BT_NIMBLE_SVC_DIS_SOFTWARE_REVISION is not set -# CONFIG_BT_NIMBLE_SVC_DIS_SYSTEM_ID is not set -# CONFIG_BT_NIMBLE_SVC_DIS_PNP_ID is not set -# CONFIG_BT_NIMBLE_SVC_DIS_INCLUDED is not set -# end of Device Information Service -# end of BLE Services - -# CONFIG_BT_NIMBLE_VS_SUPPORT is not set -# CONFIG_BT_NIMBLE_ENC_ADV_DATA is not set -# CONFIG_BT_NIMBLE_HIGH_DUTY_ADV_ITVL is not set -# CONFIG_BT_NIMBLE_HOST_ALLOW_CONNECT_WITH_SCAN is not set -# CONFIG_BT_NIMBLE_HOST_QUEUE_CONG_CHECK is not set -# CONFIG_BT_NIMBLE_GATTC_PROC_PREEMPTION_PROTECT is not set - -# -# Host-controller Transport -# -CONFIG_UART_HW_FLOWCTRL_DISABLE=y -# CONFIG_UART_HW_FLOWCTRL_CTS_RTS is not set -CONFIG_BT_NIMBLE_HCI_UART_FLOW_CTRL=0 -CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=19 -CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 -# end of Host-controller Transport - -CONFIG_BT_NIMBLE_EATT_CHAN_NUM=0 -# CONFIG_BT_NIMBLE_SUBRATE is not set -# end of NimBLE Options - -# -# Controller Options -# -CONFIG_BT_CTRL_MODE_EFF=1 -CONFIG_BT_CTRL_BLE_MAX_ACT=6 -CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=6 -CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0 -CONFIG_BT_CTRL_PINNED_TO_CORE=0 -CONFIG_BT_CTRL_HCI_MODE_VHCI=y -# CONFIG_BT_CTRL_HCI_MODE_UART_H4 is not set -CONFIG_BT_CTRL_HCI_TL=1 -CONFIG_BT_CTRL_ADV_DUP_FILT_MAX=30 -CONFIG_BT_BLE_CCA_MODE_NONE=y -# CONFIG_BT_BLE_CCA_MODE_HW is not set -# CONFIG_BT_BLE_CCA_MODE_SW is not set -CONFIG_BT_BLE_CCA_MODE=0 -CONFIG_BT_CTRL_HW_CCA_VAL=20 -CONFIG_BT_CTRL_HW_CCA_EFF=0 -CONFIG_BT_CTRL_CE_LENGTH_TYPE_ORIG=y -# CONFIG_BT_CTRL_CE_LENGTH_TYPE_CE is not set -# CONFIG_BT_CTRL_CE_LENGTH_TYPE_SD is not set -CONFIG_BT_CTRL_CE_LENGTH_TYPE_EFF=0 -CONFIG_BT_CTRL_TX_ANTENNA_INDEX_0=y -# CONFIG_BT_CTRL_TX_ANTENNA_INDEX_1 is not set -CONFIG_BT_CTRL_TX_ANTENNA_INDEX_EFF=0 -CONFIG_BT_CTRL_RX_ANTENNA_INDEX_0=y -# CONFIG_BT_CTRL_RX_ANTENNA_INDEX_1 is not set -CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0 -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N24 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N21 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N18 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N15 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N12 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N9 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N6 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N3 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set -CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set -# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set -CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11 -CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y -CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 -CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 -CONFIG_BT_CTRL_BLE_SCAN_DUPL=y -CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DEVICE=y -# CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA is not set -# CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA_DEVICE is not set -CONFIG_BT_CTRL_SCAN_DUPL_TYPE=0 -CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE=100 -CONFIG_BT_CTRL_DUPL_SCAN_CACHE_REFRESH_PERIOD=0 -# CONFIG_BT_CTRL_BLE_MESH_SCAN_DUPL_EN is not set -# CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EN is not set -CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_DIS=y -CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EFF=0 - -# -# MODEM SLEEP Options -# -# CONFIG_BT_CTRL_MODEM_SLEEP is not set -# end of MODEM SLEEP Options - -CONFIG_BT_CTRL_SLEEP_MODE_EFF=0 -CONFIG_BT_CTRL_SLEEP_CLOCK_EFF=0 -CONFIG_BT_CTRL_HCI_TL_EFF=1 -# CONFIG_BT_CTRL_AGC_RECORRECT_EN is not set -# CONFIG_BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set -# CONFIG_BT_BLE_ADV_DATA_LENGTH_ZERO_AUX is not set -CONFIG_BT_CTRL_CHAN_ASS_EN=y -CONFIG_BT_CTRL_LE_PING_EN=y - -# -# BLE disconnects when Instant Passed (0x28) occurs -# -# CONFIG_BT_CTRL_BLE_LLCP_CONN_UPDATE is not set -# CONFIG_BT_CTRL_BLE_LLCP_CHAN_MAP_UPDATE is not set -# CONFIG_BT_CTRL_BLE_LLCP_PHY_UPDATE is not set -# end of BLE disconnects when Instant Passed (0x28) occurs - -# CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY is not set -# CONFIG_BT_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set -# end of Controller Options +# CONFIG_BT_ENABLED is not set # # Common Options # -CONFIG_BT_ALARM_MAX_NUM=50 # CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set # end of Common Options - -# CONFIG_BT_HCI_LOG_DEBUG_EN is not set # end of Bluetooth -# CONFIG_BLE_MESH is not set - # # Console Library # @@ -791,11 +572,11 @@ CONFIG_BT_ALARM_MAX_NUM=50 # # -# TWAI Configuration +# Legacy TWAI Driver Configurations # -# CONFIG_TWAI_ISR_IN_IRAM is not set +# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y -# end of TWAI Configuration +# end of Legacy TWAI Driver Configurations # # Legacy ADC Driver Configuration @@ -810,13 +591,6 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # end of Legacy ADC Calibration Configuration # end of Legacy ADC Driver Configuration -# -# Legacy Timer Group Driver Configurations -# -# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy Timer Group Driver Configurations - # # Legacy RMT Driver Configurations # @@ -825,11 +599,10 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # end of Legacy RMT Driver Configurations # -# Legacy I2S Driver Configurations +# Legacy I2C Driver Configurations # -# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy I2S Driver Configurations +# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy I2C Driver Configurations # # Legacy SDM Driver Configurations @@ -837,13 +610,6 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set # CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy SDM Driver Configurations - -# -# Legacy Temperature Sensor Driver Configurations -# -# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy Temperature Sensor Driver Configurations # end of Driver Configurations # @@ -858,6 +624,7 @@ CONFIG_EFUSE_MAX_BLK_LEN=256 # ESP-TLS # CONFIG_ESP_TLS_USING_MBEDTLS=y +# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y # CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set # CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set @@ -881,8 +648,7 @@ CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y # Wireless Coexistence # CONFIG_ESP_COEX_ENABLED=y -CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y -# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set +# CONFIG_ESP_COEX_EXTERNAL_COEXIST_ENABLE is not set # CONFIG_ESP_COEX_GPIO_DEBUG is not set # end of Wireless Coexistence @@ -903,7 +669,8 @@ CONFIG_ESP_ERR_TO_NAME_LOOKUP=y # CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y # CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set -# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set +CONFIG_GPTIMER_OBJ_CACHE_SAFE=y # CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set # end of ESP-Driver:GPTimer Configurations @@ -912,7 +679,7 @@ CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y # # CONFIG_I2C_ISR_IRAM_SAFE is not set # CONFIG_I2C_ENABLE_DEBUG_LOG is not set -# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set +CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y # end of ESP-Driver:I2C Configurations # @@ -931,9 +698,15 @@ CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y # # ESP-Driver:RMT Configurations # -# CONFIG_RMT_ISR_IRAM_SAFE is not set +CONFIG_RMT_ENCODER_FUNC_IN_IRAM=y +CONFIG_RMT_TX_ISR_HANDLER_IN_IRAM=y +CONFIG_RMT_RX_ISR_HANDLER_IN_IRAM=y # CONFIG_RMT_RECV_FUNC_IN_IRAM is not set +# CONFIG_RMT_TX_ISR_CACHE_SAFE is not set +# CONFIG_RMT_RX_ISR_CACHE_SAFE is not set +CONFIG_RMT_OBJ_CACHE_SAFE=y # CONFIG_RMT_ENABLE_DEBUG_LOG is not set +# CONFIG_RMT_ISR_IRAM_SAFE is not set # end of ESP-Driver:RMT Configurations # @@ -958,12 +731,28 @@ CONFIG_SPI_SLAVE_ISR_IN_IRAM=y # CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set # end of ESP-Driver:Temperature Sensor Configurations +# +# ESP-Driver:TWAI Configurations +# +# CONFIG_TWAI_ISR_IN_IRAM is not set +# CONFIG_TWAI_ISR_CACHE_SAFE is not set +# CONFIG_TWAI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:TWAI Configurations + # # ESP-Driver:UART Configurations # # CONFIG_UART_ISR_IN_IRAM is not set # end of ESP-Driver:UART Configurations +# +# ESP-Driver:UHCI Configurations +# +# CONFIG_UHCI_ISR_HANDLER_IN_IRAM is not set +# CONFIG_UHCI_ISR_CACHE_SAFE is not set +# CONFIG_UHCI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:UHCI Configurations + # # ESP-Driver:USB Serial/JTAG Configuration # @@ -1042,6 +831,7 @@ CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 # # CONFIG_ESP_HTTPS_SERVER_ENABLE is not set CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 +# CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK is not set # end of ESP HTTPS server # @@ -1114,14 +904,16 @@ CONFIG_RTC_CLK_CAL_CYCLES=1024 # # Peripheral Control # -CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_ESP_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM=y # end of Peripheral Control # # GDMA Configurations # CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y -# CONFIG_GDMA_ISR_IRAM_SAFE is not set +CONFIG_GDMA_ISR_HANDLER_IN_IRAM=y +CONFIG_GDMA_OBJ_DRAM_SAFE=y # CONFIG_GDMA_ENABLE_DEBUG_LOG is not set # end of GDMA Configurations @@ -1132,8 +924,28 @@ CONFIG_XTAL_FREQ_40=y CONFIG_XTAL_FREQ=40 # end of Main XTAL Config +# +# Power Supplier +# + +# +# Brownout Detector +# +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set +CONFIG_ESP_BROWNOUT_DET_LVL=7 +CONFIG_ESP_BROWNOUT_USE_INTR=y +# end of Brownout Detector +# end of Power Supplier + CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y CONFIG_ESP_SPI_BUS_LOCK_FUNCS_IN_IRAM=y +CONFIG_ESP_INTR_IN_IRAM=y # end of Hardware Settings # @@ -1184,13 +996,15 @@ CONFIG_ESP_PHY_RF_CAL_PARTIAL=y CONFIG_ESP_PHY_CALIBRATION_MODE=0 # CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set # CONFIG_ESP_PHY_RECORD_USED_TIME is not set +CONFIG_ESP_PHY_IRAM_OPT=y # end of PHY # # Power Management # +CONFIG_PM_SLEEP_FUNC_IN_IRAM=y # CONFIG_PM_ENABLE is not set -# CONFIG_PM_SLP_IRAM_OPT is not set +CONFIG_PM_SLP_IRAM_OPT=y CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y # end of Power Management @@ -1204,6 +1018,12 @@ CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y # CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set # end of ESP Ringbuf +# +# ESP-ROM +# +CONFIG_ESP_ROM_PRINT_IN_IRAM=y +# end of ESP-ROM + # # ESP Security Specific # @@ -1223,7 +1043,9 @@ CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE=y CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y +CONFIG_ESP_SYSTEM_NO_BACKTRACE=y # CONFIG_ESP_SYSTEM_USE_EH_FRAME is not set +# CONFIG_ESP_SYSTEM_USE_FRAME_POINTER is not set # # Memory protection @@ -1261,21 +1083,6 @@ CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y # CONFIG_ESP_DEBUG_STUBS_ENABLE is not set CONFIG_ESP_DEBUG_OCDAWARE=y CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y - -# -# Brownout Detector -# -CONFIG_ESP_BROWNOUT_DET=y -CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set -CONFIG_ESP_BROWNOUT_DET_LVL=7 -# end of Brownout Detector - -CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y CONFIG_ESP_SYSTEM_HW_STACK_GUARD=y CONFIG_ESP_SYSTEM_HW_PC_RECORD=y # end of ESP System Settings @@ -1289,6 +1096,7 @@ CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 # # ESP Timer (High Resolution Timer) # +CONFIG_ESP_TIMER_IN_IRAM=y # CONFIG_ESP_TIMER_PROFILING is not set CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y @@ -1329,10 +1137,12 @@ CONFIG_ESP_WIFI_IRAM_OPT=y CONFIG_ESP_WIFI_RX_IRAM_OPT=y CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y CONFIG_ESP_WIFI_ENABLE_SAE_PK=y +CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y # CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +# CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT is not set CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 # CONFIG_ESP_WIFI_FTM_ENABLE is not set @@ -1416,6 +1226,14 @@ CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 # CONFIG_FATFS_IMMEDIATE_FSYNC is not set # CONFIG_FATFS_USE_LABEL is not set CONFIG_FATFS_LINK_LOCK=y +# CONFIG_FATFS_USE_DYN_BUFFERS is not set + +# +# File system free space calculation behavior +# +CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT=0 +CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 +# end of File system free space calculation behavior # end of FAT Filesystem support # @@ -1485,6 +1303,7 @@ CONFIG_FREERTOS_DEBUG_OCDAWARE=y CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y CONFIG_FREERTOS_NUMBER_OF_CORES=1 +CONFIG_FREERTOS_IN_IRAM=y # end of FreeRTOS # @@ -1495,8 +1314,7 @@ CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y # CONFIG_HAL_ASSERTION_SILENT is not set # CONFIG_HAL_ASSERTION_ENABLE is not set CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 -CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM=y -CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM=y +CONFIG_HAL_GPIO_USE_ROM_IMPL=y # end of Hardware Abstraction Layer (HAL) and Low Level (LL) # @@ -1517,6 +1335,9 @@ CONFIG_HEAP_TRACING_OFF=y # # Log # +CONFIG_LOG_VERSION_1=y +# CONFIG_LOG_VERSION_2 is not set +CONFIG_LOG_VERSION=1 # # Log Level @@ -1554,6 +1375,15 @@ CONFIG_LOG_COLORS=y CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y # CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set # end of Format + +# +# Settings +# +CONFIG_LOG_MODE_TEXT_EN=y +CONFIG_LOG_MODE_TEXT=y +# end of Settings + +CONFIG_LOG_IN_IRAM=y # end of Log # @@ -1561,7 +1391,6 @@ CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y # CONFIG_LWIP_ENABLE=y CONFIG_LWIP_LOCAL_HOSTNAME="espressif" -# CONFIG_LWIP_NETIF_API is not set CONFIG_LWIP_TCPIP_TASK_PRIO=18 # CONFIG_LWIP_TCPIP_CORE_LOCKING is not set # CONFIG_LWIP_CHECK_THREAD_SAFETY is not set @@ -1585,6 +1414,7 @@ CONFIG_LWIP_IP6_FRAG=y # CONFIG_LWIP_IP4_REASSEMBLY is not set # CONFIG_LWIP_IP6_REASSEMBLY is not set CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 +CONFIG_LWIP_IPV6_DUP_DETECT_ATTEMPTS=1 # CONFIG_LWIP_IP_FORWARD is not set # CONFIG_LWIP_STATS is not set CONFIG_LWIP_ESP_GRATUITOUS_ARP=y @@ -1598,7 +1428,7 @@ CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y # CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y # CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set -CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_DHCP_OPTIONS_LEN=69 CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 @@ -1706,6 +1536,7 @@ CONFIG_LWIP_DNS_MAX_HOST_IP=1 CONFIG_LWIP_DNS_MAX_SERVERS=3 # CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set # CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set +# CONFIG_LWIP_USE_ESP_GETADDRINFO is not set # end of DNS CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 @@ -1726,6 +1557,9 @@ CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y # CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set # CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set +CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_NONE=y +# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_DEFAULT is not set +# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_CUSTOM is not set CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y # CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set # CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set @@ -1793,6 +1627,7 @@ CONFIG_MBEDTLS_HAVE_TIME=y # CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set # CONFIG_MBEDTLS_HAVE_TIME_DATE is not set CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +CONFIG_MBEDTLS_SHA1_C=y CONFIG_MBEDTLS_SHA512_C=y # CONFIG_MBEDTLS_SHA3_C is not set CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y @@ -1874,6 +1709,7 @@ CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y # CONFIG_MBEDTLS_THREADING_C is not set CONFIG_MBEDTLS_ERROR_STRINGS=y CONFIG_MBEDTLS_FS_IO=y +# CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION is not set # end of mbedTLS # @@ -1893,20 +1729,24 @@ CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y # end of ESP-MQTT Configurations # -# Newlib +# LibC # -CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y -# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set -# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set -# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set -# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set -CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y -# CONFIG_NEWLIB_NANO_FORMAT is not set -CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y -# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set -# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set -# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set -# end of Newlib +CONFIG_LIBC_NEWLIB=y +CONFIG_LIBC_MISC_IN_IRAM=y +CONFIG_LIBC_LOCKS_PLACE_IN_IRAM=y +CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_LIBC_STDOUT_LINE_ENDING_LF is not set +# CONFIG_LIBC_STDOUT_LINE_ENDING_CR is not set +# CONFIG_LIBC_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_LIBC_STDIN_LINE_ENDING_LF is not set +CONFIG_LIBC_STDIN_LINE_ENDING_CR=y +# CONFIG_LIBC_NEWLIB_NANO_FORMAT is not set +CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y +# CONFIG_LIBC_TIME_SYSCALL_USE_RTC is not set +# CONFIG_LIBC_TIME_SYSCALL_USE_HRT is not set +# CONFIG_LIBC_TIME_SYSCALL_USE_NONE is not set +# CONFIG_LIBC_OPTIMIZED_MISALIGNED_ACCESS is not set +# end of LibC # # NVS @@ -1976,6 +1816,8 @@ CONFIG_SPI_FLASH_BROWNOUT_RESET=y # CONFIG_SPI_FLASH_AUTO_SUSPEND is not set CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 # CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set +# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set +CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y # end of Optional and Experimental Features (READ DOCS FIRST) # end of Main Flash configuration @@ -2001,13 +1843,13 @@ CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 # # Auto-detect flash chips # -CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_GD_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_BOYA_SUPPORTED=y -CONFIG_SPI_FLASH_VENDOR_TH_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_GD_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_BOYA_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_TH_SUPPORT_ENABLED=y CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y @@ -2078,6 +1920,7 @@ CONFIG_UNITY_ENABLE_DOUBLE=y CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # CONFIG_UNITY_ENABLE_FIXTURE is not set # CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE is not set # end of Unity unit testing library # @@ -2113,11 +1956,6 @@ CONFIG_WL_SECTOR_SIZE=4096 # CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 -# CONFIG_WIFI_PROV_BLE_BONDING is not set -CONFIG_WIFI_PROV_BLE_SEC_CONN=y -# CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION is not set -# CONFIG_WIFI_PROV_BLE_NOTIFY is not set -# CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV is not set CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # end of Wi-Fi Provisioning Manager @@ -2128,6 +1966,7 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # Deprecated options for backward compatibility # CONFIG_APP_BUILD_TYPE_ELF_RAM is not set # CONFIG_NO_BLOBS is not set +# CONFIG_APP_ROLLBACK_ENABLE is not set # CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set # CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y @@ -2135,7 +1974,6 @@ CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y # CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set # CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set CONFIG_LOG_BOOTLOADER_LEVEL=2 -# CONFIG_APP_ROLLBACK_ENABLE is not set # CONFIG_FLASH_ENCRYPTION_ENABLED is not set CONFIG_FLASHMODE_QIO=y # CONFIG_FLASHMODE_QOUT is not set @@ -2161,46 +1999,9 @@ CONFIG_STACK_CHECK=y # CONFIG_ESP32_APPTRACE_DEST_TRAX is not set CONFIG_ESP32_APPTRACE_DEST_NONE=y CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y -# CONFIG_BLUEDROID_ENABLED is not set -CONFIG_NIMBLE_ENABLED=y -CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y -# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set -CONFIG_NIMBLE_MAX_CONNECTIONS=3 -CONFIG_NIMBLE_MAX_BONDS=3 -CONFIG_NIMBLE_MAX_CCCDS=8 -CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0 -CONFIG_NIMBLE_PINNED_TO_CORE=0 -CONFIG_NIMBLE_TASK_STACK_SIZE=4096 -CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096 -CONFIG_NIMBLE_ROLE_CENTRAL=y -CONFIG_NIMBLE_ROLE_PERIPHERAL=y -CONFIG_NIMBLE_ROLE_BROADCASTER=y -CONFIG_NIMBLE_ROLE_OBSERVER=y -CONFIG_NIMBLE_NVS_PERSIST=y -CONFIG_NIMBLE_SM_LEGACY=y -CONFIG_NIMBLE_SM_SC=y -# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set -CONFIG_BT_NIMBLE_SM_SC_LVL=3 -CONFIG_NIMBLE_DEBUG=y -CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" -CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 -CONFIG_NIMBLE_ATT_PREFERRED_MTU=512 -CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0x0240 -CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12 -CONFIG_BT_NIMBLE_ACL_BUF_COUNT=24 -CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255 -CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 -CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30 -CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8 -# CONFIG_NIMBLE_HS_FLOW_CTRL is not set -CONFIG_NIMBLE_RPA_TIMEOUT=900 -# CONFIG_NIMBLE_MESH is not set -CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS=y -# CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_EN is not set -CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y -CONFIG_SW_COEXIST_ENABLE=y -CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y -CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y +# CONFIG_EXTERNAL_COEX_ENABLE is not set +# CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set +# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set # CONFIG_EVENT_LOOP_PROFILING is not set CONFIG_POST_EVENTS_FROM_ISR=y CONFIG_POST_EVENTS_FROM_IRAM_ISR=y @@ -2214,6 +2015,24 @@ CONFIG_ESP32C3_RTC_CLK_SRC_INT_RC=y # CONFIG_ESP32C3_RTC_CLK_SRC_EXT_OSC is not set # CONFIG_ESP32C3_RTC_CLK_SRC_INT_8MD256 is not set CONFIG_ESP32C3_RTC_CLK_CAL_CYCLES=1024 +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_BROWNOUT_DET=y +CONFIG_ESP32C3_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_2 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_ESP32C3_BROWNOUT_DET_LVL=7 +CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y # CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 @@ -2245,22 +2064,6 @@ CONFIG_TASK_WDT_TIMEOUT_S=5 CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y # CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set CONFIG_ESP32C3_DEBUG_OCDAWARE=y -CONFIG_BROWNOUT_DET=y -CONFIG_ESP32C3_BROWNOUT_DET=y -CONFIG_BROWNOUT_DET_LVL_SEL_7=y -CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_7=y -# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set -# CONFIG_ESP32C3_BROWNOUT_DET_LVL_SEL_2 is not set -CONFIG_BROWNOUT_DET_LVL=7 -CONFIG_ESP32C3_BROWNOUT_DET_LVL=7 CONFIG_IPC_TASK_STACK_SIZE=1024 CONFIG_TIMER_TASK_STACK_SIZE=3584 CONFIG_ESP32_WIFI_ENABLED=y @@ -2323,9 +2126,20 @@ CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y # CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF # CONFIG_PPP_SUPPORT is not set +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +# CONFIG_NEWLIB_NANO_FORMAT is not set +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y CONFIG_ESP32C3_TIME_SYSCALL_USE_RTC_SYSTIMER=y +# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set # CONFIG_ESP32C3_TIME_SYSCALL_USE_RTC is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set # CONFIG_ESP32C3_TIME_SYSCALL_USE_SYSTIMER is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set # CONFIG_ESP32C3_TIME_SYSCALL_USE_NONE is not set CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072