├── .github └── FUNDING.yml ├── .gitignore ├── AmbiSense.jpg ├── AmbiSense ├── AmbiSense.ino ├── async_tcp_config.h ├── compressed_html_full.h ├── config.h ├── eeprom_manager.cpp ├── eeprom_manager.h ├── espnow_manager.cpp ├── espnow_manager.h ├── html_templates.h ├── led_controller.cpp ├── led_controller.h ├── memory_analysis.h ├── radar_manager.cpp ├── radar_manager.h ├── resources.h ├── web_interface.cpp ├── web_interface.h ├── wifi_manager.cpp └── wifi_manager.h ├── Ambisense 4.1.png ├── Assets ├── AmbISense-mesh-2.jpg ├── AmbiSense-Mesh.jpg ├── AmbiSense.webp ├── TTP Touch sensor.png └── circuit-diagram.svg ├── LICENSE ├── README.md └── STL Files ├── AmbiSense-LD2410SnapFit-Top.stl └── Ambisense-LD2410SnapFit-Base.stl /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: [Techposts] # Your GitHub username for GitHub Sponsors 3 | patreon: techposts # Replace with your actual Patreon username 4 | # open_collective: # Leave commented out if not using 5 | # ko_fi: # Leave commented out if not using 6 | # tidelift: # Leave commented out if not using 7 | # community_bridge: # Leave commented out if not using 8 | # liberapay: # Leave commented out if not using 9 | # issuehunt: # Leave commented out if not using 10 | # lfx_crowdfunding: # Leave commented out if not using 11 | # polar: # Leave commented out if not using 12 | # buy_me_a_coffee: # Leave commented out if not using 13 | # thanks_dev: # Leave commented out if not using 14 | custom: ['https://www.paypal.me/RaviSingh126638'] # Your PayPal link 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /AmbiSense.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techposts/AmbiSense/c9650d710e8fed2ea3448c6db4e18d522360f701/AmbiSense.jpg -------------------------------------------------------------------------------- /AmbiSense/AmbiSense.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * AmbiSense v5.1.1 - Enhanced Radar-Controlled LED System 3 | * Created by Ravi Singh (TechPosts media) 4 | * Copyright © 2025 TechPosts Media. All rights reserved. 5 | * 6 | * This version includes optimizations: 7 | * - Reduced logging for better performance 8 | * - Standard WebServer instead of AsyncWebServer 9 | * - Optimized memory usage and reliability 10 | * - ESP-NOW master-slave support for L/U-shaped stairs 11 | * 12 | * Hardware: ESP32 + LD2410 Radar Sensor + NeoPixel LED Strip 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "config.h" 22 | #include "eeprom_manager.h" 23 | #include "led_controller.h" 24 | #include "radar_manager.h" 25 | #include "web_interface.h" 26 | #include "wifi_manager.h" 27 | #include "espnow_manager.h" // For ESP-NOW support 28 | 29 | #define WIFI_RESET_BUTTON_PIN 7 30 | #define SHORT_PRESS_TIME 2000 31 | #define LONG_PRESS_TIME 10000 32 | 33 | // Global variables for ESP-NOW 34 | uint8_t deviceRole = DEFAULT_DEVICE_ROLE; 35 | uint8_t masterAddress[6] = {0}; 36 | uint8_t slaveAddresses[MAX_SLAVE_DEVICES][6] = {0}; 37 | uint8_t numSlaveDevices = 0; 38 | 39 | unsigned long buttonPressStart = 0; 40 | bool buttonPreviouslyPressed = false; 41 | bool systemEnabled = true; 42 | 43 | unsigned long previousMillis = 0; 44 | int animationInterval = 30; 45 | 46 | // Flags for handling WiFi reset and device restart 47 | bool shouldResetWifi = false; 48 | bool shouldRestartDevice = false; 49 | unsigned long resetRequestTime = 0; 50 | 51 | int ledSegmentMode = DEFAULT_LED_SEGMENT_MODE; 52 | int ledSegmentStart = DEFAULT_LED_SEGMENT_START; 53 | int ledSegmentLength = DEFAULT_LED_SEGMENT_LENGTH; 54 | int totalSystemLeds = DEFAULT_TOTAL_SYSTEM_LEDS; 55 | 56 | // Function to check for factory reset during boot 57 | void checkForFactoryReset() { 58 | // Check if reset button is held during boot 59 | if (digitalRead(WIFI_RESET_BUTTON_PIN) == LOW) { 60 | Serial.println("Factory reset button detected at startup"); 61 | delay(3000); // Wait to confirm it's held 62 | if (digitalRead(WIFI_RESET_BUTTON_PIN) == LOW) { 63 | Serial.println("FACTORY RESET: Resetting all settings to defaults"); 64 | resetAllSettings(); 65 | // Visual confirmation 66 | for(int i=0; i<3; i++) { 67 | digitalWrite(LED_BUILTIN, HIGH); 68 | delay(200); 69 | digitalWrite(LED_BUILTIN, LOW); 70 | delay(200); 71 | } 72 | } 73 | } 74 | } 75 | 76 | void setup() { 77 | Serial.begin(115200); 78 | delay(100); // Give serial a moment to initialize 79 | 80 | Serial.println("\n\nAmbiSense v4.3.0 - Radar-Controlled LED System"); 81 | Serial.println("Copyright © 2025 TechPosts Media."); 82 | 83 | // Set up reset button 84 | pinMode(WIFI_RESET_BUTTON_PIN, INPUT_PULLUP); 85 | pinMode(LED_BUILTIN, OUTPUT); 86 | 87 | // Check for factory reset before initializing EEPROM 88 | checkForFactoryReset(); 89 | 90 | // Initialize EEPROM with better error handling 91 | if (!EEPROM.begin(EEPROM_SIZE)) { 92 | Serial.println("ERROR: Failed to initialize EEPROM!"); 93 | delay(1000); 94 | // Flash LED to indicate error 95 | for(int i=0; i<5; i++) { 96 | digitalWrite(LED_BUILTIN, HIGH); 97 | delay(100); 98 | digitalWrite(LED_BUILTIN, LOW); 99 | delay(100); 100 | } 101 | // Try again after a delay 102 | delay(500); 103 | if (!EEPROM.begin(EEPROM_SIZE)) { 104 | Serial.println("CRITICAL: EEPROM failed to initialize after retry!"); 105 | // Continue but settings won't persist 106 | } 107 | } 108 | 109 | // Initialize EEPROM before WiFi to ensure settings are loaded 110 | setupEEPROM(); 111 | 112 | // Initialize hardware components 113 | setupLEDs(); 114 | updateLEDConfig(); 115 | 116 | setupRadar(); 117 | 118 | // Initialize WiFi AFTER other hardware is set up 119 | wifiManager.begin(); 120 | 121 | // Initialize ESP-NOW for master-slave communication 122 | setupESPNOW(); 123 | 124 | // Setup Web server LAST after WiFi is connected 125 | setupWebServer(); 126 | 127 | pinMode(WIFI_RESET_BUTTON_PIN, INPUT_PULLUP); 128 | 129 | Serial.println("System ready."); 130 | 131 | // Log current device role 132 | if (deviceRole == DEVICE_ROLE_MASTER) { 133 | Serial.printf("Device configured as MASTER with %d paired slaves.\n", numSlaveDevices); 134 | } else if (deviceRole == DEVICE_ROLE_SLAVE) { 135 | char macStr[18]; 136 | sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", 137 | masterAddress[0], masterAddress[1], masterAddress[2], 138 | masterAddress[3], masterAddress[4], masterAddress[5]); 139 | Serial.printf("Device configured as SLAVE. Master: %s\n", macStr); 140 | } 141 | 142 | // Show min/max distance configuration 143 | Serial.printf("Min Distance: %d cm, Max Distance: %d cm\n", minDistance, maxDistance); 144 | 145 | // Log the CRC for debugging 146 | uint8_t currentCRC = calculateSystemCRC(); 147 | Serial.printf("Settings CRC: 0x%02X\n", currentCRC); 148 | } 149 | 150 | void loop() { 151 | // Handle button input 152 | handleButton(); 153 | 154 | // Process pending actions 155 | handlePendingActions(); 156 | 157 | // Process web server client requests 158 | server.handleClient(); 159 | 160 | if (!systemEnabled) { 161 | for (int i = 0; i < numLeds; i++) { 162 | strip.setPixelColor(i, 0); 163 | } 164 | strip.show(); 165 | wifiManager.process(); 166 | return; 167 | } 168 | 169 | // Process WiFi and radar 170 | wifiManager.process(); 171 | processRadarReading(); 172 | 173 | // Handle animation updates at specified interval 174 | unsigned long currentMillis = millis(); 175 | if (currentMillis - previousMillis >= animationInterval) { 176 | previousMillis = currentMillis; 177 | 178 | // Only use updateLEDs directly for non-standard modes 179 | // Standard mode is handled in processRadarReading() and ESP-NOW logic 180 | if (lightMode != LIGHT_MODE_STANDARD) { 181 | updateLEDs(currentDistance); 182 | } 183 | } 184 | 185 | // Check if WiFi reset button is held for a long time 186 | static bool longPressDetected = false; 187 | static unsigned long buttonPressStartTime = 0; 188 | 189 | if (digitalRead(WIFI_RESET_BUTTON_PIN) == LOW) { 190 | if (buttonPressStartTime == 0) { 191 | buttonPressStartTime = millis(); 192 | } else if (!longPressDetected && millis() - buttonPressStartTime > 5000) { 193 | // 5 second long press 194 | longPressDetected = true; 195 | 196 | // Visual feedback 197 | for (int i = 0; i < 5; i++) { 198 | digitalWrite(LED_BUILTIN, HIGH); 199 | delay(100); 200 | digitalWrite(LED_BUILTIN, LOW); 201 | delay(100); 202 | } 203 | 204 | Serial.println("[WiFi] Factory reset detected: Clearing WiFi settings"); 205 | wifiManager.resetWifiSettings(); 206 | } 207 | } else { 208 | buttonPressStartTime = 0; 209 | longPressDetected = false; 210 | } 211 | } 212 | 213 | void handleButton() { 214 | bool buttonPressed = digitalRead(WIFI_RESET_BUTTON_PIN) == LOW; 215 | 216 | if (buttonPressed && !buttonPreviouslyPressed) { 217 | buttonPressStart = millis(); 218 | buttonPreviouslyPressed = true; 219 | } 220 | 221 | if (!buttonPressed && buttonPreviouslyPressed) { 222 | unsigned long pressDuration = millis() - buttonPressStart; 223 | 224 | if (pressDuration >= LONG_PRESS_TIME) { 225 | Serial.println("Long press → Scheduling WiFi reset"); 226 | shouldResetWifi = true; 227 | resetRequestTime = millis(); 228 | } else if (pressDuration >= 50 && pressDuration <= SHORT_PRESS_TIME) { 229 | systemEnabled = !systemEnabled; 230 | Serial.println(systemEnabled ? "System ON" : "System OFF"); 231 | } 232 | 233 | buttonPreviouslyPressed = false; 234 | } 235 | } 236 | 237 | void handlePendingActions() { 238 | // Handle WiFi reset if requested from button or web interface 239 | if (shouldResetWifi && (millis() - resetRequestTime > 2000)) { 240 | shouldResetWifi = false; 241 | Serial.println("Executing WiFi reset..."); 242 | wifiManager.resetWifiSettings(); 243 | // resetWifiSettings() will restart the device 244 | } 245 | 246 | // Handle device restart if requested from web interface 247 | if (shouldRestartDevice && (millis() - resetRequestTime > 2000)) { 248 | shouldRestartDevice = false; 249 | Serial.println("Restarting device..."); 250 | ESP.restart(); 251 | } 252 | } -------------------------------------------------------------------------------- /AmbiSense/async_tcp_config.h: -------------------------------------------------------------------------------- 1 | #ifndef ASYNC_TCP_CONFIG_H 2 | #define ASYNC_TCP_CONFIG_H 3 | 4 | // Increase task stack size for AsyncTCP 5 | #define CONFIG_ASYNC_TCP_TASK_STACK_SIZE 12288 // Increased from 8192 6 | 7 | // Use higher priority for AsyncTCP task 8 | #define CONFIG_ASYNC_TCP_TASK_PRIORITY 10 // Increased from 5 9 | 10 | // Increase the number of allowed TCP connections 11 | #define CONFIG_LWIP_MAX_ACTIVE_TCP 32 // Increased from 16 12 | 13 | // Extend watchdog timeout for async tasks 14 | #define CONFIG_ASYNC_TCP_USE_WDT 1 15 | #define CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 0 16 | 17 | // Additional performance and stability tweaks 18 | #define CONFIG_LWIP_MAX_SOCKETS 32 19 | #define CONFIG_LWIP_TCPIP_RECVMBOX_SIZE 32 20 | #define CONFIG_LWIP_TCP_RECVMBOX_SIZE 32 21 | 22 | #endif // ASYNC_TCP_CONFIG_H -------------------------------------------------------------------------------- /AmbiSense/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | /* 5 | * AmbiSense v5.1.1 - Enhanced Radar-Controlled LED System 6 | * Created by Ravi Singh (techPosts media) 7 | * Copyright © 2025 TechPosts Media. All rights reserved. 8 | */ 9 | 10 | // Debug logging settings - set to false to reduce serial output 11 | #define ENABLE_DEBUG_LOGGING false 12 | #define ENABLE_MOTION_LOGGING false 13 | #define ENABLE_WIFI_LOGGING true 14 | #define ENABLE_ESPNOW_LOGGING true 15 | 16 | // 🛠 LED & Sensor Config 17 | #define LED_PIN 5 18 | #define DEFAULT_NUM_LEDS 300 19 | #define DEFAULT_BRIGHTNESS 255 20 | #define DEFAULT_MOVING_LIGHT_SPAN 40 21 | #define DEFAULT_MIN_DISTANCE 30 22 | #define DEFAULT_MAX_DISTANCE 300 23 | #define EEPROM_INITIALIZED_MARKER 123 24 | #define EEPROM_SIZE 1024 25 | 26 | // LED Distribution modes 27 | #define LED_SEGMENT_MODE_CONTINUOUS 0 28 | #define LED_SEGMENT_MODE_DISTRIBUTED 1 29 | 30 | // LED limits 31 | #define MAX_SUPPORTED_LEDS 2000 32 | 33 | #define ENABLE_MOCK_DEVICES false 34 | 35 | // Default color (white) 36 | #define DEFAULT_RED 255 37 | #define DEFAULT_GREEN 255 38 | #define DEFAULT_BLUE 255 39 | 40 | // Default settings 41 | #define DEFAULT_CENTER_SHIFT 0 42 | #define DEFAULT_TRAIL_LENGTH 0 43 | #define DEFAULT_DIRECTION_LIGHT false 44 | #define DEFAULT_BACKGROUND_MODE false 45 | #define DEFAULT_LIGHT_MODE 0 46 | 47 | // Default LED distribution values 48 | #define DEFAULT_LED_SEGMENT_MODE LED_SEGMENT_MODE_CONTINUOUS 49 | #define DEFAULT_LED_SEGMENT_START 0 50 | #define DEFAULT_LED_SEGMENT_LENGTH 300 51 | #define DEFAULT_TOTAL_SYSTEM_LEDS 300 52 | 53 | // SPIFFS configuration 54 | #define FORMAT_SPIFFS_IF_FAILED true 55 | 56 | // Light mode constants 57 | #define LIGHT_MODE_STANDARD 0 58 | #define LIGHT_MODE_RAINBOW 1 59 | #define LIGHT_MODE_COLOR_WAVE 2 60 | #define LIGHT_MODE_BREATHING 3 61 | #define LIGHT_MODE_SOLID 4 62 | #define LIGHT_MODE_COMET 5 63 | #define LIGHT_MODE_PULSE 6 64 | #define LIGHT_MODE_FIRE 7 65 | #define LIGHT_MODE_THEATER_CHASE 8 66 | #define LIGHT_MODE_DUAL_SCAN 9 67 | #define LIGHT_MODE_MOTION_PARTICLES 10 68 | 69 | #define DEFAULT_MOTION_SMOOTHING_ENABLED true 70 | #define DEFAULT_POSITION_SMOOTHING_FACTOR 0.2 71 | #define DEFAULT_VELOCITY_SMOOTHING_FACTOR 0.1 72 | #define DEFAULT_PREDICTION_FACTOR 0.5 73 | #define DEFAULT_POSITION_P_GAIN 0.1 74 | #define DEFAULT_POSITION_I_GAIN 0.01 75 | #define DEFAULT_EFFECT_SPEED 50 76 | #define DEFAULT_EFFECT_INTENSITY 50 77 | 78 | // 🎯 LD2410 Config 79 | #define RADAR_SERIAL Serial1 80 | #define RADAR_RX_PIN 3 81 | #define RADAR_TX_PIN 4 82 | 83 | // 📡 Wi-Fi Access Point 84 | #define WIFI_AP_SSID "AmbiSense" 85 | #define WIFI_AP_PASSWORD "12345678" 86 | 87 | // 📡 Web Server 88 | #define WEB_SERVER_PORT 80 89 | 90 | // ESP-NOW Master-Slave configuration 91 | #define DEVICE_ROLE_MASTER 1 92 | #define DEVICE_ROLE_SLAVE 2 93 | #define MAX_SLAVE_DEVICES 5 94 | #define DEFAULT_DEVICE_ROLE DEVICE_ROLE_MASTER 95 | 96 | // ESP-NOW improvements 97 | #define ESPNOW_CHANNEL 1 98 | #define ESPNOW_RETRY_COUNT 3 99 | #define ESPNOW_TIMEOUT_MS 5000 100 | #define AMBISENSE_DEVICE_PREFIX "AmbiSense" 101 | #define CONNECTION_HEALTH_TIMEOUT 10000 102 | 103 | // Sensor priority modes 104 | #define SENSOR_PRIORITY_MOST_RECENT 0 105 | #define SENSOR_PRIORITY_SLAVE_FIRST 1 106 | #define SENSOR_PRIORITY_MASTER_FIRST 2 107 | #define SENSOR_PRIORITY_ZONE_BASED 3 108 | 109 | // Default setting 110 | #define DEFAULT_SENSOR_PRIORITY_MODE SENSOR_PRIORITY_ZONE_BASED 111 | 112 | // Global variables 113 | extern int minDistance, maxDistance, brightness, movingLightSpan, numLeds; 114 | extern int redValue, greenValue, blueValue; 115 | extern int currentDistance; 116 | 117 | // New global variables for enhanced features 118 | extern int centerShift; 119 | extern int trailLength; 120 | extern bool directionLightEnabled; 121 | extern bool backgroundMode; 122 | extern int lightMode; 123 | 124 | // Add motion smoothing global variables 125 | extern bool motionSmoothingEnabled; 126 | extern float positionSmoothingFactor; 127 | extern float velocitySmoothingFactor; 128 | extern float predictionFactor; 129 | extern float positionPGain; 130 | extern float positionIGain; 131 | extern int effectSpeed; 132 | extern int effectIntensity; 133 | 134 | // LED Distribution globals - declared as extern since they're defined in AmbiSense.ino 135 | extern int ledSegmentMode; 136 | extern int ledSegmentStart; 137 | extern int ledSegmentLength; 138 | extern int totalSystemLeds; 139 | 140 | // ESP-NOW global variables 141 | extern uint8_t deviceRole; // Master or slave role 142 | extern uint8_t masterAddress[6]; // MAC address of master device 143 | extern uint8_t slaveAddresses[MAX_SLAVE_DEVICES][6]; // MAC addresses of slave devices 144 | extern uint8_t numSlaveDevices; // Number of paired slave devices 145 | extern uint8_t sensorPriorityMode; // How to prioritize sensors 146 | 147 | // EEPROM memory layout - explicitly define segments to avoid conflicts 148 | // System settings section (0-19) 149 | #define EEPROM_SYSTEM_START 0 150 | #define EEPROM_ADDR_MARKER (EEPROM_SYSTEM_START + 0) 151 | #define EEPROM_ADDR_MIN_DIST_L (EEPROM_SYSTEM_START + 1) 152 | #define EEPROM_ADDR_MIN_DIST_H (EEPROM_SYSTEM_START + 2) 153 | #define EEPROM_ADDR_MAX_DIST_L (EEPROM_SYSTEM_START + 3) 154 | #define EEPROM_ADDR_MAX_DIST_H (EEPROM_SYSTEM_START + 4) 155 | #define EEPROM_ADDR_BRIGHTNESS (EEPROM_SYSTEM_START + 5) 156 | #define EEPROM_ADDR_LIGHT_SPAN (EEPROM_SYSTEM_START + 6) 157 | #define EEPROM_ADDR_RED (EEPROM_SYSTEM_START + 7) 158 | #define EEPROM_ADDR_GREEN (EEPROM_SYSTEM_START + 8) 159 | #define EEPROM_ADDR_BLUE (EEPROM_SYSTEM_START + 9) 160 | #define EEPROM_ADDR_NUM_LEDS_L (EEPROM_SYSTEM_START + 10) 161 | #define EEPROM_ADDR_NUM_LEDS_H (EEPROM_SYSTEM_START + 11) 162 | #define EEPROM_ADDR_CRC (EEPROM_SYSTEM_START + 12) 163 | 164 | // Advanced features section (20-49) 165 | #define EEPROM_ADVANCED_START 20 166 | #define EEPROM_ADDR_CENTER_SHIFT_L (EEPROM_ADVANCED_START + 0) 167 | #define EEPROM_ADDR_CENTER_SHIFT_H (EEPROM_ADVANCED_START + 1) 168 | #define EEPROM_ADDR_TRAIL_LENGTH (EEPROM_ADVANCED_START + 2) 169 | #define EEPROM_ADDR_DIRECTION_LIGHT (EEPROM_ADVANCED_START + 3) 170 | #define EEPROM_ADDR_BACKGROUND_MODE (EEPROM_ADVANCED_START + 4) 171 | #define EEPROM_ADDR_LIGHT_MODE (EEPROM_ADVANCED_START + 5) 172 | #define EEPROM_ADDR_EFFECT_SPEED (EEPROM_ADVANCED_START + 6) 173 | #define EEPROM_ADDR_EFFECT_INTENSITY (EEPROM_ADVANCED_START + 7) 174 | 175 | // Motion smoothing settings in EEPROM (50-69) 176 | #define EEPROM_MOTION_START 50 177 | #define EEPROM_ADDR_MOTION_SMOOTHING (EEPROM_MOTION_START + 0) 178 | #define EEPROM_ADDR_SMOOTHING_FACTOR_L (EEPROM_MOTION_START + 1) 179 | #define EEPROM_ADDR_SMOOTHING_FACTOR_H (EEPROM_MOTION_START + 2) 180 | #define EEPROM_ADDR_VELOCITY_FACTOR_L (EEPROM_MOTION_START + 3) 181 | #define EEPROM_ADDR_VELOCITY_FACTOR_H (EEPROM_MOTION_START + 4) 182 | #define EEPROM_ADDR_PREDICTION_FACTOR_L (EEPROM_MOTION_START + 5) 183 | #define EEPROM_ADDR_PREDICTION_FACTOR_H (EEPROM_MOTION_START + 6) 184 | #define EEPROM_ADDR_POSITION_P_GAIN_L (EEPROM_MOTION_START + 7) 185 | #define EEPROM_ADDR_POSITION_P_GAIN_H (EEPROM_MOTION_START + 8) 186 | #define EEPROM_ADDR_POSITION_I_GAIN_L (EEPROM_MOTION_START + 9) 187 | #define EEPROM_ADDR_POSITION_I_GAIN_H (EEPROM_MOTION_START + 10) 188 | 189 | // ESP-NOW settings section (70-99) 190 | #define EEPROM_ESPNOW_START 70 191 | #define EEPROM_ADDR_DEVICE_ROLE (EEPROM_ESPNOW_START + 0) // 1 byte 192 | #define EEPROM_ADDR_MASTER_MAC (EEPROM_ESPNOW_START + 1) // 6 bytes 193 | #define EEPROM_ADDR_PAIRED_SLAVES (EEPROM_ESPNOW_START + 7) // 1 byte for count + 6*MAX_SLAVES bytes 194 | #define EEPROM_ADDR_SENSOR_PRIORITY_MODE (EEPROM_ESPNOW_START + 50) // 1 byte 195 | 196 | // WiFi credentials section (100-299) 197 | #define EEPROM_WIFI_START 100 198 | #define EEPROM_WIFI_MARKER_ADDR (EEPROM_WIFI_START) // 2 bytes for marker 199 | #define EEPROM_WIFI_SSID_ADDR (EEPROM_WIFI_MARKER_ADDR + 2) 200 | #define EEPROM_WIFI_PASS_ADDR (EEPROM_WIFI_SSID_ADDR + MAX_SSID_LENGTH) 201 | #define EEPROM_DEVICE_NAME_ADDR (EEPROM_WIFI_PASS_ADDR + MAX_PASSWORD_LENGTH) 202 | #define EEPROM_WIFI_MODE_ADDR (EEPROM_DEVICE_NAME_ADDR + MAX_DEVICE_NAME_LENGTH) 203 | #define EEPROM_WIFI_USE_STATIC_IP (EEPROM_WIFI_MODE_ADDR + 1) 204 | #define EEPROM_WIFI_STATIC_IP (EEPROM_WIFI_USE_STATIC_IP + 1) // 4 bytes 205 | 206 | // LED Distribution settings section (300-319) 207 | #define EEPROM_LED_DIST_START 300 208 | #define EEPROM_ADDR_LED_SEGMENT_MODE (EEPROM_LED_DIST_START + 0) 209 | #define EEPROM_ADDR_LED_SEGMENT_START_L (EEPROM_LED_DIST_START + 1) 210 | #define EEPROM_ADDR_LED_SEGMENT_START_H (EEPROM_LED_DIST_START + 2) 211 | #define EEPROM_ADDR_LED_SEGMENT_LENGTH_L (EEPROM_LED_DIST_START + 3) 212 | #define EEPROM_ADDR_LED_SEGMENT_LENGTH_H (EEPROM_LED_DIST_START + 4) 213 | #define EEPROM_ADDR_TOTAL_SYSTEM_LEDS_L (EEPROM_LED_DIST_START + 5) 214 | #define EEPROM_ADDR_TOTAL_SYSTEM_LEDS_H (EEPROM_LED_DIST_START + 6) 215 | 216 | #endif // CONFIG_H -------------------------------------------------------------------------------- /AmbiSense/eeprom_manager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "config.h" 4 | #include "eeprom_manager.h" 5 | #include "led_controller.h" 6 | 7 | // 📌 Global Variables (defined in main file, declared in config.h) 8 | int minDistance, maxDistance, brightness, movingLightSpan, numLeds; 9 | int redValue, greenValue, blueValue; 10 | int currentDistance = 0; 11 | 12 | // New global variables for enhanced features 13 | int centerShift; 14 | int trailLength; 15 | bool directionLightEnabled; 16 | bool backgroundMode; 17 | int lightMode; 18 | int effectSpeed = DEFAULT_EFFECT_SPEED; 19 | int effectIntensity = DEFAULT_EFFECT_INTENSITY; 20 | 21 | // Motion smoothing settings 22 | bool motionSmoothingEnabled = DEFAULT_MOTION_SMOOTHING_ENABLED; 23 | float positionSmoothingFactor = DEFAULT_POSITION_SMOOTHING_FACTOR; 24 | float velocitySmoothingFactor = DEFAULT_VELOCITY_SMOOTHING_FACTOR; 25 | float predictionFactor = DEFAULT_PREDICTION_FACTOR; 26 | float positionPGain = DEFAULT_POSITION_P_GAIN; 27 | float positionIGain = DEFAULT_POSITION_I_GAIN; 28 | 29 | // LED Distribution settings - these are declared extern since they're defined in AmbiSense.ino 30 | // Remove the definitions here to avoid multiple definition errors 31 | 32 | // Magic marker for EEPROM validation 33 | #define EEPROM_MAGIC_MARKER 0xA55A 34 | #define EEPROM_DATA_VERSION 1 35 | 36 | // EEPROM Header structure for validation 37 | struct EEPROMHeader { 38 | uint16_t magicMarker; // Fixed value (0xA55A) to identify valid data 39 | uint8_t dataVersion; // Increment when data structure changes 40 | uint8_t systemSettings; // CRC for system settings section 41 | uint8_t advancedSettings;// CRC for advanced settings section 42 | uint8_t motionSettings; // CRC for motion settings section 43 | uint8_t espnowSettings; // CRC for ESP-NOW settings section 44 | uint8_t ledDistSettings; // CRC for LED distribution settings section 45 | uint8_t reserved[1]; // Reserved for future use 46 | }; 47 | 48 | // Additional validation function for critical settings 49 | void validateCriticalSettings() { 50 | bool settingsChanged = false; 51 | 52 | // Check for invalid min/max distance values (most critical for UI) 53 | if (minDistance < 0 || minDistance > 500 || maxDistance < 50 || maxDistance > 1000 || minDistance >= maxDistance) { 54 | Serial.println("CRITICAL: Invalid min/max distance values detected, restoring defaults"); 55 | minDistance = DEFAULT_MIN_DISTANCE; 56 | maxDistance = DEFAULT_MAX_DISTANCE; 57 | settingsChanged = true; 58 | } 59 | 60 | // Check for invalid LED count 61 | if (numLeds <= 0 || numLeds > 2000) { 62 | Serial.println("CRITICAL: Invalid LED count detected, restoring default"); 63 | numLeds = DEFAULT_NUM_LEDS; 64 | settingsChanged = true; 65 | } 66 | 67 | // Save if changes were made 68 | if (settingsChanged) { 69 | // Only update the affected fields 70 | EEPROM.write(EEPROM_ADDR_MIN_DIST_L, minDistance & 0xFF); 71 | EEPROM.write(EEPROM_ADDR_MIN_DIST_H, (minDistance >> 8) & 0xFF); 72 | EEPROM.write(EEPROM_ADDR_MAX_DIST_L, maxDistance & 0xFF); 73 | EEPROM.write(EEPROM_ADDR_MAX_DIST_H, (maxDistance >> 8) & 0xFF); 74 | EEPROM.write(EEPROM_ADDR_NUM_LEDS_L, numLeds & 0xFF); 75 | EEPROM.write(EEPROM_ADDR_NUM_LEDS_H, (numLeds >> 8) & 0xFF); 76 | 77 | // Recalculate and update CRC 78 | EEPROM.write(EEPROM_ADDR_CRC, calculateSystemCRC()); 79 | 80 | // Commit changes 81 | EEPROM.commit(); 82 | 83 | // Log the corrected values 84 | Serial.printf("Corrected settings - Min: %d, Max: %d, LEDs: %d\n", 85 | minDistance, maxDistance, numLeds); 86 | } 87 | } 88 | 89 | void setupEEPROM() { 90 | Serial.println("Initializing EEPROM..."); 91 | 92 | // Initialize EEPROM with specified size 93 | if (!EEPROM.begin(EEPROM_SIZE)) { 94 | Serial.println("ERROR: Failed to initialize EEPROM!"); 95 | delay(1000); 96 | return; 97 | } 98 | 99 | // Read header 100 | EEPROMHeader header; 101 | EEPROM.get(0, header); 102 | 103 | // Check if header is valid 104 | if (header.magicMarker != EEPROM_MAGIC_MARKER) { 105 | Serial.println("First time initialization or corrupted EEPROM. Setting up with defaults."); 106 | 107 | // Initialize header with magic marker 108 | header.magicMarker = EEPROM_MAGIC_MARKER; 109 | header.dataVersion = EEPROM_DATA_VERSION; 110 | 111 | // Set defaults for all settings 112 | resetSystemSettings(); 113 | resetAdvancedSettings(); 114 | resetMotionSettings(); 115 | resetEspnowSettings(); 116 | resetLEDDistributionSettings(); 117 | 118 | // Save all settings and update CRCs 119 | saveSettings(); 120 | 121 | // Write the header 122 | EEPROM.put(0, header); 123 | EEPROM.commit(); 124 | 125 | Serial.println("EEPROM initialized with defaults"); 126 | return; 127 | } 128 | 129 | // Check individual section CRCs 130 | bool systemValid = (header.systemSettings == calculateSystemCRC()); 131 | bool advancedValid = (header.advancedSettings == calculateAdvancedCRC()); 132 | bool motionValid = (header.motionSettings == calculateMotionCRC()); 133 | bool espnowValid = (header.espnowSettings == calculateEspnowCRC()); 134 | bool ledDistValid = (header.ledDistSettings == calculateLEDDistributionCRC()); 135 | 136 | if (!systemValid || !advancedValid || !motionValid || !espnowValid || !ledDistValid) { 137 | Serial.println("CRC mismatch detected in one or more sections"); 138 | Serial.printf("System: %d, Advanced: %d, Motion: %d, ESPNOW: %d, LEDDist: %d\n", 139 | systemValid, advancedValid, motionValid, espnowValid, ledDistValid); 140 | } 141 | 142 | // Load settings with corruption detection per section 143 | loadSettings(systemValid, advancedValid, motionValid, espnowValid); 144 | 145 | // Load LED distribution settings separately 146 | if (ledDistValid) { 147 | loadLEDDistributionSettings(); 148 | } else { 149 | Serial.println("WARNING: LED distribution settings corrupted! Using defaults."); 150 | resetLEDDistributionSettings(); 151 | } 152 | 153 | // If any section was corrupted, save the restored values 154 | if (!systemValid || !advancedValid || !motionValid || !espnowValid || !ledDistValid) { 155 | Serial.println("Saving restored values for corrupted sections"); 156 | saveSettings(); 157 | } 158 | 159 | // Perform final validation of key values 160 | validateAllSettings(); 161 | 162 | Serial.println("EEPROM initialization complete"); 163 | } 164 | 165 | // MAINTAIN ORIGINAL FUNCTION SIGNATURES FOR WEB INTERFACE COMPATIBILITY 166 | void loadSettings() { 167 | // Load all sections with full validation 168 | loadSettings(true, true, true, true); 169 | loadLEDDistributionSettings(); 170 | } 171 | 172 | void loadSettings(bool systemValid, bool advancedValid, bool motionValid, bool espnowValid) { 173 | // Load system settings 174 | if (systemValid) { 175 | // Read system settings 176 | minDistance = EEPROM.read(EEPROM_ADDR_MIN_DIST_L) | (EEPROM.read(EEPROM_ADDR_MIN_DIST_H) << 8); 177 | maxDistance = EEPROM.read(EEPROM_ADDR_MAX_DIST_L) | (EEPROM.read(EEPROM_ADDR_MAX_DIST_H) << 8); 178 | brightness = EEPROM.read(EEPROM_ADDR_BRIGHTNESS); 179 | movingLightSpan = EEPROM.read(EEPROM_ADDR_LIGHT_SPAN); 180 | redValue = EEPROM.read(EEPROM_ADDR_RED); 181 | greenValue = EEPROM.read(EEPROM_ADDR_GREEN); 182 | blueValue = EEPROM.read(EEPROM_ADDR_BLUE); 183 | numLeds = EEPROM.read(EEPROM_ADDR_NUM_LEDS_L) | (EEPROM.read(EEPROM_ADDR_NUM_LEDS_H) << 8); 184 | 185 | // Debug log actual bytes for troubleshooting 186 | if (ENABLE_DEBUG_LOGGING) { 187 | Serial.println("EEPROM Raw Bytes:"); 188 | Serial.printf("MIN_DIST: L=0x%02X, H=0x%02X => %d\n", 189 | EEPROM.read(EEPROM_ADDR_MIN_DIST_L), 190 | EEPROM.read(EEPROM_ADDR_MIN_DIST_H), 191 | minDistance); 192 | Serial.printf("MAX_DIST: L=0x%02X, H=0x%02X => %d\n", 193 | EEPROM.read(EEPROM_ADDR_MAX_DIST_L), 194 | EEPROM.read(EEPROM_ADDR_MAX_DIST_H), 195 | maxDistance); 196 | } 197 | } else { 198 | Serial.println("WARNING: System settings corrupted! Using defaults."); 199 | resetSystemSettings(); 200 | } 201 | 202 | // Load advanced settings 203 | if (advancedValid) { 204 | // Read advanced features 205 | int16_t loadedCenterShift; 206 | EEPROM.get(EEPROM_ADDR_CENTER_SHIFT_L, loadedCenterShift); 207 | centerShift = loadedCenterShift; 208 | 209 | trailLength = EEPROM.read(EEPROM_ADDR_TRAIL_LENGTH); 210 | directionLightEnabled = EEPROM.read(EEPROM_ADDR_DIRECTION_LIGHT) == 1; 211 | backgroundMode = EEPROM.read(EEPROM_ADDR_BACKGROUND_MODE) == 1; 212 | lightMode = EEPROM.read(EEPROM_ADDR_LIGHT_MODE); 213 | effectSpeed = EEPROM.read(EEPROM_ADDR_EFFECT_SPEED); 214 | effectIntensity = EEPROM.read(EEPROM_ADDR_EFFECT_INTENSITY); 215 | } else { 216 | Serial.println("WARNING: Advanced settings corrupted! Using defaults."); 217 | resetAdvancedSettings(); 218 | } 219 | 220 | // Load motion settings 221 | if (motionValid) { 222 | // Read motion smoothing settings 223 | motionSmoothingEnabled = EEPROM.read(EEPROM_ADDR_MOTION_SMOOTHING) == 1; 224 | 225 | int positionSmoothingRaw = EEPROM.read(EEPROM_ADDR_SMOOTHING_FACTOR_L) | 226 | (EEPROM.read(EEPROM_ADDR_SMOOTHING_FACTOR_H) << 8); 227 | positionSmoothingFactor = positionSmoothingRaw / 100.0; 228 | 229 | int velocitySmoothingRaw = EEPROM.read(EEPROM_ADDR_VELOCITY_FACTOR_L) | 230 | (EEPROM.read(EEPROM_ADDR_VELOCITY_FACTOR_H) << 8); 231 | velocitySmoothingFactor = velocitySmoothingRaw / 100.0; 232 | 233 | int predictionRaw = EEPROM.read(EEPROM_ADDR_PREDICTION_FACTOR_L) | 234 | (EEPROM.read(EEPROM_ADDR_PREDICTION_FACTOR_H) << 8); 235 | predictionFactor = predictionRaw / 100.0; 236 | 237 | int pGainRaw = EEPROM.read(EEPROM_ADDR_POSITION_P_GAIN_L) | 238 | (EEPROM.read(EEPROM_ADDR_POSITION_P_GAIN_H) << 8); 239 | positionPGain = pGainRaw / 1000.0; 240 | 241 | int iGainRaw = EEPROM.read(EEPROM_ADDR_POSITION_I_GAIN_L) | 242 | (EEPROM.read(EEPROM_ADDR_POSITION_I_GAIN_H) << 8); 243 | positionIGain = iGainRaw / 1000.0; 244 | } else { 245 | Serial.println("WARNING: Motion settings corrupted! Using defaults."); 246 | resetMotionSettings(); 247 | } 248 | 249 | // Load ESP-NOW settings 250 | if (espnowValid) { 251 | // Read ESP-NOW settings 252 | deviceRole = EEPROM.read(EEPROM_ADDR_DEVICE_ROLE); 253 | if (deviceRole != DEVICE_ROLE_MASTER && deviceRole != DEVICE_ROLE_SLAVE) { 254 | deviceRole = DEFAULT_DEVICE_ROLE; 255 | } 256 | 257 | // Load sensor priority mode 258 | sensorPriorityMode = EEPROM.read(EEPROM_ADDR_SENSOR_PRIORITY_MODE); 259 | if (sensorPriorityMode > SENSOR_PRIORITY_ZONE_BASED) { 260 | sensorPriorityMode = DEFAULT_SENSOR_PRIORITY_MODE; 261 | } 262 | 263 | // Master address 264 | for (int i = 0; i < 6; i++) { 265 | masterAddress[i] = EEPROM.read(EEPROM_ADDR_MASTER_MAC + i); 266 | } 267 | 268 | // Paired slaves 269 | numSlaveDevices = EEPROM.read(EEPROM_ADDR_PAIRED_SLAVES); 270 | numSlaveDevices = constrain(numSlaveDevices, 0, MAX_SLAVE_DEVICES); 271 | 272 | for (int s = 0; s < numSlaveDevices; s++) { 273 | for (int i = 0; i < 6; i++) { 274 | slaveAddresses[s][i] = EEPROM.read(EEPROM_ADDR_PAIRED_SLAVES + 1 + (s * 6) + i); 275 | } 276 | } 277 | } else { 278 | Serial.println("WARNING: ESP-NOW settings corrupted! Using defaults."); 279 | resetEspnowSettings(); 280 | } 281 | } 282 | 283 | // MOVED OUTSIDE: All the functions that were incorrectly nested inside loadSettings() 284 | void loadLEDDistributionSettings() { 285 | ledSegmentMode = EEPROM.read(EEPROM_ADDR_LED_SEGMENT_MODE); 286 | if (ledSegmentMode != LED_SEGMENT_MODE_CONTINUOUS && 287 | ledSegmentMode != LED_SEGMENT_MODE_DISTRIBUTED) { 288 | ledSegmentMode = LED_SEGMENT_MODE_CONTINUOUS; 289 | } 290 | 291 | ledSegmentStart = EEPROM.read(EEPROM_ADDR_LED_SEGMENT_START_L) | 292 | (EEPROM.read(EEPROM_ADDR_LED_SEGMENT_START_H) << 8); 293 | 294 | ledSegmentLength = EEPROM.read(EEPROM_ADDR_LED_SEGMENT_LENGTH_L) | 295 | (EEPROM.read(EEPROM_ADDR_LED_SEGMENT_LENGTH_H) << 8); 296 | 297 | totalSystemLeds = EEPROM.read(EEPROM_ADDR_TOTAL_SYSTEM_LEDS_L) | 298 | (EEPROM.read(EEPROM_ADDR_TOTAL_SYSTEM_LEDS_H) << 8); 299 | 300 | // Validate loaded values 301 | validateLEDDistributionSettings(); 302 | 303 | Serial.printf("Loaded LED distribution - Mode: %d, Start: %d, Length: %d, Total: %d\n", 304 | ledSegmentMode, ledSegmentStart, ledSegmentLength, totalSystemLeds); 305 | } 306 | 307 | void validateLEDDistributionSettings() { 308 | if (ledSegmentStart < 0 || ledSegmentStart > MAX_SUPPORTED_LEDS) { 309 | ledSegmentStart = 0; 310 | } 311 | 312 | if (ledSegmentLength < 1 || ledSegmentLength > MAX_SUPPORTED_LEDS) { 313 | ledSegmentLength = numLeds; 314 | } 315 | 316 | if (totalSystemLeds < 1 || totalSystemLeds > MAX_SUPPORTED_LEDS) { 317 | totalSystemLeds = numLeds; 318 | } 319 | } 320 | 321 | void validateAllSettings() { 322 | // Validate system settings 323 | if (minDistance < 0 || minDistance > 500) minDistance = DEFAULT_MIN_DISTANCE; 324 | if (maxDistance < 50 || maxDistance > 1000) maxDistance = DEFAULT_MAX_DISTANCE; 325 | 326 | // Ensure min is always less than max with a reasonable gap 327 | if (minDistance >= maxDistance) { 328 | // Fix by resetting both to defaults 329 | minDistance = DEFAULT_MIN_DISTANCE; 330 | maxDistance = DEFAULT_MAX_DISTANCE; 331 | } 332 | 333 | if (brightness < 0 || brightness > 255) brightness = DEFAULT_BRIGHTNESS; 334 | if (movingLightSpan < 1 || movingLightSpan > 300) movingLightSpan = DEFAULT_MOVING_LIGHT_SPAN; 335 | if (numLeds < 1 || numLeds > 2000) numLeds = DEFAULT_NUM_LEDS; 336 | 337 | // Validate advanced settings 338 | if (centerShift < -100 || centerShift > 100) centerShift = DEFAULT_CENTER_SHIFT; 339 | if (trailLength < 0 || trailLength > 100) trailLength = DEFAULT_TRAIL_LENGTH; 340 | if (lightMode < 0 || lightMode > 10) lightMode = DEFAULT_LIGHT_MODE; 341 | if (effectSpeed < 1 || effectSpeed > 100) effectSpeed = DEFAULT_EFFECT_SPEED; 342 | if (effectIntensity < 1 || effectIntensity > 100) effectIntensity = DEFAULT_EFFECT_INTENSITY; 343 | 344 | // Validate motion parameters 345 | positionSmoothingFactor = constrain(positionSmoothingFactor, 0.0, 1.0); 346 | velocitySmoothingFactor = constrain(velocitySmoothingFactor, 0.0, 1.0); 347 | predictionFactor = constrain(predictionFactor, 0.0, 1.0); 348 | positionPGain = constrain(positionPGain, 0.0, 1.0); 349 | positionIGain = constrain(positionIGain, 0.0, 0.1); 350 | 351 | // Validate LED distribution settings 352 | validateLEDDistributionSettings(); 353 | 354 | // Log important values after validation 355 | Serial.printf("Validated Min/Max: %d/%d\n", minDistance, maxDistance); 356 | } 357 | 358 | void saveSettings() { 359 | Serial.println("Saving settings to EEPROM..."); 360 | 361 | // Validate critical settings before saving 362 | validateAllSettings(); 363 | 364 | // Save all sections with explicit commit after each section 365 | saveSystemSettings(); 366 | saveAdvancedSettings(); 367 | saveMotionSettings(); 368 | saveEspnowSettings(); 369 | saveLEDDistributionSettings(); 370 | 371 | // Calculate CRCs 372 | uint8_t systemCRC = calculateSystemCRC(); 373 | uint8_t advancedCRC = calculateAdvancedCRC(); 374 | uint8_t motionCRC = calculateMotionCRC(); 375 | uint8_t espnowCRC = calculateEspnowCRC(); 376 | uint8_t ledDistCRC = calculateLEDDistributionCRC(); 377 | 378 | // Update header with magic marker and CRCs 379 | EEPROMHeader header; 380 | header.magicMarker = EEPROM_MAGIC_MARKER; 381 | header.dataVersion = EEPROM_DATA_VERSION; 382 | header.systemSettings = systemCRC; 383 | header.advancedSettings = advancedCRC; 384 | header.motionSettings = motionCRC; 385 | header.espnowSettings = espnowCRC; 386 | header.ledDistSettings = ledDistCRC; 387 | header.reserved[0] = 0; 388 | 389 | // Write header to EEPROM 390 | EEPROM.put(0, header); 391 | 392 | // Explicitly commit the data to flash and verify success 393 | bool committed = EEPROM.commit(); 394 | if (committed) { 395 | Serial.println("EEPROM committed successfully"); 396 | 397 | // Additional verification for debug 398 | if (ENABLE_DEBUG_LOGGING) { 399 | int storedMinDist = EEPROM.read(EEPROM_ADDR_MIN_DIST_L) | (EEPROM.read(EEPROM_ADDR_MIN_DIST_H) << 8); 400 | int storedMaxDist = EEPROM.read(EEPROM_ADDR_MAX_DIST_L) | (EEPROM.read(EEPROM_ADDR_MAX_DIST_H) << 8); 401 | Serial.printf("Verification - Min: %d (exp:%d), Max: %d (exp:%d)\n", 402 | storedMinDist, minDistance, storedMaxDist, maxDistance); 403 | } 404 | } else { 405 | // Try again with a delay if the first commit fails 406 | Serial.println("ERROR: EEPROM commit failed! Retrying..."); 407 | delay(100); 408 | if (EEPROM.commit()) { 409 | Serial.println("EEPROM commit succeeded on retry"); 410 | } else { 411 | Serial.println("ERROR: EEPROM commit failed on retry!"); 412 | } 413 | } 414 | } 415 | 416 | void saveSystemSettings() { 417 | // Store system values 418 | EEPROM.write(EEPROM_ADDR_MARKER, EEPROM_INITIALIZED_MARKER); 419 | 420 | // Ensure min is always less than max with clear debugging 421 | if (minDistance >= maxDistance) { 422 | Serial.printf("WARNING: Invalid min/max detected before save: %d/%d\n", minDistance, maxDistance); 423 | minDistance = DEFAULT_MIN_DISTANCE; 424 | maxDistance = DEFAULT_MAX_DISTANCE; 425 | } 426 | 427 | // Save min/max distance with explicit byte ordering 428 | EEPROM.write(EEPROM_ADDR_MIN_DIST_L, minDistance & 0xFF); 429 | EEPROM.write(EEPROM_ADDR_MIN_DIST_H, (minDistance >> 8) & 0xFF); 430 | 431 | EEPROM.write(EEPROM_ADDR_MAX_DIST_L, maxDistance & 0xFF); 432 | EEPROM.write(EEPROM_ADDR_MAX_DIST_H, (maxDistance >> 8) & 0xFF); 433 | 434 | EEPROM.write(EEPROM_ADDR_BRIGHTNESS, brightness); 435 | EEPROM.write(EEPROM_ADDR_LIGHT_SPAN, movingLightSpan); 436 | 437 | EEPROM.write(EEPROM_ADDR_RED, redValue); 438 | EEPROM.write(EEPROM_ADDR_GREEN, greenValue); 439 | EEPROM.write(EEPROM_ADDR_BLUE, blueValue); 440 | 441 | EEPROM.write(EEPROM_ADDR_NUM_LEDS_L, numLeds & 0xFF); 442 | EEPROM.write(EEPROM_ADDR_NUM_LEDS_H, (numLeds >> 8) & 0xFF); 443 | 444 | // Add CRC for integrity check 445 | EEPROM.write(EEPROM_ADDR_CRC, calculateSystemCRC()); 446 | 447 | // Ensure this section is committed 448 | EEPROM.commit(); 449 | 450 | // Debug log for troubleshooting 451 | if (ENABLE_DEBUG_LOGGING) { 452 | Serial.printf("Saving Min: %d (0x%02X,0x%02X), Max: %d (0x%02X,0x%02X)\n", 453 | minDistance, 454 | minDistance & 0xFF, (minDistance >> 8) & 0xFF, 455 | maxDistance, 456 | maxDistance & 0xFF, (maxDistance >> 8) & 0xFF); 457 | } 458 | } 459 | 460 | void saveAdvancedSettings() { 461 | // Save advanced features 462 | EEPROM.put(EEPROM_ADDR_CENTER_SHIFT_L, (int16_t)centerShift); 463 | EEPROM.write(EEPROM_ADDR_TRAIL_LENGTH, trailLength); 464 | EEPROM.write(EEPROM_ADDR_DIRECTION_LIGHT, directionLightEnabled ? 1 : 0); 465 | EEPROM.write(EEPROM_ADDR_BACKGROUND_MODE, backgroundMode ? 1 : 0); 466 | EEPROM.write(EEPROM_ADDR_LIGHT_MODE, lightMode); 467 | EEPROM.write(EEPROM_ADDR_EFFECT_SPEED, effectSpeed); 468 | EEPROM.write(EEPROM_ADDR_EFFECT_INTENSITY, effectIntensity); 469 | 470 | // Add explicit commit 471 | EEPROM.commit(); 472 | } 473 | 474 | void saveMotionSettings() { 475 | // Save motion smoothing settings 476 | EEPROM.write(EEPROM_ADDR_MOTION_SMOOTHING, motionSmoothingEnabled ? 1 : 0); 477 | 478 | // Store smoothing factors (16-bit values) 479 | EEPROM.write(EEPROM_ADDR_SMOOTHING_FACTOR_L, (int)(positionSmoothingFactor * 100) & 0xFF); 480 | EEPROM.write(EEPROM_ADDR_SMOOTHING_FACTOR_H, ((int)(positionSmoothingFactor * 100) >> 8) & 0xFF); 481 | 482 | EEPROM.write(EEPROM_ADDR_VELOCITY_FACTOR_L, (int)(velocitySmoothingFactor * 100) & 0xFF); 483 | EEPROM.write(EEPROM_ADDR_VELOCITY_FACTOR_H, ((int)(velocitySmoothingFactor * 100) >> 8) & 0xFF); 484 | 485 | EEPROM.write(EEPROM_ADDR_PREDICTION_FACTOR_L, (int)(predictionFactor * 100) & 0xFF); 486 | EEPROM.write(EEPROM_ADDR_PREDICTION_FACTOR_H, ((int)(predictionFactor * 100) >> 8) & 0xFF); 487 | 488 | EEPROM.write(EEPROM_ADDR_POSITION_P_GAIN_L, (int)(positionPGain * 1000) & 0xFF); 489 | EEPROM.write(EEPROM_ADDR_POSITION_P_GAIN_H, ((int)(positionPGain * 1000) >> 8) & 0xFF); 490 | 491 | EEPROM.write(EEPROM_ADDR_POSITION_I_GAIN_L, (int)(positionIGain * 1000) & 0xFF); 492 | EEPROM.write(EEPROM_ADDR_POSITION_I_GAIN_H, ((int)(positionIGain * 1000) >> 8) & 0xFF); 493 | 494 | // Add explicit commit 495 | EEPROM.commit(); 496 | } 497 | 498 | void saveEspnowSettings() { 499 | // Save ESP-NOW settings 500 | EEPROM.write(EEPROM_ADDR_DEVICE_ROLE, deviceRole); 501 | 502 | // Save sensor priority mode 503 | EEPROM.write(EEPROM_ADDR_SENSOR_PRIORITY_MODE, sensorPriorityMode); 504 | 505 | // Save master MAC address 506 | for (int i = 0; i < 6; i++) { 507 | EEPROM.write(EEPROM_ADDR_MASTER_MAC + i, masterAddress[i]); 508 | } 509 | 510 | // Save paired slaves 511 | EEPROM.write(EEPROM_ADDR_PAIRED_SLAVES, numSlaveDevices); 512 | for (int s = 0; s < numSlaveDevices; s++) { 513 | for (int i = 0; i < 6; i++) { 514 | EEPROM.write(EEPROM_ADDR_PAIRED_SLAVES + 1 + (s * 6) + i, slaveAddresses[s][i]); 515 | } 516 | } 517 | 518 | // Add explicit commit 519 | EEPROM.commit(); 520 | } 521 | 522 | void saveLEDDistributionSettings() { 523 | // Use additional EEPROM addresses for LED distribution settings 524 | EEPROM.write(EEPROM_ADDR_LED_SEGMENT_MODE, ledSegmentMode); 525 | EEPROM.write(EEPROM_ADDR_LED_SEGMENT_START_L, ledSegmentStart & 0xFF); 526 | EEPROM.write(EEPROM_ADDR_LED_SEGMENT_START_H, (ledSegmentStart >> 8) & 0xFF); 527 | EEPROM.write(EEPROM_ADDR_LED_SEGMENT_LENGTH_L, ledSegmentLength & 0xFF); 528 | EEPROM.write(EEPROM_ADDR_LED_SEGMENT_LENGTH_H, (ledSegmentLength >> 8) & 0xFF); 529 | EEPROM.write(EEPROM_ADDR_TOTAL_SYSTEM_LEDS_L, totalSystemLeds & 0xFF); 530 | EEPROM.write(EEPROM_ADDR_TOTAL_SYSTEM_LEDS_H, (totalSystemLeds >> 8) & 0xFF); 531 | 532 | EEPROM.commit(); 533 | } 534 | 535 | void resetAllSettings() { 536 | // Reset all settings to defaults 537 | resetSystemSettings(); 538 | resetAdvancedSettings(); 539 | resetMotionSettings(); 540 | resetEspnowSettings(); 541 | resetLEDDistributionSettings(); 542 | 543 | // Save the defaults to EEPROM 544 | saveSettings(); 545 | } 546 | 547 | void resetSystemSettings() { 548 | minDistance = DEFAULT_MIN_DISTANCE; 549 | maxDistance = DEFAULT_MAX_DISTANCE; 550 | brightness = DEFAULT_BRIGHTNESS; 551 | movingLightSpan = DEFAULT_MOVING_LIGHT_SPAN; 552 | redValue = DEFAULT_RED; 553 | greenValue = DEFAULT_GREEN; 554 | blueValue = DEFAULT_BLUE; 555 | numLeds = DEFAULT_NUM_LEDS; 556 | } 557 | 558 | void resetAdvancedSettings() { 559 | centerShift = DEFAULT_CENTER_SHIFT; 560 | trailLength = DEFAULT_TRAIL_LENGTH; 561 | directionLightEnabled = DEFAULT_DIRECTION_LIGHT; 562 | backgroundMode = DEFAULT_BACKGROUND_MODE; 563 | lightMode = DEFAULT_LIGHT_MODE; 564 | effectSpeed = DEFAULT_EFFECT_SPEED; 565 | effectIntensity = DEFAULT_EFFECT_INTENSITY; 566 | } 567 | 568 | void resetMotionSettings() { 569 | motionSmoothingEnabled = DEFAULT_MOTION_SMOOTHING_ENABLED; 570 | positionSmoothingFactor = DEFAULT_POSITION_SMOOTHING_FACTOR; 571 | velocitySmoothingFactor = DEFAULT_VELOCITY_SMOOTHING_FACTOR; 572 | predictionFactor = DEFAULT_PREDICTION_FACTOR; 573 | positionPGain = DEFAULT_POSITION_P_GAIN; 574 | positionIGain = DEFAULT_POSITION_I_GAIN; 575 | } 576 | 577 | void resetEspnowSettings() { 578 | deviceRole = DEFAULT_DEVICE_ROLE; 579 | sensorPriorityMode = DEFAULT_SENSOR_PRIORITY_MODE; 580 | 581 | // Clear master address 582 | for (int i = 0; i < 6; i++) { 583 | masterAddress[i] = 0; 584 | } 585 | 586 | // Clear slave addresses 587 | numSlaveDevices = 0; 588 | for (int s = 0; s < MAX_SLAVE_DEVICES; s++) { 589 | for (int i = 0; i < 6; i++) { 590 | slaveAddresses[s][i] = 0; 591 | } 592 | } 593 | } 594 | 595 | void resetLEDDistributionSettings() { 596 | ledSegmentMode = DEFAULT_LED_SEGMENT_MODE; 597 | ledSegmentStart = DEFAULT_LED_SEGMENT_START; 598 | ledSegmentLength = DEFAULT_LED_SEGMENT_LENGTH; 599 | totalSystemLeds = DEFAULT_TOTAL_SYSTEM_LEDS; 600 | } 601 | 602 | // CRC calculation functions 603 | uint8_t calculateSystemCRC() { 604 | uint8_t crc = 0; 605 | 606 | for (int i = EEPROM_SYSTEM_START; i < EEPROM_ADVANCED_START; i++) { 607 | if (i != EEPROM_ADDR_MARKER) { // Skip marker address for CRC calculation 608 | crc ^= EEPROM.read(i); 609 | } 610 | } 611 | 612 | return crc; 613 | } 614 | 615 | uint8_t calculateAdvancedCRC() { 616 | uint8_t crc = 0; 617 | 618 | for (int i = EEPROM_ADVANCED_START; i < EEPROM_MOTION_START; i++) { 619 | crc ^= EEPROM.read(i); 620 | } 621 | 622 | return crc; 623 | } 624 | 625 | uint8_t calculateMotionCRC() { 626 | uint8_t crc = 0; 627 | 628 | for (int i = EEPROM_MOTION_START; i < EEPROM_ESPNOW_START; i++) { 629 | crc ^= EEPROM.read(i); 630 | } 631 | 632 | return crc; 633 | } 634 | 635 | uint8_t calculateEspnowCRC() { 636 | uint8_t crc = 0; 637 | 638 | for (int i = EEPROM_ESPNOW_START; i < EEPROM_WIFI_START; i++) { 639 | crc ^= EEPROM.read(i); 640 | } 641 | 642 | return crc; 643 | } 644 | 645 | uint8_t calculateLEDDistributionCRC() { 646 | uint8_t crc = 0; 647 | 648 | for (int i = EEPROM_LED_DIST_START; i < EEPROM_LED_DIST_START + 7; i++) { 649 | crc ^= EEPROM.read(i); 650 | } 651 | 652 | return crc; 653 | } -------------------------------------------------------------------------------- /AmbiSense/eeprom_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef EEPROM_MANAGER_H 2 | #define EEPROM_MANAGER_H 3 | 4 | /** 5 | * Initializes the EEPROM module 6 | */ 7 | void setupEEPROM(); 8 | 9 | /** 10 | * Saves all settings to EEPROM 11 | */ 12 | void saveSettings(); 13 | 14 | /** 15 | * Loads all settings from EEPROM 16 | */ 17 | void loadSettings(); 18 | 19 | /** 20 | * Loads settings with selective validation 21 | */ 22 | void loadSettings(bool systemValid, bool advancedValid, bool motionValid, bool espnowValid); 23 | 24 | /** 25 | * Validate all settings 26 | */ 27 | void validateAllSettings(); 28 | 29 | /** 30 | * Perform validation of critical settings like min/max distance 31 | * Used to detect and fix corrupted values 32 | */ 33 | void validateCriticalSettings(); 34 | 35 | /** 36 | * Save individual section settings 37 | */ 38 | void saveSystemSettings(); 39 | void saveAdvancedSettings(); 40 | void saveMotionSettings(); 41 | void saveEspnowSettings(); 42 | void saveLEDDistributionSettings(); 43 | 44 | /** 45 | * Reset settings to defaults 46 | */ 47 | void resetAllSettings(); 48 | void resetSystemSettings(); 49 | void resetAdvancedSettings(); 50 | void resetMotionSettings(); 51 | void resetEspnowSettings(); 52 | void resetLEDDistributionSettings(); 53 | 54 | /** 55 | * Calculate CRCs for different sections 56 | */ 57 | uint8_t calculateSystemCRC(); 58 | uint8_t calculateAdvancedCRC(); 59 | uint8_t calculateMotionCRC(); 60 | uint8_t calculateEspnowCRC(); 61 | uint8_t calculateLEDDistributionCRC(); 62 | 63 | /** 64 | * Load LED distribution settings from EEPROM 65 | */ 66 | void loadLEDDistributionSettings(); 67 | 68 | /** 69 | * Validate LED distribution settings 70 | */ 71 | void validateLEDDistributionSettings(); 72 | 73 | #endif // EEPROM_MANAGER_H -------------------------------------------------------------------------------- /AmbiSense/espnow_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "espnow_manager.h" 2 | #include "radar_manager.h" 3 | #include "led_controller.h" 4 | #include "config.h" 5 | #include "eeprom_manager.h" // Add this include for LED distribution functions 6 | #include 7 | 8 | // Array to store latest readings from each sensor 9 | sensor_data_t latestSensorData[MAX_SLAVE_DEVICES + 1]; // +1 for the master's own reading 10 | 11 | // Global variable for sensor priority mode 12 | uint8_t sensorPriorityMode = DEFAULT_SENSOR_PRIORITY_MODE; 13 | 14 | // LED distribution mode globals (these should be declared as extern in header) 15 | extern int ledSegmentMode; 16 | extern int ledSegmentStart; 17 | extern int ledSegmentLength; 18 | extern int totalSystemLeds; 19 | 20 | // Connection health monitoring 21 | struct ConnectionHealth { 22 | unsigned long lastReceived; 23 | uint32_t packetsReceived; 24 | uint32_t packetsLost; 25 | bool isHealthy; 26 | }; 27 | 28 | static ConnectionHealth slaveHealth[MAX_SLAVE_DEVICES + 1]; 29 | 30 | // Zone-based switching state 31 | struct ZoneSwitchingState { 32 | bool usingSlaveReading; 33 | unsigned long lastSwitchTime; 34 | int lastSelectedDistance; 35 | bool initialized; 36 | }; 37 | 38 | static ZoneSwitchingState zoneState = {false, 0, 0, false}; 39 | 40 | // Callback function for when data is sent 41 | void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { 42 | if (ENABLE_ESPNOW_LOGGING) { 43 | char macStr[18]; 44 | sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", 45 | mac_addr[0], mac_addr[1], mac_addr[2], 46 | mac_addr[3], mac_addr[4], mac_addr[5]); 47 | Serial.printf("ESP-NOW: Packet to %s status: %s\n", 48 | macStr, (status == ESP_NOW_SEND_SUCCESS ? "Success" : "Failed")); 49 | } 50 | } 51 | 52 | // Updated callback function for when data is received 53 | void OnDataReceive(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len) { 54 | if (len == sizeof(sensor_data_t)) { 55 | sensor_data_t sensorData; 56 | memcpy(&sensorData, data, sizeof(sensor_data_t)); 57 | 58 | const uint8_t *mac_addr = recv_info->src_addr; 59 | char macStr[18]; 60 | sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", 61 | mac_addr[0], mac_addr[1], mac_addr[2], 62 | mac_addr[3], mac_addr[4], mac_addr[5]); 63 | 64 | if (ENABLE_ESPNOW_LOGGING) { 65 | Serial.printf("ESP-NOW: Received sensor data from %s - ID: %d, Distance: %d cm\n", 66 | macStr, sensorData.sensorId, sensorData.distance); 67 | } 68 | 69 | // Update connection health 70 | updateConnectionHealth(sensorData.sensorId); 71 | 72 | // Process the received distance data 73 | processSensorData(sensorData); 74 | } 75 | else if (len == sizeof(led_segment_data_t)) { 76 | led_segment_data_t segmentData; 77 | memcpy(&segmentData, data, sizeof(led_segment_data_t)); 78 | 79 | if (ENABLE_ESPNOW_LOGGING) { 80 | Serial.printf("ESP-NOW: Received LED segment data - Distance: %d, Start: %d, Total: %d\n", 81 | segmentData.distance, segmentData.startLed, segmentData.totalLeds); 82 | } 83 | 84 | // Process LED segment data (for distributed mode) 85 | processLEDSegmentData(segmentData); 86 | } 87 | } 88 | 89 | // Initialize ESP-NOW with improved error handling and channel management 90 | void setupESPNOW() { 91 | Serial.println("ESP-NOW: Initializing..."); 92 | 93 | // Force specific channel for all devices 94 | WiFi.disconnect(true); 95 | delay(100); 96 | WiFi.mode(WIFI_AP_STA); 97 | delay(100); 98 | 99 | // Set fixed channel for consistent communication 100 | WiFi.channel(ESPNOW_CHANNEL); 101 | 102 | // Print MAC address (useful for setup) 103 | Serial.print("ESP-NOW: Device MAC Address: "); 104 | Serial.println(WiFi.macAddress()); 105 | Serial.printf("ESP-NOW: Using channel %d\n", ESPNOW_CHANNEL); 106 | 107 | // Initialize ESP-NOW with retry logic 108 | esp_err_t initResult = esp_now_init(); 109 | if (initResult != ESP_OK) { 110 | Serial.printf("ESP-NOW: Init failed with error %d, retrying...\n", initResult); 111 | delay(1000); 112 | 113 | // Cleanup and retry 114 | esp_now_deinit(); 115 | delay(100); 116 | initResult = esp_now_init(); 117 | 118 | if (initResult != ESP_OK) { 119 | Serial.printf("ESP-NOW: Critical failure - init failed after retry: %d\n", initResult); 120 | return; 121 | } 122 | } 123 | 124 | // Get device role from EEPROM 125 | deviceRole = EEPROM.read(EEPROM_ADDR_DEVICE_ROLE); 126 | if (deviceRole != DEVICE_ROLE_MASTER && deviceRole != DEVICE_ROLE_SLAVE) { 127 | deviceRole = DEFAULT_DEVICE_ROLE; // Default to master if not set 128 | EEPROM.write(EEPROM_ADDR_DEVICE_ROLE, deviceRole); 129 | EEPROM.commit(); 130 | } 131 | 132 | // Get sensor priority mode from EEPROM 133 | sensorPriorityMode = EEPROM.read(EEPROM_ADDR_SENSOR_PRIORITY_MODE); 134 | if (sensorPriorityMode > SENSOR_PRIORITY_ZONE_BASED) { 135 | sensorPriorityMode = DEFAULT_SENSOR_PRIORITY_MODE; 136 | EEPROM.write(EEPROM_ADDR_SENSOR_PRIORITY_MODE, sensorPriorityMode); 137 | EEPROM.commit(); 138 | } 139 | 140 | // Load LED distribution settings from EEPROM (function from eeprom_manager) 141 | loadLEDDistributionSettings(); 142 | 143 | // Set up callback functions 144 | esp_now_register_send_cb(OnDataSent); 145 | esp_now_register_recv_cb(OnDataReceive); 146 | 147 | // Initialize connection health monitoring 148 | initializeConnectionHealth(); 149 | 150 | // Configure peers based on role 151 | if (deviceRole == DEVICE_ROLE_MASTER) { 152 | Serial.println("ESP-NOW: Configuring device as MASTER"); 153 | configureMasterPeers(); 154 | } else if (deviceRole == DEVICE_ROLE_SLAVE) { 155 | Serial.println("ESP-NOW: Configuring device as SLAVE"); 156 | configureSlavePeer(); 157 | } 158 | 159 | Serial.printf("ESP-NOW: Initialization complete. LED Mode: %s\n", 160 | (ledSegmentMode == LED_SEGMENT_MODE_DISTRIBUTED) ? "Distributed" : "Continuous"); 161 | } 162 | 163 | // Configure master device peers 164 | void configureMasterPeers() { 165 | // Read number of paired slaves 166 | numSlaveDevices = EEPROM.read(EEPROM_ADDR_PAIRED_SLAVES); 167 | numSlaveDevices = constrain(numSlaveDevices, 0, MAX_SLAVE_DEVICES); 168 | 169 | if (ENABLE_ESPNOW_LOGGING) { 170 | Serial.printf("ESP-NOW: Master configuring %d slave devices\n", numSlaveDevices); 171 | } 172 | 173 | // Register all slave devices as peers 174 | for (int i = 0; i < numSlaveDevices; i++) { 175 | uint8_t macAddr[6]; 176 | for (int j = 0; j < 6; j++) { 177 | macAddr[j] = EEPROM.read(EEPROM_ADDR_PAIRED_SLAVES + 1 + (i * 6) + j); 178 | slaveAddresses[i][j] = macAddr[j]; // Store in global array 179 | } 180 | 181 | // Remove peer if it already exists 182 | esp_now_del_peer(macAddr); 183 | 184 | // Add as new peer with fixed channel 185 | esp_now_peer_info_t peerInfo = {}; 186 | memcpy(peerInfo.peer_addr, macAddr, 6); 187 | peerInfo.channel = ESPNOW_CHANNEL; // Use fixed channel 188 | peerInfo.encrypt = false; 189 | 190 | char macStr[18]; 191 | sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", 192 | macAddr[0], macAddr[1], macAddr[2], 193 | macAddr[3], macAddr[4], macAddr[5]); 194 | 195 | esp_err_t addResult = esp_now_add_peer(&peerInfo); 196 | if (addResult == ESP_OK) { 197 | if (ENABLE_ESPNOW_LOGGING) { 198 | Serial.printf("ESP-NOW: Successfully added slave peer: %s\n", macStr); 199 | } 200 | } else { 201 | Serial.printf("ESP-NOW: Failed to add slave peer %s (error: %d)\n", macStr, addResult); 202 | } 203 | } 204 | } 205 | 206 | // Configure slave device peer 207 | void configureSlavePeer() { 208 | // Read master MAC address 209 | for (int i = 0; i < 6; i++) { 210 | masterAddress[i] = EEPROM.read(EEPROM_ADDR_MASTER_MAC + i); 211 | } 212 | 213 | // Check if master MAC is valid (not all zeros) 214 | bool validMaster = false; 215 | for (int i = 0; i < 6; i++) { 216 | if (masterAddress[i] != 0) { 217 | validMaster = true; 218 | break; 219 | } 220 | } 221 | 222 | if (!validMaster) { 223 | Serial.println("ESP-NOW: No valid master MAC configured for slave"); 224 | return; 225 | } 226 | 227 | // Remove any existing peer 228 | esp_now_del_peer(masterAddress); 229 | 230 | // Add master as peer with fixed channel 231 | esp_now_peer_info_t peerInfo = {}; 232 | memcpy(peerInfo.peer_addr, masterAddress, 6); 233 | peerInfo.channel = ESPNOW_CHANNEL; // Use fixed channel 234 | peerInfo.encrypt = false; 235 | 236 | char macStr[18]; 237 | sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", 238 | masterAddress[0], masterAddress[1], masterAddress[2], 239 | masterAddress[3], masterAddress[4], masterAddress[5]); 240 | 241 | esp_err_t addResult = esp_now_add_peer(&peerInfo); 242 | if (addResult == ESP_OK) { 243 | Serial.printf("ESP-NOW: Successfully added master peer: %s\n", macStr); 244 | 245 | // Send initial test message with delay 246 | delay(100); 247 | sendTestMessage(); 248 | } else { 249 | Serial.printf("ESP-NOW: Failed to add master peer %s (error: %d)\n", macStr, addResult); 250 | } 251 | } 252 | 253 | // Send test message from slave to master 254 | void sendTestMessage() { 255 | sensor_data_t testData = {0}; 256 | testData.sensorId = 1; 257 | testData.distance = 0; 258 | testData.timestamp = millis(); 259 | 260 | esp_err_t result = esp_now_send(masterAddress, (uint8_t*)&testData, sizeof(sensor_data_t)); 261 | if (result == ESP_OK) { 262 | Serial.println("ESP-NOW: Test message sent to master"); 263 | } else { 264 | Serial.printf("ESP-NOW: Failed to send test message (error: %d)\n", result); 265 | } 266 | } 267 | 268 | // Initialize connection health monitoring 269 | void initializeConnectionHealth() { 270 | for (int i = 0; i <= MAX_SLAVE_DEVICES; i++) { 271 | slaveHealth[i].lastReceived = millis(); 272 | slaveHealth[i].packetsReceived = 0; 273 | slaveHealth[i].packetsLost = 0; 274 | slaveHealth[i].isHealthy = false; 275 | } 276 | 277 | // Initialize latestSensorData array 278 | for (int i = 0; i <= MAX_SLAVE_DEVICES; i++) { 279 | latestSensorData[i].sensorId = i; 280 | latestSensorData[i].distance = 0; 281 | latestSensorData[i].direction = 0; 282 | latestSensorData[i].timestamp = 0; 283 | } 284 | 285 | // Initialize zone switching state 286 | zoneState.usingSlaveReading = false; 287 | zoneState.lastSwitchTime = millis(); 288 | zoneState.lastSelectedDistance = minDistance; 289 | zoneState.initialized = true; 290 | } 291 | 292 | // Update connection health for a sensor 293 | void updateConnectionHealth(uint8_t sensorId) { 294 | if (sensorId <= MAX_SLAVE_DEVICES) { 295 | slaveHealth[sensorId].lastReceived = millis(); 296 | slaveHealth[sensorId].packetsReceived++; 297 | slaveHealth[sensorId].isHealthy = true; 298 | } 299 | } 300 | 301 | // Check connection health and log issues 302 | void checkConnectionHealth() { 303 | unsigned long currentTime = millis(); 304 | 305 | for (int i = 0; i <= numSlaveDevices; i++) { 306 | if (currentTime - slaveHealth[i].lastReceived > CONNECTION_HEALTH_TIMEOUT) { 307 | if (slaveHealth[i].isHealthy) { 308 | Serial.printf("ESP-NOW: WARNING - Lost connection to sensor %d\n", i); 309 | slaveHealth[i].isHealthy = false; 310 | } 311 | } 312 | } 313 | } 314 | 315 | // Send sensor data (called by slave devices) with improved error handling 316 | void sendSensorData(int distance, int8_t direction) { 317 | if (deviceRole != DEVICE_ROLE_SLAVE) return; 318 | 319 | // Check if master MAC is valid 320 | bool validMaster = false; 321 | for (int i = 0; i < 6; i++) { 322 | if (masterAddress[i] != 0) { 323 | validMaster = true; 324 | break; 325 | } 326 | } 327 | 328 | if (!validMaster) { 329 | static unsigned long lastLogTime = 0; 330 | if (millis() - lastLogTime > 10000) { 331 | Serial.println("ESP-NOW: Slave mode active but no master configured"); 332 | lastLogTime = millis(); 333 | } 334 | return; 335 | } 336 | 337 | // Prepare sensor data 338 | sensor_data_t sensorData; 339 | sensorData.sensorId = 1; 340 | sensorData.distance = distance; 341 | sensorData.direction = direction; 342 | sensorData.battery = 100; 343 | sensorData.timestamp = millis(); 344 | 345 | // Send with retry logic 346 | int retryCount = 0; 347 | esp_err_t result; 348 | 349 | do { 350 | result = esp_now_send(masterAddress, (uint8_t*)&sensorData, sizeof(sensor_data_t)); 351 | if (result == ESP_OK) { 352 | break; 353 | } 354 | 355 | retryCount++; 356 | if (retryCount < ESPNOW_RETRY_COUNT) { 357 | delay(10); // Small delay before retry 358 | } 359 | } while (retryCount < ESPNOW_RETRY_COUNT); 360 | 361 | if (result != ESP_OK && ENABLE_ESPNOW_LOGGING) { 362 | static unsigned long lastErrorTime = 0; 363 | if (millis() - lastErrorTime > 1000) { 364 | Serial.printf("ESP-NOW: Failed to send data after %d retries (error: %d)\n", 365 | ESPNOW_RETRY_COUNT, result); 366 | lastErrorTime = millis(); 367 | } 368 | } 369 | } 370 | 371 | // Send LED segment data to slaves (for distributed mode) 372 | void sendLEDSegmentData(int distance, int globalStartPos) { 373 | if (deviceRole != DEVICE_ROLE_MASTER || 374 | ledSegmentMode != LED_SEGMENT_MODE_DISTRIBUTED || 375 | numSlaveDevices == 0) { 376 | return; 377 | } 378 | 379 | led_segment_data_t segmentData; 380 | segmentData.sensorId = 0; // Master 381 | segmentData.distance = distance; 382 | segmentData.direction = 0; 383 | segmentData.timestamp = millis(); 384 | segmentData.totalLeds = totalSystemLeds; 385 | segmentData.lightMode = lightMode; 386 | segmentData.brightness = brightness; 387 | segmentData.redValue = redValue; 388 | segmentData.greenValue = greenValue; 389 | segmentData.blueValue = blueValue; 390 | 391 | // Send to all slaves with their specific segment information 392 | for (int i = 0; i < numSlaveDevices; i++) { 393 | // Calculate segment for this slave (assuming equal distribution) 394 | int ledsPerDevice = totalSystemLeds / (numSlaveDevices + 1); // +1 for master 395 | int slaveSegmentStart = (i + 1) * ledsPerDevice; 396 | 397 | segmentData.startLed = slaveSegmentStart; 398 | segmentData.segmentLength = ledsPerDevice; 399 | 400 | esp_err_t result = esp_now_send(slaveAddresses[i], 401 | (uint8_t*)&segmentData, 402 | sizeof(led_segment_data_t)); 403 | 404 | if (result != ESP_OK && ENABLE_ESPNOW_LOGGING) { 405 | Serial.printf("ESP-NOW: Failed to send LED segment data to slave %d (error: %d)\n", 406 | i, result); 407 | } 408 | } 409 | 410 | if (ENABLE_ESPNOW_LOGGING) { 411 | Serial.printf("ESP-NOW: Sent LED segment data - Distance: %d, Global start: %d\n", 412 | distance, globalStartPos); 413 | } 414 | } 415 | 416 | // Process LED segment data (on slave devices) 417 | void processLEDSegmentData(led_segment_data_t segmentData) { 418 | if (deviceRole != DEVICE_ROLE_SLAVE || 419 | ledSegmentMode != LED_SEGMENT_MODE_DISTRIBUTED) { 420 | return; 421 | } 422 | 423 | // Update local settings from master 424 | brightness = segmentData.brightness; 425 | redValue = segmentData.redValue; 426 | greenValue = segmentData.greenValue; 427 | blueValue = segmentData.blueValue; 428 | 429 | // Calculate global LED position from distance 430 | int globalStartPos = map(segmentData.distance, minDistance, maxDistance, 431 | 0, segmentData.totalLeds - movingLightSpan); 432 | 433 | // Update LED segment based on the received data 434 | updateLEDSegment(globalStartPos, segmentData); 435 | } 436 | 437 | // Update LED segment for distributed mode 438 | void updateLEDSegment(int globalStartPos, led_segment_data_t segmentData) { 439 | strip.clear(); 440 | 441 | // Calculate which part of the moving light belongs to this segment 442 | for (int i = 0; i < movingLightSpan; i++) { 443 | int globalLedPos = globalStartPos + i; 444 | int localLedPos = globalLedPos - ledSegmentStart; 445 | 446 | // Check if this LED position belongs to this device's segment 447 | if (localLedPos >= 0 && localLedPos < numLeds) { 448 | strip.setPixelColor(localLedPos, strip.Color(segmentData.redValue, 449 | segmentData.greenValue, 450 | segmentData.blueValue)); 451 | } 452 | } 453 | 454 | strip.show(); 455 | 456 | if (ENABLE_ESPNOW_LOGGING) { 457 | Serial.printf("ESP-NOW: Updated LED segment - Global start: %d, Local LEDs updated\n", 458 | globalStartPos); 459 | } 460 | } 461 | 462 | // Process received sensor data (called by master device) 463 | void processSensorData(sensor_data_t sensorData) { 464 | if (deviceRole != DEVICE_ROLE_MASTER) return; 465 | 466 | // Validate incoming data 467 | if (sensorData.distance < 0 || sensorData.distance > 500) { 468 | if (ENABLE_ESPNOW_LOGGING) { 469 | Serial.printf("ESP-NOW: Discarding invalid distance %d from sensor %d\n", 470 | sensorData.distance, sensorData.sensorId); 471 | } 472 | return; 473 | } 474 | 475 | // Store the latest reading from this sensor 476 | if (sensorData.sensorId <= MAX_SLAVE_DEVICES) { 477 | latestSensorData[sensorData.sensorId] = sensorData; 478 | 479 | if (ENABLE_ESPNOW_LOGGING) { 480 | Serial.printf("ESP-NOW: Stored data from sensor %d: Distance = %d cm\n", 481 | sensorData.sensorId, sensorData.distance); 482 | } 483 | } 484 | 485 | // Update LEDs based on combined sensor data 486 | updateLEDsWithMultiSensorData(); 487 | } 488 | 489 | // Enhanced LED update with improved zone-based switching and LED distribution 490 | void updateLEDsWithMultiSensorData() { 491 | if (deviceRole != DEVICE_ROLE_MASTER) return; 492 | 493 | unsigned long currentTime = millis(); 494 | 495 | // Check connection health periodically 496 | static unsigned long lastHealthCheck = 0; 497 | if (currentTime - lastHealthCheck > 5000) { 498 | checkConnectionHealth(); 499 | lastHealthCheck = currentTime; 500 | } 501 | 502 | // Store master's own sensor reading 503 | latestSensorData[0].sensorId = 0; 504 | latestSensorData[0].distance = currentDistance; 505 | latestSensorData[0].timestamp = currentTime; 506 | 507 | int selectedDistance = 0; 508 | 509 | // Implementation based on the selected sensor priority mode 510 | switch (sensorPriorityMode) { 511 | case SENSOR_PRIORITY_MOST_RECENT: { 512 | selectedDistance = handleMostRecentPriority(currentTime); 513 | break; 514 | } 515 | 516 | case SENSOR_PRIORITY_SLAVE_FIRST: { 517 | selectedDistance = handleSlaveFirstPriority(currentTime); 518 | break; 519 | } 520 | 521 | case SENSOR_PRIORITY_MASTER_FIRST: { 522 | selectedDistance = handleMasterFirstPriority(currentTime); 523 | break; 524 | } 525 | 526 | case SENSOR_PRIORITY_ZONE_BASED: { 527 | selectedDistance = handleZoneBasedPriority(currentTime); 528 | break; 529 | } 530 | 531 | default: { 532 | selectedDistance = handleMostRecentPriority(currentTime); 533 | break; 534 | } 535 | } 536 | 537 | // Apply constraints and update LEDs 538 | selectedDistance = constrain(selectedDistance, minDistance, maxDistance); 539 | currentDistance = selectedDistance; 540 | 541 | if (lightMode == LIGHT_MODE_STANDARD) { 542 | if (ledSegmentMode == LED_SEGMENT_MODE_DISTRIBUTED) { 543 | // Calculate global LED position 544 | int globalStartPos = map(selectedDistance, minDistance, maxDistance, 545 | 0, totalSystemLeds - movingLightSpan); 546 | 547 | // Update master's segment 548 | int localStartPos = globalStartPos - ledSegmentStart; 549 | localStartPos = constrain(localStartPos, 0, numLeds - movingLightSpan); 550 | 551 | updateStandardMode(localStartPos); 552 | 553 | // Send segment data to slaves 554 | sendLEDSegmentData(selectedDistance, globalStartPos); 555 | } else { 556 | // Continuous mode - normal LED update 557 | updateLEDs(selectedDistance); 558 | } 559 | } 560 | } 561 | 562 | // Handle most recent priority mode 563 | int handleMostRecentPriority(unsigned long currentTime) { 564 | unsigned long mostRecentTime = 0; 565 | int selectedDistance = 0; 566 | bool movementDetected = false; 567 | 568 | for (int i = 0; i <= numSlaveDevices; i++) { 569 | if (currentTime - latestSensorData[i].timestamp > 5000) { 570 | continue; // Skip old data 571 | } 572 | 573 | if (latestSensorData[i].distance > 0) { 574 | movementDetected = true; 575 | if (latestSensorData[i].timestamp > mostRecentTime) { 576 | mostRecentTime = latestSensorData[i].timestamp; 577 | selectedDistance = latestSensorData[i].distance; 578 | } 579 | } 580 | } 581 | 582 | return movementDetected ? selectedDistance : currentDistance; 583 | } 584 | 585 | // Handle slave first priority mode 586 | int handleSlaveFirstPriority(unsigned long currentTime) { 587 | bool slaveDetected = false; 588 | int slaveDistance = 0; 589 | unsigned long mostRecentSlaveTime = 0; 590 | 591 | // Check slave sensors first 592 | for (int i = 1; i <= numSlaveDevices; i++) { 593 | if (currentTime - latestSensorData[i].timestamp > 5000) { 594 | continue; 595 | } 596 | 597 | if (latestSensorData[i].distance > 0) { 598 | slaveDetected = true; 599 | if (latestSensorData[i].timestamp > mostRecentSlaveTime) { 600 | mostRecentSlaveTime = latestSensorData[i].timestamp; 601 | slaveDistance = latestSensorData[i].distance; 602 | } 603 | } 604 | } 605 | 606 | if (slaveDetected) { 607 | return slaveDistance; 608 | } else if (latestSensorData[0].distance > 0 && 609 | currentTime - latestSensorData[0].timestamp < 5000) { 610 | return latestSensorData[0].distance; 611 | } 612 | 613 | return currentDistance; 614 | } 615 | 616 | // Handle master first priority mode 617 | int handleMasterFirstPriority(unsigned long currentTime) { 618 | if (latestSensorData[0].distance > 0 && 619 | currentTime - latestSensorData[0].timestamp < 5000) { 620 | return latestSensorData[0].distance; 621 | } else { 622 | // Check slaves as fallback 623 | return handleSlaveFirstPriority(currentTime); 624 | } 625 | } 626 | 627 | // Enhanced zone-based priority with better hysteresis 628 | int handleZoneBasedPriority(unsigned long currentTime) { 629 | if (!zoneState.initialized) { 630 | zoneState.usingSlaveReading = false; 631 | zoneState.lastSwitchTime = currentTime; 632 | zoneState.lastSelectedDistance = currentDistance; 633 | zoneState.initialized = true; 634 | } 635 | 636 | // Check for activity in slave zones (L-section) 637 | bool slaveDetectedMovement = false; 638 | int bestSlaveDistance = 0; 639 | unsigned long bestSlaveTimestamp = 0; 640 | 641 | for (int i = 1; i <= numSlaveDevices; i++) { 642 | // Skip old readings (more than 4 seconds old) 643 | if (currentTime - latestSensorData[i].timestamp > 4000) { 644 | continue; 645 | } 646 | 647 | if (latestSensorData[i].distance > 0) { 648 | slaveDetectedMovement = true; 649 | // If multiple slaves detect movement, use the most recent one 650 | if (latestSensorData[i].timestamp > bestSlaveTimestamp) { 651 | bestSlaveTimestamp = latestSensorData[i].timestamp; 652 | bestSlaveDistance = latestSensorData[i].distance; 653 | } 654 | } 655 | } 656 | 657 | // Check for activity in master zone (lower section) 658 | bool masterDetectedMovement = (latestSensorData[0].distance > 0 && 659 | currentTime - latestSensorData[0].timestamp < 4000); 660 | 661 | // Enhanced decision logic with hysteresis and momentum 662 | if (slaveDetectedMovement) { 663 | // If slave detects movement, switch to slave control 664 | if (!zoneState.usingSlaveReading) { 665 | zoneState.usingSlaveReading = true; 666 | zoneState.lastSwitchTime = currentTime; 667 | if (ENABLE_ESPNOW_LOGGING) { 668 | Serial.println("ESP-NOW: Zone switch -> SLAVE sensors (L-section)"); 669 | } 670 | } 671 | zoneState.lastSelectedDistance = bestSlaveDistance; 672 | return bestSlaveDistance; 673 | } 674 | else if (!slaveDetectedMovement && masterDetectedMovement) { 675 | // If only master detects movement and no active slave, consider switching 676 | if (zoneState.usingSlaveReading && currentTime - zoneState.lastSwitchTime > 2000) { 677 | // Add hysteresis delay to prevent rapid switching 678 | zoneState.usingSlaveReading = false; 679 | zoneState.lastSwitchTime = currentTime; 680 | if (ENABLE_ESPNOW_LOGGING) { 681 | Serial.println("ESP-NOW: Zone switch -> MASTER sensor (lower section)"); 682 | } 683 | } 684 | 685 | if (!zoneState.usingSlaveReading) { 686 | zoneState.lastSelectedDistance = latestSensorData[0].distance; 687 | return latestSensorData[0].distance; 688 | } 689 | } 690 | else if (!slaveDetectedMovement && !masterDetectedMovement) { 691 | // No movement detected - implement gradual fade or maintain last reading 692 | if (currentTime - zoneState.lastSwitchTime > 3000) { 693 | // After 3 seconds of no detection, gradually increase distance (fade effect) 694 | int fadeDistance = min(maxDistance, zoneState.lastSelectedDistance + 2); 695 | zoneState.lastSelectedDistance = fadeDistance; 696 | return fadeDistance; 697 | } 698 | } 699 | 700 | // Default: maintain last selected distance 701 | return zoneState.lastSelectedDistance; 702 | } 703 | 704 | // Set the sensor priority mode 705 | void setSensorPriorityMode(uint8_t mode) { 706 | if (mode <= SENSOR_PRIORITY_ZONE_BASED) { 707 | sensorPriorityMode = mode; 708 | EEPROM.write(EEPROM_ADDR_SENSOR_PRIORITY_MODE, sensorPriorityMode); 709 | EEPROM.commit(); 710 | 711 | // Reset zone state when changing modes 712 | if (mode == SENSOR_PRIORITY_ZONE_BASED) { 713 | zoneState.initialized = false; 714 | } 715 | 716 | if (ENABLE_ESPNOW_LOGGING) { 717 | const char* modeNames[] = {"Most Recent", "Slave First", "Master First", "Zone-Based"}; 718 | Serial.printf("ESP-NOW: Sensor priority mode set to %s (%d)\n", 719 | modeNames[mode], mode); 720 | } 721 | } 722 | } 723 | 724 | // Get the current sensor priority mode 725 | uint8_t getSensorPriorityMode() { 726 | return sensorPriorityMode; 727 | } 728 | 729 | // LED Distribution Mode functions 730 | void setLEDSegmentMode(int mode) { 731 | if (mode == LED_SEGMENT_MODE_CONTINUOUS || mode == LED_SEGMENT_MODE_DISTRIBUTED) { 732 | ledSegmentMode = mode; 733 | saveLEDDistributionSettings(); // This function is from eeprom_manager 734 | 735 | if (ENABLE_ESPNOW_LOGGING) { 736 | Serial.printf("ESP-NOW: LED segment mode set to %s\n", 737 | (mode == LED_SEGMENT_MODE_DISTRIBUTED) ? "Distributed" : "Continuous"); 738 | } 739 | } 740 | } 741 | 742 | int getLEDSegmentMode() { 743 | return ledSegmentMode; 744 | } 745 | 746 | void setLEDSegmentInfo(int start, int length, int total) { 747 | ledSegmentStart = constrain(start, 0, total - 1); 748 | ledSegmentLength = constrain(length, 1, total); 749 | totalSystemLeds = constrain(total, 1, MAX_SUPPORTED_LEDS); 750 | 751 | saveLEDDistributionSettings(); // This function is from eeprom_manager 752 | 753 | Serial.printf("ESP-NOW: LED segment info set - Start: %d, Length: %d, Total: %d\n", 754 | ledSegmentStart, ledSegmentLength, totalSystemLeds); 755 | } 756 | 757 | void getLEDSegmentInfo(int* start, int* length, int* total) { 758 | *start = ledSegmentStart; 759 | *length = ledSegmentLength; 760 | *total = totalSystemLeds; 761 | } 762 | 763 | // Get connection health status for diagnostics 764 | bool getSensorHealth(uint8_t sensorId) { 765 | if (sensorId <= MAX_SLAVE_DEVICES) { 766 | return slaveHealth[sensorId].isHealthy; 767 | } 768 | return false; 769 | } 770 | 771 | // Get packet statistics for diagnostics 772 | uint32_t getSensorPacketCount(uint8_t sensorId) { 773 | if (sensorId <= MAX_SLAVE_DEVICES) { 774 | return slaveHealth[sensorId].packetsReceived; 775 | } 776 | return 0; 777 | } 778 | 779 | // Get last received time for diagnostics 780 | unsigned long getSensorLastReceived(uint8_t sensorId) { 781 | if (sensorId <= MAX_SLAVE_DEVICES) { 782 | return slaveHealth[sensorId].lastReceived; 783 | } 784 | return 0; 785 | } 786 | 787 | // Reset ESP-NOW and reinitialize (useful for recovery) 788 | void resetESPNOW() { 789 | Serial.println("ESP-NOW: Resetting and reinitializing..."); 790 | 791 | esp_now_deinit(); 792 | delay(100); 793 | 794 | setupESPNOW(); 795 | } 796 | 797 | // Enhanced diagnostic information 798 | void printESPNOWDiagnostics() { 799 | Serial.println("\n=== ESP-NOW Diagnostics ==="); 800 | Serial.printf("Device Role: %s\n", 801 | (deviceRole == DEVICE_ROLE_MASTER) ? "Master" : "Slave"); 802 | Serial.printf("Priority Mode: %d\n", sensorPriorityMode); 803 | Serial.printf("Channel: %d\n", ESPNOW_CHANNEL); 804 | Serial.printf("LED Mode: %s\n", 805 | (ledSegmentMode == LED_SEGMENT_MODE_DISTRIBUTED) ? "Distributed" : "Continuous"); 806 | 807 | if (ledSegmentMode == LED_SEGMENT_MODE_DISTRIBUTED) { 808 | Serial.printf("LED Segment: Start=%d, Length=%d, Total=%d\n", 809 | ledSegmentStart, ledSegmentLength, totalSystemLeds); 810 | } 811 | 812 | if (deviceRole == DEVICE_ROLE_MASTER) { 813 | Serial.printf("Paired Slaves: %d\n", numSlaveDevices); 814 | for (int i = 0; i <= numSlaveDevices; i++) { 815 | Serial.printf("Sensor %d: %s, Packets: %d, Last: %lu ms ago\n", 816 | i, 817 | slaveHealth[i].isHealthy ? "Healthy" : "Disconnected", 818 | slaveHealth[i].packetsReceived, 819 | millis() - slaveHealth[i].lastReceived); 820 | } 821 | 822 | if (sensorPriorityMode == SENSOR_PRIORITY_ZONE_BASED) { 823 | Serial.printf("Zone State: Using %s, Last switch: %lu ms ago\n", 824 | zoneState.usingSlaveReading ? "Slave" : "Master", 825 | millis() - zoneState.lastSwitchTime); 826 | } 827 | } else { 828 | char macStr[18]; 829 | sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", 830 | masterAddress[0], masterAddress[1], masterAddress[2], 831 | masterAddress[3], masterAddress[4], masterAddress[5]); 832 | Serial.printf("Master MAC: %s\n", macStr); 833 | } 834 | 835 | // Memory usage 836 | Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap()); 837 | Serial.printf("Min Free Heap: %d bytes\n", ESP.getMinFreeHeap()); 838 | 839 | Serial.println("========================\n"); 840 | } 841 | 842 | // Force synchronize all devices (master only) 843 | void synchronizeAllDevices() { 844 | if (deviceRole != DEVICE_ROLE_MASTER) return; 845 | 846 | Serial.println("ESP-NOW: Synchronizing all devices..."); 847 | 848 | // Send current settings to all slaves 849 | led_segment_data_t syncData; 850 | syncData.sensorId = 0; 851 | syncData.distance = currentDistance; 852 | syncData.direction = 0; 853 | syncData.timestamp = millis(); 854 | syncData.totalLeds = totalSystemLeds; 855 | syncData.lightMode = lightMode; 856 | syncData.brightness = brightness; 857 | syncData.redValue = redValue; 858 | syncData.greenValue = greenValue; 859 | syncData.blueValue = blueValue; 860 | 861 | for (int i = 0; i < numSlaveDevices; i++) { 862 | // Calculate segment for this slave 863 | int ledsPerDevice = totalSystemLeds / (numSlaveDevices + 1); 864 | syncData.startLed = (i + 1) * ledsPerDevice; 865 | syncData.segmentLength = ledsPerDevice; 866 | 867 | esp_err_t result = esp_now_send(slaveAddresses[i], 868 | (uint8_t*)&syncData, 869 | sizeof(led_segment_data_t)); 870 | 871 | if (result == ESP_OK) { 872 | Serial.printf("ESP-NOW: Sync data sent to slave %d\n", i); 873 | } else { 874 | Serial.printf("ESP-NOW: Failed to sync slave %d (error: %d)\n", i, result); 875 | } 876 | 877 | delay(10); // Small delay between sends 878 | } 879 | 880 | Serial.println("ESP-NOW: Synchronization complete"); 881 | } 882 | 883 | // Get system status for web interface 884 | void getSystemStatus(char* statusJson, size_t maxLength) { 885 | snprintf(statusJson, maxLength, 886 | "{" 887 | "\"role\":%d," 888 | "\"priorityMode\":%d," 889 | "\"ledMode\":%d," 890 | "\"ledSegmentStart\":%d," 891 | "\"ledSegmentLength\":%d," 892 | "\"totalSystemLeds\":%d," 893 | "\"numSlaves\":%d," 894 | "\"channel\":%d," 895 | "\"currentDistance\":%d," 896 | "\"usingSlaveReading\":%s," 897 | "\"freeHeap\":%d" 898 | "}", 899 | deviceRole, 900 | sensorPriorityMode, 901 | ledSegmentMode, 902 | ledSegmentStart, 903 | ledSegmentLength, 904 | totalSystemLeds, 905 | numSlaveDevices, 906 | ESPNOW_CHANNEL, 907 | currentDistance, 908 | (zoneState.usingSlaveReading ? "true" : "false"), 909 | ESP.getFreeHeap() 910 | ); 911 | } 912 | 913 | // Emergency stop all LEDs across the network 914 | void emergencyStopAllLEDs() { 915 | if (deviceRole == DEVICE_ROLE_MASTER) { 916 | Serial.println("ESP-NOW: Emergency stop - turning off all LEDs"); 917 | 918 | // Turn off local LEDs 919 | strip.clear(); 920 | strip.show(); 921 | 922 | // Send emergency stop to all slaves 923 | led_segment_data_t stopData = {0}; 924 | stopData.sensorId = 255; // Special ID for emergency stop 925 | stopData.distance = 0; 926 | stopData.timestamp = millis(); 927 | stopData.brightness = 0; 928 | 929 | for (int i = 0; i < numSlaveDevices; i++) { 930 | esp_now_send(slaveAddresses[i], (uint8_t*)&stopData, sizeof(led_segment_data_t)); 931 | delay(5); 932 | } 933 | } else { 934 | // Slave emergency stop 935 | strip.clear(); 936 | strip.show(); 937 | } 938 | } 939 | 940 | // Process emergency stop (for slaves) 941 | void processEmergencyStop() { 942 | Serial.println("ESP-NOW: Emergency stop received"); 943 | strip.clear(); 944 | strip.show(); 945 | } 946 | 947 | // Enhanced packet loss detection 948 | void checkPacketLoss() { 949 | if (deviceRole != DEVICE_ROLE_MASTER) return; 950 | 951 | unsigned long currentTime = millis(); 952 | static unsigned long lastPacketLossCheck = 0; 953 | 954 | if (currentTime - lastPacketLossCheck > 30000) { // Check every 30 seconds 955 | lastPacketLossCheck = currentTime; 956 | 957 | for (int i = 1; i <= numSlaveDevices; i++) { 958 | unsigned long timeSinceLastPacket = currentTime - slaveHealth[i].lastReceived; 959 | 960 | if (timeSinceLastPacket > 60000) { // No packet for 1 minute 961 | Serial.printf("ESP-NOW: WARNING - Sensor %d has been offline for %lu seconds\n", 962 | i, timeSinceLastPacket / 1000); 963 | 964 | // Mark as unhealthy 965 | slaveHealth[i].isHealthy = false; 966 | 967 | // Consider automatic slave removal after extended offline period 968 | if (timeSinceLastPacket > 300000) { // 5 minutes 969 | Serial.printf("ESP-NOW: Considering removal of sensor %d (offline for 5+ minutes)\n", i); 970 | } 971 | } 972 | } 973 | } 974 | } 975 | 976 | // Auto-discovery mode for new slaves 977 | void startSlaveDiscovery(unsigned long duration) { 978 | if (deviceRole != DEVICE_ROLE_MASTER) return; 979 | 980 | Serial.printf("ESP-NOW: Starting slave discovery for %lu seconds\n", duration / 1000); 981 | 982 | // Implementation would involve listening for discovery packets 983 | // and automatically adding responsive slaves 984 | // This is a framework for future enhancement 985 | } 986 | 987 | // Network performance metrics 988 | void getNetworkMetrics(uint32_t* totalPacketsReceived, uint32_t* totalPacketsLost, 989 | float* averageRSSI) { 990 | *totalPacketsReceived = 0; 991 | *totalPacketsLost = 0; 992 | *averageRSSI = 0; 993 | 994 | int activeSensors = 0; 995 | 996 | for (int i = 0; i <= numSlaveDevices; i++) { 997 | *totalPacketsReceived += slaveHealth[i].packetsReceived; 998 | *totalPacketsLost += slaveHealth[i].packetsLost; 999 | 1000 | if (slaveHealth[i].isHealthy) { 1001 | activeSensors++; 1002 | } 1003 | } 1004 | 1005 | // RSSI would need to be tracked separately in real implementation 1006 | *averageRSSI = -50.0; // Placeholder 1007 | } 1008 | 1009 | // Periodic maintenance function - call this from main loop 1010 | void espnowMaintenance() { 1011 | static unsigned long lastMaintenance = 0; 1012 | unsigned long currentTime = millis(); 1013 | 1014 | if (currentTime - lastMaintenance > 5000) { // Every 5 seconds 1015 | lastMaintenance = currentTime; 1016 | 1017 | checkConnectionHealth(); 1018 | checkPacketLoss(); 1019 | 1020 | // Additional maintenance tasks can be added here 1021 | } 1022 | } -------------------------------------------------------------------------------- /AmbiSense/espnow_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPNOW_MANAGER_H 2 | #define ESPNOW_MANAGER_H 3 | 4 | #include 5 | #include 6 | #include "config.h" 7 | 8 | // Data structure for ESP-NOW communication 9 | typedef struct sensor_data_t { 10 | uint8_t sensorId; // Identifier for the sending sensor 11 | int distance; // Distance reading in cm 12 | int8_t direction; // -1: moving closer, 0: stationary, 1: moving away 13 | uint8_t battery; // Battery level (if applicable) 14 | uint32_t timestamp; // Milliseconds since boot 15 | } sensor_data_t; 16 | 17 | typedef struct led_segment_data_t { 18 | uint8_t sensorId; 19 | int distance; 20 | int8_t direction; 21 | uint32_t timestamp; 22 | 23 | // LED segment control 24 | int startLed; // Which LED this device should start from 25 | int segmentLength; // How many LEDs this device controls 26 | int totalLeds; // Total LEDs in the system 27 | uint8_t lightMode; // Current light mode 28 | uint8_t brightness; // Current brightness 29 | uint8_t redValue; 30 | uint8_t greenValue; 31 | uint8_t blueValue; 32 | } led_segment_data_t; 33 | 34 | extern sensor_data_t latestSensorData[MAX_SLAVE_DEVICES + 1]; 35 | 36 | /** 37 | * Initialize ESP-NOW communication with improved error handling 38 | * Sets up callbacks and configures peers based on device role 39 | */ 40 | void setupESPNOW(); 41 | 42 | /** 43 | * Configure master device peers 44 | */ 45 | void configureMasterPeers(); 46 | 47 | /** 48 | * Configure slave device peer 49 | */ 50 | void configureSlavePeer(); 51 | 52 | /** 53 | * Send test message from slave to master 54 | */ 55 | void sendTestMessage(); 56 | 57 | /** 58 | * Initialize connection health monitoring 59 | */ 60 | void initializeConnectionHealth(); 61 | 62 | /** 63 | * Update connection health for a sensor 64 | * @param sensorId The sensor ID to update 65 | */ 66 | void updateConnectionHealth(uint8_t sensorId); 67 | 68 | /** 69 | * Check connection health and log issues 70 | */ 71 | void checkConnectionHealth(); 72 | 73 | /** 74 | * Send sensor data from slave to master with retry logic 75 | * @param distance The current distance reading 76 | * @param direction The detected direction of movement 77 | */ 78 | void sendSensorData(int distance, int8_t direction); 79 | 80 | /** 81 | * Process received sensor data (called by master device) 82 | * @param sensorData The sensor data structure received 83 | */ 84 | void processSensorData(sensor_data_t sensorData); 85 | 86 | /** 87 | * Update LEDs with combined sensor data using enhanced algorithms 88 | */ 89 | void updateLEDsWithMultiSensorData(); 90 | 91 | /** 92 | * Priority mode handlers 93 | */ 94 | int handleMostRecentPriority(unsigned long currentTime); 95 | int handleSlaveFirstPriority(unsigned long currentTime); 96 | int handleMasterFirstPriority(unsigned long currentTime); 97 | int handleZoneBasedPriority(unsigned long currentTime); 98 | 99 | /** 100 | * Set the sensor priority mode 101 | * @param mode The priority mode to set (0-3) 102 | */ 103 | void setSensorPriorityMode(uint8_t mode); 104 | 105 | /** 106 | * Get the current sensor priority mode 107 | * @return The current priority mode 108 | */ 109 | uint8_t getSensorPriorityMode(); 110 | 111 | /** 112 | * LED Distribution Mode functions 113 | * Note: These call functions from eeprom_manager for persistence 114 | */ 115 | void setLEDSegmentMode(int mode); 116 | int getLEDSegmentMode(); 117 | void setLEDSegmentInfo(int start, int length, int total); 118 | void getLEDSegmentInfo(int* start, int* length, int* total); 119 | 120 | /** 121 | * Send LED segment data to slaves (for distributed mode) 122 | * @param distance Current distance reading 123 | * @param globalStartPos Global LED position 124 | */ 125 | void sendLEDSegmentData(int distance, int globalStartPos); 126 | 127 | /** 128 | * Process LED segment data (on slave devices) 129 | * @param segmentData LED segment data structure 130 | */ 131 | void processLEDSegmentData(struct led_segment_data_t segmentData); 132 | 133 | /** 134 | * Update LED segment for distributed mode 135 | * @param globalStartPos Global LED start position 136 | * @param segmentData LED segment data 137 | */ 138 | void updateLEDSegment(int globalStartPos, struct led_segment_data_t segmentData); 139 | 140 | /** 141 | * Get connection health status for diagnostics 142 | * @param sensorId The sensor ID to check 143 | * @return True if sensor is healthy 144 | */ 145 | bool getSensorHealth(uint8_t sensorId); 146 | 147 | /** 148 | * Get packet statistics for diagnostics 149 | * @param sensorId The sensor ID to check 150 | * @return Number of packets received 151 | */ 152 | uint32_t getSensorPacketCount(uint8_t sensorId); 153 | 154 | /** 155 | * Get last received time for diagnostics 156 | * @param sensorId The sensor ID to check 157 | * @return Last received timestamp 158 | */ 159 | unsigned long getSensorLastReceived(uint8_t sensorId); 160 | 161 | /** 162 | * Reset ESP-NOW and reinitialize 163 | */ 164 | void resetESPNOW(); 165 | 166 | /** 167 | * Print diagnostic information 168 | */ 169 | void printESPNOWDiagnostics(); 170 | 171 | /** 172 | * Synchronize all devices (master only) 173 | */ 174 | void synchronizeAllDevices(); 175 | 176 | /** 177 | * Get system status for web interface 178 | * @param statusJson Buffer to store JSON status 179 | * @param maxLength Maximum buffer length 180 | */ 181 | void getSystemStatus(char* statusJson, size_t maxLength); 182 | 183 | /** 184 | * Emergency stop all LEDs across the network 185 | */ 186 | void emergencyStopAllLEDs(); 187 | 188 | /** 189 | * Process emergency stop (for slaves) 190 | */ 191 | void processEmergencyStop(); 192 | 193 | /** 194 | * Enhanced packet loss detection 195 | */ 196 | void checkPacketLoss(); 197 | 198 | /** 199 | * Auto-discovery mode for new slaves 200 | * @param duration Discovery duration in milliseconds 201 | */ 202 | void startSlaveDiscovery(unsigned long duration); 203 | 204 | /** 205 | * Network performance metrics 206 | * @param totalPacketsReceived Total packets received across all sensors 207 | * @param totalPacketsLost Total packets lost across all sensors 208 | * @param averageRSSI Average signal strength 209 | */ 210 | void getNetworkMetrics(uint32_t* totalPacketsReceived, uint32_t* totalPacketsLost, 211 | float* averageRSSI); 212 | 213 | /** 214 | * Periodic maintenance function - call this from main loop 215 | */ 216 | void espnowMaintenance(); 217 | 218 | // Callback for ESP-NOW data receive 219 | void OnDataReceive(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len); 220 | 221 | // Callback for ESP-NOW data sent 222 | void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status); 223 | 224 | #endif // ESPNOW_MANAGER_H -------------------------------------------------------------------------------- /AmbiSense/led_controller.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // Add this missing include 3 | #include "config.h" 4 | #include "led_controller.h" 5 | 6 | // Initialize LED strip - make it global and accessible from other modules 7 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(DEFAULT_NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800); 8 | 9 | // Variables for tracking light effects and animations 10 | unsigned long lastUpdateTime = 0; 11 | int effectStep = 0; 12 | int lastDirection = 0; 13 | int lastPosition = 0; 14 | 15 | // Track current LED configuration 16 | static int currentConfiguredLeds = DEFAULT_NUM_LEDS; 17 | 18 | void setupLEDs() { 19 | Serial.println("Initializing LED strip"); 20 | strip.begin(); 21 | strip.setBrightness(brightness); 22 | strip.show(); // Initialize all pixels to 'off' 23 | currentConfiguredLeds = numLeds; 24 | Serial.printf("LED strip initialized with %d LEDs\n", numLeds); 25 | } 26 | 27 | void updateLEDConfig() { 28 | if (ENABLE_DEBUG_LOGGING) { 29 | Serial.printf("Updating LED configuration from %d to %d LEDs\n", 30 | currentConfiguredLeds, numLeds); 31 | } 32 | 33 | // Check if LED count actually changed 34 | if (currentConfiguredLeds != numLeds) { 35 | Serial.printf("LED count changed: %d -> %d. Reinitializing strip...\n", 36 | currentConfiguredLeds, numLeds); 37 | 38 | // Clear the current strip 39 | strip.clear(); 40 | strip.show(); 41 | 42 | // CRITICAL FIX: Properly reinitialize the strip with new LED count 43 | // The updateLength() method doesn't work reliably, so we recreate the object 44 | strip = Adafruit_NeoPixel(numLeds, LED_PIN, NEO_GRB + NEO_KHZ800); 45 | strip.begin(); 46 | strip.setBrightness(brightness); 47 | strip.clear(); 48 | strip.show(); 49 | 50 | currentConfiguredLeds = numLeds; 51 | 52 | Serial.printf("LED strip reinitialized successfully with %d LEDs\n", numLeds); 53 | 54 | // Test pattern to verify all LEDs work 55 | if (ENABLE_DEBUG_LOGGING) { 56 | Serial.println("Running LED test pattern..."); 57 | for (int i = 0; i < min(numLeds, 10); i++) { 58 | strip.setPixelColor(i, strip.Color(255, 0, 0)); // Red test 59 | strip.show(); 60 | delay(50); 61 | } 62 | strip.clear(); 63 | strip.show(); 64 | Serial.println("LED test pattern complete"); 65 | } 66 | } else { 67 | // Just update brightness if LED count didn't change 68 | strip.setBrightness(brightness); 69 | } 70 | } 71 | 72 | // Enhanced LED validation 73 | bool validateLEDCount(int requestedLeds) { 74 | if (requestedLeds < 1) { 75 | Serial.println("ERROR: LED count cannot be less than 1"); 76 | return false; 77 | } 78 | 79 | if (requestedLeds > MAX_SUPPORTED_LEDS) { 80 | Serial.printf("ERROR: LED count %d exceeds maximum supported %d\n", 81 | requestedLeds, MAX_SUPPORTED_LEDS); 82 | return false; 83 | } 84 | 85 | // Check available memory for LED buffer 86 | size_t ledMemoryRequired = requestedLeds * 3; // 3 bytes per LED (RGB) 87 | if (ledMemoryRequired > ESP.getFreeHeap() / 4) { // Use max 25% of free heap 88 | Serial.printf("ERROR: Insufficient memory for %d LEDs (need %d bytes)\n", 89 | requestedLeds, ledMemoryRequired); 90 | return false; 91 | } 92 | 93 | return true; 94 | } 95 | 96 | // Force LED strip reinitialization 97 | void reinitializeLEDStrip(int newLedCount) { 98 | if (!validateLEDCount(newLedCount)) { 99 | Serial.printf("Cannot reinitialize with %d LEDs, keeping current count %d\n", 100 | newLedCount, numLeds); 101 | return; 102 | } 103 | 104 | Serial.printf("Force reinitializing LED strip: %d -> %d LEDs\n", 105 | currentConfiguredLeds, newLedCount); 106 | 107 | // Clear current strip 108 | strip.clear(); 109 | strip.show(); 110 | delay(10); 111 | 112 | // Update global variable 113 | numLeds = newLedCount; 114 | 115 | // Recreate the strip object completely 116 | strip = Adafruit_NeoPixel(numLeds, LED_PIN, NEO_GRB + NEO_KHZ800); 117 | strip.begin(); 118 | strip.setBrightness(brightness); 119 | strip.clear(); 120 | strip.show(); 121 | 122 | currentConfiguredLeds = numLeds; 123 | 124 | Serial.printf("LED strip successfully reinitialized with %d LEDs\n", numLeds); 125 | 126 | // Save to EEPROM 127 | EEPROM.write(EEPROM_ADDR_NUM_LEDS_L, numLeds & 0xFF); 128 | EEPROM.write(EEPROM_ADDR_NUM_LEDS_H, (numLeds >> 8) & 0xFF); 129 | EEPROM.commit(); 130 | } 131 | 132 | // Helper function to create a color with a specific intensity 133 | uint32_t dimColor(uint8_t r, uint8_t g, uint8_t b, float intensity) { 134 | return strip.Color( 135 | (uint8_t)(r * intensity), 136 | (uint8_t)(g * intensity), 137 | (uint8_t)(b * intensity) 138 | ); 139 | } 140 | 141 | // Rainbow color wheel function 142 | uint32_t wheelColor(byte wheelPos) { 143 | wheelPos = 255 - wheelPos; 144 | if (wheelPos < 85) { 145 | return strip.Color(255 - wheelPos * 3, 0, wheelPos * 3); 146 | } 147 | if (wheelPos < 170) { 148 | wheelPos -= 85; 149 | return strip.Color(0, wheelPos * 3, 255 - wheelPos * 3); 150 | } 151 | wheelPos -= 170; 152 | return strip.Color(wheelPos * 3, 255 - wheelPos * 3, 0); 153 | } 154 | 155 | // Helper function: Subtract with 8-bit saturation 156 | uint8_t qsub8(uint8_t a, uint8_t b) { 157 | int result = (int)a - (int)b; 158 | if (result < 0) result = 0; 159 | return (uint8_t)result; 160 | } 161 | 162 | // Helper function: Add with 8-bit saturation 163 | uint8_t qadd8(uint8_t a, uint8_t b) { 164 | int result = (int)a + (int)b; 165 | if (result > 255) result = 255; 166 | return (uint8_t)result; 167 | } 168 | 169 | // Helper function: Generate random 8-bit number 170 | uint8_t random8() { 171 | return random(256); 172 | } 173 | 174 | // Helper function: Generate random number in range 175 | uint8_t random8(uint8_t lim) { 176 | return random(lim); 177 | } 178 | 179 | // Helper function: Generate random number in range 180 | uint8_t random8(uint8_t min, uint8_t lim) { 181 | return random(min, lim); 182 | } 183 | 184 | // Update LEDs based on distance reading and current mode 185 | void updateLEDs(int distance) { 186 | // Validate LED count before updating 187 | if (numLeds <= 0 || numLeds > MAX_SUPPORTED_LEDS) { 188 | Serial.printf("ERROR: Invalid LED count %d, skipping update\n", numLeds); 189 | return; 190 | } 191 | 192 | // Update direction based on distance change 193 | int direction = 0; 194 | if (distance < lastPosition) { 195 | direction = -1; // Moving closer 196 | } else if (distance > lastPosition) { 197 | direction = 1; // Moving away 198 | } 199 | 200 | // Update last position if there's a significant change 201 | if (abs(distance - lastPosition) > 5) { 202 | lastPosition = distance; 203 | lastDirection = direction; 204 | } 205 | 206 | // Calculate the LED start position based on current distance 207 | int startLed = map(distance, minDistance, maxDistance, 0, numLeds - movingLightSpan); 208 | startLed = constrain(startLed, 0, numLeds - movingLightSpan); 209 | 210 | // Apply center shift adjustment 211 | startLed += centerShift; 212 | 213 | // Handle different light modes 214 | switch (lightMode) { 215 | case LIGHT_MODE_STANDARD: 216 | updateStandardMode(startLed); 217 | break; 218 | 219 | case LIGHT_MODE_RAINBOW: 220 | updateRainbowMode(); 221 | break; 222 | 223 | case LIGHT_MODE_COLOR_WAVE: 224 | updateColorWaveMode(); 225 | break; 226 | 227 | case LIGHT_MODE_BREATHING: 228 | updateBreathingMode(); 229 | break; 230 | 231 | case LIGHT_MODE_SOLID: 232 | updateSolidMode(); 233 | break; 234 | 235 | case LIGHT_MODE_COMET: 236 | updateCometMode(startLed); 237 | break; 238 | 239 | case LIGHT_MODE_PULSE: 240 | updatePulseMode(startLed); 241 | break; 242 | 243 | case LIGHT_MODE_FIRE: 244 | updateFireMode(); 245 | break; 246 | 247 | case LIGHT_MODE_THEATER_CHASE: 248 | updateTheaterChaseMode(); 249 | break; 250 | 251 | case LIGHT_MODE_DUAL_SCAN: 252 | updateDualScanMode(startLed); 253 | break; 254 | 255 | case LIGHT_MODE_MOTION_PARTICLES: 256 | updateMotionParticlesMode(startLed); 257 | break; 258 | 259 | default: 260 | updateStandardMode(startLed); 261 | break; 262 | } 263 | 264 | // Update effect step for animations 265 | effectStep = (effectStep + 1) % 256; 266 | } 267 | 268 | // Forward declarations for functions used in ESP-NOW 269 | void processLEDSegmentData(led_segment_data_t segmentData); 270 | void updateLEDSegment(int globalStartPos, led_segment_data_t segmentData); 271 | 272 | // Update LEDs in standard mode (based on distance) 273 | void updateStandardMode(int startLed) { 274 | strip.clear(); 275 | 276 | // If background mode is enabled, set all LEDs to a dim background color 277 | if (backgroundMode) { 278 | float backgroundIntensity = 0.05; // Low background light 279 | for (int i = 0; i < numLeds; i++) { 280 | strip.setPixelColor(i, dimColor(redValue, greenValue, blueValue, backgroundIntensity)); 281 | } 282 | } 283 | 284 | // Set moving light 285 | for (int i = startLed; i < startLed + movingLightSpan; i++) { 286 | if (i >= 0 && i < numLeds) { 287 | strip.setPixelColor(i, strip.Color(redValue, greenValue, blueValue)); 288 | } 289 | } 290 | 291 | // Add directional trailing effect if enabled 292 | if (directionLightEnabled && trailLength > 0 && lastDirection != 0) { 293 | int trailStartPos = (lastDirection > 0) ? startLed - trailLength : startLed + movingLightSpan; 294 | int trailEndPos = (lastDirection > 0) ? startLed : startLed + movingLightSpan + trailLength; 295 | 296 | for (int i = trailStartPos; i < trailEndPos; i++) { 297 | if (i >= 0 && i < numLeds) { 298 | // Calculate fade based on position in trail 299 | float fadePercent = (float)abs(i - (lastDirection > 0 ? startLed : startLed + movingLightSpan)) / trailLength; 300 | fadePercent = constrain(fadePercent, 0.0, 1.0); 301 | fadePercent = 1.0 - fadePercent; // Invert so it fades away from main light 302 | 303 | strip.setPixelColor(i, dimColor(redValue, greenValue, blueValue, fadePercent * 0.8)); 304 | } 305 | } 306 | } 307 | 308 | strip.show(); 309 | } 310 | 311 | // Update LEDs in rainbow mode 312 | void updateRainbowMode() { 313 | int animationSpeed = map(effectSpeed, 1, 100, 1, 10); 314 | for (int i = 0; i < numLeds; i++) { 315 | int colorIndex = (i + (effectStep * animationSpeed)) % 256; 316 | strip.setPixelColor(i, wheelColor(colorIndex)); 317 | } 318 | strip.show(); 319 | } 320 | 321 | // Update LEDs in color wave mode 322 | void updateColorWaveMode() { 323 | // Map effectSpeed (1-100) to a reasonable animation speed value (1-10) 324 | int animationSpeed = map(effectSpeed, 1, 100, 1, 10); 325 | 326 | for (int i = 0; i < numLeds; i++) { 327 | // Create sine wave pattern for intensity 328 | float wave = sin((i + (effectStep * animationSpeed)) * 0.1) * 0.5 + 0.5; 329 | 330 | // Create color wave 331 | int colorIndex = (i * 3 + (effectStep * animationSpeed)) % 256; 332 | uint32_t baseColor = wheelColor(colorIndex); 333 | 334 | // Extract RGB components 335 | uint8_t r = (baseColor >> 16) & 0xFF; 336 | uint8_t g = (baseColor >> 8) & 0xFF; 337 | uint8_t b = baseColor & 0xFF; 338 | 339 | // Apply wave intensity modulated by effectIntensity 340 | float intensity = wave * (effectIntensity / 100.0); 341 | strip.setPixelColor(i, dimColor(r, g, b, intensity)); 342 | } 343 | strip.show(); 344 | } 345 | 346 | // Update LEDs in breathing mode 347 | void updateBreathingMode() { 348 | // Map effectSpeed (1-100) to breathing speed (1-10) 349 | int breathSpeed = map(effectSpeed, 1, 100, 1, 10); 350 | 351 | // Map effectIntensity (1-100) to intensity multiplier (0.1-1.0) 352 | float intensityMultiplier = map(effectIntensity, 1, 100, 10, 100) / 100.0; 353 | 354 | // Calculate breathing effect using sine wave 355 | float breath = sin(effectStep * 0.05 * breathSpeed) * 0.5 + 0.5; 356 | breath *= intensityMultiplier; // Apply intensity multiplier 357 | 358 | for (int i = 0; i < numLeds; i++) { 359 | strip.setPixelColor(i, dimColor(redValue, greenValue, blueValue, breath)); 360 | } 361 | strip.show(); 362 | } 363 | 364 | // Update LEDs in solid color mode 365 | void updateSolidMode() { 366 | for (int i = 0; i < numLeds; i++) { 367 | strip.setPixelColor(i, strip.Color(redValue, greenValue, blueValue)); 368 | } 369 | strip.show(); 370 | } 371 | 372 | // Comet effect: trailing gradient that follows motion 373 | void updateCometMode(int startLed) { 374 | // Map effectSpeed (1-100) to a reasonable tail length (10-50) 375 | int tailLength = map(effectSpeed, 1, 100, 10, 50); 376 | 377 | // Map effectIntensity (1-100) to a fade factor (0.75-0.98) 378 | float fadeFactor = map(effectIntensity, 1, 100, 75, 98) / 100.0; 379 | 380 | // First, dim all LEDs slightly (creates the tail fade effect) 381 | for (int i = 0; i < numLeds; i++) { 382 | uint32_t color = strip.getPixelColor(i); 383 | 384 | // Extract RGB components 385 | uint8_t r = (color >> 16) & 0xFF; 386 | uint8_t g = (color >> 8) & 0xFF; 387 | uint8_t b = color & 0xFF; 388 | 389 | // Apply fade factor 390 | r = r * fadeFactor; 391 | g = g * fadeFactor; 392 | b = b * fadeFactor; 393 | 394 | strip.setPixelColor(i, strip.Color(r, g, b)); 395 | } 396 | 397 | // Add the new "head" of the comet 398 | if (startLed >= 0 && startLed < numLeds) { 399 | strip.setPixelColor(startLed, strip.Color(redValue, greenValue, blueValue)); 400 | } 401 | 402 | strip.show(); 403 | } 404 | 405 | // Pulse effect: pulses emanate from the motion point 406 | void updatePulseMode(int startLed) { 407 | strip.clear(); 408 | 409 | // Map effectSpeed (1-100) to pulse speed (1-10) 410 | int pulseSpeed = map(effectSpeed, 1, 100, 1, 10); 411 | 412 | // Map effectIntensity (1-100) to maximum pulse radius (10-50) 413 | int maxRadius = map(effectIntensity, 1, 100, 10, 50); 414 | 415 | // Create multiple pulses 416 | for (int pulse = 0; pulse < 3; pulse++) { 417 | // Calculate pulse radius based on effectStep 418 | int radius = (effectStep * pulseSpeed + (pulse * 85)) % (maxRadius * 2); 419 | if (radius > maxRadius) radius = (maxRadius * 2) - radius; // Reflect back 420 | 421 | // Draw the pulse 422 | for (int i = startLed - radius; i <= startLed + radius; i++) { 423 | if (i >= 0 && i < numLeds) { 424 | // Fade intensity based on distance from center 425 | float distFactor = 1.0 - abs(i - startLed) / (float)radius; 426 | distFactor = distFactor * distFactor; // Square for nicer falloff 427 | 428 | uint8_t r = redValue * distFactor; 429 | uint8_t g = greenValue * distFactor; 430 | uint8_t b = blueValue * distFactor; 431 | 432 | strip.setPixelColor(i, strip.Color(r, g, b)); 433 | } 434 | } 435 | } 436 | 437 | strip.show(); 438 | } 439 | 440 | // Fire effect: simulates flickering flames 441 | void updateFireMode() { 442 | // Map effectSpeed (1-100) to fire speed (1-20) 443 | int fireSpeed = map(effectSpeed, 1, 100, 1, 20); 444 | 445 | // Map effectIntensity (1-100) to cooling rate (20-100) 446 | int cooling = map(effectIntensity, 1, 100, 20, 100); 447 | 448 | // Dynamic array allocation for heat values 449 | static uint8_t* heat = nullptr; 450 | static int allocatedLeds = 0; 451 | 452 | // Reallocate heat array if LED count changed 453 | if (allocatedLeds != numLeds) { 454 | if (heat != nullptr) { 455 | free(heat); 456 | } 457 | heat = (uint8_t*)malloc(numLeds * sizeof(uint8_t)); 458 | if (heat == nullptr) { 459 | Serial.println("ERROR: Cannot allocate memory for fire effect"); 460 | return; 461 | } 462 | // Initialize heat array 463 | for (int i = 0; i < numLeds; i++) { 464 | heat[i] = 0; 465 | } 466 | allocatedLeds = numLeds; 467 | Serial.printf("Allocated fire effect buffer for %d LEDs\n", numLeds); 468 | } 469 | 470 | // Step 1: Cool down every cell a little 471 | for (int i = 0; i < numLeds; i++) { 472 | heat[i] = qsub8(heat[i], random(0, ((cooling * 10) / numLeds) + 2)); 473 | } 474 | 475 | // Step 2: Heat rises - transfer heat from each cell to the one above 476 | for (int k = numLeds - 1; k >= 2; k--) { 477 | heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3; 478 | } 479 | 480 | // Step 3: Randomly ignite new sparks near the bottom 481 | if (random8() < fireSpeed) { 482 | int y = random8(min(7, numLeds / 4)); // Scale spark area with LED count 483 | heat[y] = qadd8(heat[y], random8(160, 255)); 484 | } 485 | 486 | // Step 4: Convert heat to LED colors 487 | for (int j = 0; j < numLeds; j++) { 488 | // Map heat level to color palette 489 | uint8_t t = heat[j]; 490 | uint32_t color; 491 | 492 | if (t > 240) { // White (hottest) 493 | color = strip.Color(255, 255, 255); 494 | } else if (t > 210) { // Yellow 495 | color = strip.Color(255, 255, t - 210); 496 | } else if (t > 140) { // Orange 497 | color = strip.Color(255, map(t, 140, 210, 0, 255), 0); 498 | } else { // Red (coolest fire) 499 | color = strip.Color(map(t, 0, 140, 0, 255), 0, 0); 500 | } 501 | 502 | strip.setPixelColor(j, color); 503 | } 504 | 505 | strip.show(); 506 | } 507 | 508 | // Theater chase effect: alternating on/off lights that move 509 | void updateTheaterChaseMode() { 510 | // Map effectSpeed (1-100) to chase speed (1-10) 511 | int chaseSpeed = map(effectSpeed, 1, 100, 1, 10); 512 | 513 | // Map effectIntensity (1-100) to gap size (1-5) 514 | int gapSize = map(effectIntensity, 1, 100, 1, 5); 515 | 516 | strip.clear(); 517 | 518 | // Calculate chase position based on effectStep 519 | int pos = (effectStep * chaseSpeed) % (gapSize * 2); 520 | 521 | // Set every nth LED based on the current chase position 522 | for (int i = 0; i < numLeds; i++) { 523 | if ((i + pos) % (gapSize * 2) < gapSize) { 524 | strip.setPixelColor(i, strip.Color(redValue, greenValue, blueValue)); 525 | } 526 | } 527 | 528 | strip.show(); 529 | } 530 | 531 | // Dual scan effect: two scanning lights that move in opposite directions 532 | void updateDualScanMode(int startLed) { 533 | // Map effectSpeed (1-100) to scan speed (1-10) 534 | int scanSpeed = map(effectSpeed, 1, 100, 1, 10); 535 | 536 | // Map effectIntensity (1-100) to scan width (1-20) 537 | int scanWidth = map(effectIntensity, 1, 100, 1, 20); 538 | 539 | strip.clear(); 540 | 541 | // Calculate scan position based on effectStep 542 | int pos1 = (effectStep * scanSpeed) % numLeds; 543 | int pos2 = numLeds - 1 - pos1; // Opposite direction 544 | 545 | // Create two moving scan beams 546 | for (int i = 0; i < scanWidth; i++) { 547 | int p1 = pos1 + i; 548 | int p2 = pos2 - i; 549 | 550 | if (p1 >= 0 && p1 < numLeds) { 551 | float fade = 1.0 - (float)i / scanWidth; 552 | strip.setPixelColor(p1, dimColor(redValue, greenValue, blueValue, fade)); 553 | } 554 | 555 | if (p2 >= 0 && p2 < numLeds) { 556 | float fade = 1.0 - (float)i / scanWidth; 557 | strip.setPixelColor(p2, dimColor(redValue, greenValue, blueValue, fade)); 558 | } 559 | } 560 | 561 | // Add brighter point at the tracked motion position 562 | if (startLed >= 0 && startLed < numLeds) { 563 | strip.setPixelColor(startLed, strip.Color(redValue, greenValue, blueValue)); 564 | } 565 | 566 | strip.show(); 567 | } 568 | 569 | // Motion particles effect: particles that spawn from the motion point 570 | void updateMotionParticlesMode(int startLed) { 571 | // Define particle structure 572 | struct Particle { 573 | float position; 574 | float velocity; 575 | float brightness; 576 | bool active; 577 | }; 578 | 579 | // Static array to hold particles - scale with LED count 580 | static Particle* particles = nullptr; 581 | static int maxParticles = 0; 582 | 583 | // Reallocate particle array if needed 584 | int neededParticles = min(50, numLeds / 10); // Scale particles with LED count 585 | if (maxParticles != neededParticles) { 586 | if (particles != nullptr) { 587 | free(particles); 588 | } 589 | particles = (Particle*)malloc(neededParticles * sizeof(Particle)); 590 | if (particles == nullptr) { 591 | Serial.println("ERROR: Cannot allocate memory for particles"); 592 | return; 593 | } 594 | 595 | // Initialize particles 596 | for (int i = 0; i < neededParticles; i++) { 597 | particles[i].active = false; 598 | } 599 | maxParticles = neededParticles; 600 | Serial.printf("Allocated particle buffer for %d particles\n", maxParticles); 601 | } 602 | 603 | // Map effectSpeed (1-100) to particle speed (0.5-3.0) 604 | float particleMaxSpeed = map(effectSpeed, 1, 100, 50, 300) / 100.0; 605 | 606 | // Map effectIntensity (1-100) to spawn rate (1-10) 607 | int spawnRate = map(effectIntensity, 1, 100, 1, 10); 608 | 609 | // Spawn new particles from the motion point 610 | for (int i = 0; i < spawnRate; i++) { 611 | if (random(100) < 30) { // 30% chance to spawn a new particle 612 | // Find an inactive particle slot 613 | for (int j = 0; j < maxParticles; j++) { 614 | if (!particles[j].active) { 615 | particles[j].position = startLed; 616 | particles[j].velocity = (random(100) / 100.0 * 2.0 - 1.0) * particleMaxSpeed; // Random direction 617 | particles[j].brightness = 1.0; 618 | particles[j].active = true; 619 | break; 620 | } 621 | } 622 | } 623 | } 624 | 625 | // Clear the strip 626 | strip.clear(); 627 | 628 | // Update and draw particles 629 | for (int i = 0; i < maxParticles; i++) { 630 | if (particles[i].active) { 631 | // Update position 632 | particles[i].position += particles[i].velocity; 633 | particles[i].brightness -= 0.02; // Fade out 634 | 635 | // Deactivate if off strip or too dim 636 | if (particles[i].position < 0 || particles[i].position >= numLeds || particles[i].brightness <= 0) { 637 | particles[i].active = false; 638 | continue; 639 | } 640 | 641 | // Draw particle 642 | int pos = (int)particles[i].position; 643 | if (pos >= 0 && pos < numLeds) { 644 | strip.setPixelColor(pos, dimColor(redValue, greenValue, blueValue, particles[i].brightness)); 645 | } 646 | } 647 | } 648 | 649 | strip.show(); 650 | } 651 | 652 | // Get current configured LED count 653 | int getCurrentLEDCount() { 654 | return currentConfiguredLeds; 655 | } 656 | 657 | // Test all LEDs function 658 | void testAllLEDs() { 659 | Serial.printf("Testing all %d LEDs...\n", numLeds); 660 | 661 | // Red sweep 662 | for (int i = 0; i < numLeds; i++) { 663 | strip.setPixelColor(i, strip.Color(255, 0, 0)); 664 | if (i % 10 == 0) { 665 | strip.show(); 666 | delay(10); 667 | } 668 | } 669 | strip.show(); 670 | delay(500); 671 | 672 | // Green sweep 673 | for (int i = 0; i < numLeds; i++) { 674 | strip.setPixelColor(i, strip.Color(0, 255, 0)); 675 | if (i % 10 == 0) { 676 | strip.show(); 677 | delay(10); 678 | } 679 | } 680 | strip.show(); 681 | delay(500); 682 | 683 | // Blue sweep 684 | for (int i = 0; i < numLeds; i++) { 685 | strip.setPixelColor(i, strip.Color(0, 0, 255)); 686 | if (i % 10 == 0) { 687 | strip.show(); 688 | delay(10); 689 | } 690 | } 691 | strip.show(); 692 | delay(500); 693 | 694 | // Clear all 695 | strip.clear(); 696 | strip.show(); 697 | 698 | Serial.println("LED test complete"); 699 | } -------------------------------------------------------------------------------- /AmbiSense/led_controller.h: -------------------------------------------------------------------------------- 1 | #ifndef LED_CONTROLLER_H 2 | #define LED_CONTROLLER_H 3 | 4 | #include 5 | #include "config.h" 6 | 7 | // Forward declaration for ESP-NOW LED segment data structure 8 | struct led_segment_data_t; 9 | 10 | // Maximum supported LEDs (can be increased based on available memory) 11 | #define MAX_SUPPORTED_LEDS 2000 12 | 13 | // Make the LED strip available to other modules 14 | extern Adafruit_NeoPixel strip; 15 | extern int numLeds; 16 | 17 | /** 18 | * Initialize the LED strip 19 | */ 20 | void setupLEDs(); 21 | 22 | /** 23 | * Update LED strip configuration (number of LEDs, brightness) 24 | * Called after settings are loaded or changed 25 | */ 26 | void updateLEDConfig(); 27 | 28 | /** 29 | * Validate LED count against memory and hardware limits 30 | * @param requestedLeds Number of LEDs to validate 31 | * @return true if valid, false otherwise 32 | */ 33 | bool validateLEDCount(int requestedLeds); 34 | 35 | /** 36 | * Force LED strip reinitialization with new LED count 37 | * @param newLedCount New number of LEDs 38 | */ 39 | void reinitializeLEDStrip(int newLedCount); 40 | 41 | /** 42 | * Get current configured LED count 43 | * @return Currently configured LED count 44 | */ 45 | int getCurrentLEDCount(); 46 | 47 | /** 48 | * Test all LEDs with color sweep 49 | */ 50 | void testAllLEDs(); 51 | 52 | /** 53 | * Update LEDs based on distance reading 54 | * @param distance The current distance reading from the radar sensor 55 | */ 56 | void updateLEDs(int distance); 57 | 58 | /** 59 | * Update LEDs in standard mode (based on distance) 60 | * @param startLed The starting LED position based on distance 61 | */ 62 | void updateStandardMode(int startLed); 63 | 64 | /** 65 | * Update LEDs in rainbow mode 66 | */ 67 | void updateRainbowMode(); 68 | 69 | /** 70 | * Update LEDs in color wave mode 71 | */ 72 | void updateColorWaveMode(); 73 | 74 | /** 75 | * Update LEDs in breathing mode 76 | */ 77 | void updateBreathingMode(); 78 | 79 | /** 80 | * Update LEDs in solid color mode 81 | */ 82 | void updateSolidMode(); 83 | 84 | /** 85 | * Update LEDs in comet trail mode 86 | * @param startLed The starting LED position based on distance 87 | */ 88 | void updateCometMode(int startLed); 89 | 90 | /** 91 | * Update LEDs in pulse waves mode 92 | * @param startLed The starting LED position based on distance 93 | */ 94 | void updatePulseMode(int startLed); 95 | 96 | /** 97 | * Update LEDs in fire effect mode 98 | */ 99 | void updateFireMode(); 100 | 101 | /** 102 | * Update LEDs in theater chase mode 103 | */ 104 | void updateTheaterChaseMode(); 105 | 106 | /** 107 | * Update LEDs in dual scanning mode 108 | * @param startLed The starting LED position based on distance 109 | */ 110 | void updateDualScanMode(int startLed); 111 | 112 | /** 113 | * Update LEDs in motion particles mode 114 | * @param startLed The starting LED position based on distance 115 | */ 116 | void updateMotionParticlesMode(int startLed); 117 | 118 | /** 119 | * Process LED segment data (ESP-NOW distributed mode) 120 | * @param segmentData LED segment data structure 121 | */ 122 | void processLEDSegmentData(led_segment_data_t segmentData); 123 | 124 | /** 125 | * Update LED segment for distributed mode 126 | * @param globalStartPos Global LED start position 127 | * @param segmentData LED segment data 128 | */ 129 | void updateLEDSegment(int globalStartPos, led_segment_data_t segmentData); 130 | 131 | /** 132 | * Helper function to create a color with a specific intensity 133 | * @param r Red component (0-255) 134 | * @param g Green component (0-255) 135 | * @param b Blue component (0-255) 136 | * @param intensity Brightness factor (0.0-1.0) 137 | * @return 32-bit color value 138 | */ 139 | uint32_t dimColor(uint8_t r, uint8_t g, uint8_t b, float intensity); 140 | 141 | /** 142 | * Generate a color from the rainbow wheel 143 | * @param wheelPos Position on the wheel (0-255) 144 | * @return 32-bit color value 145 | */ 146 | uint32_t wheelColor(byte wheelPos); 147 | 148 | /** 149 | * Saturated subtraction to prevent underflow 150 | * @param a First value 151 | * @param b Value to subtract 152 | * @return Saturated result 153 | */ 154 | uint8_t qsub8(uint8_t a, uint8_t b); 155 | 156 | /** 157 | * Saturated addition to prevent overflow 158 | * @param a First value 159 | * @param b Value to add 160 | * @return Saturated result 161 | */ 162 | uint8_t qadd8(uint8_t a, uint8_t b); 163 | 164 | /** 165 | * Generate random 8-bit number 166 | * @return Random value 0-255 167 | */ 168 | uint8_t random8(); 169 | 170 | /** 171 | * Generate random number in range 172 | * @param lim Upper limit (exclusive) 173 | * @return Random value 0 to lim-1 174 | */ 175 | uint8_t random8(uint8_t lim); 176 | 177 | /** 178 | * Generate random number in range 179 | * @param min Lower limit (inclusive) 180 | * @param lim Upper limit (exclusive) 181 | * @return Random value min to lim-1 182 | */ 183 | uint8_t random8(uint8_t min, uint8_t lim); 184 | 185 | #endif // LED_CONTROLLER_H -------------------------------------------------------------------------------- /AmbiSense/memory_analysis.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_ANALYSIS_H 2 | #define MEMORY_ANALYSIS_H 3 | 4 | #include 5 | 6 | // Memory analysis utilities for AmbiSense 7 | // Use these functions to monitor flash and RAM usage 8 | 9 | void printMemoryUsage() { 10 | Serial.println("=== Memory Usage Analysis ==="); 11 | 12 | // Heap Memory 13 | Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap()); 14 | Serial.printf("Total Heap: %d bytes\n", ESP.getHeapSize()); 15 | Serial.printf("Min Free Heap: %d bytes\n", ESP.getMinFreeHeap()); 16 | Serial.printf("Max Alloc Heap: %d bytes\n", ESP.getMaxAllocHeap()); 17 | Serial.printf("Heap Usage: %.1f%%\n", 18 | (float)(ESP.getHeapSize() - ESP.getFreeHeap()) / ESP.getHeapSize() * 100); 19 | 20 | // Flash Memory 21 | Serial.printf("Flash Chip Size: %d bytes\n", ESP.getFlashChipSize()); 22 | Serial.printf("Sketch Size: %d bytes\n", ESP.getSketchSize()); 23 | Serial.printf("Free Sketch Space: %d bytes\n", ESP.getFreeSketchSpace()); 24 | Serial.printf("Flash Usage: %.1f%%\n", 25 | (float)ESP.getSketchSize() / ESP.getFlashChipSize() * 100); 26 | 27 | // PSRAM (if available) 28 | if (ESP.getPsramSize() > 0) { 29 | Serial.printf("Total PSRAM: %d bytes\n", ESP.getPsramSize()); 30 | Serial.printf("Free PSRAM: %d bytes\n", ESP.getFreePsram()); 31 | Serial.printf("PSRAM Usage: %.1f%%\n", 32 | (float)(ESP.getPsramSize() - ESP.getFreePsram()) / ESP.getPsramSize() * 100); 33 | } else { 34 | Serial.println("No PSRAM available"); 35 | } 36 | 37 | Serial.println("============================="); 38 | } 39 | 40 | // Estimate memory savings from compression 41 | void printCompressionBenefits() { 42 | Serial.println("=== HTML Compression Benefits ==="); 43 | 44 | // Estimated sizes (you can measure these during compilation) 45 | const size_t uncompressed_html_size = 8500; // Estimated original HTML size 46 | const size_t compressed_html_size = 3200; // Estimated compressed size 47 | const size_t css_reduction = 2800; // CSS minification savings 48 | const size_t js_reduction = 1500; // JS minification savings 49 | 50 | size_t total_savings = uncompressed_html_size - compressed_html_size; 51 | float compression_ratio = (float)total_savings / uncompressed_html_size * 100; 52 | 53 | Serial.printf("Original HTML size: ~%d bytes\n", uncompressed_html_size); 54 | Serial.printf("Compressed size: ~%d bytes\n", compressed_html_size); 55 | Serial.printf("Memory saved: %d bytes (%.1f%%)\n", total_savings, compression_ratio); 56 | Serial.printf("CSS minification: ~%d bytes saved\n", css_reduction); 57 | Serial.printf("JS minification: ~%d bytes saved\n", js_reduction); 58 | Serial.println("================================="); 59 | } 60 | 61 | // Call this during setup to see memory impact 62 | void analyzeMemoryOnStartup() { 63 | Serial.println("\n=== Startup Memory Analysis ==="); 64 | printMemoryUsage(); 65 | printCompressionBenefits(); 66 | } 67 | 68 | #endif // MEMORY_ANALYSIS_H -------------------------------------------------------------------------------- /AmbiSense/radar_manager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "config.h" 3 | #include "radar_manager.h" 4 | #include "led_controller.h" 5 | #include "espnow_manager.h" 6 | 7 | // LD2410 radar sensor instance 8 | ld2410 radar; 9 | 10 | // Detailed motion smoothing state 11 | struct MotionState { 12 | float smoothedDistance = 0.0; 13 | float smoothedVelocity = 0.0; 14 | float predictedDistance = 0.0; 15 | float positionError = 0.0; 16 | float positionErrorIntegral = 0.0; 17 | unsigned long lastUpdateTime = 0; 18 | } motionState; 19 | 20 | void setupRadar() { 21 | Serial.println("Initializing radar sensor..."); 22 | RADAR_SERIAL.begin(256000, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN); 23 | radar.begin(RADAR_SERIAL); 24 | 25 | // Initialize motion state 26 | motionState.lastUpdateTime = millis(); 27 | motionState.smoothedDistance = (float)minDistance; 28 | 29 | if (ENABLE_MOTION_LOGGING) { 30 | Serial.println("Motion smoothing configuration:"); 31 | Serial.printf("Motion Smoothing: %s\n", motionSmoothingEnabled ? "Enabled" : "Disabled"); 32 | Serial.printf("Position Factor: %.2f\n", positionSmoothingFactor); 33 | Serial.printf("Velocity Factor: %.2f\n", velocitySmoothingFactor); 34 | Serial.printf("Prediction Factor: %.2f\n", predictionFactor); 35 | } 36 | } 37 | 38 | bool processRadarReading() { 39 | radar.read(); 40 | 41 | if (radar.isConnected()) { 42 | int rawDistance = radar.movingTargetDistance(); 43 | 44 | // If no moving target, check for stationary target 45 | if (rawDistance == 0) rawDistance = radar.stationaryTargetDistance(); 46 | 47 | // Ensure rawDistance is in valid range 48 | if (rawDistance < minDistance) rawDistance = minDistance; 49 | if (rawDistance > maxDistance) rawDistance = maxDistance; 50 | 51 | // Calculate direction 52 | int8_t direction = 0; 53 | static int lastRawDistance = rawDistance; 54 | if (rawDistance < lastRawDistance - 5) direction = -1; // Moving closer 55 | else if (rawDistance > lastRawDistance + 5) direction = 1; // Moving away 56 | lastRawDistance = rawDistance; 57 | 58 | // Motion smoothing logic 59 | if (motionSmoothingEnabled) { 60 | // Time since last update (for velocity calculation) 61 | unsigned long currentTime = millis(); 62 | float deltaTime = (currentTime - motionState.lastUpdateTime) / 1000.0; // Convert to seconds 63 | motionState.lastUpdateTime = currentTime; 64 | 65 | // If this is the first time after enabling, log it 66 | static bool firstRun = true; 67 | if (firstRun && ENABLE_MOTION_LOGGING) { 68 | Serial.printf("[Motion] Initial raw distance: %d cm\n", rawDistance); 69 | firstRun = false; 70 | } 71 | 72 | // Prevent divide-by-zero and extreme delta times 73 | if (deltaTime <= 0.001) deltaTime = 0.001; // Minimum 1ms 74 | if (deltaTime > 1.0) deltaTime = 1.0; // Cap at 1 second 75 | 76 | // If this is the first reading or smoothedDistance is not initialized 77 | if (motionState.smoothedDistance <= 0) { 78 | motionState.smoothedDistance = (float)rawDistance; 79 | motionState.predictedDistance = (float)rawDistance; 80 | motionState.smoothedVelocity = 0.0; 81 | motionState.positionError = 0.0; 82 | motionState.positionErrorIntegral = 0.0; 83 | } 84 | 85 | // Low-pass filter for position 86 | motionState.smoothedDistance = 87 | (1.0f - positionSmoothingFactor) * motionState.smoothedDistance + 88 | positionSmoothingFactor * (float)rawDistance; 89 | 90 | // Velocity estimation - calculate difference between actual and predicted 91 | float instantVelocity = (motionState.smoothedDistance - motionState.predictedDistance) / deltaTime; 92 | 93 | // Apply constraints to velocity (prevent extreme values) 94 | instantVelocity = constrain(instantVelocity, -200.0f, 200.0f); // Maximum 2 m/s 95 | 96 | // Apply low-pass filter to velocity 97 | motionState.smoothedVelocity = 98 | (1.0f - velocitySmoothingFactor) * motionState.smoothedVelocity + 99 | velocitySmoothingFactor * instantVelocity; 100 | 101 | // Position prediction based on velocity 102 | motionState.predictedDistance = 103 | motionState.smoothedDistance + 104 | motionState.smoothedVelocity * predictionFactor; 105 | 106 | // Simple PI controller for position 107 | motionState.positionError = motionState.predictedDistance - motionState.smoothedDistance; 108 | motionState.positionErrorIntegral += motionState.positionError * deltaTime; 109 | 110 | // Anti-windup for integral term 111 | motionState.positionErrorIntegral = constrain( 112 | motionState.positionErrorIntegral, 113 | -100.0f, 114 | 100.0f 115 | ); 116 | 117 | // Calculate control output 118 | float controlOutput = 119 | positionPGain * motionState.positionError + 120 | positionIGain * motionState.positionErrorIntegral; 121 | 122 | // Final smoothed and predicted distance with constraints 123 | float calculatedDistance = motionState.predictedDistance + controlOutput; 124 | 125 | // Apply final constraints to ensure it's in valid range 126 | currentDistance = constrain((int)calculatedDistance, minDistance, maxDistance); 127 | 128 | // Debug output only occasionally to avoid flooding serial 129 | static unsigned long lastDebugTime = 0; 130 | if (ENABLE_MOTION_LOGGING && currentTime - lastDebugTime > 3000) { // Only print every 3 seconds 131 | lastDebugTime = currentTime; 132 | // Only log if there's significant change 133 | static int lastLoggedDistance = 0; 134 | if (abs(rawDistance - lastLoggedDistance) > 10) { 135 | Serial.printf("[Motion] Raw: %d, Smoothed: %.1f, Final: %d\n", 136 | rawDistance, motionState.smoothedDistance, currentDistance); 137 | lastLoggedDistance = rawDistance; 138 | } 139 | } 140 | } else { 141 | // Standard mode without smoothing 142 | currentDistance = rawDistance; 143 | } 144 | 145 | // Handle device role-specific logic 146 | if (deviceRole == DEVICE_ROLE_SLAVE) { 147 | // SLAVES: Only send data to master, NO LED control in master-slave setup 148 | bool validMaster = false; 149 | for (int i = 0; i < 6; i++) { 150 | if (masterAddress[i] != 0) { 151 | validMaster = true; 152 | break; 153 | } 154 | } 155 | 156 | if (validMaster) { 157 | sendSensorData(currentDistance, direction); 158 | // REMOVED: LED updates for slaves in master-slave mode 159 | // Slaves should not control LEDs when connected to a master 160 | } else { 161 | // Only log occasionally to prevent flooding 162 | static unsigned long lastNoMasterLog = 0; 163 | if (millis() - lastNoMasterLog > 10000) { // Only log every 10 seconds 164 | Serial.println("INFO: Slave mode active but no master configured yet"); 165 | lastNoMasterLog = millis(); 166 | } 167 | 168 | // If no master configured, slave can control its own LEDs for testing 169 | if (lightMode == LIGHT_MODE_STANDARD) { 170 | updateLEDs(currentDistance); 171 | } 172 | } 173 | return true; 174 | } 175 | 176 | // MASTER DEVICE: Handle LED updates appropriately 177 | if (deviceRole == DEVICE_ROLE_MASTER) { 178 | if (numSlaveDevices == 0) { 179 | // No slaves configured, update LEDs directly for standard mode 180 | if (lightMode == LIGHT_MODE_STANDARD) { 181 | updateLEDs(currentDistance); 182 | } 183 | } 184 | // If slaves are configured, updateLEDsWithMultiSensorData() will handle LED updates 185 | // This is called automatically from ESP-NOW when data is received 186 | } 187 | 188 | // STANDALONE MODE: If no role is set or unknown role, act as standalone 189 | if (deviceRole != DEVICE_ROLE_MASTER && deviceRole != DEVICE_ROLE_SLAVE) { 190 | if (lightMode == LIGHT_MODE_STANDARD) { 191 | updateLEDs(currentDistance); 192 | } 193 | } 194 | 195 | return true; 196 | } 197 | 198 | return false; 199 | } 200 | 201 | // Helper functions to access motion state 202 | float getSmoothedDistance() { 203 | return motionState.smoothedDistance; 204 | } 205 | 206 | float getEstimatedVelocity() { 207 | return motionState.smoothedVelocity; 208 | } 209 | 210 | float getPredictedDistance() { 211 | return motionState.predictedDistance; 212 | } -------------------------------------------------------------------------------- /AmbiSense/radar_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef RADAR_MANAGER_H 2 | #define RADAR_MANAGER_H 3 | 4 | /** 5 | * Initialize the LD2410 radar module 6 | */ 7 | void setupRadar(); 8 | 9 | /** 10 | * Process the radar readings and update the LED strip 11 | * @return true if a valid reading was obtained, false otherwise 12 | */ 13 | bool processRadarReading(); 14 | 15 | /** 16 | * Get the current smoothed distance reading 17 | * @return Current smoothed distance in centimeters 18 | */ 19 | float getSmoothedDistance(); 20 | 21 | /** 22 | * Get the current estimated velocity 23 | * @return Current estimated velocity in cm/s 24 | */ 25 | float getEstimatedVelocity(); 26 | 27 | /** 28 | * Get the predicted distance based on motion smoothing 29 | * @return Predicted distance in centimeters 30 | */ 31 | float getPredictedDistance(); 32 | 33 | #endif // RADAR_MANAGER_H -------------------------------------------------------------------------------- /AmbiSense/resources.h: -------------------------------------------------------------------------------- 1 | #ifndef RESOURCES_H 2 | #define RESOURCES_H 3 | 4 | #include 5 | 6 | // Check available memory and PSRAM status 7 | inline void checkMemory() { 8 | Serial.println("Memory diagnostics:"); 9 | Serial.printf("Total heap: %d\n", ESP.getHeapSize()); 10 | Serial.printf("Free heap: %d\n", ESP.getFreeHeap()); 11 | Serial.printf("Min free heap: %d\n", ESP.getMinFreeHeap()); 12 | Serial.printf("Max alloc heap: %d\n", ESP.getMaxAllocHeap()); 13 | 14 | #ifdef ESP_IDF_VERSION_MAJOR // Check if IDF version is available 15 | // Only for ESP32s with PSRAM 16 | if (ESP.getPsramSize() > 0) { 17 | Serial.printf("Total PSRAM: %d\n", ESP.getPsramSize()); 18 | Serial.printf("Free PSRAM: %d\n", ESP.getFreePsram()); 19 | } else { 20 | Serial.println("No PSRAM detected"); 21 | } 22 | #endif 23 | } 24 | 25 | // Initialize PSRAM if available 26 | inline bool initPSRAM() { 27 | #ifdef ESP_IDF_VERSION_MAJOR 28 | if (ESP.getPsramSize() > 0) { 29 | Serial.println("PSRAM is enabled"); 30 | // ESP32 with PSRAM detected 31 | heap_caps_malloc_extmem_enable(20000); // Prefer external memory for allocations over 20KB 32 | return true; 33 | } else { 34 | Serial.println("No PSRAM available"); 35 | return false; 36 | } 37 | #else 38 | return false; 39 | #endif 40 | } 41 | 42 | #endif // RESOURCES_H -------------------------------------------------------------------------------- /AmbiSense/web_interface.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_INTERFACE_H 2 | #define WEB_INTERFACE_H 3 | 4 | /* 5 | * UTF-8 Character Encoding Support 6 | * All web responses now include proper UTF-8 charset declarations 7 | * to ensure correct display of special characters and apostrophes 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | // Global WebServer object 14 | extern WebServer server; 15 | 16 | /** 17 | * Initializes the web server 18 | */ 19 | void setupWebServer(); 20 | 21 | /** 22 | * Generate HTML for different pages 23 | */ 24 | String getMainHTML(); 25 | String getAdvancedHTML(); 26 | String getEffectsHTML(); 27 | String getMeshHTML(); 28 | String getNetworkHTML(); 29 | 30 | /** 31 | * Route handlers for web pages 32 | */ 33 | void handleRoot(); 34 | void handleAdvanced(); 35 | void handleEffects(); 36 | void handleMesh(); 37 | void handleNetwork(); 38 | void handleSimplePage(); 39 | 40 | /** 41 | * API endpoint handlers 42 | */ 43 | void handleDistance(); 44 | void handleSettings(); 45 | void handleSet(); 46 | void handleSetLightMode(); 47 | void handleSetDirectionalLight(); 48 | void handleSetCenterShift(); 49 | void handleSetTrailLength(); 50 | void handleSetBackgroundMode(); 51 | void handleSetMotionSmoothing(); 52 | void handleSetMotionSmoothingParam(); 53 | void handleSetEffectSpeed(); 54 | void handleSetEffectIntensity(); 55 | void handleSetSensorPriorityMode(); // Handler for sensor priority mode 56 | void handleResetDistanceValues(); // Handler for resetting distance values 57 | 58 | /** 59 | * LED testing and configuration handlers 60 | */ 61 | void handleTestLEDs(); 62 | void handleReinitLEDs(); 63 | 64 | /** 65 | * LED Distribution handlers 66 | */ 67 | void handleSetLEDSegmentMode(); 68 | void handleGetLEDSegmentInfo(); 69 | void handleSetLEDSegmentInfo(); 70 | 71 | /** 72 | * ESP-NOW handlers 73 | */ 74 | void handleGetDeviceInfo(); 75 | void handleSetDeviceRole(); 76 | void handleScanForSlaves(); 77 | void handleAddSlave(); 78 | void handleRemoveSlave(); 79 | void handleSetMasterMac(); 80 | void handleGetSensorData(); 81 | void handleDiagnostics(); 82 | 83 | /** 84 | * WiFi management handlers 85 | */ 86 | void handleNetworkPost(); 87 | void handleResetWifi(); 88 | void handleScanNetworks(); 89 | 90 | #endif // WEB_INTERFACE_H -------------------------------------------------------------------------------- /AmbiSense/wifi_manager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "config.h" 4 | #include "wifi_manager.h" 5 | 6 | // Connection timeout constants 7 | #define WIFI_CONNECTION_TIMEOUT 15000 // 15 seconds 8 | #define WIFI_RECONNECT_DELAY 500 // Wait between reconnect attempts 9 | #define MAX_CONNECTION_ATTEMPTS 2 // Attempts before falling back to AP mode 10 | #define WIFI_CHECK_INTERVAL 30000 // Check connection every 30 seconds 11 | 12 | // Global instance 13 | WifiManager wifiManager; 14 | 15 | void WifiManager::begin() { 16 | Serial.println("[WiFi] Initializing WiFi manager"); 17 | 18 | pinMode(WIFI_STATUS_LED_PIN, OUTPUT); 19 | digitalWrite(WIFI_STATUS_LED_PIN, LOW); 20 | 21 | // Always start with WiFi off to ensure clean state 22 | WiFi.disconnect(true); 23 | delay(100); 24 | WiFi.mode(WIFI_OFF); 25 | delay(200); // Give WiFi subsystem time to reset 26 | 27 | // Check if WiFi section is properly marked 28 | uint16_t wifiMarker; 29 | EEPROM.get(EEPROM_WIFI_MARKER_ADDR, wifiMarker); 30 | if (wifiMarker != 0xA55A) { 31 | Serial.println("[WiFi] EEPROM WiFi section not initialized"); 32 | 33 | // Initialize marker and clear WiFi section 34 | EEPROM.put(EEPROM_WIFI_MARKER_ADDR, (uint16_t)0xA55A); 35 | for (int i = EEPROM_WIFI_SSID_ADDR; 36 | i < EEPROM_WIFI_SSID_ADDR + MAX_SSID_LENGTH + MAX_PASSWORD_LENGTH + MAX_DEVICE_NAME_LENGTH; 37 | i++) { 38 | EEPROM.write(i, 0); 39 | } 40 | EEPROM.commit(); 41 | Serial.println("[WiFi] EEPROM WiFi section initialized"); 42 | } 43 | 44 | // Load credentials from EEPROM 45 | char ssid[MAX_SSID_LENGTH] = {0}; 46 | char password[MAX_PASSWORD_LENGTH] = {0}; 47 | char deviceName[MAX_DEVICE_NAME_LENGTH] = {0}; 48 | 49 | // Try to load saved credentials 50 | if (loadWifiCredentials(ssid, password, deviceName) && strlen(ssid) > 0) { 51 | Serial.printf("[WiFi] Saved credentials found: SSID='%s'\n", ssid); 52 | 53 | // Try to connect 54 | if (connectToWifi(ssid, password)) { 55 | _currentMode = WIFI_MANAGER_MODE_STA; 56 | 57 | // Setup mDNS with device name 58 | if (strlen(deviceName) > 0) { 59 | String hostname = getSanitizedHostname(deviceName); 60 | setupMDNS(hostname.c_str()); 61 | } else { 62 | // Use default hostname with MAC address if no device name was set 63 | char defaultName[32]; 64 | uint32_t chipId = ESP.getEfuseMac() & 0xFFFFFFFF; 65 | sprintf(defaultName, "ambisense-%08X", chipId); 66 | setupMDNS(defaultName); 67 | } 68 | } else { 69 | Serial.println("[WiFi] Failed to connect. Starting AP mode"); 70 | startAPMode(); 71 | _currentMode = WIFI_MANAGER_MODE_FALLBACK; 72 | } 73 | } else { 74 | Serial.println("[WiFi] No saved credentials. Starting AP mode"); 75 | startAPMode(); 76 | _currentMode = WIFI_MANAGER_MODE_AP; 77 | } 78 | 79 | // Print current connection status 80 | if (_currentMode == WIFI_MANAGER_MODE_STA) { 81 | Serial.printf("[WiFi] Connected: IP=%s, RSSI=%d\n", 82 | WiFi.localIP().toString().c_str(), 83 | WiFi.RSSI()); 84 | } else { 85 | Serial.printf("[WiFi] Running in AP mode: IP=%s\n", 86 | WiFi.softAPIP().toString().c_str()); 87 | } 88 | } 89 | 90 | bool WifiManager::startAPMode() { 91 | Serial.println("[WiFi] Starting Access Point mode"); 92 | 93 | // Use a more unique AP name based on chip ID and timestamp 94 | char apName[32]; 95 | uint32_t chipId = ESP.getEfuseMac() & 0xFFFF; // Use last 16 bits (4 hex digits) 96 | sprintf(apName, "AmbiSense-%04X", chipId); 97 | 98 | // First ensure WiFi is in the correct mode 99 | WiFi.disconnect(true); 100 | delay(100); 101 | WiFi.mode(WIFI_OFF); 102 | delay(100); 103 | WiFi.mode(WIFI_AP); 104 | delay(100); 105 | 106 | // Use channel 6 (middle of 1,6,11 non-overlapping channels) for better compatibility 107 | int channel = 6; // Channel 6 is often less congested than 1, more compatible than 11 108 | Serial.printf("[WiFi] Using channel %d for AP mode\n", channel); 109 | 110 | // Configure WiFi power and performance 111 | WiFi.setTxPower(WIFI_POWER_19_5dBm); // Max power for better range 112 | 113 | // Start AP with fixed channel 114 | bool success = WiFi.softAP(apName, WIFI_AP_PASSWORD, channel, 0, 4); // Channel 6, not hidden, max 4 clients 115 | 116 | if (success) { 117 | Serial.printf("[WiFi] AP started. Name: %s\n", apName); 118 | Serial.printf("[WiFi] AP IP Address: %s\n", WiFi.softAPIP().toString().c_str()); 119 | } else { 120 | Serial.println("[WiFi] Failed to start AP mode!"); 121 | } 122 | 123 | return success; 124 | } 125 | 126 | bool WifiManager::loadWifiCredentials(char* ssid, char* password, char* deviceName) { 127 | Serial.println("[WiFi] Loading WiFi credentials"); 128 | 129 | // First check for a specific initialization marker 130 | uint16_t marker; 131 | EEPROM.get(EEPROM_WIFI_MARKER_ADDR, marker); 132 | 133 | if (marker != 0xA55A) { 134 | Serial.println("[WiFi] No valid marker found in EEPROM WiFi section"); 135 | // Zero out the credentials 136 | memset(ssid, 0, MAX_SSID_LENGTH); 137 | memset(password, 0, MAX_PASSWORD_LENGTH); 138 | memset(deviceName, 0, MAX_DEVICE_NAME_LENGTH); 139 | 140 | // Write marker for next time 141 | EEPROM.put(EEPROM_WIFI_MARKER_ADDR, (uint16_t)0xA55A); 142 | EEPROM.commit(); 143 | 144 | return false; 145 | } 146 | 147 | // Read SSID 148 | for (int i = 0; i < MAX_SSID_LENGTH; i++) { 149 | ssid[i] = EEPROM.read(EEPROM_WIFI_SSID_ADDR + i); 150 | } 151 | ssid[MAX_SSID_LENGTH - 1] = '\0'; // Ensure null termination 152 | 153 | // If SSID is empty, there are no saved credentials 154 | if (strlen(ssid) == 0) { 155 | Serial.println("[WiFi] No SSID found in EEPROM"); 156 | memset(password, 0, MAX_PASSWORD_LENGTH); 157 | memset(deviceName, 0, MAX_DEVICE_NAME_LENGTH); 158 | return false; 159 | } 160 | 161 | // Check for non-printable characters in SSID 162 | bool validSsid = true; 163 | for (int i = 0; i < strlen(ssid); i++) { 164 | if (ssid[i] < 32 || ssid[i] > 126) { 165 | validSsid = false; 166 | break; 167 | } 168 | } 169 | 170 | if (!validSsid) { 171 | Serial.println("[WiFi] Corrupt SSID with non-printable characters detected"); 172 | memset(ssid, 0, MAX_SSID_LENGTH); 173 | memset(password, 0, MAX_PASSWORD_LENGTH); 174 | memset(deviceName, 0, MAX_DEVICE_NAME_LENGTH); 175 | return false; 176 | } 177 | 178 | // Read password 179 | for (int i = 0; i < MAX_PASSWORD_LENGTH; i++) { 180 | password[i] = EEPROM.read(EEPROM_WIFI_PASS_ADDR + i); 181 | } 182 | password[MAX_PASSWORD_LENGTH - 1] = '\0'; // Ensure null termination 183 | 184 | // Read device name 185 | for (int i = 0; i < MAX_DEVICE_NAME_LENGTH; i++) { 186 | deviceName[i] = EEPROM.read(EEPROM_DEVICE_NAME_ADDR + i); 187 | } 188 | deviceName[MAX_DEVICE_NAME_LENGTH - 1] = '\0'; // Ensure null termination 189 | 190 | Serial.printf("[WiFi] Loaded SSID: '%s' (length: %d)\n", ssid, strlen(ssid)); 191 | 192 | return (strlen(ssid) > 0); // Consider valid only if SSID exists 193 | } 194 | 195 | void WifiManager::saveWifiCredentials(const char* ssid, const char* password, const char* deviceName) { 196 | Serial.printf("[WiFi] Saving credentials: SSID='%s'\n", ssid); 197 | 198 | // Save WiFi marker 199 | EEPROM.put(EEPROM_WIFI_MARKER_ADDR, (uint16_t)0xA55A); 200 | 201 | // Clear existing data first 202 | for (int i = EEPROM_WIFI_SSID_ADDR; 203 | i < EEPROM_WIFI_SSID_ADDR + MAX_SSID_LENGTH + MAX_PASSWORD_LENGTH + MAX_DEVICE_NAME_LENGTH; 204 | i++) { 205 | EEPROM.write(i, 0); 206 | } 207 | 208 | // Write SSID (with length checking) 209 | for (int i = 0; i < strlen(ssid) && i < MAX_SSID_LENGTH - 1; i++) { 210 | EEPROM.write(EEPROM_WIFI_SSID_ADDR + i, ssid[i]); 211 | } 212 | 213 | // Write password (with length checking) 214 | for (int i = 0; i < strlen(password) && i < MAX_PASSWORD_LENGTH - 1; i++) { 215 | EEPROM.write(EEPROM_WIFI_PASS_ADDR + i, password[i]); 216 | } 217 | 218 | // Write device name (with length checking) 219 | for (int i = 0; i < strlen(deviceName) && i < MAX_DEVICE_NAME_LENGTH - 1; i++) { 220 | EEPROM.write(EEPROM_DEVICE_NAME_ADDR + i, deviceName[i]); 221 | } 222 | 223 | // Commit changes to flash 224 | if (EEPROM.commit()) { 225 | Serial.println("[WiFi] Credentials saved successfully"); 226 | 227 | // Verify the SSID 228 | char verifySSID[MAX_SSID_LENGTH] = {0}; 229 | for (int i = 0; i < MAX_SSID_LENGTH - 1; i++) { 230 | verifySSID[i] = EEPROM.read(EEPROM_WIFI_SSID_ADDR + i); 231 | } 232 | 233 | if (String(verifySSID) != String(ssid)) { 234 | Serial.println("[WiFi] WARNING: SSID verification failed"); 235 | } else { 236 | Serial.println("[WiFi] SSID verification successful"); 237 | } 238 | } else { 239 | Serial.println("[WiFi] ERROR: Failed to commit credentials to EEPROM"); 240 | } 241 | } 242 | 243 | bool WifiManager::connectToWifi(const char* ssid, const char* password) { 244 | // Extra validation to avoid connection attempt with empty/invalid SSID 245 | if (strlen(ssid) == 0) { 246 | Serial.println("[WiFi] Cannot connect: SSID is empty"); 247 | return false; 248 | } 249 | 250 | // Log SSID with quotes to show if there are leading/trailing spaces 251 | Serial.printf("[WiFi] Connecting to: '%s'\n", ssid); 252 | 253 | // Check if SSID contains only valid characters 254 | for (int i = 0; i < strlen(ssid); i++) { 255 | if (ssid[i] < 32 || ssid[i] > 126) { 256 | Serial.println("[WiFi] Cannot connect: SSID contains invalid characters"); 257 | return false; 258 | } 259 | } 260 | 261 | // Disconnect from any previous connection 262 | WiFi.disconnect(true); 263 | delay(100); 264 | 265 | // Set mode explicitly 266 | WiFi.mode(WIFI_STA); 267 | delay(100); 268 | 269 | // Configure WiFi power and performance 270 | WiFi.setTxPower(WIFI_POWER_19_5dBm); // Max power for better range 271 | WiFi.setAutoReconnect(true); // Enable auto-reconnect 272 | 273 | // Start connection 274 | WiFi.begin(ssid, password); 275 | 276 | // Wait for connection with timeout 277 | unsigned long startTime = millis(); 278 | int dotCount = 0; 279 | 280 | while (WiFi.status() != WL_CONNECTED) { 281 | delay(500); 282 | 283 | // Print feedback every 500ms (but group dots for readability) 284 | Serial.print("."); 285 | dotCount++; 286 | 287 | if (dotCount >= 20) { 288 | Serial.println(); // Start a new line if too many dots 289 | dotCount = 0; 290 | } 291 | 292 | if (millis() - startTime > WIFI_CONNECTION_TIMEOUT) { 293 | Serial.println("\n[WiFi] Connection timeout"); 294 | return false; 295 | } 296 | } 297 | 298 | Serial.println(); 299 | Serial.printf("[WiFi] Connected! IP: %s\n", WiFi.localIP().toString().c_str()); 300 | 301 | return true; 302 | } 303 | 304 | bool WifiManager::setupMDNS(const char* hostname) { 305 | if (strlen(hostname) == 0) { 306 | Serial.println("[mDNS] Hostname is empty, cannot setup mDNS"); 307 | return false; 308 | } 309 | 310 | // Ensure we only try to start mDNS once 311 | if (_mDNSStarted) { 312 | MDNS.end(); 313 | _mDNSStarted = false; 314 | } 315 | 316 | Serial.printf("[mDNS] Setting up hostname: %s\n", hostname); 317 | 318 | if (!MDNS.begin(hostname)) { 319 | Serial.println("[mDNS] Error setting up mDNS responder!"); 320 | return false; 321 | } 322 | 323 | // Add service to mDNS 324 | MDNS.addService("http", "tcp", 80); 325 | Serial.printf("[mDNS] mDNS responder started at http://%s.local\n", hostname); 326 | 327 | _mDNSStarted = true; 328 | return true; 329 | } 330 | 331 | void WifiManager::checkWifiConnection() { 332 | if (WiFi.status() != WL_CONNECTED) { 333 | Serial.println("[WiFi] Connection lost, attempting to reconnect"); 334 | 335 | _connectionAttempts++; 336 | 337 | if (_connectionAttempts > MAX_CONNECTION_ATTEMPTS) { 338 | Serial.println("[WiFi] Max reconnection attempts reached. Switching to AP mode"); 339 | 340 | startAPMode(); 341 | _currentMode = WIFI_MANAGER_MODE_FALLBACK; 342 | return; 343 | } 344 | 345 | // Attempt to reconnect with clean WiFi state 346 | WiFi.disconnect(true); 347 | delay(100); 348 | 349 | char ssid[MAX_SSID_LENGTH] = {0}; 350 | char password[MAX_PASSWORD_LENGTH] = {0}; 351 | char deviceName[MAX_DEVICE_NAME_LENGTH] = {0}; 352 | 353 | if (loadWifiCredentials(ssid, password, deviceName) && strlen(ssid) > 0) { 354 | // Reconnect 355 | WiFi.begin(ssid, password); 356 | 357 | // Short wait for quick reconnection 358 | unsigned long startTime = millis(); 359 | Serial.print("[WiFi] Reconnecting"); 360 | 361 | while (WiFi.status() != WL_CONNECTED && 362 | (millis() - startTime < WIFI_CONNECTION_TIMEOUT / 2)) { 363 | delay(500); 364 | Serial.print("."); 365 | } 366 | 367 | if (WiFi.status() == WL_CONNECTED) { 368 | Serial.println("\n[WiFi] Reconnected successfully"); 369 | _connectionAttempts = 0; 370 | _currentMode = WIFI_MANAGER_MODE_STA; 371 | } else { 372 | Serial.println("\n[WiFi] Reconnect failed"); 373 | } 374 | } 375 | } 376 | } 377 | std::vector WifiManager::scanNetworks() { 378 | std::vector networks; 379 | 380 | Serial.println("[WiFi] Scanning for networks..."); 381 | 382 | int numNetworks = WiFi.scanNetworks(false, true); // Non-blocking, include hidden networks 383 | 384 | if (numNetworks > 0) { 385 | Serial.printf("[WiFi] Found %d networks\n", numNetworks); 386 | 387 | // Sort networks by signal strength 388 | for (int i = 0; i < numNetworks; i++) { 389 | WiFiNetwork network; 390 | network.ssid = WiFi.SSID(i); 391 | network.rssi = WiFi.RSSI(i); 392 | network.encType = WiFi.encryptionType(i); 393 | 394 | networks.push_back(network); 395 | } 396 | 397 | // Sort by signal strength 398 | std::sort(networks.begin(), networks.end(), 399 | [](const WiFiNetwork& a, const WiFiNetwork& b) { 400 | return a.rssi > b.rssi; // Stronger signal first 401 | }); 402 | } else { 403 | Serial.println("[WiFi] No networks found"); 404 | } 405 | 406 | // Clean up scan results to free memory 407 | WiFi.scanDelete(); 408 | 409 | return networks; 410 | } 411 | void WifiManager::process() { 412 | unsigned long now = millis(); 413 | 414 | // Update LED status 415 | updateLedStatus(); 416 | 417 | // Less frequent WiFi check to reduce processing overhead 418 | if (_currentMode == WIFI_MANAGER_MODE_STA && (now - _lastWifiCheck > WIFI_CHECK_INTERVAL)) { 419 | _lastWifiCheck = now; 420 | checkWifiConnection(); 421 | } 422 | } 423 | 424 | void WifiManager::updateLedStatus() { 425 | unsigned long now = millis(); 426 | 427 | if (_currentMode == WIFI_MANAGER_MODE_STA && WiFi.status() == WL_CONNECTED) { 428 | // Connected mode: Blink every 3 seconds 429 | digitalWrite(WIFI_STATUS_LED_PIN, ((now / 1000) % 3 == 0) ? HIGH : LOW); 430 | } else { 431 | // AP/Fallback Mode: Fast blink (500ms) 432 | digitalWrite(WIFI_STATUS_LED_PIN, ((now / 500) % 2 == 0) ? HIGH : LOW); 433 | } 434 | } 435 | 436 | void WifiManager::resetWifiSettings() { 437 | Serial.println("[WiFi] Resetting WiFi settings"); 438 | 439 | // Clear the WiFi credential area 440 | for (int i = EEPROM_WIFI_SSID_ADDR; 441 | i < EEPROM_WIFI_SSID_ADDR + MAX_SSID_LENGTH + MAX_PASSWORD_LENGTH + MAX_DEVICE_NAME_LENGTH; 442 | i++) { 443 | EEPROM.write(i, 0); 444 | } 445 | 446 | // Keep the marker to indicate section is initialized 447 | EEPROM.put(EEPROM_WIFI_MARKER_ADDR, (uint16_t)0xA55A); 448 | 449 | // Commit changes to flash 450 | EEPROM.commit(); 451 | 452 | Serial.println("[WiFi] WiFi settings reset. Restarting..."); 453 | 454 | // Restart to apply changes 455 | ESP.restart(); 456 | } 457 | 458 | String WifiManager::getIPAddress() { 459 | if (WiFi.status() == WL_CONNECTED) { 460 | return WiFi.localIP().toString(); 461 | } else { 462 | return WiFi.softAPIP().toString(); 463 | } 464 | } 465 | 466 | WifiManagerMode WifiManager::getMode() { 467 | return _currentMode; 468 | } 469 | 470 | String WifiManager::getSanitizedHostname(const char* deviceName) { 471 | String hostname; 472 | 473 | if (deviceName && strlen(deviceName) > 0) { 474 | String name = String(deviceName); 475 | 476 | // Convert to lowercase 477 | name.toLowerCase(); 478 | 479 | // Replace spaces with hyphens 480 | name.replace(" ", "-"); 481 | 482 | // Replace other invalid characters 483 | name.replace(".", "-"); 484 | name.replace("_", "-"); 485 | 486 | // Remove any other non-alphanumeric characters except hyphen 487 | String validName = ""; 488 | for (unsigned int i = 0; i < name.length(); i++) { 489 | char c = name.charAt(i); 490 | if (isalnum(c) || c == '-') { 491 | validName += c; 492 | } 493 | } 494 | 495 | // Make sure we don't double-prefix with "ambisense-" 496 | if (validName.startsWith("ambisense-")) { 497 | hostname = validName; // Keep as is if already prefixed 498 | } else { 499 | hostname = "ambisense-" + validName; // Add prefix if not present 500 | } 501 | } else { 502 | // Use MAC address as fallback 503 | uint32_t chipId = ESP.getEfuseMac() & 0xFFFFFFFF; 504 | char idStr[9]; 505 | sprintf(idStr, "%08X", chipId); 506 | hostname = "ambisense-" + String(idStr); 507 | } 508 | 509 | return hostname; 510 | } -------------------------------------------------------------------------------- /AmbiSense/wifi_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef WIFI_MANAGER_H 2 | #define WIFI_MANAGER_H 3 | 4 | #include // For std::exception 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Define constants here for use in other files 11 | #define MAX_SSID_LENGTH 32 12 | #define MAX_PASSWORD_LENGTH 64 13 | #define MAX_DEVICE_NAME_LENGTH 32 14 | 15 | #define WIFI_STATUS_LED_PIN 8 16 | 17 | // Wi-Fi operation modes 18 | enum WifiManagerMode { 19 | WIFI_MANAGER_MODE_AP, // Access Point mode (initial setup) 20 | WIFI_MANAGER_MODE_STA, // Station mode (connected to home network) 21 | WIFI_MANAGER_MODE_FALLBACK // Fallback to AP mode when connection fails 22 | }; 23 | 24 | // Network information structure 25 | struct WiFiNetwork { 26 | String ssid; 27 | int32_t rssi; 28 | uint8_t encType; 29 | }; 30 | 31 | // Wi-Fi manager class 32 | class WifiManager { 33 | public: 34 | // Initialize WiFi manager 35 | void begin(); 36 | 37 | // Setup Access Point mode 38 | bool startAPMode(); 39 | 40 | // Connect to Wi-Fi network in Station mode 41 | bool connectToWifi(const char* ssid, const char* password); 42 | 43 | // Setup mDNS with given hostname 44 | bool setupMDNS(const char* hostname); 45 | 46 | // Save Wi-Fi credentials to EEPROM 47 | void saveWifiCredentials(const char* ssid, const char* password, const char* deviceName); 48 | 49 | // Load Wi-Fi credentials from EEPROM 50 | bool loadWifiCredentials(char* ssid, char* password, char* deviceName); 51 | 52 | // Reset Wi-Fi settings (clear EEPROM) 53 | void resetWifiSettings(); 54 | 55 | // Check if device is connected to Wi-Fi 56 | bool isConnected(); 57 | 58 | // Get current IP address (AP or STA) 59 | String getIPAddress(); 60 | 61 | // Clean network name of invalid characters 62 | String cleanNetworkName(const String& rawName); 63 | 64 | // Get current Wi-Fi mode 65 | WifiManagerMode getMode(); 66 | 67 | // Get sanitized mDNS hostname 68 | String getSanitizedHostname(const char* deviceName); 69 | 70 | // Process WiFi events and maintain connection 71 | void process(); 72 | 73 | // Scan for available networks and return top results 74 | std::vector scanNetworks(); 75 | 76 | // Update LED status indicator 77 | void updateLedStatus(); 78 | 79 | // Check and handle WiFi connection status 80 | void checkWifiConnection(); 81 | 82 | private: 83 | WifiManagerMode _currentMode = WIFI_MANAGER_MODE_AP; 84 | unsigned long _lastWifiCheck = 0; 85 | int _connectionAttempts = 0; 86 | bool _mDNSStarted = false; 87 | unsigned long _lastLedUpdateTime = 0; 88 | int _ledBlinkPhase = 0; 89 | bool _ledState = HIGH; 90 | 91 | // Configure static IP if enabled 92 | void configureStaticIP(); 93 | }; 94 | 95 | // Global instance 96 | extern WifiManager wifiManager; 97 | 98 | #endif // WIFI_MANAGER_H -------------------------------------------------------------------------------- /Ambisense 4.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techposts/AmbiSense/c9650d710e8fed2ea3448c6db4e18d522360f701/Ambisense 4.1.png -------------------------------------------------------------------------------- /Assets/AmbISense-mesh-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techposts/AmbiSense/c9650d710e8fed2ea3448c6db4e18d522360f701/Assets/AmbISense-mesh-2.jpg -------------------------------------------------------------------------------- /Assets/AmbiSense-Mesh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techposts/AmbiSense/c9650d710e8fed2ea3448c6db4e18d522360f701/Assets/AmbiSense-Mesh.jpg -------------------------------------------------------------------------------- /Assets/AmbiSense.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techposts/AmbiSense/c9650d710e8fed2ea3448c6db4e18d522360f701/Assets/AmbiSense.webp -------------------------------------------------------------------------------- /Assets/TTP Touch sensor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techposts/AmbiSense/c9650d710e8fed2ea3448c6db4e18d522360f701/Assets/TTP Touch sensor.png -------------------------------------------------------------------------------- /Assets/circuit-diagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ESP32C3 SuperMini 26 | 27 | 28 | 29 | PIN 3 30 | 31 | 32 | PIN 4 33 | 34 | 35 | 5V 36 | 37 | 38 | GND 39 | 40 | 41 | PIN 5 42 | 43 | 44 | 45 | LD2410C Sensor 46 | 47 | 48 | 49 | RX_PIN 50 | 51 | 52 | TX_PIN 53 | 54 | 55 | 5V 56 | 57 | 58 | GND 59 | 60 | 61 | 62 | WS2812 LED 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 5V 73 | 74 | 75 | DATA 76 | 77 | 78 | GND 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | RX_PIN → PIN 3 172 | TX_PIN → PIN 4 173 | DATA → PIN 5 174 | 5V 175 | GND 176 | 177 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ravi Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AmbiSense - Radar-Controlled LED System 2 |

3 | AmbiSense Logo 4 |

5 | 6 | AmbiSense is an innovative smart lighting solution that uses radar sensing technology to create responsive ambient lighting experiences. The system detects movement and distance using an LD2410 radar sensor and dynamically controls NeoPixel LED strips in real-time, creating an interactive lighting environment. 7 | 8 | The core of AmbiSense is built around an ESP32 microcontroller that interfaces with an LD2410 radar module and NeoPixel LED strips. The system creates a moving light pattern that responds to a person's proximity, with the illuminated section of the LED strip changing based on detected distance. 9 | 10 | We also developed a [Custom Home Assistant Integration](https://github.com/Techposts/ambisense-homeassistant) allowing you to integrate and control the AmbiSense from the Home Assistant and also run powerful automations. 11 |

12 | Custom HA Integration 13 |

14 | 15 | # AmbiSense: Radar-Controlled LED System 16 |

17 | 18 | AmbiSense v4.1 Release - Intelligent DIY Motion-Tracking Lights That Illuminate Path Dynamically 19 | 20 |
21 | Click the image above to watch the AmbiSense v4.1 Release video 22 |

23 | 24 |

25 | 26 | AmbiSense - Radar-Controlled LED System 27 | 28 |
29 | Click the image above to watch the video Guide 30 |

31 | 32 |

33 | 34 | AmbiSense - Radar-Controlled LED System 35 | 36 |
37 | Click the image above to watch the demo video 38 |

39 | 40 | --- 41 | 42 | ## 📋 Table of Contents 43 | 44 | - [Version History](#-version-history) 45 | - [What's New in v5.1](#-whats-new-in-v43) 46 | - [Key Features](#key-features) 47 | - [Hardware Requirements](#hardware-requirements) 48 | - [Circuit Connections](#circuit-connections) 49 | - [Software Setup](#software-setup) 50 | - [Multi-Sensor Setup Guide](#-multi-sensor-setup-guide) 51 | - [Web Interface Features](#web-interface-features) 52 | - [Physical Controls](#physical-controls) 53 | - [Use Cases](#use-cases) 54 | - [Technical Details](#technical-details) 55 | 56 | --- 57 | 58 | ## 📚 Version History 59 | 60 | ### **Current Version: v5.1** *(Latest)* 61 | **Released:** June 2025 62 | **Status:** Stable Release 63 | 64 |

65 | AmbiSense Logo 66 |

67 | 68 |

69 | AmbiSense Logo 70 |

71 | 72 | #### 🚀 What's New in v5.1 73 | - **🔗 Multi-Sensor ESP-NOW Support**: Connect multiple AmbiSense devices for complex layouts 74 | - **📊 Enhanced Diagnostics**: Real-time monitoring and troubleshooting tools 75 | - **🛠️ Critical Bug Fixes**: Resolved compilation and stability issues 76 | - **🌐 Improved Network Management**: Better WiFi handling and connection reliability 77 | - **🎨 Advanced LED Features**: Enhanced background mode and directional trails 78 | 79 | #### Previous Versions 80 | - **v4.1** *(April 2024)*: Added expanded light effects, motion smoothing, Home Assistant compatibility 81 | - **v4.0** *(Initial Release)*: Core radar-controlled LED functionality 82 | 83 | > **💡 Upgrade Recommendation**: If you're using v4.1 or earlier, upgrading to v5.1 is strongly recommended for bug fixes and new multi-sensor capabilities. 84 | 85 | --- 86 | 87 | ## 🆕 What's New in v5.1 88 | 89 | ### 🔗 Multi-Sensor ESP-NOW Support 90 | AmbiSense v5.1 introduces support for multiple devices working together using ESP-NOW wireless communication. Perfect for: 91 | 92 | - **L-shaped Staircases**: Place sensors at each turn for seamless lighting transitions 93 | - **U-shaped Staircases**: Multiple sensors ensure complete coverage without dead zones 94 | - **Long Hallways**: Distribute LED strips across multiple devices for extended coverage 95 | - **Complex Layouts**: Handle any architectural configuration with intelligent sensor switching 96 | 97 |

98 | Multi-Sensor Setup Diagram 99 |
100 | Example: L-shaped staircase with master and slave sensors working together 101 |

102 | 103 | #### 🎯 Sensor Priority Modes 104 | Choose how your multi-sensor system prioritizes readings: 105 | 106 | - **🧠 Zone-Based** *(Recommended)*: Intelligent switching perfect for L-shaped layouts 107 | - **⏱️ Most Recent**: Uses whichever sensor detected motion most recently 108 | - **🔝 Slave First**: Prioritizes slave sensors over master for upper-level priority 109 | - **🏠 Master First**: Prioritizes master sensor for main-area control 110 | 111 | ### 📊 Enhanced Diagnostics & Monitoring 112 | 113 | The new **Diagnostics Tab** provides comprehensive system insights: 114 | 115 | - **📡 Real-time Sensor Data**: Live readings from all connected devices 116 | - **💚 Connection Health**: Monitor ESP-NOW signal strength and packet success rates 117 | - **🧠 System Performance**: Track memory usage, uptime, and processing efficiency 118 | - **🗺️ Network Topology**: Visual representation of your multi-sensor setup 119 | - **🔧 Troubleshooting Tools**: Identify and resolve connectivity issues instantly 120 | 121 |

122 | Diagnostics Interface 123 |
124 | Real-time diagnostics showing multi-sensor network status 125 |

126 | 127 | ### 🛠️ Critical Improvements 128 | 129 | - **✅ Fixed Compilation Issues**: Resolved linker errors that prevented building from source 130 | - **🧠 Better Memory Management**: Improved handling of different LED strip configurations 131 | - **📡 ESP-NOW Stability**: Enhanced wireless communication reliability 132 | - **💾 EEPROM Validation**: Robust settings storage with automatic corruption recovery 133 | 134 | --- 135 | 136 | ## Key Features 137 | 138 | ### 🎯 Core Functionality 139 | - **Radar-Based Motion Detection**: Uses the LD2410 24GHz radar module for accurate presence and distance sensing without privacy concerns of cameras 140 | - **Dynamic LED Control**: Creates moving light patterns that respond to user proximity 141 | - **🌈 Multiple Lighting Modes**: Choose from 10+ effects including Standard, Rainbow, Color Wave, Breathing, Fire, Theater Chase, and more 142 | - **🎨 Directional Light Trails**: Adds trailing effects that follow movement direction with customizable trail length 143 | - **🌙 Background Lighting**: Optional ambient background illumination when no motion is detected 144 | - **📍 Center Shift Adjustment**: Reposition the active LED zone relative to detected position 145 | 146 | ### 🌐 Advanced Connectivity *(New in v5.1)* 147 | - **🔗 Multi-Sensor Networks**: Connect up to 5 slave devices to one master for complex layouts 148 | - **📡 ESP-NOW Communication**: Low-latency wireless coordination between devices 149 | - **🎛️ Distributed LED Control**: Split long LED strips across multiple devices 150 | - **🧠 Intelligent Sensor Fusion**: Smart algorithms combine data from multiple sensors 151 | 152 | ### 💻 Web Interface & Management 153 | - **📱 Modern Web Interface**: Intuitive tab-based configuration with responsive design 154 | - **📊 Real-Time Visualization**: Live distance detection and LED response preview 155 | - **🌐 WiFi Network Management**: 156 | - Connect to existing networks or create access point 157 | - Scan for available networks with signal strength indicators 158 | - mDNS support for easy device discovery (access via `http://ambisense-[name].local`) 159 | - **💾 Persistent Settings**: All configurations saved to EEPROM with CRC validation 160 | 161 | ### 🏠 Smart Home Integration 162 | - **🏡 Home Assistant Ready**: Full compatibility with our [custom integration](https://github.com/Techposts/ambisense-homeassistant) 163 | - **📊 Enhanced API**: Improved endpoints for external automation systems 164 | - **🔄 Multi-Device Support**: Complete support for master-slave configurations 165 | 166 | --- 167 | 168 | ## Hardware Requirements 169 | 170 | ### Essential Components 171 | - **ESP32 development board** (recommended: ESP32-C3 SuperMini) 172 | - **LD2410 radar sensor module** (24GHz frequency) 173 | - **WS2812B (NeoPixel) compatible LED strip** 174 | - **5V power supply** (adequate for your LED strip length) 175 | - **Connecting wires** and breadboard/PCB for prototyping 176 | 177 | ### Multi-Sensor Setup *(Additional)* 178 | - **Additional ESP32 + LD2410 modules** (up to 5 slaves per master) 179 | - **Individual power supplies** for each sensor location 180 | - **Strategic placement** at turns, landings, or coverage gaps 181 | 182 | --- 183 | 184 | ## Circuit Connections 185 | 186 | ### ESP32-C3 SuperMini to LD2410 Radar 187 | 188 | | ESP32-C3 Pin | LD2410 Pin | Function | 189 | |--------------|------------|----------------------| 190 | | GPIO3 (RX) | TX | Serial Communication | 191 | | GPIO4 (TX) | RX | Serial Communication | 192 | | 5V | VCC | Power Supply | 193 | | GND | GND | Ground | 194 | 195 | ### ESP32-C3 SuperMini to WS2812B LED Strip 196 | 197 | | ESP32-C3 Pin | WS2812B Pin | Function | 198 | |--------------|-------------|-------------| 199 | | GPIO5 | DIN | Data Signal | 200 | | 5V | VCC | Power Supply| 201 | | GND | GND | Ground | 202 | 203 | ### Power Supply Connections 204 | 205 | | Power Supply | ESP32-C3 Pin | Function | 206 | |--------------|--------------|-----------------| 207 | | +5V | 5V | Positive Supply | 208 | | GND | GND | Ground | 209 | 210 |

211 | LD2410C to ESP32C3 SuperMini Circuit Diagram 212 |
213 | Complete wiring diagram for single AmbiSense device 214 |

215 | 216 |

217 | TTP TouchSensor Short A to GND 218 |
219 | Short the A to GND on the Touch Sensor 220 |

221 | 222 | > **⚠️ Power Supply Notes:** 223 | > - For LED strips longer than 30 LEDs, connect 5V power supply directly to WS2812B VCC 224 | > - Calculate power needs: ~60mA per LED at full brightness 225 | > - Use 5V 2A+ power supply for strips with 50+ LEDs 226 | > - Ensure adequate current capacity for your specific LED count 227 | 228 | --- 229 | 230 | ## Software Setup 231 | 232 | ### Option 1: Using Arduino IDE *(Recommended for Customization)* 233 | 234 | 1. **Download the Code** 235 | ```bash 236 | git clone [https://github.com/Techposts/AmbiSense.git](https://github.com/Techposts/AmbiSense.git) 237 | ``` 238 | Or download the ZIP file and extract. 239 | 2. **Install Arduino IDE Libraries** 240 | Go to `Sketch > Include Library > Manage Libraries` 241 | Install these libraries: 242 | - Adafruit NeoPixel (LED control) 243 | - ArduinoJson (Configuration handling) 244 | - WebServer (ESP32 built-in web server) 245 | - WiFi (ESP32 built-in WiFi) 246 | - LD2410 (Radar sensor communication) 247 | 3. **Configure Arduino IDE** 248 | - Board: `Tools > Board > ESP32 > ESP32C3 Dev Module` 249 | - Flash Size: `4MB` 250 | - Partition Scheme: `Default 4MB with spiffs` 251 | - Upload Speed: `921600` 252 | 4. **Upload and Configure** 253 | - Select correct COM port 254 | - Click `Upload` 255 | - Connect to "AmbiSense" WiFi (password: `12345678`) 256 | - Navigate to `http://192.168.4.1` 257 | 258 | ### Option 2: Pre-compiled Binaries *(Recommended for Quick Setup)* 259 | 260 | #### Using ESP Flash Download Tool 261 | 1. **Download Tools** 262 | - ESP Flash Download Tool 263 | - Latest AmbiSense Binaries 264 | 2. **Configure Flash Tool** 265 | - Chip Type: `ESP32-C3 RISC-V` 266 | - SPI Speed: `80MHz` 267 | - SPI Mode: `DIO` 268 | - Flash Size: `4MB` 269 | 3. **Add Binary Files with these addresses:** 270 | ``` 271 | AmbiSense-ESP32C3-v5.1-bootloader.bin → 0x0 272 | AmbiSense-ESP32C3-v5.1-partitions.bin → 0x8000 273 | AmbiSense-ESP32C3-v5.1.bin → 0x10000 274 | ``` 275 | 4. **Flash Device** 276 | - Select COM port and set baud to `921600` 277 | - Click `START` and wait for `FINISH` 278 | 279 | #### Using esptool (Command Line) 280 | ```bash 281 | # Install esptool 282 | pip install esptool 283 | 284 | # Flash firmware (replace COM3 with your port) 285 | esptool.exe --chip esp32c3 --port COM3 --baud 921600 \ 286 | --before default_reset --after hard_reset write_flash -z \ 287 | --flash_mode dio --flash_freq 80m --flash_size 4MB \ 288 | 0x0 AmbiSense-ESP32C3-v5.1-bootloader.bin \ 289 | 0x8000 AmbiSense-ESP32C3-v5.1-partitions.bin \ 290 | 0x10000 AmbiSense-ESP32C3-v5.1.bin 291 | ``` 292 | 293 | # Troubleshooting Multi-Sensor Issues 294 | 295 | **Connection Problems:** 296 | * Check all devices are on the same WiFi channel 297 | * Verify MAC addresses are entered correctly 298 | * Use Diagnostics tab to monitor connection health 299 | * Ensure devices are within ESP-NOW range (~100m line of sight) 300 | 301 | **LED Synchronization Issues:** 302 | * Verify master device has adequate power supply 303 | * Check LED distribution settings match physical setup 304 | * Monitor packet loss in diagnostics 305 | * Consider reducing number of connected slaves 306 | 307 | # Web Interface Features 308 | 309 | ## 🏠 Basic Tab 310 | * **LED Count Configuration**: Set total number of LEDs (1-2000) 311 | * **Distance Range Settings**: Customize min/max detection distances 312 | * **🎨 RGB Color Selection**: Intuitive color picker with live preview 313 | * **💡 Brightness Control**: Global brightness adjustment (0-255) 314 | * **🔄 Quick Reset**: Reset distance values to factory defaults 315 | 316 | ## ⚙️ Advanced Tab 317 | * **🧠 Motion Smoothing**: Enable/disable intelligent motion tracking 318 | * **📍 Position Controls**: Fine-tune position smoothing and prediction 319 | * **🎯 Center Shift**: Adjust LED positioning relative to detected motion 320 | * **✨ Trail Effects**: Configure directional light trails 321 | * **🌙 Background Mode**: Set ambient lighting when no motion detected 322 | * **🎚️ Control Sensitivity**: Adjust motion tracking parameters 323 | 324 | ## 🎆 Effects Tab 325 | * **🌈 Light Mode Selection**: Choose from 10+ visual effects: 326 | * Standard (motion tracking) 327 | * Rainbow (flowing spectrum) 328 | * Color Wave (rippling colors) 329 | * Breathing (fade in/out) 330 | * Fire Effect (realistic flames) 331 | * Theater Chase (marquee lights) 332 | * And more! 333 | * **⚡ Effect Speed**: Control animation speed (1-100) 334 | * **🔥 Effect Intensity**: Adjust effect strength and brightness 335 | 336 | ## 🔗 Multi-Sensor Tab (New in v5.1) 337 | * **👑 Device Role Selection**: Configure as Master or Slave 338 | * **🔍 Device Discovery**: Automatic scanning for nearby AmbiSense devices 339 | * **📡 Network Management**: Add/remove slave devices 340 | * **🎯 Priority Mode Selection**: Choose sensor prioritization strategy 341 | * **📊 LED Distribution**: Configure distributed LED control across devices 342 | * **🩺 Connection Health**: Monitor ESP-NOW network status 343 | 344 | ## 📊 Diagnostics Tab (New in v5.1) 345 | * **📡 Live Sensor Data**: Real-time readings from all connected sensors 346 | * **💚 Connection Status**: ESP-NOW health and signal strength monitoring 347 | * **📈 Performance Metrics**: Memory usage, packet statistics, system uptime 348 | * **🗺️ Network Topology**: Visual representation of your sensor network 349 | * **🔧 Troubleshooting**: Tools for identifying and resolving issues 350 | 351 | ## 🌐 Network Tab 352 | * **📶 WiFi Scanning**: Discover and connect to available networks 353 | * **🏷️ Device Naming**: Set custom mDNS hostname for easy access 354 | * **📊 Status Monitoring**: Real-time connection status and IP information 355 | * **🔄 Network Reset**: Reset WiFi settings to factory defaults 356 | 357 | # Physical Controls 358 | 359 | AmbiSense includes intuitive physical controls for convenient operation: 360 | 361 | * **🔘 Main Control Button (GPIO7)** 362 | * **Short Press (< 2 seconds)**: Toggle System ON/OFF 363 | * Instantly enables/disables all lighting effects 364 | * Useful for quick control without web interface access 365 | * **Long Press (≥ 10 seconds)**: Factory WiFi Reset 366 | * Clears all saved WiFi credentials 367 | * Restarts device in Access Point mode 368 | * LED indicator confirms reset completion 369 | * **💡 LED Status Indicators** 370 | * **Solid Blue**: System active and functioning normally 371 | * **Blinking Blue**: Motion detected and tracking 372 | * **Red Flash**: WiFi reset initiated 373 | * **No Light**: System disabled or error state 374 | 375 | > **💡 Pro Tip**: The physical button provides essential backup control when web interface is inaccessible due to network issues. 376 | 377 | # 3D Printed Case 378 | 379 | Professional enclosure designs for your AmbiSense installation: 380 | 381 |
382 | AmbiSense 3D Case 383 |

Custom 3D printed case housing ESP32 and LD2410 radar module

384 |
385 | 386 | * **📁 Available Files** 387 | * **Main Enclosure**: Houses ESP32-C3 and LD2410 radar sensor 388 | * **Mounting Bracket**: Wall/ceiling mount options 389 | * **Cable Management**: Organized wire routing solutions 390 | * **Download STL files**: [STL Files Folder](https://github.com/Techposts/AmbiSense/tree/main/STL%20Files) * **🖨️ Printing Recommendations** 391 | * **Layer Height**: 0.2mm for optimal detail 392 | * **Infill**: 20% for structural strength 393 | * **Support**: Required for mounting bracket overhangs 394 | * **Material**: PLA or PETG for indoor use 395 | 396 | # Use Cases 397 | 398 | ## 🏠 Residential Applications 399 | * **🚶 Smart Staircases**: Automatic illumination following your path up/down stairs 400 | * **🏃 Hallway Lighting**: Responsive corridor lighting that activates as you approach 401 | * **🛏️ Bedroom Ambiance**: Gentle nighttime navigation lighting 402 | * **🎭 Home Theater**: Dynamic bias lighting that responds to viewer movement 403 | * **♿ Accessibility Aid**: Assistive lighting for visually impaired navigation 404 | 405 | ## 🏢 Commercial & Creative Uses 406 | * **🎨 Interactive Art**: Installations that respond to viewer proximity and movement 407 | * **🏪 Retail Displays**: Eye-catching product showcases with motion-responsive lighting 408 | * **🏛️ Museum Exhibits**: Engaging displays that activate as visitors approach 409 | * **🎪 Event Lighting**: Dynamic stage or venue lighting that follows performers 410 | * **🏥 Healthcare Facilities**: Gentle wayfinding assistance in medical environments 411 | 412 | ## 🌱 Energy Efficiency Benefits 413 | * **💚 Motion-Only Activation**: Lights only when needed, reducing energy waste 414 | * **⏰ Automatic Shutoff**: Configurable timeout when no motion detected 415 | * **🎯 Targeted Illumination**: Only lights necessary areas, not entire spaces 416 | * **📊 Usage Analytics**: Monitor activation patterns for optimization 417 | 418 | # Technical Details 419 | 420 | ## 🔧 Hardware Specifications 421 | * **Microcontroller**: ESP32-C3 (RISC-V, 160MHz, WiFi + Bluetooth) 422 | * **Radar Sensor**: LD2410 (24GHz, 0-6m range, ±60° detection angle) 423 | * **LED Support**: WS2812B/NeoPixel compatible strips (up to 2000 LEDs) 424 | * **Communication**: ESP-NOW for multi-sensor coordination 425 | * **Power**: 5V DC input, automatic LED power management 426 | 427 | ## 💻 Software Architecture 428 | * **Framework**: Arduino Core for ESP32 429 | * **Web Server**: Built-in ESP32 WebServer (no external dependencies) 430 | * **Data Storage**: EEPROM with CRC validation and corruption recovery 431 | * **Communication Protocols**: 432 | * UART for LD2410 radar communication 433 | * ESP-NOW for inter-device coordination 434 | * WiFi for web interface and network connectivity 435 | 436 | ## 📡 Network Capabilities 437 | * **WiFi Modes**: Station (client) and Access Point modes 438 | * **mDNS Support**: Easy device discovery via `hostname.local` 439 | * **ESP-NOW Range**: Up to 100 meters line-of-sight between devices 440 | * **Network Security**: WPA2 WiFi encryption, no external cloud dependencies 441 | 442 | ## 🔒 Security & Privacy 443 | * **Local Processing**: All motion detection handled on-device 444 | * **No Cloud Dependencies**: Complete offline operation capability 445 | * **Privacy-First Design**: Radar sensing provides presence detection without cameras 446 | * **Secure Communication**: Encrypted ESP-NOW and WiFi protocols 447 | 448 | ## ⚡ Performance Metrics 449 | * **Response Time**: <50ms motion detection to LED update 450 | * **Update Rate**: 20Hz radar sensor polling 451 | * **ESP-NOW Latency**: <10ms between master and slave devices 452 | * **Memory Usage**: Optimized for 4MB flash, <80KB RAM usage 453 | * **Power Consumption**: <500mA at 5V with 30 LEDs active 454 | 455 | # Troubleshooting & Support 456 | 457 | ## 🔧 Common Issues 458 | * **Compilation Errors**: 459 | * Ensure you're using AmbiSense v5.1 or later 460 | * Verify all required libraries are installed 461 | * Check ESP32 board package is up to date 462 | * **WiFi Connection Problems**: 463 | * Use physical button to reset WiFi settings 464 | * Check network password and signal strength 465 | * Try manual configuration via Access Point mode 466 | * **Multi-Sensor Connectivity**: 467 | * Verify all devices are running the same firmware version 468 | * Check ESP-NOW range limitations (~100m) 469 | * Use Diagnostics tab to monitor connection health 470 | 471 | ## 📞 Getting Help 472 | * **Issues & Bugs**: [GitHub Issues](https://github.com/Techposts/AmbiSense/issues) * **Discussions**: [GitHub Discussions](https://github.com/Techposts/AmbiSense/discussions) * **Documentation**: [Wiki](https://github.com/Techposts/AmbiSense/wiki) # 📜 License & Credits 473 | * **Created by**: Ravi Singh for TechPosts Media 474 | * **Copyright**: © 2025 TechPosts Media. All rights reserved. 475 | * **License**: MIT License - see `LICENSE` file for details 476 | 477 | ## 🙏 Acknowledgments 478 | * Espressif Systems: ESP32 platform and development tools 479 | * Adafruit: NeoPixel library and hardware ecosystem 480 | * HiLink: LD2410 radar sensor technology 481 | * Community Contributors: Bug reports, feature suggestions, and feedback 482 | 483 |
484 | ⭐ If you find AmbiSense useful, please star this repository! ⭐ 485 |

486 | 487 | GitHub Stars 488 | 489 | 490 | GitHub Forks 491 | 492 | 493 | GitHub Issues 494 | 495 |
496 | 497 | --- 498 | 499 | -------------------------------------------------------------------------------- /STL Files/AmbiSense-LD2410SnapFit-Top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techposts/AmbiSense/c9650d710e8fed2ea3448c6db4e18d522360f701/STL Files/AmbiSense-LD2410SnapFit-Top.stl -------------------------------------------------------------------------------- /STL Files/Ambisense-LD2410SnapFit-Base.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techposts/AmbiSense/c9650d710e8fed2ea3448c6db4e18d522360f701/STL Files/Ambisense-LD2410SnapFit-Base.stl --------------------------------------------------------------------------------