diff --git a/Enhanced-WiFi-Analyzer/README.md b/Enhanced-WiFi-Analyzer/README.md
new file mode 100644
index 0000000..9814d45
--- /dev/null
+++ b/Enhanced-WiFi-Analyzer/README.md
@@ -0,0 +1,340 @@
+# 🧠 Enhanced WiFi Analyzer
+
+> **Advanced WiFi diagnostics and troubleshooting for Linux with WiFi 7, DFS monitoring, and modern VPN support**
+
+[](https://www.gnu.org/software/bash/)
+[]()
+[]()
+
+A comprehensive WiFi analysis and troubleshooting tool that diagnoses connectivity issues, optimizes performance, and provides distribution-specific fixes for modern Linux systems. Features DFS radar interference detection, WiFi 7/6E support, and modern VPN integration.
+
+## ⚠️ Framework Support Disclaimer
+
+**Before implementing any power management changes recommended by this tool, please verify with Framework Support first.**
+
+While the Enhanced WiFi Analyzer provides valuable diagnostic information and generates safe configuration scripts, power management settings should only be modified when addressing specific connectivity issues. The tool may recommend disabling PCIe ASPM (Active State Power Management) or NetworkManager power saving features, but these changes should only be applied if:
+
+1. **You are experiencing actual WiFi connectivity problems** (disconnections, micro-dropouts, poor roaming)
+2. **The analysis clearly identifies power management as the root cause**
+3. **Framework Support has reviewed your specific situation and confirmed the recommendation**
+
+### Why This Matters
+
+Power management features exist for good reasons - they extend battery life and reduce heat generation. Disabling them unnecessarily can impact your system's efficiency without providing any benefits. The diagnostic tools help identify *potential* power management conflicts, but not every detection requires action.
+
+### Recommended Workflow
+
+1. **Run the analysis** to identify potential issues: Run the script per the instructions.
+2. **Document your specific symptoms** (connection drops, poor performance, etc.)
+3. **Contact Framework Support** with both your symptoms and the tool's findings
+4. **Apply recommended changes only** after confirmation from Support
+5. **Test thoroughly** and revert changes if they don't resolve your specific issues
+
+### Contact Framework Support
+
+[Contact](https://framework.kustomer.help/contact/support-request-ryon9uAuq) - Ask to send your findings to the Linux Support Team
+
+Remember: These diagnostic tools are designed to help identify issues, not automatically fix them. Always verify recommendations with Framework Support before making system changes.
+
+## 📚 Table of Contents
+
+- [🚀 Key Features](#-key-features)
+- [🎯 Why Use This Tool?](#-why-use-this-tool)
+- [📋 Quick Start](#-quick-start)
+- [🎛️ Main Menu Options](#️-main-menu-options)
+- [🔧 Common Use Cases](#-common-use-cases)
+- [📊 Example Analysis Output](#-example-analysis-output)
+- [🚀 Advanced Features](#-advanced-features)
+- [💡 Pro Tips](#-pro-tips)
+- [🔍 Troubleshooting Matrix](#-troubleshooting-matrix)
+- [🛡️ Safety & Compatibility](#️-safety--compatibility)
+- [📈 Future Development](#-future-development)
+- [🔗 Related Tools](#-related-tools)
+- [🤝 Contributing](#-contributing)
+
+## 🚀 Key Features
+
+### 🔬 **Advanced Diagnostics**
+- **Complete WiFi Health Analysis** - Full system assessment with scoring
+- **Modern Chipset Detection** - Supports WiFi 7, 6E, MLO, and new hardware
+- **Real-time Connectivity Testing** - Tests actual data flow beyond basic connection status
+- **Distribution-Aware Analysis** - Tailored for Fedora, Ubuntu, Arch (NetworkManager), Debian, openSUSE, and immutable systems
+
+### 📡 **DFS Radar Interference Monitoring**
+- **Radar Event Detection** - Identifies DFS channel issues causing sudden disconnections
+- **Smart Channel Switching** - Automated migration to non-DFS safe channels
+- **Environment Analysis** - Maps DFS usage patterns in your area
+- **6GHz Migration Path** - Recommendations for DFS-free 6GHz operation
+
+### 🔒 **Modern VPN Integration**
+- **Advanced VPN Detection** - Supports Tailscale, ZeroTier, Nebula, WireGuard, commercial VPNs
+- **VPN-WiFi Conflict Resolution** - Diagnoses and fixes VPN-related connectivity issues
+- **Split Tunneling Optimization** - Configures optimal routing for modern mesh VPNs
+- **MTU Optimization** - Automatic sizing for VPN tunnels
+
+### 🌐 **WiFi 6E/7 Optimization**
+- **6GHz Band Analysis** - Clean spectrum identification and optimization
+- **320MHz Channel Width** - Ultra-wide channel support for maximum throughput
+- **MLO (Multi-Link Operation)** - WiFi 7 multi-band aggregation support
+- **Regulatory Domain Optimization** - Proper power limits and channel availability
+
+### 🛠️ **Interactive Troubleshooting**
+- **Guided Problem Solving** - Step-by-step fixes for common issues
+- **Emergency Repair Mode** - Quick fixes for critical failures
+- **Distribution-Specific Commands** - Tailored solutions for your Linux distribution
+- **Thermal Management** - Overheating detection and fixes
+
+### ⚡ **Performance Optimization**
+- **Band Switching Automation** - Intelligent 2.4/5/6GHz selection
+- **Signal Strength Analysis** - RF environment mapping and optimization
+- **Power Management** - Battery life vs performance balancing
+- **Channel Width Optimization** - Maximizes throughput while maintaining stability
+
+## 🎯 Why Use This Tool?
+
+### **Solves Real Problems**
+- **DFS Disconnections**: Identifies and fixes mysterious 30+ second WiFi drops caused by radar interference
+- **Modern VPN Issues**: Resolves connectivity problems with Tailscale, ZeroTier, and other mesh VPNs
+- **WiFi 7 Optimization**: Optimizes cutting-edge WiFi hardware within driver/firmware limitations
+- **Distribution Chaos**: Provides correct commands for your specific Linux distribution
+
+### **Beyond Basic Tools**
+- Most WiFi tools only check connection status - this analyzes actual data flow
+- Detects issues that `nmcli` and GUI tools miss
+- Provides root cause analysis, not just symptoms
+- Includes proactive recommendations to prevent future issues
+
+### **Expert Knowledge Built-In**
+- Incorporates knowledge of MediaTek, Intel, and Qualcomm chipset quirks
+- Understands regulatory domain impacts on performance
+- Knows which channels are safe vs DFS across different regions
+- Includes thermal management strategies for high-performance WiFi cards
+
+## 📋 Quick Start
+
+### Prerequisites
+```bash
+# Required tools (install via package manager)
+sudo apt install iw curl # Ubuntu/Debian
+sudo dnf install iw curl # Fedora
+sudo pacman -S iw curl # Arch (NetworkManager required - not iwd compatible)
+```
+
+### Installation
+```bash
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh -o wifi_diagnostic.sh && clear && sudo bash wifi_diagnostic.sh
+```
+
+If already downloaded, just run:
+```bash
+sudo bash wifi_diagnostic.sh
+```
+
+### Quick Analysis
+Download and run (first time):
+```bash
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh -o wifi_diagnostic.sh && clear && sudo bash wifi_diagnostic.sh
+```
+
+If already downloaded:
+```bash
+sudo bash wifi_diagnostic.sh
+```
+
+Then choose your option:
+- Option 1: Complete analysis (recommended first run)
+- Option 4: DFS-specific monitoring for radar interference
+- Option 9: Emergency fixes for immediate solutions
+
+## 🎛️ Main Menu Options
+
+| Option | Feature | Use Case |
+|--------|---------|----------|
+| **1** | 🎯 **Complete Analysis** | Full system health check with all modern features |
+| **2** | 🚨 **Error Analysis** | Deep dive into logs and failure patterns |
+| **3** | 🛠️ **Interactive Troubleshooting** | Guided problem-solving with custom solutions |
+| **4** | 📡 **DFS Channel Monitor** | Dedicated radar interference analysis |
+| **5** | 🧪 **TX Power Band Test** | Diagnose power limitations and optimize range |
+| **6** | 📡 **Manual Band Switching** | Direct CLI commands for 2.4/5/6GHz control |
+
+## 🔧 Common Use Cases
+
+### **Scenario 1: Mysterious Disconnections**
+Symptoms: WiFi drops for 30+ seconds randomly
+
+```bash
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh -o wifi_diagnostic.sh && clear && sudo bash wifi_diagnostic.sh
+```
+
+→ Choose Option 4 (DFS Monitor)
+Tool identifies DFS radar interference and provides non-DFS channel solutions
+
+### **Scenario 2: Slow WiFi 7 Performance**
+Symptoms: New WiFi 7 card performing poorly
+
+```bash
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh -o wifi_diagnostic.sh && clear && sudo bash wifi_diagnostic.sh
+```
+
+→ Choose Option 1 (Complete Analysis)
+Tool detects 6GHz capability and recommends router upgrade/configuration
+
+### **Scenario 3: VPN Breaks WiFi**
+Symptoms: WiFi unstable when Tailscale/ZeroTier active
+
+```bash
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh -o wifi_diagnostic.sh && clear && sudo bash wifi_diagnostic.sh
+```
+
+→ Choose Option 3 → Option 4 (VPN conflicts)
+Tool provides MTU optimization and split tunneling configuration
+
+### **Scenario 4: Overheating Laptop**
+Symptoms: High temperatures, fan noise during WiFi use
+
+```bash
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh -o wifi_diagnostic.sh && clear && sudo bash wifi_diagnostic.sh
+```
+
+→ Choose Option 3 → Option 5 (thermal issues)
+Tool provides ASPM fixes and power management optimization
+
+## 📊 Example Analysis Output
+
+```
+🧠 === ENHANCED SYSTEM INTELLIGENCE GATHERING ===
+🐧 Distribution: Fedora Linux 39
+🔍 WiFi Interface: wlp1s0
+🔧 WiFi Functional Status: ✅ WORKING
+🔧 Hardware: MediaTek Inc. MT7925 WiFi 7 (802.11be)
+📊 Chip Analysis:
+ Vendor: MediaTek
+ Model: MT7925
+ Generation: WiFi 7 (802.11be) - 160MHz capable
+ Known Issues: Excellent Linux support in kernel 6.8+
+
+📡 === DFS CHANNEL ANALYSIS ===
+📍 Regulatory Domain: US
+🔍 Current Connection DFS Analysis:
+ ⚠️ Currently connected to DFS channel: 100 (5500 MHz)
+ 🎯 DFS Impact: Medium to High risk of disconnections
+🚨 Found 3 DFS/radar events in last 24 hours
+
+🎯 === FINAL ANALYSIS SUMMARY ===
+📊 Overall WiFi Health: Fair (DFS risk) (50/100)
+🔧 WiFi Status: ✅ Working
+📡 DFS Status: ⚠️ Connected to DFS channel (radar risk)
+🌟 WiFi 7 Features: Standard WiFi 7
+
+⚠️ DFS RECOMMENDATION: Switch to non-DFS channel for stability
+💡 Suggested channels: 36, 40, 44, 48 (low 5GHz) or 149+ (high 5GHz)
+```
+
+## 🚀 Advanced Features
+
+### **Advanced Features**
+- **Automatic Radar Detection**: Monitors system logs for DFS events
+- **Environmental Mapping**: Scans for DFS channel usage in your area
+- **Smart Channel Recommendations**: Suggests optimal non-DFS alternatives
+- **6GHz Migration Planning**: Path to DFS-free operation
+
+### **Modern VPN Support**
+- **Mesh VPN Optimization**: Tailscale, ZeroTier, Nebula configuration
+- **Commercial VPN Fixes**: NordVPN, ExpressVPN, Surfshark compatibility
+- **Split Tunneling**: Optimizes traffic routing for better performance
+- **DNS Conflict Resolution**: Fixes modern VPN DNS issues
+
+### **WiFi 7 Optimization**
+- **6GHz Band Access**: Identifies clean spectrum opportunities
+- **320MHz Channels**: Ultra-wide channel detection and recommendations
+- **MLO Support**: Multi-Link Operation analysis for maximum throughput
+- **Advanced Power Management**: Thermal optimization for high-performance cards
+
+### **Distribution Intelligence**
+- **Immutable Systems**: Special handling for Silverblue, Kinoite, Bluefin
+- **Package Manager Detection**: Uses correct commands for dnf, apt, pacman, zypper
+- **Firmware Management**: Distribution-specific update procedures
+- **Kernel Parameter Handling**: Proper GRUB vs rpm-ostree vs bootc configuration
+
+## 💡 Pro Tips
+
+### **For System Administrators**
+- Use Option 1 for baseline health assessment of fleet WiFi systems
+- Option 4 provides regulatory compliance checking for enterprise environments
+- Log outputs to files for trend analysis: `./wifi_diagnostic.sh | tee wifi_analysis.log`
+
+### **For Developers/Power Users**
+- Option 6 provides direct CLI commands for automation and scripting
+- All temporary fixes can be converted to permanent configurations
+- Regulatory domain optimization maximizes performance within legal limits
+
+### **For Gamers/Streamers**
+- DFS monitoring eliminates lagspikes from radar interference
+- 6GHz optimization provides lowest latency connections
+- Thermal management prevents throttling during extended use
+
+## 🔍 Troubleshooting Matrix
+
+| Symptom | Likely Cause | Tool Solution |
+|---------|--------------|---------------|
+| **Random 30s+ disconnections** | DFS radar interference | Option 4 → Non-DFS channels |
+| **WiFi fails after suspend** | Driver power management | Option 3 → Suspend/resume fixes |
+| **Slow WiFi 7 speeds** | Wrong band/channel width | Option 1 → 6GHz optimization |
+| **VPN breaks WiFi** | MTU/routing conflicts | Option 3 → VPN optimization |
+| **Overheating during WiFi** | ASPM/power management | Option 3 → Thermal fixes |
+| **Connection fails entirely** | Driver/firmware issues | Option 2 → Distribution fixes |
+
+## 🛡️ Safety & Compatibility
+
+### **Safe Operation**
+- All temporary changes revert on reboot
+- Permanent changes clearly marked and reversible
+- No destructive operations without explicit user confirmation
+- Comprehensive logging for audit trails
+
+### **Broad Compatibility**
+- **Distributions**: Fedora, Ubuntu, Debian, Arch (NetworkManager only), openSUSE, Pop!_OS, Mint, immutable variants
+- **Hardware**: Intel, MediaTek, Qualcomm, Broadcom WiFi chipsets
+- **Standards**: WiFi 4/5/6/6E/7, 2.4/5/6GHz bands
+- **VPNs**: WireGuard, OpenVPN, modern mesh protocols
+- **Network Managers**: NetworkManager (iwd support not yet implemented)
+
+## 📈 Future Development
+
+- [ ] iwd network manager support (currently NetworkManager only)
+- [ ] Integration with WiFi 8 (802.11bn) when available
+- [ ] Bluetooth coexistence analysis
+- [ ] Automated performance benchmarking
+- [ ] Web interface for remote diagnostics
+- [ ] Integration with network monitoring systems
+
+## 🔗 Related Tools
+
+### **Specialized WiFi Analysis**
+- **[Framework WiFi Mesh Network Analyzer](https://github.com/FrameworkComputer/linux-docs/tree/main/MeshAnalyzer#wifi-mesh-network-analyzer)** - Framework's dedicated tool for mesh network performance analysis and optimization
+
+### **When to Use Which Tool**
+| Scenario | Enhanced WiFi Analyzer | Framework Mesh Analyzer |
+|----------|----------------------|------------------------|
+| **General WiFi issues** | ✅ Primary tool | ⚪ Not needed |
+| **DFS disconnections** | ✅ Specialized detection | ⚪ Limited coverage |
+| **VPN conflicts** | ✅ Modern VPN support | ⚪ Not covered |
+| **Framework + Mesh** | ✅ General analysis | ✅ Mesh optimization |
+| **Thermal/power issues** | ✅ Comprehensive | ⚪ Not covered |
+| **Mesh performance tuning** | ⚪ Basic detection | ✅ Specialized analysis |
+
+### **Complementary Workflow**
+1. **Start here** - Run Enhanced WiFi Analyzer for comprehensive system health
+2. **Framework + Mesh users** - Follow up with Framework's Mesh Analyzer for specialized optimization
+3. **Best results** - Use both tools for complete coverage of modern WiFi challenges
+
+## 🤝 Contributing
+
+Contributions welcome! Areas of particular interest:
+- Additional chipset quirks and optimizations
+- Distribution-specific command improvements
+- VPN protocol support expansion
+- Regional regulatory domain data
+- Performance optimization techniques
+
+---
diff --git a/Enhanced-WiFi-Analyzer/scripts/readme b/Enhanced-WiFi-Analyzer/scripts/readme
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Enhanced-WiFi-Analyzer/scripts/readme
@@ -0,0 +1 @@
+
diff --git a/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh b/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh
new file mode 100644
index 0000000..2a40563
--- /dev/null
+++ b/Enhanced-WiFi-Analyzer/scripts/wifi_diagnostic.sh
@@ -0,0 +1,3649 @@
+# Look for DFS-related events in journalctl#!/bin/bash
+
+# Advanced WiFi Disconnect Intelligence Analyzer
+# Enhanced with VPN detection, RF/frequency analysis, WiFi 7 support, distribution detection, and DFS monitoring
+# Corrected version
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+BOLD='\033[1m'
+CYAN='\033[0;36m'
+MAGENTA='\033[0;35m'
+NC='\033[0m'
+
+# Global variables
+IFACE=""
+WIFI_HARDWARE=""
+DRIVER=""
+CURRENT_FREQ=""
+CURRENT_BAND=""
+CURRENT_SSID=""
+POWER_SAVE=""
+INTELLIGENCE_SCORE=0
+DISCONNECT_PATTERN=""
+VPN_ACTIVE="No"
+VPN_TYPE="None"
+VPN_INTERFACE="None"
+VPN_IMPACT_SCORE=0
+WIFI_FUNCTIONAL=false
+SEVERE_ISSUES=0
+FAILURE_LOG_DIR="/tmp/wifi_failure_logs"
+DISTRO_ID=""
+DISTRO_NAME=""
+SUPPORTS_WIFI7=false
+SUPPORTS_MLO=false
+CHANNEL_WIDTH=""
+CHIP_MODEL=""
+CHIP_VENDOR=""
+CHIP_GENERATION=""
+KNOWN_ISSUES=""
+DRIVER_NAME=""
+CURRENT_SIGNAL=""
+CURRENT_BITRATE=""
+SCAN_RESULTS=""
+
+# Function to sanitize numeric variables
+sanitize_number() {
+ local value="$1"
+ local default="${2:-0}"
+
+ # Extract first line, remove non-digits, default to 0 if empty
+ echo "$value" | head -1 | tr -d '\n' | grep -o '[0-9]*' | head -1 | sed 's/^$/'"$default"'/'
+}
+
+# DFS-specific global variables
+DFS_CHANNELS_DETECTED=0
+DFS_CURRENT_CONNECTION=false
+DFS_RADAR_EVENTS=0
+DFS_CHANNELS_LIST=""
+DFS_IMPACT_SCORE=0
+DFS_CAC_EVENTS=0
+DFS_COUNT=0
+
+# DFS channel definitions by region (focusing on common regions)
+declare -A DFS_CHANNELS
+DFS_CHANNELS[US]="52 56 60 64 100 104 108 112 116 120 124 128 132 136 140 144"
+DFS_CHANNELS[EU]="52 56 60 64 100 104 108 112 116 120 124 128 132 136 140"
+DFS_CHANNELS[JP]="52 56 60 64 100 104 108 112 116 120 124 128 132 136 140"
+
+# Function to convert frequency to channel number
+freq_to_channel() {
+ local freq=$1
+ local channel=""
+
+ # Convert floating point frequency to integer
+ local freq_int=$(printf "%.0f" "$freq" 2>/dev/null || echo "$freq" | cut -d'.' -f1)
+
+ if [ "$freq_int" -ge 2412 ] && [ "$freq_int" -le 2484 ]; then
+ # 2.4GHz band
+ if [ "$freq_int" -eq 2484 ]; then
+ channel=14
+ else
+ channel=$(echo "scale=0; ($freq_int - 2412) / 5 + 1" | bc 2>/dev/null || echo "$(( (freq_int - 2412) / 5 + 1 ))")
+ fi
+ elif [ "$freq_int" -ge 5000 ] && [ "$freq_int" -le 6000 ]; then
+ # 5GHz band
+ channel=$(echo "scale=0; ($freq_int - 5000) / 5" | bc 2>/dev/null || echo "$(( (freq_int - 5000) / 5 ))")
+ elif [ "$freq_int" -ge 6000 ] && [ "$freq_int" -le 7200 ]; then
+ # 6GHz band (simplified - actual 6GHz channels are more complex)
+ channel="6GHz-$(echo "scale=0; ($freq_int - 5950) / 5" | bc 2>/dev/null || echo "$(( (freq_int - 5950) / 5 ))")"
+ fi
+
+ echo "$channel"
+}
+
+# Check if channel is DFS
+is_dfs_channel() {
+ local channel=$1
+ local region=${2:-US} # Default to US
+
+ # Skip 6GHz channels (no DFS in 6GHz)
+ if echo "$channel" | grep -q "6GHz"; then
+ return 1
+ fi
+
+ # Get DFS channels for region
+ local dfs_list="${DFS_CHANNELS[$region]}"
+
+ # Check if channel is in DFS list
+ for dfs_ch in $dfs_list; do
+ if [ "$channel" = "$dfs_ch" ]; then
+ return 0 # Is DFS
+ fi
+ done
+
+ return 1 # Not DFS
+}
+
+# Enhanced DFS channel analysis
+analyze_dfs_channels() {
+ echo -e "${MAGENTA}📡 === DFS CHANNEL ANALYSIS ===${NC}"
+ echo "🔍 Dynamic Frequency Selection monitoring for radar interference"
+ echo ""
+
+ if [ -z "$IFACE" ]; then
+ echo "❌ Cannot analyze DFS channels - WiFi interface not available"
+ return 1
+ fi
+
+ # Get regulatory domain
+ REG_DOMAIN="US" # Default
+ REG_INFO=$(iw reg get 2>/dev/null)
+ if [ -n "$REG_INFO" ]; then
+ REG_COUNTRY=$(echo "$REG_INFO" | grep "country" | head -1 | awk '{print $2}' | tr -d ':')
+ if [ -n "$REG_COUNTRY" ]; then
+ REG_DOMAIN="$REG_COUNTRY"
+ fi
+ fi
+
+ echo "📍 Regulatory Domain: $REG_DOMAIN"
+ echo "📋 DFS Channels in $REG_DOMAIN: ${DFS_CHANNELS[$REG_DOMAIN]:-${DFS_CHANNELS[US]}}"
+ echo ""
+
+ # Get current connection details if not already available
+if [ -z "$CURRENT_FREQ" ]; then
+ WIFI_INFO=$(iw dev "$IFACE" link 2>/dev/null)
+ if ! echo "$WIFI_INFO" | grep -q "Not connected"; then
+ CURRENT_FREQ=$(echo "$WIFI_INFO" | grep "freq:" | awk '{print $2}')
+ CURRENT_SSID=$(echo "$WIFI_INFO" | grep "SSID:" | awk '{print $2}')
+ fi
+fi
+
+# Check current connection for DFS usage
+echo "🔍 Current Connection DFS Analysis:"
+if [ -n "$CURRENT_FREQ" ] && [ "$CURRENT_FREQ" != "Unknown" ]; then
+ CURRENT_CHANNEL=$(freq_to_channel "$CURRENT_FREQ")
+
+ if is_dfs_channel "$CURRENT_CHANNEL" "$REG_DOMAIN"; then
+ DFS_CURRENT_CONNECTION=true
+ echo -e " ${YELLOW}⚠️ Currently connected to DFS channel: $CURRENT_CHANNEL ($CURRENT_FREQ MHz)${NC}"
+ echo " 🎯 DFS Impact: Medium to High risk of disconnections"
+ DFS_IMPACT_SCORE=75
+ else
+ DFS_CURRENT_CONNECTION=false
+ echo -e " ${GREEN}✅ Current channel $CURRENT_CHANNEL ($CURRENT_FREQ MHz) is NOT DFS${NC}"
+ echo " 🎯 DFS Impact: No risk from current connection"
+ DFS_IMPACT_SCORE=0
+ fi
+else
+ echo " ❓ Cannot determine current channel - not connected"
+fi
+echo ""
+
+ # Scan for DFS channels in environment
+ echo "🔍 Scanning for DFS channels in area..."
+
+ SCAN_RESULTS=$(timeout 20 iw dev "$IFACE" scan 2>/dev/null)
+
+ if [ -n "$SCAN_RESULTS" ]; then
+ DFS_CHANNELS_DETECTED=0
+ DFS_NETWORKS_LIST=""
+
+ # Parse scan results for DFS channels
+ echo "$SCAN_RESULTS" | grep -E "freq:|SSID:" | while IFS= read -r line; do
+ if echo "$line" | grep -q "freq:"; then
+ FREQ=$(echo "$line" | awk '{print $2}')
+ CHANNEL=$(freq_to_channel "$FREQ")
+
+ if is_dfs_channel "$CHANNEL" "$REG_DOMAIN"; then
+ DFS_CHANNELS_DETECTED=$((DFS_CHANNELS_DETECTED + 1))
+ # Get the SSID for this frequency (next SSID line after freq)
+ SSID=$(echo "$SCAN_RESULTS" | grep -A5 "freq: $FREQ" | grep "SSID:" | head -1 | awk '{print $2}')
+ if [ -n "$SSID" ] && [ "$SSID" != "\\x00" ]; then
+ echo " 🚨 DFS Network: $SSID (Channel $CHANNEL, $FREQ MHz)"
+ else
+ echo " 🚨 DFS Channel: $CHANNEL ($FREQ MHz) - Hidden SSID"
+ fi
+ fi
+ fi
+ done
+
+ # Count DFS networks separately - FIXED VERSION
+ DFS_NETWORKS=$(echo "$SCAN_RESULTS" | awk '
+ /freq:/ {
+ freq = $2;
+ # Convert floating point to integer for channel calculation
+ freq_int = int(freq + 0.5)
+ if (freq_int >= 5000 && freq_int <= 6000) {
+ channel = int((freq_int - 5000) / 5)
+ } else {
+ channel = 0
+ }
+ }
+ /SSID:/ && $2 != "\\x00" && $2 != "" {
+ if (channel == 52 || channel == 56 || channel == 60 || channel == 64 ||
+ (channel >= 100 && channel <= 144)) {
+ print $2 " (Ch " channel ", " freq " MHz)"
+ }
+ }
+ ')
+
+ # FIXED: Get proper count without newlines
+ if [ -n "$DFS_NETWORKS" ]; then
+ DFS_COUNT=$(echo "$DFS_NETWORKS" | grep -c "Ch " 2>/dev/null)
+ else
+ DFS_COUNT=0
+ fi
+
+ # Ensure DFS_COUNT is a single integer
+ DFS_COUNT=$(sanitize_number "$DFS_COUNT" "0")
+
+ echo ""
+ echo "📊 DFS Environment Summary:"
+ echo " DFS Networks Detected: $DFS_COUNT"
+
+ if [ "$DFS_COUNT" -gt 0 ]; then
+ echo " 🚨 DFS Networks in Area:"
+ echo "$DFS_NETWORKS" | head -10 | while IFS= read -r network; do
+ if [ -n "$network" ]; then
+ echo " • $network"
+ fi
+ done
+
+ if [ "$DFS_COUNT" -gt 10 ]; then
+ echo " ... and $((DFS_COUNT - 10)) more DFS networks"
+ fi
+ fi
+ else
+ echo " ❌ Cannot scan environment - scan failed"
+ DFS_COUNT=0
+ fi
+
+ echo ""
+
+ # Check for recent DFS/radar events in system logs
+ echo "🔍 Checking for recent DFS/radar events..."
+
+ DFS_RADAR_EVENTS=0
+ DFS_CAC_EVENTS=0
+
+ # Look for radar detection events
+RADAR_EVENTS=$(journalctl --since "24 hours ago" --no-pager 2>/dev/null | \
+ grep -iE "radar.*(detect|found)|dfs.*(detect|switch|cac)|channel.*(blocked|switch).*radar|cfg80211.*radar|ieee80211.*radar|ath.*radar|iwlwifi.*radar|mt76.*radar" | \
+ grep -v "packagekit\|cache\|python" | \
+ wc -l)
+
+ if [ "$RADAR_EVENTS" -gt 0 ]; then
+ DFS_RADAR_EVENTS="$RADAR_EVENTS"
+ echo -e " ${RED}🚨 Found $RADAR_EVENTS DFS/radar events in last 24 hours${NC}"
+ echo " 📋 Recent DFS events:"
+ journalctl --since "6 hours ago" --no-pager 2>/dev/null | \
+ grep -iE "radar.*(detect|found)|dfs.*(detect|switch|cac)|channel.*(blocked|switch).*radar|cfg80211.*radar|ieee80211.*radar|ath.*radar|iwlwifi.*radar|mt76.*radar" | \
+ grep -v "packagekit\|cache\|python" | \
+ tail -5 | while IFS= read -r event; do
+ echo " $event"
+done
+
+ DFS_IMPACT_SCORE=$((DFS_IMPACT_SCORE + 50))
+ else
+ echo -e " ${GREEN}✅ No recent DFS/radar events detected${NC}"
+ fi
+
+ # Look for CAC (Channel Availability Check) events
+ if command -v dmesg >/dev/null 2>&1; then
+ CAC_EVENTS=$(dmesg | grep -iE "cac.*start|cac.*complete|cac.*abort" | wc -l)
+ if [ "$CAC_EVENTS" -gt 0 ]; then
+ DFS_CAC_EVENTS="$CAC_EVENTS"
+ echo " 📡 CAC (Channel Availability Check) events: $CAC_EVENTS"
+ echo " 💡 CAC events indicate DFS channel switching activity"
+ fi
+ fi
+
+ echo ""
+
+ # DFS Impact Assessment
+ echo "🎯 === DFS IMPACT ASSESSMENT ==="
+
+ # Sanitize all numeric variables before comparisons
+ DFS_COUNT=$(sanitize_number "$DFS_COUNT" "0")
+ DFS_RADAR_EVENTS=$(sanitize_number "$DFS_RADAR_EVENTS" "0")
+ DFS_IMPACT_SCORE=$(sanitize_number "$DFS_IMPACT_SCORE" "0")
+
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo -e " ${YELLOW}⚠️ HIGH RISK: Connected to DFS channel${NC}"
+ echo " 💡 DFS channels must stop transmission when radar is detected"
+ echo " 💡 This can cause sudden disconnections lasting 30+ seconds"
+ fi
+
+ if [ "$DFS_COUNT" -gt 5 ]; then
+ echo -e " ${YELLOW}⚠️ MEDIUM RISK: High DFS usage in area ($DFS_COUNT networks)${NC}"
+ echo " 💡 Heavy DFS usage indicates radar-prone environment"
+ DFS_IMPACT_SCORE=$((DFS_IMPACT_SCORE + 25))
+ elif [ "$DFS_COUNT" -gt 0 ]; then
+ echo -e " ${GREEN}✅ LOW RISK: Some DFS usage in area ($DFS_COUNT networks)${NC}"
+ echo " 💡 Moderate DFS environment - watch for patterns"
+ DFS_IMPACT_SCORE=$((DFS_IMPACT_SCORE + 10))
+ else
+ echo -e " ${GREEN}✅ NO RISK: No DFS channels detected in area${NC}"
+ echo " 💡 Clean environment - DFS not a factor"
+ fi
+
+ if [ "$DFS_RADAR_EVENTS" -gt 0 ]; then
+ echo -e " ${RED}🚨 CRITICAL: Recent radar detection events${NC}"
+ echo " 💡 Active radar environment - expect frequent DFS disconnections"
+ fi
+
+ echo ""
+ echo "📊 DFS Risk Score: $DFS_IMPACT_SCORE/100"
+
+ if [ "$DFS_IMPACT_SCORE" -ge 75 ]; then
+ echo -e " ${RED}🚨 HIGH DFS RISK - Immediate action recommended${NC}"
+ elif [ "$DFS_IMPACT_SCORE" -ge 50 ]; then
+ echo -e " ${YELLOW}⚠️ MODERATE DFS RISK - Monitor and optimize${NC}"
+ elif [ "$DFS_IMPACT_SCORE" -ge 25 ]; then
+ echo -e " ${YELLOW}📊 LOW DFS RISK - Minor impact possible${NC}"
+ else
+ echo -e " ${GREEN}✅ MINIMAL DFS RISK - Not a significant factor${NC}"
+ fi
+
+ echo ""
+}
+
+# Test actual connectivity with VPN awareness
+test_actual_connectivity() {
+ echo "🔍 Testing WiFi connection and data flow..."
+
+ if [ -n "$IFACE" ]; then
+ WIFI_LINK_STATUS=$(iw dev "$IFACE" link 2>/dev/null)
+
+ if echo "$WIFI_LINK_STATUS" | grep -q "Not connected"; then
+ echo " ❌ SEVERE: WiFi not connected to any network"
+ return 1
+ elif echo "$WIFI_LINK_STATUS" | grep -q "Connected to"; then
+ CURRENT_SSID=$(echo "$WIFI_LINK_STATUS" | grep "SSID" | awk '{print $2}')
+ echo " 📡 WiFi connected to: ${CURRENT_SSID:-Unknown SSID}"
+
+ # Test internet connectivity with VPN awareness
+ if ! timeout 10 ping -c 3 -W 5 8.8.8.8 >/dev/null 2>&1; then
+ echo " ❌ SEVERE: No internet connectivity despite WiFi connection"
+ if [ "$VPN_ACTIVE" = "Yes" ]; then
+ echo " 🔒 VPN active - may be blocking or routing traffic incorrectly"
+ fi
+ return 1
+ fi
+
+ echo " ✅ Data flow verified - connectivity working"
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+# Severe issue detection with DFS awareness
+detect_severe_wifi_issues() {
+ echo -e "${RED}🚨 === SEVERE ISSUE DETECTION ===${NC}"
+ echo "🔍 Testing for issues requiring NetworkManager restart..."
+ echo ""
+
+ SEVERE_ISSUES=0
+
+ # Test actual connectivity
+ if ! test_actual_connectivity; then
+ SEVERE_ISSUES=$((SEVERE_ISSUES + 1))
+ fi
+
+ # Check for DFS-related disconnection patterns
+ echo ""
+ echo "🔍 Checking for DFS-related disconnection patterns..."
+
+ if [ "$DFS_CURRENT_CONNECTION" = true ] && [ "$DFS_RADAR_EVENTS" -gt 0 ]; then
+ echo -e "${RED}🚨 DFS RADAR INTERFERENCE DETECTED${NC}"
+ echo " Current connection uses DFS channel with recent radar events"
+ echo " This likely explains WiFi disconnections"
+ SEVERE_ISSUES=$((SEVERE_ISSUES + 1))
+ elif [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo -e "${YELLOW}⚠️ DFS RISK: Connected to radar-sensitive channel${NC}"
+ echo " Monitor for sudden disconnections lasting 30+ seconds"
+ fi
+
+ echo ""
+
+ # Provide distribution-aware recommendations
+ if [ "$SEVERE_ISSUES" -gt 0 ]; then
+ echo -e "${RED}🚨 SEVERE ISSUES DETECTED: $SEVERE_ISSUES${NC}"
+ echo ""
+ echo -e "${YELLOW}🔧 IMMEDIATE ACTION REQUIRED:${NC}"
+ echo ""
+
+ if [ "$VPN_ACTIVE" = "Yes" ]; then
+ echo -e "${CYAN}🔒 VPN DETECTED - Try VPN-specific fixes first:${NC}"
+ echo "0. Disconnect VPN temporarily and test WiFi"
+ echo " If WiFi works without VPN, the issue is VPN-related"
+ echo ""
+ fi
+
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo -e "${MAGENTA}📡 DFS CHANNEL DETECTED - Try DFS-specific fixes first:${NC}"
+ echo "0a. Switch to non-DFS channel immediately:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg # Force 2.4GHz (no DFS)"
+ echo " sudo nmcli connection up \"$CURRENT_SSID\""
+ fi
+ echo "0b. Configure router to use non-DFS channels: 36, 40, 44, 48 (low 5GHz)"
+ echo "0c. Alternative channels: 149, 153, 157, 161, 165 (high 5GHz)"
+ echo ""
+ fi
+
+ echo "Standard WiFi fixes:"
+ echo "1. sudo systemctl restart NetworkManager"
+ echo "2. sudo modprobe -r $DRIVER && sleep 2 && sudo modprobe $DRIVER"
+ echo "3. sudo systemctl restart wpa_supplicant"
+
+ if [ "$VPN_ACTIVE" = "Yes" ]; then
+ echo ""
+ echo "VPN-specific fixes:"
+ echo "4. Restart VPN service"
+ echo "5. Try different VPN server"
+ echo "6. Lower VPN MTU: sudo ip link set $VPN_INTERFACE mtu 1200"
+ fi
+
+ else
+ echo -e "${GREEN}✅ NO SEVERE ISSUES DETECTED${NC}"
+ echo ""
+ echo "🎉 WiFi system is stable and functioning properly"
+
+ # Still provide DFS recommendations if relevant
+ if [ "$DFS_IMPACT_SCORE" -gt 25 ]; then
+ echo ""
+ echo "💡 Note: DFS channels detected in environment - monitor for patterns"
+ fi
+ fi
+}
+
+# Distribution detection
+detect_distribution() {
+ if [ -f /etc/os-release ]; then
+ . /etc/os-release
+ DISTRO_ID="$ID"
+ DISTRO_NAME="$PRETTY_NAME"
+
+ # Handle Bluefin/Silverblue/Kinoite as Fedora-based
+ if echo "$PRETTY_NAME" | grep -qi "bluefin\|silverblue\|kinoite"; then
+ DISTRO_ID="fedora"
+ echo "🔍 Detected immutable Fedora variant: $PRETTY_NAME"
+ elif echo "$ID_LIKE" | grep -qi "fedora"; then
+ DISTRO_ID="fedora"
+ elif echo "$ID_LIKE" | grep -qi "debian"; then
+ DISTRO_ID="debian"
+ elif echo "$ID_LIKE" | grep -qi "arch"; then
+ DISTRO_ID="arch"
+ fi
+ else
+ DISTRO_ID="unknown"
+ DISTRO_NAME="Unknown Linux"
+ fi
+}
+
+# Get distribution-specific commands
+get_distro_command() {
+ local command_type="$1"
+
+ case "$DISTRO_ID" in
+ "fedora"|"rhel"|"centos"|"rocky"|"almalinux")
+ case "$command_type" in
+ "firmware_update")
+ # Check for bootc first (newer immutable systems like Bluefin)
+ if command -v bootc >/dev/null 2>&1; then
+ echo "sudo bootc upgrade || (rpm-ostree reset && sudo bootc upgrade)"
+ # Check for rpm-ostree (Silverblue/Kinoite)
+ elif command -v rpm-ostree >/dev/null 2>&1; then
+ echo "rpm-ostree update && sudo reboot"
+ else
+ echo "sudo dnf update linux-firmware"
+ fi
+ ;;
+ "grub_update")
+ # Immutable systems don't need manual GRUB updates
+ if command -v rpm-ostree >/dev/null 2>&1 || command -v bootc >/dev/null 2>&1; then
+ echo "# GRUB updated automatically on reboot for immutable systems"
+ else
+ echo "sudo grub2-mkconfig -o /boot/grub2/grub.cfg"
+ fi
+ ;;
+ "initrd_update")
+ # Immutable systems rebuild initrd automatically
+ if command -v rpm-ostree >/dev/null 2>&1 || command -v bootc >/dev/null 2>&1; then
+ echo "# initrd rebuilt automatically on reboot for immutable systems"
+ else
+ echo "sudo dracut -f"
+ fi
+ ;;
+ "kernel_param")
+ # Check for immutable Fedora variants first
+ if command -v rpm-ostree >/dev/null 2>&1; then
+ echo "sudo rpm-ostree kargs --append="
+ elif command -v bootc >/dev/null 2>&1; then
+ echo "sudo bootc kargs --append="
+ else
+ echo "sudo grubby --update-kernel=ALL --args="
+ fi
+ ;;
+ "package_manager")
+ if command -v bootc >/dev/null 2>&1; then
+ echo "bootc"
+ elif command -v rpm-ostree >/dev/null 2>&1; then
+ echo "rpm-ostree"
+ else
+ echo "dnf"
+ fi
+ ;;
+ esac
+ ;;
+ "ubuntu"|"debian"|"pop"|"mint"|"linuxmint")
+ case "$command_type" in
+ "firmware_update") echo "sudo apt update && sudo apt upgrade linux-firmware" ;;
+ "grub_update") echo "sudo update-grub" ;;
+ "initrd_update") echo "sudo update-initramfs -u" ;;
+ "kernel_param") echo "Edit /etc/default/grub and add to GRUB_CMDLINE_LINUX=" ;;
+ "package_manager") echo "apt" ;;
+ esac
+ ;;
+ "arch"|"manjaro"|"endeavouros")
+ case "$command_type" in
+ "firmware_update") echo "sudo pacman -S linux-firmware" ;;
+ "grub_update") echo "sudo grub-mkconfig -o /boot/grub/grub.cfg" ;;
+ "initrd_update") echo "sudo mkinitcpio -P" ;;
+ "kernel_param") echo "Edit GRUB_CMDLINE_LINUX in /etc/default/grub" ;;
+ "package_manager") echo "pacman" ;;
+ esac
+ ;;
+ "opensuse"|"opensuse-leap"|"opensuse-tumbleweed")
+ case "$command_type" in
+ "firmware_update") echo "sudo zypper update kernel-firmware" ;;
+ "grub_update") echo "sudo grub2-mkconfig -o /boot/grub2/grub.cfg" ;;
+ "initrd_update") echo "sudo dracut -f" ;;
+ "kernel_param") echo "Edit /etc/default/grub and add to GRUB_CMDLINE_LINUX=" ;;
+ "package_manager") echo "zypper" ;;
+ esac
+ ;;
+ *)
+ case "$command_type" in
+ "firmware_update") echo "# Update firmware using your distribution's package manager" ;;
+ "grub_update") echo "# Update GRUB configuration" ;;
+ "initrd_update") echo "# Rebuild initramfs" ;;
+ "kernel_param") echo "# Edit GRUB configuration manually" ;;
+ "package_manager") echo "# Use your distribution's package manager" ;;
+ esac
+ ;;
+ esac
+}
+
+# Function to run command with privilege
+run_with_privilege() {
+ if [ "$EUID" -eq 0 ]; then
+ "$@"
+ else
+ if sudo -n true 2>/dev/null; then
+ sudo "$@"
+ else
+ "$@" 2>/dev/null
+ fi
+ fi
+}
+
+# Test actual WiFi functionality - THE SINGLE SOURCE OF TRUTH
+test_wifi_functionality() {
+ local functional=false
+
+ if [ -n "$IFACE" ]; then
+ # Test 1: Interface exists and is up
+ if ip link show "$IFACE" 2>/dev/null | grep -q "state UP"; then
+ # Test 2: Check actual WiFi connection status first
+ WIFI_LINK_STATUS=$(iw dev "$IFACE" link 2>/dev/null)
+
+ if echo "$WIFI_LINK_STATUS" | grep -q "Connected to"; then
+ # Test 3: Driver is responding to commands
+ if iw dev "$IFACE" info >/dev/null 2>&1; then
+ # Test 4: Internet connectivity working
+ if ping -c 1 -W 2 1.1.1.1 >/dev/null 2>&1; then
+ functional=true
+ fi
+ fi
+ fi
+ fi
+ fi
+
+ echo "$functional"
+}
+
+# Modern chipset analysis - ENHANCED with PCI ID detection
+analyze_modern_chipsets() {
+ # Method 1: Check by chip name in hardware string
+ if echo "$WIFI_HARDWARE" | grep -qi "mt7925"; then
+ CHIP_MODEL="MT7925"
+ CHIP_GENERATION="WiFi 7 (802.11be) - 160MHz capable"
+ KNOWN_ISSUES="Excellent Linux support in kernel 6.8+, stable WiFi 7 implementation"
+ DRIVER_NAME="mt7925e"
+ SUPPORTS_WIFI7=true
+ elif echo "$WIFI_HARDWARE" | grep -qi "mt7927"; then
+ CHIP_MODEL="MT7927"
+ CHIP_GENERATION="WiFi 7 (802.11be) - 320MHz capable"
+ KNOWN_ISSUES="Advanced WiFi 7 with 320MHz, requires kernel 6.9+"
+ DRIVER_NAME="mt7925e"
+ SUPPORTS_WIFI7=true
+ # Method 2: Check by PCI Device ID (for newer chips not yet named in lspci database)
+ elif echo "$WIFI_HARDWARE" | grep -qi "device 0717"; then
+ CHIP_MODEL="MT7925"
+ CHIP_GENERATION="WiFi 7 (802.11be) - 6GHz + MLO capable"
+ KNOWN_ISSUES="Latest WiFi 7 with full 6GHz and MLO support, requires kernel 6.8+"
+ DRIVER_NAME="mt7925e"
+ SUPPORTS_WIFI7=true
+ SUPPORTS_MLO=true
+ elif echo "$WIFI_HARDWARE" | grep -qi "device 0718\|device 0719"; then
+ CHIP_MODEL="MT7925/MT7927 variant"
+ CHIP_GENERATION="WiFi 7 (802.11be) - Advanced features"
+ KNOWN_ISSUES="Cutting-edge WiFi 7, may need latest firmware"
+ DRIVER_NAME="mt7925e"
+ SUPPORTS_WIFI7=true
+ SUPPORTS_MLO=true
+ elif echo "$WIFI_HARDWARE" | grep -qi "qcncm865\|wcn7850"; then
+ CHIP_MODEL="Qualcomm WCN7850/FastConnect 7800"
+ CHIP_GENERATION="WiFi 7 (802.11be) with MLO"
+ KNOWN_ISSUES="Excellent WiFi 7 MLO support via ath12k driver"
+ DRIVER_NAME="ath12k"
+ SUPPORTS_WIFI7=true
+ SUPPORTS_MLO=true
+ elif echo "$WIFI_HARDWARE" | grep -qi "be200"; then
+ CHIP_MODEL="Intel BE200"
+ CHIP_GENERATION="WiFi 7 (802.11be)"
+ KNOWN_ISSUES="Intel WiFi 7, requires kernel 6.8+, AMD compatibility issues"
+ DRIVER_NAME="iwlwifi"
+ SUPPORTS_WIFI7=true
+ elif echo "$WIFI_HARDWARE" | grep -qi "mt7922"; then
+ CHIP_MODEL="MT7922"
+ CHIP_GENERATION="WiFi 6E (802.11ax) with WiFi 7 features"
+ KNOWN_ISSUES="Mature WiFi 6E with some WiFi 7 capabilities, excellent 6GHz support"
+ DRIVER_NAME="mt7921e"
+ # MT7922 has some WiFi 7 features even though it's primarily WiFi 6E
+ SUPPORTS_WIFI7=true
+ elif echo "$WIFI_HARDWARE" | grep -qi "ax210\|ax211"; then
+ CHIP_MODEL="Intel AX210/AX211"
+ CHIP_GENERATION="WiFi 6E (802.11ax)"
+ KNOWN_ISSUES="Stable WiFi 6E with 6GHz support"
+ DRIVER_NAME="iwlwifi"
+ fi
+
+ # Set chip vendor properly
+ if echo "$WIFI_HARDWARE" | grep -qi "mediatek"; then
+ CHIP_VENDOR="MediaTek"
+ elif echo "$WIFI_HARDWARE" | grep -qi "intel"; then
+ CHIP_VENDOR="Intel"
+ elif echo "$WIFI_HARDWARE" | grep -qi "qualcomm\|qcom"; then
+ CHIP_VENDOR="Qualcomm"
+ else
+ CHIP_VENDOR="Unknown"
+ fi
+}
+
+# Enhanced channel width detection for modern WiFi
+detect_advanced_channel_width() {
+ # Multiple detection methods for WiFi 6E/7
+ CHANNEL_WIDTH=""
+
+ # Method 1: Direct iw link parsing
+ CHANNEL_WIDTH=$(iw dev "$IFACE" link 2>/dev/null | grep -oE "(20|40|80|160|320)MHz" | head -1 | grep -o "[0-9]*")
+
+ if [ -z "$CHANNEL_WIDTH" ]; then
+ # Method 2: Parse from width field
+ CHANNEL_WIDTH=$(iw dev "$IFACE" link 2>/dev/null | grep -oE "width: [0-9]+" | awk '{print $2}')
+ fi
+
+ if [ -z "$CHANNEL_WIDTH" ]; then
+ # Method 3: WiFi 7 detection - Look for BE-MCS indicators
+ BE_MCS=$(iw dev "$IFACE" link 2>/dev/null | grep "BE-MCS")
+ HE_MCS=$(iw dev "$IFACE" link 2>/dev/null | grep "HE-MCS")
+
+ if [ -n "$BE_MCS" ] || [ -n "$HE_MCS" ]; then
+ # Estimate from bitrate (WiFi 6E/7 specific)
+ BITRATE_NUM=$(echo "$CURRENT_BITRATE" | grep -o "[0-9]*" | head -1)
+ if [ -n "$BITRATE_NUM" ]; then
+ if [ "$BITRATE_NUM" -gt 5000 ]; then
+ CHANNEL_WIDTH="320"
+ echo " 📊 Estimated channel width: 320MHz (based on $BITRATE_NUM Mbps - WiFi 7)"
+ elif [ "$BITRATE_NUM" -gt 2000 ]; then
+ CHANNEL_WIDTH="160"
+ echo " 📊 Estimated channel width: 160MHz (based on $BITRATE_NUM Mbps - WiFi 6E)"
+ elif [ "$BITRATE_NUM" -gt 1000 ]; then
+ CHANNEL_WIDTH="80"
+ echo " 📊 Estimated channel width: 80MHz (based on $BITRATE_NUM Mbps)"
+ fi
+ fi
+ fi
+ fi
+
+ if [ -n "$CHANNEL_WIDTH" ] && [ "$CHANNEL_WIDTH" != "0" ]; then
+ echo " Current channel width: $CHANNEL_WIDTH MHz"
+
+ # Modern WiFi analysis
+ case "$CHANNEL_WIDTH" in
+ "320")
+ echo " 🚀 Ultra-wide 320MHz - WiFi 7 maximum performance mode"
+ echo " 💡 Requires clean 6GHz spectrum and WiFi 7 router"
+ ;;
+ "160")
+ echo " 📊 Wide 160MHz - WiFi 6E/7 high performance mode"
+ echo " 💡 Excellent for high-bandwidth applications"
+ ;;
+ "80")
+ echo " 📈 Standard 80MHz - WiFi 6 optimal performance"
+ ;;
+ "40")
+ echo " 📊 Narrow 40MHz - conservative bandwidth"
+ ;;
+ "20")
+ echo " 📉 Basic 20MHz - maximum compatibility mode"
+ ;;
+ esac
+ else
+ echo " ⚠️ Channel width not detected - check modern WiFi support"
+ fi
+}
+
+# Enhanced VPN Detection System with modern protocols
+detect_vpn_configuration() {
+ echo -e "${CYAN}🔒 === VPN DETECTION & ANALYSIS ===${NC}"
+ echo ""
+
+ local vpn_detected=false
+ local detected_vpn_type="None"
+ local detected_vpn_interface="None"
+ local vpn_impact_score=0
+
+ # Method 1: Enhanced VPN interface detection using more reliable parsing
+ echo "🔍 Scanning for VPN interfaces..."
+
+ # Enhanced interface detection using reliable methods
+ VPN_IFACES=$(ip -o link show | awk -F': ' '{print $2}' | grep -Ei 'tailscale|zerotier|nebula|wg[0-9]*|tun[0-9]*|tap[0-9]*|nordlynx|utun[0-9]*')
+
+ if [ -n "$VPN_IFACES" ]; then
+ while IFS= read -r vpn_iface; do
+ if [ -n "$vpn_iface" ]; then
+ # Check if interface is UP
+ if ip link show "$vpn_iface" 2>/dev/null | grep -q "state UP"; then
+ vpn_detected=true
+
+ # Determine modern VPN type based on interface name
+ if echo "$vpn_iface" | grep -qi "tailscale"; then
+ detected_vpn_type="Tailscale (WireGuard-based mesh)"
+ elif echo "$vpn_iface" | grep -qi "zerotier"; then
+ detected_vpn_type="ZeroTier (SD-WAN)"
+ elif echo "$vpn_iface" | grep -qi "nebula"; then
+ detected_vpn_type="Nebula (Overlay mesh)"
+ elif echo "$vpn_iface" | grep -qi "wg"; then
+ detected_vpn_type="WireGuard"
+ elif echo "$vpn_iface" | grep -qi "tun"; then
+ detected_vpn_type="OpenVPN/Generic TUN"
+ elif echo "$vpn_iface" | grep -qi "nordlynx"; then
+ detected_vpn_type="NordVPN (WireGuard)"
+ else
+ detected_vpn_type="Unknown VPN"
+ fi
+
+ echo " ✅ Active VPN: $vpn_iface ($detected_vpn_type)"
+ detected_vpn_interface="$vpn_iface"
+ break # Exit after finding first active VPN interface
+ fi
+ fi
+ done <<< "$VPN_IFACES"
+ fi
+
+ # Method 2: Fallback - Check for VPN processes if no interface found
+ if [ "$vpn_detected" != "true" ]; then
+ echo ""
+ echo "🔍 Scanning for modern VPN processes..."
+
+ VPN_PROCESSES=$(ps aux | grep -E "tailscaled|zerotier-one|nebula|headscale|wireguard|wg-quick|nordvpn|expressvpn|surfshark|mullvad|protonvpn" | grep -v grep)
+
+ if [ -n "$VPN_PROCESSES" ]; then
+ vpn_detected=true
+ echo " 📋 Active modern VPN processes detected:"
+ while IFS= read -r process; do
+ if echo "$process" | grep -q "tailscaled"; then
+ echo " • tailscaled"
+ detected_vpn_type="Tailscale (WireGuard-based mesh)"
+ # Try to find Tailscale interface using more methods
+ if [ "$detected_vpn_interface" = "None" ]; then
+ TAILSCALE_IF=$(ip -o link show | awk -F': ' '{print $2}' | grep -i tailscale | head -1)
+ if [ -n "$TAILSCALE_IF" ]; then
+ detected_vpn_interface="$TAILSCALE_IF"
+ else
+ TAILSCALE_IF=$(ip addr show | grep -E "inet 100\." | awk '{print $NF}' | head -1)
+ detected_vpn_interface="${TAILSCALE_IF:-tailscale (process-based detection)}"
+ fi
+ fi
+ elif echo "$process" | grep -q "zerotier-one"; then
+ echo " • zerotier-one"
+ detected_vpn_type="ZeroTier (SD-WAN)"
+ elif echo "$process" | grep -q "nordvpn"; then
+ echo " • nordvpn"
+ detected_vpn_type="NordVPN"
+ else
+ PROC_NAME=$(echo "$process" | awk '{for(i=11;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
+ echo " • $PROC_NAME"
+ detected_vpn_type="Modern VPN"
+ fi
+ done <<< "$VPN_PROCESSES"
+ fi
+ fi
+
+ # Method 3: Check for DNS changes (modern VPN DNS)
+ echo ""
+ echo "🔍 Checking DNS configuration for VPN indicators..."
+
+ if command -v resolvectl >/dev/null 2>&1; then
+ DNS_INFO=$(resolvectl status 2>/dev/null)
+ COMMON_DNS=$(echo "$DNS_INFO" | grep -E "1\.1\.1\.1|8\.8\.8\.8|208\.67\.222\.222|94\.140\.14\.14")
+
+ if [ -n "$COMMON_DNS" ]; then
+ echo " 📡 Public DNS detected (may be VPN-managed):"
+ echo "$COMMON_DNS" | head -3 | while IFS= read -r dns; do
+ echo " $dns"
+ done
+ fi
+ fi
+
+ # VPN Impact Analysis
+ echo ""
+ echo "🎯 === VPN IMPACT ANALYSIS ==="
+
+ # Set global variables based on detection
+ VPN_ACTIVE="No"
+ VPN_TYPE="$detected_vpn_type"
+ VPN_INTERFACE="$detected_vpn_interface"
+ VPN_IMPACT_SCORE="$vpn_impact_score"
+
+ if [ "$vpn_detected" = true ]; then
+ VPN_ACTIVE="Yes"
+ echo -e " ${GREEN}✅ VPN Active: $detected_vpn_type${NC}"
+ echo " Interface: $detected_vpn_interface"
+ else
+ VPN_ACTIVE="No"
+ VPN_TYPE="None"
+ VPN_INTERFACE="None"
+ VPN_IMPACT_SCORE=0
+ echo -e " ${GREEN}✅ No VPN detected${NC}"
+ echo " 💡 WiFi issues not related to VPN configuration"
+ fi
+
+ echo ""
+}
+
+# RF and Frequency Analysis System (enhanced for WiFi 7) - WITH DFS INTEGRATION
+analyze_rf_frequency_environment() {
+ echo -e "${CYAN}📡 === RF & FREQUENCY ENVIRONMENT ANALYSIS ===${NC}"
+ echo ""
+
+ if [ -z "$IFACE" ]; then
+ echo "❌ Cannot analyze RF environment - WiFi interface not available"
+ return 1
+ fi
+
+ # Get current connection details
+ echo "🔍 Current WiFi Connection Analysis..."
+
+ WIFI_INFO=$(iw dev "$IFACE" link 2>/dev/null)
+
+ if echo "$WIFI_INFO" | grep -q "Not connected"; then
+ echo " ❌ WiFi not connected - cannot analyze current RF environment"
+ return 1
+ fi
+
+ # Extract current RF parameters
+ CURRENT_FREQ=$(echo "$WIFI_INFO" | grep "freq:" | awk '{print $2}')
+ CURRENT_SIGNAL=$(echo "$WIFI_INFO" | grep "signal:" | awk '{print $2}')
+ CURRENT_BITRATE=$(echo "$WIFI_INFO" | grep "tx bitrate:" | awk '{print $3}')
+ CURRENT_SSID=$(echo "$WIFI_INFO" | grep "SSID:" | awk '{print $2}')
+
+ echo "📊 Current RF Status:"
+ echo " SSID: ${CURRENT_SSID:-Unknown}"
+ echo " Frequency: ${CURRENT_FREQ:-Unknown} MHz"
+ echo " Signal: ${CURRENT_SIGNAL:-Unknown} dBm"
+ echo " TX Bitrate: ${CURRENT_BITRATE:-Unknown} Mbps"
+
+ # Determine band and channel with modern WiFi support
+ if [ -n "$CURRENT_FREQ" ]; then
+ FREQ_INT=$(echo "$CURRENT_FREQ" | cut -d'.' -f1)
+
+ if [ "$FREQ_INT" -ge 2400 ] && [ "$FREQ_INT" -le 2500 ]; then
+ CURRENT_BAND="2.4 GHz"
+ CHANNEL=$(echo "scale=0; ($CURRENT_FREQ - 2412) / 5 + 1" | bc 2>/dev/null || echo "Unknown")
+ elif [ "$FREQ_INT" -ge 5000 ] && [ "$FREQ_INT" -le 6000 ]; then
+ CURRENT_BAND="5 GHz"
+ CHANNEL=$(echo "scale=0; ($CURRENT_FREQ - 5000) / 5" | bc 2>/dev/null || echo "Unknown")
+ elif [ "$FREQ_INT" -ge 6000 ] && [ "$FREQ_INT" -le 7200 ]; then
+ CURRENT_BAND="6 GHz (WiFi 6E/7)"
+ CHANNEL="6GHz Channel"
+ else
+ CURRENT_BAND="Unknown"
+ CHANNEL="Unknown"
+ fi
+
+ echo " Band: $CURRENT_BAND"
+ echo " Channel: $CHANNEL"
+
+ # DFS Analysis for current connection
+ if [ "$CURRENT_BAND" = "5 GHz" ] && [ "$CHANNEL" != "Unknown" ]; then
+ if is_dfs_channel "$CHANNEL"; then
+ echo -e " ${YELLOW}⚠️ DFS Channel: Current connection uses DFS channel $CHANNEL${NC}"
+ echo " 💡 DFS channels can cause disconnections when radar is detected"
+ else
+ echo -e " ${GREEN}✅ Non-DFS Channel: Current channel $CHANNEL is safe from radar interference${NC}"
+ fi
+ fi
+ fi
+
+ # Enhanced channel width detection
+ detect_advanced_channel_width
+
+ # 6GHz environment analysis
+ analyze_6ghz_environment
+
+ # RF Environment Scan
+ echo ""
+ echo "🔍 Scanning RF environment for interference..."
+
+ SCAN_RESULTS=$(timeout 15 iw dev "$IFACE" scan 2>/dev/null)
+
+ if [ -n "$SCAN_RESULTS" ]; then
+ # Count networks by band (including 6GHz)
+ NETWORKS_24=$(echo "$SCAN_RESULTS" | grep "freq:" | awk '{print $2}' | awk '$1 >= 2400 && $1 <= 2500' | wc -l)
+ NETWORKS_5=$(echo "$SCAN_RESULTS" | grep "freq:" | awk '{print $2}' | awk '$1 >= 5000 && $1 <= 6000' | wc -l)
+ NETWORKS_6=$(echo "$SCAN_RESULTS" | grep "freq:" | awk '{print $2}' | awk '$1 >= 6000 && $1 <= 7200' | sort -u | wc -l)
+
+ echo "📊 Nearby Networks:"
+ echo " 2.4 GHz: $NETWORKS_24 networks"
+ echo " 5 GHz: $NETWORKS_5 networks"
+ echo " 6 GHz: $NETWORKS_6 networks"
+
+ # Find strongest interfering networks
+ echo ""
+ echo "🚨 Top interfering networks on your band:"
+
+ case "$CURRENT_BAND" in
+ "2.4 GHz") FREQ_RANGE="freq: 24[0-9][0-9]" ;;
+ "5 GHz") FREQ_RANGE="freq: 5[0-9][0-9][0-9]" ;;
+ "6 GHz (WiFi 6E/7)") FREQ_RANGE="freq: 6[0-9][0-9][0-9]" ;;
+ *) FREQ_RANGE="freq:" ;;
+ esac
+
+ # Extract and sort networks by signal strength - FIXED signal filter
+ echo "$SCAN_RESULTS" | grep -A10 -B2 "$FREQ_RANGE" | grep -E "BSS|signal|SSID|freq:" | \
+ awk '/BSS/ {bss=$2} /freq:/ {freq=$2} /signal:/ {signal=$2} /SSID:/ {ssid=$2; if(signal<-20 && ssid!="") print signal " dBm - " ssid " (" freq " MHz)"}' | \
+ sort -n | tail -5 | while IFS= read -r network; do
+ echo " $network"
+ done
+
+ else
+ echo " ❌ Cannot scan RF environment - scan failed"
+ fi
+
+ # Power and regulatory analysis
+ echo ""
+ echo "🔍 Power and regulatory analysis..."
+
+ REG_INFO=$(iw reg get 2>/dev/null)
+ if [ -n "$REG_INFO" ]; then
+ COUNTRY_LINE=$(echo "$REG_INFO" | grep "country" | head -1)
+ GLOBAL_LINE=$(echo "$REG_INFO" | grep "global")
+
+ if [ -n "$COUNTRY_LINE" ]; then
+ REG_DOMAIN="$COUNTRY_LINE"
+ elif [ -n "$GLOBAL_LINE" ]; then
+ REG_DOMAIN="$GLOBAL_LINE"
+ else
+ REG_DOMAIN=$(echo "$REG_INFO" | head -1)
+ fi
+ else
+ REG_DOMAIN="Not available"
+ fi
+ echo " Regulatory domain: ${REG_DOMAIN:-Unknown}"
+
+ TX_POWER=$(iw dev "$IFACE" info 2>/dev/null | grep "txpower" | awk '{print $2, $3}')
+ echo " TX Power: ${TX_POWER:-Unknown}"
+
+ # RF Quality Assessment
+ echo ""
+ echo "🎯 === RF QUALITY ASSESSMENT ==="
+
+ RF_SCORE=100
+ RF_ISSUES=()
+
+ # Signal strength assessment - ACTUALLY FIXED LOGIC
+ if [ -n "$CURRENT_SIGNAL" ]; then
+ # Extract just the number (keep it positive for easier comparison)
+ SIGNAL_NUM=$(echo "$CURRENT_SIGNAL" | sed 's/-//')
+
+ if command -v bc >/dev/null 2>&1; then
+ # CORRECTLY FIXED: Lower absolute values = better signal strength
+ # Remember: -30 dBm is excellent, -90 dBm is terrible
+ if [ "$(echo "$SIGNAL_NUM <= 40" | bc)" -eq 1 ]; then
+ echo " ✅ Excellent signal strength ($CURRENT_SIGNAL dBm)"
+ # No penalty for excellent signal
+ elif [ "$(echo "$SIGNAL_NUM <= 60" | bc)" -eq 1 ]; then
+ echo " 📊 Good signal strength ($CURRENT_SIGNAL dBm)"
+ RF_SCORE=$((RF_SCORE - 10))
+ elif [ "$(echo "$SIGNAL_NUM <= 80" | bc)" -eq 1 ]; then
+ echo " ⚠️ Weak signal strength ($CURRENT_SIGNAL dBm)"
+ RF_SCORE=$((RF_SCORE - 30))
+ RF_ISSUES+=("Weak signal - move closer to router")
+ else
+ echo " 🚨 Poor signal strength ($CURRENT_SIGNAL dBm)"
+ RF_SCORE=$((RF_SCORE - 50))
+ RF_ISSUES+=("Very poor signal - major connectivity issues expected")
+ fi
+ else
+ # Fallback without bc - CORRECTLY FIXED logic
+ SIGNAL_INT=$(printf "%.0f" "$SIGNAL_NUM" 2>/dev/null || echo "$SIGNAL_NUM")
+ if [ "$SIGNAL_INT" -le 40 ]; then
+ echo " ✅ Excellent signal strength ($CURRENT_SIGNAL dBm)"
+ # No penalty for excellent signal
+ elif [ "$SIGNAL_INT" -le 60 ]; then
+ echo " 📊 Good signal strength ($CURRENT_SIGNAL dBm)"
+ RF_SCORE=$((RF_SCORE - 10))
+ elif [ "$SIGNAL_INT" -le 80 ]; then
+ echo " ⚠️ Weak signal strength ($CURRENT_SIGNAL dBm)"
+ RF_SCORE=$((RF_SCORE - 30))
+ RF_ISSUES+=("Weak signal - move closer to router")
+ else
+ echo " 🚨 Poor signal strength ($CURRENT_SIGNAL dBm)"
+ RF_SCORE=$((RF_SCORE - 50))
+ RF_ISSUES+=("Very poor signal - major connectivity issues expected")
+ fi
+ fi
+ fi
+
+ # Modern congestion assessment
+ if [ "$CURRENT_BAND" = "2.4 GHz" ] && [ "$NETWORKS_24" -gt 15 ]; then
+ echo " 🚨 High 2.4GHz congestion ($NETWORKS_24 networks)"
+ RF_SCORE=$((RF_SCORE - 25))
+ RF_ISSUES+=("Switch to 5GHz or 6GHz if available")
+ elif [ "$CURRENT_BAND" = "5 GHz" ] && [ "$NETWORKS_5" -gt 20 ]; then
+ echo " ⚠️ Moderate 5GHz congestion ($NETWORKS_5 networks)"
+ RF_SCORE=$((RF_SCORE - 15))
+ RF_ISSUES+=("Consider WiFi 6E/7 (6GHz) if available")
+ elif [ "$CURRENT_BAND" = "6 GHz (WiFi 6E/7)" ]; then
+ echo " 🌟 6GHz band - clean spectrum advantage"
+ RF_SCORE=$((RF_SCORE + 10)) # Bonus for 6GHz
+ fi
+
+ # Final RF assessment
+ echo ""
+ echo "📊 RF Environment Score: $RF_SCORE/100"
+
+ if [ "$RF_SCORE" -ge 90 ]; then
+ echo -e " ${GREEN}🌟 Outstanding RF environment${NC}"
+ elif [ "$RF_SCORE" -ge 80 ]; then
+ echo -e " ${GREEN}✅ Excellent RF environment${NC}"
+ elif [ "$RF_SCORE" -ge 60 ]; then
+ echo -e " ${YELLOW}⚠️ Good RF environment with minor issues${NC}"
+ elif [ "$RF_SCORE" -ge 40 ]; then
+ echo -e " ${YELLOW}🚨 Poor RF environment - optimization needed${NC}"
+ else
+ echo -e " ${RED}🚨 Critical RF environment - major issues${NC}"
+ fi
+
+ echo ""
+
+ # INTEGRATED DFS ANALYSIS
+ analyze_dfs_channels
+}
+
+# Fixed and Enhanced 6GHz analysis function - ENHANCED for MT7925 PCI ID detection
+analyze_6ghz_environment() {
+ echo ""
+ echo "🔍 6GHz band analysis..."
+
+ # Multiple detection methods for 6GHz capability
+ local supports_6ghz=false
+ local detection_method=""
+
+ # Method 1: Standard iw phy Band 3 detection
+ SUPPORTS_6GHZ_BAND3=$(iw phy 2>/dev/null | grep -A 30 "Band 3:" | grep -E "freq.*6[0-9][0-9][0-9]")
+
+ # Method 2: Alternative band detection (some drivers report differently)
+ SUPPORTS_6GHZ_ALT=$(iw phy 2>/dev/null | grep -E "freq.*6[0-9][0-9][0-9]")
+
+ # Method 3: Check if we've actually connected to 6GHz before (proof positive)
+ CURRENT_FREQ_CHECK=false
+ if [ -n "$CURRENT_FREQ" ]; then
+ FREQ_INT=$(echo "$CURRENT_FREQ" | cut -d'.' -f1)
+ if [ "$FREQ_INT" -ge 6000 ] && [ "$FREQ_INT" -le 7200 ]; then
+ CURRENT_FREQ_CHECK=true
+ fi
+ fi
+
+ # Method 4: Check for 6GHz frequencies in scan results (active proof) - FIXED
+ if [ -n "$SCAN_RESULTS" ]; then
+ SCAN_6GHZ=$(echo "$SCAN_RESULTS" | grep "freq:" | awk '$2 >= 6000 && $2 <= 7200' | wc -l)
+ else
+ # SCAN_RESULTS not available yet, do our own scan
+ LOCAL_SCAN_RESULTS=$(timeout 15 iw dev "$IFACE" scan 2>/dev/null)
+ SCAN_6GHZ=$(echo "$LOCAL_SCAN_RESULTS" | grep "freq:" | awk '$2 >= 6000 && $2 <= 7200' | wc -l)
+ fi
+
+ # Method 5: Enhanced chip model detection (including PCI IDs)
+ CHIP_6GHZ_CAPABLE=false
+ if echo "$CHIP_MODEL" | grep -qi "mt7925\|mt7927"; then
+ CHIP_6GHZ_CAPABLE=true
+ elif echo "$CHIP_MODEL" | grep -qi "mt7922"; then
+ CHIP_6GHZ_CAPABLE=true
+ elif echo "$WIFI_HARDWARE" | grep -qi "device 0717\|device 0718\|device 0719"; then
+ # PCI device IDs for MT7925/MT7927 variants - these ARE 6GHz capable
+ CHIP_6GHZ_CAPABLE=true
+ elif echo "$CHIP_MODEL" | grep -qi "ax210\|ax211\|be200\|wcn7850"; then
+ CHIP_6GHZ_CAPABLE=true
+ fi
+
+ # Method 6: Driver name detection (mt7925e driver indicates 6GHz capability)
+ DRIVER_6GHZ_CAPABLE=false
+ if echo "$DRIVER" | grep -qi "mt7925e"; then
+ DRIVER_6GHZ_CAPABLE=true
+ elif echo "$DRIVER" | grep -qi "iwlwifi" && echo "$CHIP_MODEL" | grep -qi "ax210\|ax211\|be200"; then
+ DRIVER_6GHZ_CAPABLE=true
+ fi
+
+ # Determine 6GHz support using multiple evidence sources
+ if [ -n "$SUPPORTS_6GHZ_BAND3" ]; then
+ supports_6ghz=true
+ detection_method="iw phy Band 3"
+ elif [ -n "$SUPPORTS_6GHZ_ALT" ]; then
+ supports_6ghz=true
+ detection_method="frequency scan"
+ elif [ "$CURRENT_FREQ_CHECK" = true ]; then
+ supports_6ghz=true
+ detection_method="active 6GHz connection"
+ elif [ "$SCAN_6GHZ" -gt 0 ]; then
+ supports_6ghz=true
+ detection_method="6GHz networks detected ($SCAN_6GHZ found)"
+ elif [ "$CHIP_6GHZ_CAPABLE" = true ]; then
+ supports_6ghz=true
+ detection_method="Known 6GHz hardware: $CHIP_MODEL"
+ elif [ "$DRIVER_6GHZ_CAPABLE" = true ]; then
+ supports_6ghz=true
+ detection_method="6GHz-capable driver: $DRIVER"
+ fi
+
+ # Report results
+ if [ "$supports_6ghz" = true ]; then
+ echo " ✅ Hardware: WiFi 6E/7 with 6GHz support confirmed"
+ echo " 🔍 Detection method: $detection_method"
+
+ # Current connection analysis
+ if [ "$CURRENT_FREQ_CHECK" = true ]; then
+ echo " 🌟 Currently connected to 6GHz spectrum ($CURRENT_FREQ MHz)"
+ echo " 🚀 Excellent choice - 6GHz provides clean spectrum with minimal interference"
+ echo " ✅ 6GHz band: NO DFS channels - no radar interference possible"
+ else
+ echo " 📊 Currently on ${CURRENT_BAND:-unknown band}, but 6GHz available"
+ echo " 💡 Consider switching to 6GHz for cleaner spectrum"
+ echo " 🌟 6GHz advantage: NO DFS channels means no radar-related disconnections"
+
+ # Enhanced troubleshooting for specific chips
+ if echo "$WIFI_HARDWARE" | grep -qi "device 0717"; then
+ echo " 🧪 MT7925 note: Latest WiFi 7 chip with full 6GHz support"
+ echo " 💡 Try: sudo iw reg set US && sudo nmcli radio wifi off && sudo nmcli radio wifi on"
+ echo " 💡 Or check router has 6GHz enabled and broadcasting"
+ elif echo "$CHIP_MODEL" | grep -qi "mt7922"; then
+ echo " 🧪 MT7922 note: 6GHz support may require regulatory domain setup"
+ echo " 💡 Try: sudo iw reg set US && sudo nmcli radio wifi off && sudo nmcli radio wifi on"
+ fi
+ fi
+
+ # Show 6GHz network availability if scan worked - ENHANCED FIXED logic
+ if [ -n "$SCAN_6GHZ" ] && [ "$SCAN_6GHZ" -gt 0 ]; then
+ echo " 📡 Found $SCAN_6GHZ available 6GHz networks in area"
+
+ # Show 6GHz network details if current connection is 6GHz
+ if [ "$CURRENT_FREQ_CHECK" = true ]; then
+ echo " 🌟 You're connected to one of these 6GHz networks!"
+ else
+ echo " 💡 Consider switching to 6GHz for access to these clean spectrum networks"
+ fi
+ else
+ echo " 📡 No 6GHz networks currently visible in detailed scan"
+ echo " 💡 Your hardware supports 6GHz, but no 6GHz networks detected in range"
+ echo " 💡 Router may need 6GHz enabled or be out of range"
+ fi
+
+ else
+ echo " ❌ No 6GHz capability detected in hardware"
+ echo " 🧪 Note: Some WiFi 6E cards require specific firmware/driver versions"
+
+ # Special case for known 6GHz hardware that isn't detected
+ if [ "$CHIP_6GHZ_CAPABLE" = true ] || [ "$DRIVER_6GHZ_CAPABLE" = true ]; then
+ echo " ⚠️ WARNING: Hardware/driver suggests 6GHz capability but not detected"
+ echo " 💡 Possible fixes:"
+ echo " • Update firmware: sudo apt update && sudo apt upgrade linux-firmware"
+ echo " • Set regulatory domain: sudo iw reg set US"
+ echo " • Check kernel version (6GHz requires 6.2+)"
+ echo " • Verify router has 6GHz enabled"
+ fi
+ fi
+}
+
+# Enhanced system intelligence gathering with modern chipset support AND power save detection
+gather_system_intelligence() {
+ echo -e "${CYAN}🧠 === ENHANCED SYSTEM INTELLIGENCE GATHERING ===${NC}"
+
+ # Detect distribution first
+ detect_distribution
+ echo "🐧 Distribution: $DISTRO_NAME"
+
+ # Get interface with fallback methods
+ IFACE=$(ip route get 1.1.1.1 2>/dev/null | grep dev | awk '{print $5}')
+ if [ -z "$IFACE" ]; then
+ IFACE=$(nmcli -t -f DEVICE,TYPE device status | grep wifi | head -1 | cut -d: -f1)
+ fi
+ if [ -z "$IFACE" ]; then
+ IFACE=$(iw dev 2>/dev/null | awk '/Interface/ {print $2; exit}')
+ fi
+
+ echo "🔍 WiFi Interface: ${IFACE:-❌ Not found}"
+
+ # Test WiFi functionality - SINGLE SOURCE OF TRUTH
+ WIFI_FUNCTIONAL=$(test_wifi_functionality)
+ echo "🔧 WiFi Functional Status: $([ "$WIFI_FUNCTIONAL" = "true" ] && echo "✅ WORKING" || echo "❌ BROKEN")"
+
+ # Deep hardware analysis with modern chipset support
+ WIFI_HARDWARE=$(lspci | grep -i "network\|wireless" | head -1)
+ echo "🔧 Hardware: $WIFI_HARDWARE"
+
+ # Analyze modern chipsets
+ analyze_modern_chipsets
+
+ echo "📊 Chip Analysis:"
+ echo " Vendor: ${CHIP_VENDOR:-Unknown}"
+ echo " Model: ${CHIP_MODEL:-Unknown}"
+ echo " Generation: ${CHIP_GENERATION:-Unknown}"
+ echo " Known Issues: ${KNOWN_ISSUES:-Generally stable}"
+
+ # Driver and firmware intelligence
+ if [ -n "$IFACE" ]; then
+ DRIVER=$(ls -l /sys/class/net/$IFACE/device/driver/module 2>/dev/null | awk -F/ '{print $NF}')
+ echo "🚗 Driver: ${DRIVER:-Unknown}"
+
+ # Driver version and build info
+ if [ -n "$DRIVER" ]; then
+ DRIVER_VERSION=$(modinfo "$DRIVER" 2>/dev/null | grep "^version:" | awk '{print $2}')
+ DRIVER_DATE=$(modinfo "$DRIVER" 2>/dev/null | grep "^srcversion:" | awk '{print $2}')
+ echo " Version: ${DRIVER_VERSION:-Unknown}"
+ echo " Build ID: ${DRIVER_DATE:-Unknown}"
+ fi
+
+ # Power Management Analysis - NEW ADDITION
+ echo ""
+ echo "🔋 Power Management Analysis:"
+ POWER_SAVE=$(iw dev "$IFACE" get power_save 2>/dev/null | grep "Power save:" | awk '{print $3}')
+ if [ -n "$POWER_SAVE" ]; then
+ case "$POWER_SAVE" in
+ "on")
+ echo " ⚠️ Power Save: ON (may cause disconnections)"
+ echo " 💡 Consider disabling: sudo iw dev $IFACE set power_save off"
+ ;;
+ "off")
+ echo " ✅ Power Save: OFF (optimal for stability)"
+ ;;
+ *)
+ echo " 🔍 Power Save: $POWER_SAVE"
+ ;;
+ esac
+ else
+ echo " ❓ Power Save: Unable to detect (driver may not support query)"
+ echo " 💡 Try manually: iw dev $IFACE get power_save"
+ fi
+
+ # Check for ASPM status if MediaTek - FIXED to target only WiFi device
+ if echo "$CHIP_MODEL" | grep -qi "mt79"; then
+ # Get WiFi PCI device ID specifically
+ WIFI_PCI_ID=$(lspci | grep -i wireless | awk '{print $1}' | head -1)
+ if [ -n "$WIFI_PCI_ID" ]; then
+ ASMP_STATUS=$(lspci -vv -s "$WIFI_PCI_ID" 2>/dev/null | grep "LnkCtl:" | grep -o "ASPM [^;]*")
+ if [ -n "$ASMP_STATUS" ]; then
+ echo " 🔗 PCIe ASPM: $ASMP_STATUS"
+ if echo "$ASMP_STATUS" | grep -q "L1"; then
+ echo " 💡 ASPM L1 active - may cause MediaTek issues"
+ echo " 💡 Consider: pcie_aspm=off kernel parameter"
+ fi
+ fi
+ fi
+ fi
+
+ # Distribution-aware firmware analysis
+ case "$DISTRO_ID" in
+ "fedora"|"rhel"|"centos"|"rocky"|"almalinux")
+ FIRMWARE_VERSION=$(rpm -q --queryformat '%{VERSION}-%{RELEASE}' linux-firmware 2>/dev/null || echo "Unknown")
+ ;;
+ "ubuntu"|"debian"|"pop"|"mint"|"linuxmint")
+ FIRMWARE_VERSION=$(dpkg -l linux-firmware 2>/dev/null | grep "^ii" | awk '{print $3}' || echo "Unknown")
+ ;;
+ "arch"|"manjaro"|"endeavouros")
+ FIRMWARE_VERSION=$(pacman -Q linux-firmware 2>/dev/null | awk '{print $2}' || echo "Unknown")
+ ;;
+ *)
+ FIRMWARE_VERSION="Unknown"
+ ;;
+ esac
+
+ echo ""
+ echo "📦 Firmware: $FIRMWARE_VERSION"
+
+ # Kernel compatibility check
+ KERNEL_VERSION=$(uname -r)
+ echo "🐧 Kernel: $KERNEL_VERSION"
+
+ # Modern kernel recommendations
+ KERNEL_MAJOR=$(echo "$KERNEL_VERSION" | cut -d'.' -f1)
+ KERNEL_MINOR=$(echo "$KERNEL_VERSION" | cut -d'.' -f2)
+
+ if [ "$KERNEL_MAJOR" -ge 6 ] && [ "$KERNEL_MINOR" -ge 8 ]; then
+ echo " ✅ Modern kernel with WiFi 7 support"
+ elif [ "$KERNEL_MAJOR" -ge 6 ] && [ "$KERNEL_MINOR" -ge 5 ]; then
+ echo " 📊 Good kernel with WiFi 6E support"
+ else
+ echo " ⚠️ Older kernel - consider upgrading for modern WiFi features"
+ fi
+ fi
+
+ echo ""
+}
+
+# Distribution-aware workaround system
+provide_distribution_specific_workarounds() {
+ local issue_type="$1"
+
+ echo ""
+ echo -e "${CYAN}🐧 Distribution-Specific Commands for $DISTRO_NAME:${NC}"
+ echo ""
+
+ case "$issue_type" in
+ "DRIVER_ERROR")
+ echo "1. MediaTek-specific kernel parameter workaround:"
+ echo ""
+ # MediaTek-specific explanation
+ if echo "$CHIP_MODEL" | grep -qi "mt79"; then
+ echo -e "${GREEN}✅ MediaTek chipset detected: $CHIP_MODEL${NC}"
+ echo " 💡 Possible Fix When Drops Are Due to PCI Config Errors:"
+ echo " Some MediaTek chipsets (like mt7921e or mt7925e) suffer from unstable"
+ echo " PCIe behavior, especially on AMD-based laptops or quirky ACPI tables"
+ echo " (common in early Framework AMD laptops). If your dmesg/journal logs show"
+ echo " PCI-related errors (e.g. config read failures, bus enumeration issues),"
+ echo " pci=nommconf might stabilize the bus behavior."
+ echo ""
+ echo -e "${RED}❌ Won't Help for Firmware Bugs or ASPM Issues:${NC}"
+ echo " This won't fix issues caused by:"
+ echo " • ASPM power saving (pcie_aspm=off or policy=performance is needed)"
+ echo " • Firmware regressions (need linux-firmware downgrades or kernel patches)"
+ echo " • Power management instability (need iw dev ... set power_save off)"
+ echo " • DFS radar interference (need non-DFS channels)"
+ echo ""
+ echo " 📋 Check logs first: dmesg | grep -E 'mt79|pci.*error|config.*read'"
+ echo ""
+ else
+ echo " 💡 Generic PCI configuration workaround (may help with PCIe issues)"
+ echo ""
+ fi
+
+ if [ "$DISTRO_ID" = "fedora" ] || [ "$DISTRO_ID" = "rhel" ]; then
+ echo " 🔒 PERMANENT (kernel parameter): $(get_distro_command "kernel_param")'pci=nommconf'"
+ echo " 🔒 PERMANENT (GRUB update): $(get_distro_command "grub_update")"
+ else
+ echo " 🔒 PERMANENT (kernel parameter): $(get_distro_command "kernel_param")'pci=nommconf'"
+ echo " 🔒 PERMANENT (GRUB update): $(get_distro_command "grub_update")"
+ fi
+ echo " ⚠️ REQUIRES REBOOT: sudo reboot"
+ echo ""
+ echo "2. Firmware update (try this first):"
+ echo " 🔒 PERMANENT (system upgrade): $(get_distro_command "firmware_update")"
+ echo " ⚠️ REQUIRES REBOOT: After dnf, apt, bootc/rpm-ostree upgrade"
+ echo ""
+ echo "3. Initrd rebuild:"
+ echo " 🔒 PERMANENT (initramfs update): $(get_distro_command "initrd_update")"
+ echo " ⚠️ REQUIRES REBOOT: sudo reboot"
+ echo ""
+
+ # MediaTek-specific additional fixes
+ if echo "$CHIP_MODEL" | grep -qi "mt79"; then
+ echo "4. MediaTek-specific additional fixes:"
+ echo ""
+ echo " a) ASPM power management issues:"
+ echo " 🔒 PERMANENT (module config): echo 'options mt7921e disable_aspm=1' | sudo tee /etc/modprobe.d/mt7921e.conf"
+ echo " 🔒 PERMANENT (apply config): $(get_distro_command "initrd_update")"
+ echo " ⚠️ REQUIRES REBOOT: After initrd rebuild"
+ echo ""
+ echo " b) Power management workaround:"
+ echo " ⏰ TEMPORARY (until reboot): sudo iw dev $IFACE set power_save off"
+ echo " 🔒 PERMANENT: Use modprobe option above instead"
+ echo ""
+ echo " c) Alternative ASPM kernel parameter:"
+ echo " 🔒 PERMANENT (kernel param): $(get_distro_command "kernel_param")'pcie_aspm=off'"
+ echo " 🔒 PERMANENT (GRUB update): $(get_distro_command "grub_update")"
+ echo " ⚠️ REQUIRES REBOOT: After GRUB configuration"
+ echo ""
+ echo " 💡 Try solutions in order: firmware update → power_save off → ASPM fixes → PCI workarounds"
+ echo " 🧪 TESTING STRATEGY: Apply temporary fixes first to verify they work before making permanent"
+ fi
+
+ # DFS-specific fixes if applicable
+ if [ "$DFS_IMPACT_SCORE" -gt 50 ]; then
+ echo ""
+ echo "5. DFS-specific fixes (radar interference detected):"
+ echo ""
+ echo " a) Immediate non-DFS channel switch:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " ⏰ IMMEDIATE: sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo " ⏰ IMMEDIATE: sudo nmcli connection up \"$CURRENT_SSID\""
+ fi
+ echo ""
+ echo " b) Router configuration (CRITICAL):"
+ echo " 🔒 PERMANENT: Set router to channels 36, 40, 44, 48 (non-DFS)"
+ echo " 🔒 PERMANENT: Alternative: channels 149, 153, 157, 161, 165 (non-DFS)"
+ echo " 🔒 PERMANENT: Disable automatic channel selection"
+ echo ""
+ echo " c) 6GHz migration (NO DFS in 6GHz):"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " 🌟 Your hardware supports 6GHz - upgrade to WiFi 6E/7 router"
+ echo " 💡 6GHz band is completely DFS-free"
+ fi
+ fi
+ ;;
+ "FIRMWARE_CRASH")
+ echo "1. Update firmware (most important for MediaTek):"
+ echo " 🔒 PERMANENT (system upgrade): $(get_distro_command "firmware_update")"
+ echo " ⚠️ REQUIRES REBOOT: After firmware update"
+ echo ""
+
+ if echo "$CHIP_MODEL" | grep -qi "mt79"; then
+ echo "2. MediaTek-specific module configuration:"
+ echo " 🔒 PERMANENT (module config): echo 'options mt7921e disable_aspm=1' | sudo tee /etc/modprobe.d/mt7921e.conf"
+ echo " 🔒 PERMANENT (power config): echo 'options mt7921e power_save=0' | sudo tee -a /etc/modprobe.d/mt7921e.conf"
+ echo " 🔒 PERMANENT (apply config): $(get_distro_command "initrd_update")"
+ echo " ⚠️ REQUIRES REBOOT: After initrd rebuild"
+ else
+ echo "2. Module configuration:"
+ echo " 🔒 PERMANENT (module config): echo 'options $DRIVER disable_aspm=1' | sudo tee /etc/modprobe.d/$DRIVER.conf"
+ echo " 🔒 PERMANENT (apply config): $(get_distro_command "initrd_update")"
+ echo " ⚠️ REQUIRES REBOOT: After initrd rebuild"
+ fi
+ ;;
+ esac
+}
+
+# TX Power Band Test - COMPLETE IMPLEMENTATION
+tx_power_band_test() {
+ echo -e "${BOLD}${BLUE}🧪 === TX POWER BAND TEST ===${NC}"
+ echo "🔍 Testing transmission power limitations across different bands"
+ echo ""
+
+ # Quick system check first
+ gather_system_intelligence >/dev/null 2>&1
+
+ if [ -z "$IFACE" ]; then
+ echo "❌ No WiFi interface available for testing"
+ return 1
+ fi
+
+ echo "🎯 Current System Analysis:"
+ echo " Interface: $IFACE"
+ echo " Chip: ${CHIP_VENDOR:-Unknown} ${CHIP_MODEL:-Unknown}"
+ echo " Driver: ${DRIVER:-Unknown}"
+ echo ""
+
+ # Get current TX power
+ CURRENT_TX_POWER=$(iw dev "$IFACE" info 2>/dev/null | grep "txpower" | awk '{print $2, $3}')
+ echo "📊 Current TX Power: ${CURRENT_TX_POWER:-Unable to detect}"
+ echo ""
+
+ # Test TX power changes on different power levels
+ echo "🧪 Testing TX power control capabilities:"
+ echo ""
+
+ # Store original power for restoration
+ echo ""
+ echo "🎯 Band-specific TX Power Analysis:"
+ echo ""
+
+ # Get current connection info for band analysis
+ WIFI_INFO=$(iw dev "$IFACE" link 2>/dev/null)
+ if echo "$WIFI_INFO" | grep -q "Connected to"; then
+ CURRENT_FREQ=$(echo "$WIFI_INFO" | grep "freq:" | awk '{print $2}')
+ CURRENT_SIGNAL=$(echo "$WIFI_INFO" | grep "signal:" | awk '{print $2}')
+
+ if [ -n "$CURRENT_FREQ" ]; then
+ FREQ_INT=$(echo "$CURRENT_FREQ" | cut -d'.' -f1)
+
+ if [ "$FREQ_INT" -ge 2400 ] && [ "$FREQ_INT" -le 2500 ]; then
+ CURRENT_BAND="2.4 GHz"
+ echo "📡 Currently on 2.4GHz band:"
+ echo " • Typical max TX power: 20dBm (100mW)"
+ echo " • Range: Good penetration through walls"
+ echo " • Congestion: Usually high in urban areas"
+ elif [ "$FREQ_INT" -ge 5000 ] && [ "$FREQ_INT" -le 6000 ]; then
+ CURRENT_BAND="5 GHz"
+ echo "📡 Currently on 5GHz band:"
+ echo " • Typical max TX power: 20-23dBm (100-200mW)"
+ echo " • Range: Shorter than 2.4GHz but less congested"
+ echo " • DFS channels: May have radar interference"
+ elif [ "$FREQ_INT" -ge 6000 ] && [ "$FREQ_INT" -le 7200 ]; then
+ CURRENT_BAND="6 GHz"
+ echo "📡 Currently on 6GHz band (WiFi 6E/7):"
+ echo " • Typical max TX power: 20dBm (100mW)"
+ echo " • Range: Shortest but cleanest spectrum"
+ echo " • DFS channels: NONE - completely DFS-free!"
+ fi
+
+ echo " • Current frequency: $CURRENT_FREQ MHz"
+ echo " • Current signal: ${CURRENT_SIGNAL:-Unknown} dBm"
+ fi
+ else
+ echo "❌ Not connected - cannot analyze current band"
+ fi
+
+ echo ""
+ echo "🎯 Regulatory Domain Impact:"
+ echo ""
+
+ REG_INFO=$(iw reg get 2>/dev/null)
+ if [ -n "$REG_INFO" ]; then
+ REG_COUNTRY=$(echo "$REG_INFO" | grep "country" | head -1 | awk '{print $2}' | tr -d ':')
+ echo "📍 Current regulatory domain: ${REG_COUNTRY:-Global}"
+
+ case "$REG_COUNTRY" in
+ "US")
+ echo " • 2.4GHz: Max 30dBm EIRP (1W)"
+ echo " • 5GHz low: Max 30dBm EIRP (1W)"
+ echo " • 5GHz high: Max 30dBm EIRP (1W)"
+ echo " • 6GHz: Max 30dBm EIRP (1W)"
+ ;;
+ "EU"|"DE"|"FR"|"GB")
+ echo " • 2.4GHz: Max 20dBm EIRP (100mW)"
+ echo " • 5GHz: Max 23dBm EIRP (200mW)"
+ echo " • 6GHz: Max 23dBm EIRP (200mW)"
+ ;;
+ "JP")
+ echo " • 2.4GHz: Max 20dBm EIRP (100mW)"
+ echo " • 5GHz: Max 20dBm EIRP (100mW)"
+ echo " • 6GHz: Varies by channel"
+ ;;
+ *)
+ echo " • Check local regulations for your country"
+ echo " • Set correct domain: sudo iw reg set [COUNTRY_CODE]"
+ ;;
+ esac
+ else
+ echo "❓ Cannot determine regulatory domain"
+ echo "💡 Set manually: sudo iw reg set US (or your country code)"
+ fi
+
+ echo ""
+ echo "🎯 Chipset-Specific TX Power Behavior:"
+ echo ""
+
+ case "$CHIP_VENDOR" in
+ "MediaTek")
+ echo "📊 MediaTek chipset behavior:"
+ echo " • Manual TX power: Often ignored by driver"
+ echo " • Automatic control: Usually works well"
+ echo " • Regional limits: Strictly enforced"
+ echo " • Recommendation: Use auto mode, optimize router-side"
+ ;;
+ "Intel")
+ echo "📊 Intel chipset behavior:"
+ echo " • Manual TX power: Limited by regulatory database"
+ echo " • Automatic control: Excellent dynamic adjustment"
+ echo " • Regional limits: Strictly enforced"
+ echo " • Recommendation: Ensure correct regulatory domain"
+ ;;
+ "Qualcomm")
+ echo "📊 Qualcomm chipset behavior:"
+ echo " • Manual TX power: Better support than MediaTek"
+ echo " • Automatic control: Good"
+ echo " • Regional limits: Enforced"
+ echo " • Recommendation: Manual adjustment may work"
+ ;;
+ *)
+ echo "📊 Generic chipset recommendations:"
+ echo " • Try auto mode first"
+ echo " • Check regulatory domain setting"
+ echo " • Monitor for driver restrictions"
+ ;;
+ esac
+
+ echo ""
+ echo "💡 TX Power Optimization Recommendations:"
+ echo ""
+ echo "1. For better range:"
+ echo " • Ensure correct regulatory domain: sudo iw reg set [COUNTRY]"
+ echo " • Optimize router TX power settings"
+ echo " • Consider external antennas if supported"
+ echo ""
+ echo "2. For MediaTek cards specifically:"
+ echo " • Router-side power optimization more effective"
+ echo " • Use 2.4GHz for maximum range"
+ echo " • Avoid DFS channels (radar interference)"
+ echo ""
+ echo "3. For 6GHz optimization:"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " ✅ Your hardware supports 6GHz"
+ echo " • 6GHz = shortest range but cleanest spectrum"
+ echo " • NO DFS interference possible"
+ echo " • Best for high-speed, short-distance connections"
+ else
+ echo " 💡 Upgrade to WiFi 6E/7 for 6GHz access"
+ echo " • 6GHz provides DFS-free operation"
+ echo " • Clean spectrum with minimal interference"
+ fi
+
+ echo ""
+ echo "📋 Quick Commands for Testing:"
+ echo " • Check current power: iw dev $IFACE info | grep txpower"
+ echo " • Test signal strength: watch -n 1 'iw dev $IFACE link | grep signal'"
+ echo " • Speed test: speedtest-cli"
+ echo ""
+}
+
+# Manual Band Switching - COMPLETE IMPLEMENTATION
+manual_band_switching() {
+ echo -e "${BOLD}${GREEN}📡 === MANUAL BAND SWITCHING ===${NC}"
+ echo "🎯 Direct CLI commands for switching between WiFi bands"
+ echo ""
+
+ # Quick system check
+ gather_system_intelligence >/dev/null 2>&1
+
+ if [ -z "$IFACE" ]; then
+ echo "❌ No WiFi interface available"
+ return 1
+ fi
+
+ # Get current connection
+ ACTIVE_CONNECTION=$(nmcli -t connection show --active | grep -E "wifi|802-11-wireless" | head -1 | cut -d: -f1)
+
+ if [ -z "$ACTIVE_CONNECTION" ]; then
+ echo "❌ No active WiFi connection detected"
+ echo "💡 Connect to WiFi first, then run band switching"
+ return 1
+ fi
+
+ echo "📡 Current active connection: $ACTIVE_CONNECTION"
+
+ # Get current connection details
+ CURRENT_FREQ=$(iw dev "$IFACE" link 2>/dev/null | grep "freq:" | awk '{print $2}')
+ CURRENT_SIGNAL=$(iw dev "$IFACE" link 2>/dev/null | grep "signal:" | awk '{print $2}')
+
+ if [ -n "$CURRENT_FREQ" ]; then
+ FREQ_INT=$(echo "$CURRENT_FREQ" | cut -d'.' -f1)
+
+ if [ "$FREQ_INT" -ge 2400 ] && [ "$FREQ_INT" -le 2500 ]; then
+ CURRENT_BAND_DISPLAY="2.4 GHz"
+ elif [ "$FREQ_INT" -ge 5000 ] && [ "$FREQ_INT" -le 6000 ]; then
+ CURRENT_BAND_DISPLAY="5 GHz"
+ elif [ "$FREQ_INT" -ge 6000 ] && [ "$FREQ_INT" -le 7200 ]; then
+ CURRENT_BAND_DISPLAY="6 GHz (WiFi 6E/7)"
+ else
+ CURRENT_BAND_DISPLAY="Unknown"
+ fi
+ else
+ CURRENT_BAND_DISPLAY="Unknown"
+ fi
+
+ echo "📊 Current status:"
+ echo " Band: $CURRENT_BAND_DISPLAY"
+ echo " Frequency: ${CURRENT_FREQ:-Unknown} MHz"
+ echo " Signal: ${CURRENT_SIGNAL:-Unknown} dBm"
+ echo ""
+
+ echo -e "${CYAN}🎯 === BAND SWITCHING COMMANDS ===${NC}"
+ echo ""
+
+ echo "1) 🔵 Force 2.4GHz Band (Maximum Range)"
+ echo " Command: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.band bg"
+ echo " Effect: Forces connection to 2.4GHz only"
+ echo " Use for: Maximum range, penetration through walls"
+ echo " DFS risk: ZERO (no DFS channels in 2.4GHz)"
+ echo ""
+
+ echo "2) 🟢 Force 5GHz Band (Balanced Performance)"
+ echo " Command: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.band a"
+ echo " Effect: Forces connection to 5GHz only"
+ echo " Use for: Better speed, less congestion"
+ echo " DFS risk: MEDIUM (some 5GHz channels use DFS)"
+ echo ""
+
+ echo "3) 🟡 Auto Band Selection (Default)"
+ echo " Command: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.band ''"
+ echo " Effect: Allows automatic band selection"
+ echo " Use for: Let system choose best band"
+ echo " DFS risk: VARIES (depends on router channel selection)"
+ echo ""
+
+ # 6GHz options only if supported
+ if [ "$SUPPORTS_WIFI7" = true ] || echo "$CHIP_MODEL" | grep -qi "6E"; then
+ echo "4) 🔴 6GHz Band Access (WiFi 6E/7 - Clean Spectrum)"
+ echo " Note: 6GHz typically included in 5GHz 'a' band setting"
+ echo " Command: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.band a"
+ echo " Requirement: Router must broadcast 6GHz SSID"
+ echo " DFS risk: ZERO (NO DFS channels exist in 6GHz)"
+ echo " 💡 6GHz advantages: Clean spectrum, no radar interference"
+ echo ""
+ fi
+
+ echo -e "${CYAN}🔧 === SPECIFIC CHANNEL SELECTION ===${NC}"
+ echo ""
+
+ echo "Safe 5GHz Channels (NO DFS - No Radar Interference):"
+ echo " Low band: 36, 40, 44, 48"
+ echo " • Channel 36: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 36"
+ echo " • Channel 44: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 44"
+ echo ""
+ echo " High band: 149, 153, 157, 161, 165"
+ echo " • Channel 149: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 149"
+ echo " • Channel 157: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 157"
+ echo ""
+
+ echo "⚠️ Avoid These Channels (DFS - Radar Interference Risk):"
+ echo " DFS channels: 52, 56, 60, 64, 100-144"
+ echo " 💡 These channels can cause sudden 30+ second disconnections"
+ echo ""
+
+ echo -e "${CYAN}🧪 === INTERACTIVE BAND SWITCHING ===${NC}"
+ echo ""
+ echo "Would you like to switch bands now? [y/N]"
+ read -r switch_choice
+
+ if [[ "$switch_choice" =~ ^[Yy]$ ]]; then
+ echo ""
+ echo "Select band to switch to:"
+ echo "1) 2.4GHz (maximum range, DFS-free)"
+ echo "2) 5GHz (balanced performance, some DFS risk)"
+ echo "3) Auto selection (system chooses)"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo "4) Force specific safe channel (recommended)"
+ fi
+ echo "5) Cancel"
+ echo ""
+ echo -n "Choice [1-5]: "
+ read -r band_choice
+
+ case $band_choice in
+ 1)
+ echo "🔵 Switching to 2.4GHz band..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band bg; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Switched to 2.4GHz band"
+ echo "💡 This provides maximum range and is completely DFS-free"
+ else
+ echo "❌ Failed to switch to 2.4GHz"
+ fi
+ ;;
+ 2)
+ echo "🟢 Switching to 5GHz band..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band a; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Switched to 5GHz band"
+ echo "⚠️ Monitor for DFS-related disconnections"
+ else
+ echo "❌ Failed to switch to 5GHz"
+ fi
+ ;;
+ 3)
+ echo "🟡 Enabling automatic band selection..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band ""; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Enabled automatic band selection"
+ else
+ echo "❌ Failed to enable auto selection"
+ fi
+ ;;
+ 4)
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ # Detect network type (mesh, hotspot, single-band)
+ MESH_DETECTED=false
+ HOTSPOT_DETECTED=false
+ SUPPORTS_5GHZ=true
+ SUPPORTS_24GHZ=true
+
+ # Get current SSID if not already set
+if [ -z "$CURRENT_SSID" ]; then
+ CURRENT_SSID=$(iw dev "$IFACE" link 2>/dev/null | grep "SSID:" | awk '{print $2}')
+ if [ -z "$CURRENT_SSID" ]; then
+ CURRENT_SSID=$(nmcli -t -f active,ssid dev wifi | grep '^yes' | cut -d: -f2)
+ fi
+fi
+
+# Check for hotspots and band capabilities FIRST
+if echo "$CURRENT_SSID" | grep -qi "iphone\|android\|hotspot\|mobile\|pixel_\|galaxy_\|oneplus_\|xiaomi_\|huawei_\|samsung_\|nokia_\|motorola_\|lg_\|sony_\|oppo_\|vivo_\|realme_\|htc_" || \
+ echo "$CURRENT_SSID" | grep -qE "^[A-Za-z]+_[0-9]{3,4}$" || \
+ echo "$CURRENT_SSID" | grep -qE "^[A-Za-z]+'s (iPhone|Android|Phone)" || \
+ echo "$CURRENT_SSID" | grep -qi "phone\|tether\|share\|wifi.*direct\|portable.*wifi"; then
+ HOTSPOT_DETECTED=true
+fi
+
+# Check for mesh networks (improved logic - after hotspot detection)
+if echo "$CURRENT_SSID" | grep -qi "eero\|orbi\|deco\|velop\|amplifi\|nest"; then
+ MESH_DETECTED=true
+elif [ "$HOTSPOT_DETECTED" = false ]; then
+ # Only consider it mesh if we find MANY similar SSIDs (3+ nodes typical for mesh)
+ BASE_SSID=$(echo "$CURRENT_SSID" | sed 's/_5G\|_2G\|_6G\|-5G\|-2G\|-6G//')
+ SIMILAR_SSID_COUNT=$(nmcli -t -f SSID dev wifi | grep -c "$BASE_SSID" 2>/dev/null || echo "0")
+
+ # Mesh networks typically have 3+ nodes broadcasting
+ if [ "$SIMILAR_SSID_COUNT" -ge 3 ]; then
+ MESH_DETECTED=true
+ fi
+fi
+
+ # Get scan results if not already available
+if [ -z "$SCAN_RESULTS" ]; then
+ echo "🔍 Scanning for available bands..."
+ SCAN_RESULTS=$(timeout 15 iw dev "$IFACE" scan 2>/dev/null)
+fi
+
+# For connected networks, use current connection info instead of scan
+CURRENT_CONNECTION_FREQ=$(iw dev "$IFACE" link 2>/dev/null | grep "freq:" | awk '{print $2}')
+
+# Check actual band availability for this network
+if [ -n "$CURRENT_CONNECTION_FREQ" ]; then
+ # We're connected - use current connection frequency to determine bands
+ FREQ_INT=$(echo "$CURRENT_CONNECTION_FREQ" | cut -d'.' -f1)
+
+ if [ "$FREQ_INT" -ge 2400 ] && [ "$FREQ_INT" -le 2500 ]; then
+ # Connected on 2.4GHz - this network definitely supports 2.4GHz
+ SUPPORTS_24GHZ=true
+ # Check if we can find 5GHz for this SSID in scan results
+ if ! echo "$SCAN_RESULTS" | grep -A1 "SSID: $CURRENT_SSID" | grep -q "freq: 5[0-9][0-9][0-9]"; then
+ SUPPORTS_5GHZ=false
+ fi
+ elif [ "$FREQ_INT" -ge 5000 ] && [ "$FREQ_INT" -le 6000 ]; then
+ # Connected on 5GHz - this network definitely supports 5GHz
+ SUPPORTS_5GHZ=true
+ # Check if we can find 2.4GHz for this SSID in scan results
+ if ! echo "$SCAN_RESULTS" | grep -A1 "SSID: $CURRENT_SSID" | grep -q "freq: 24[0-9][0-9]"; then
+ SUPPORTS_24GHZ=false
+ fi
+ fi
+elif [ -n "$SCAN_RESULTS" ]; then
+ # Not connected - use scan results
+ if ! echo "$SCAN_RESULTS" | grep -A1 "SSID: $CURRENT_SSID" | grep -q "freq: 5[0-9][0-9][0-9]"; then
+ SUPPORTS_5GHZ=false
+ fi
+ if ! echo "$SCAN_RESULTS" | grep -A1 "SSID: $CURRENT_SSID" | grep -q "freq: 24[0-9][0-9]"; then
+ SUPPORTS_24GHZ=false
+ fi
+fi
+
+ # Handle single-band networks first
+ if [ "$SUPPORTS_5GHZ" = false ] && [ "$SUPPORTS_24GHZ" = true ]; then
+ echo ""
+ echo "📱 2.4GHz-ONLY NETWORK DETECTED"
+ if [ "$HOTSPOT_DETECTED" = true ]; then
+ echo "(Likely mobile hotspot - most only support 2.4GHz)"
+ fi
+ echo ""
+ echo "This network only broadcasts on 2.4GHz band."
+ echo "5GHz switching is not available for this network."
+ echo ""
+ echo "Available options:"
+ echo "1) Stay on 2.4GHz (only option for this network)"
+ echo "2) Reset to auto"
+ echo ""
+ echo -n "Choose option [1-2]: "
+ read -r single_band_choice
+
+ case $single_band_choice in
+ 1)
+ echo "🔵 Confirming 2.4GHz connection..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band bg; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Confirmed 2.4GHz connection (only available band)"
+ else
+ echo "❌ Failed to confirm 2.4GHz connection"
+ fi
+ ;;
+ 2)
+ echo "🔄 Resetting to auto..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band ""; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Reset to auto (will use 2.4GHz anyway)"
+ else
+ echo "❌ Failed to reset to auto"
+ fi
+ ;;
+ *)
+ echo "❌ Invalid choice"
+ ;;
+ esac
+ elif [ "$SUPPORTS_24GHZ" = false ] && [ "$SUPPORTS_5GHZ" = true ]; then
+ echo ""
+ echo "📡 5GHz-ONLY NETWORK DETECTED"
+ echo ""
+ echo "This network only broadcasts on 5GHz band."
+ echo "2.4GHz switching is not available for this network."
+ echo ""
+ echo "Available options:"
+ echo "1) Stay on 5GHz (only option for this network)"
+ echo "2) Reset to auto"
+ echo ""
+ echo -n "Choose option [1-2]: "
+ read -r single_band_choice
+
+ case $single_band_choice in
+ 1)
+ echo "🟢 Confirming 5GHz connection..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band a; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Confirmed 5GHz connection (only available band)"
+ else
+ echo "❌ Failed to confirm 5GHz connection"
+ fi
+ ;;
+ 2)
+ echo "🔄 Resetting to auto..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band ""; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Reset to auto (will use 5GHz anyway)"
+ else
+ echo "❌ Failed to reset to auto"
+ fi
+ ;;
+ *)
+ echo "❌ Invalid choice"
+ ;;
+ esac
+ elif [ "$MESH_DETECTED" = true ]; then
+ echo ""
+ echo "🌐 MESH NETWORK DETECTED - Modified approach:"
+ echo ""
+ echo "Mesh networks manage channels automatically. Instead of forcing"
+ echo "specific channels, try these mesh-friendly approaches:"
+ echo ""
+ echo "1) Force 2.4GHz (mesh usually has dedicated 2.4GHz nodes)"
+ echo "2) Force 5GHz (let mesh choose best 5GHz channel)"
+ echo "3) Reset to auto (recommended for mesh)"
+ echo ""
+ echo -n "Choose approach [1-3]: "
+ read -r mesh_choice
+
+ case $mesh_choice in
+ 1)
+ echo "🔵 Forcing 2.4GHz for mesh compatibility..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band bg; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Switched to 2.4GHz (mesh will choose optimal 2.4GHz channel)"
+ else
+ echo "❌ Failed to switch to 2.4GHz"
+ fi
+ ;;
+ 2)
+ echo "🟢 Forcing 5GHz for mesh compatibility..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band a; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Switched to 5GHz (mesh will choose optimal 5GHz channel)"
+ else
+ echo "❌ Failed to switch to 5GHz"
+ fi
+ ;;
+ 3)
+ echo "🔄 Resetting to auto for optimal mesh performance..."
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band "" && \
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.channel ""; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Reset to auto (recommended for mesh networks)"
+ else
+ echo "❌ Failed to reset to auto"
+ fi
+ ;;
+ *)
+ echo "❌ Invalid choice"
+ ;;
+ esac
+ else
+ echo ""
+ echo "Select safe channel (no DFS):"
+ echo "36) Channel 36 (5180 MHz, safe)"
+ echo "44) Channel 44 (5220 MHz, safe)"
+ echo "149) Channel 149 (5745 MHz, safe)"
+ echo "157) Channel 157 (5785 MHz, safe)"
+ echo ""
+ echo -n "Enter channel number: "
+ read -r channel_choice
+
+ if [[ "$channel_choice" =~ ^(36|44|149|157)$ ]]; then
+ echo "🔧 Switching to channel $channel_choice..."
+ # FIXED: Set band FIRST, then channel
+ if sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band a && \
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.channel "$channel_choice"; then
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Switched to channel $channel_choice (5GHz, DFS-free)"
+ else
+ echo "❌ Failed to switch to channel $channel_choice"
+ echo "💡 Try: Set band first, then channel manually"
+ fi
+ else
+ echo "❌ Invalid channel. Use: 36, 44, 149, or 157"
+ fi
+ fi
+ fi
+ ;;
+ 5)
+ echo "Cancelled - no changes made"
+ ;;
+ *)
+ echo "❌ Invalid option"
+ ;;
+ esac
+
+ if [[ "$band_choice" =~ ^[1-4]$ ]]; then
+ echo ""
+ echo "🔍 Waiting 10 seconds for connection to stabilize..."
+ sleep 10
+ echo ""
+ echo "📊 New connection status:"
+ NEW_FREQ=$(iw dev "$IFACE" link 2>/dev/null | grep "freq:" | awk '{print $2}')
+ NEW_SIGNAL=$(iw dev "$IFACE" link 2>/dev/null | grep "signal:" | awk '{print $2}')
+
+ if [ -n "$NEW_FREQ" ]; then
+ NEW_FREQ_INT=$(echo "$NEW_FREQ" | cut -d'.' -f1)
+
+ if [ "$NEW_FREQ_INT" -ge 2400 ] && [ "$NEW_FREQ_INT" -le 2500 ]; then
+ NEW_BAND="2.4 GHz"
+ elif [ "$NEW_FREQ_INT" -ge 5000 ] && [ "$NEW_FREQ_INT" -le 6000 ]; then
+ NEW_BAND="5 GHz"
+ elif [ "$NEW_FREQ_INT" -ge 6000 ] && [ "$NEW_FREQ_INT" -le 7200 ]; then
+ NEW_BAND="6 GHz"
+ else
+ NEW_BAND="Unknown"
+ fi
+
+ echo " New band: $NEW_BAND"
+ echo " New frequency: $NEW_FREQ MHz"
+ echo " New signal: ${NEW_SIGNAL:-Unknown} dBm"
+
+ # DFS analysis for new connection
+ if [ "$NEW_BAND" = "5 GHz" ]; then
+ NEW_CHANNEL=$(echo "scale=0; ($NEW_FREQ_INT - 5000) / 5" | bc 2>/dev/null || echo "Unknown")
+ if [ "$NEW_CHANNEL" != "Unknown" ] && is_dfs_channel "$NEW_CHANNEL"; then
+ echo -e " ${YELLOW}⚠️ DFS Warning: Connected to DFS channel $NEW_CHANNEL${NC}"
+ echo " 💡 Monitor for sudden disconnections lasting 30+ seconds"
+ else
+ echo -e " ${GREEN}✅ Safe channel: $NEW_CHANNEL (non-DFS)${NC}"
+ fi
+ fi
+ else
+ echo " ❌ Connection status not available"
+ fi
+ fi
+ fi
+
+ echo ""
+ echo -e "${GREEN}💡 Band Switching Tips:${NC}"
+ echo "• 2.4GHz: Best range, completely DFS-free, but often congested"
+ echo "• 5GHz: Good balance, but check for DFS channel usage"
+ echo "• 6GHz: Shortest range but cleanest spectrum (NO DFS ever)"
+ echo "• Safe 5GHz channels: 36, 40, 44, 48, 149, 153, 157, 161, 165"
+ echo "• Avoid DFS channels: 52-64, 100-144 (radar interference risk)"
+ echo ""
+ echo "📋 Useful monitoring commands:"
+ echo "• Current status: iw dev $IFACE link"
+ echo "• Signal monitoring: watch -n 2 'iw dev $IFACE link | grep signal'"
+ echo "• Speed test: speedtest-cli"
+ echo ""
+}
+
+# Enhanced menu system - COMPLETE WITH ALL OPTIONS
+show_menu() {
+ clear
+ echo -e "${BOLD}${CYAN}🧠 === ENHANCED WiFi ANALYZER ===${NC}"
+ echo -e "${BOLD}WiFi 7 • MLO • 6GHz • DFS Monitoring • Distribution Adaptive • Modern VPN Support${NC}"
+ echo ""
+ echo "1) 🎯 Complete WiFi Analysis (All-in-One with WiFi 7 + DFS support)"
+ echo "2) 🚨 Error Analysis & Troubleshooting (Distribution-aware fixes + DFS)"
+ echo "3) 🛠️ Interactive Workaround Generator (Modern solutions + DFS fixes)"
+ echo "4) 📡 DFS Channel Monitor (Dedicated radar interference analysis)"
+ echo "5) 🧪 TX Power Band Test (diagnose power limitations)"
+ echo "6) 📡 Manual Band Switching (2.4GHz/5GHz/6GHz CLI commands)"
+ echo "7) 🚪 Exit"
+ echo ""
+ echo -n "Select option [1-7]: "
+}
+
+# Complete comprehensive analysis with modern features and DFS monitoring
+complete_wifi_analysis() {
+ echo -e "${BOLD}${BLUE}🧠 === COMPLETE WiFi ANALYSIS ===${NC}"
+ echo "🔬 WiFi 7 • MLO • 6GHz • DFS Monitoring • Modern VPN • Distribution adaptive"
+ echo "📊 Comprehensive analysis for modern WiFi systems with radar interference detection"
+ echo ""
+
+ # Run all analysis components
+ gather_system_intelligence
+ detect_vpn_configuration
+ analyze_rf_frequency_environment # This now includes DFS analysis
+ detect_severe_wifi_issues # This now includes DFS-aware detection
+
+ # Provide DFS recommendations if relevant
+ if [ "$DFS_IMPACT_SCORE" -gt 25 ]; then
+ echo ""
+ provide_dfs_recommendations
+ fi
+
+ # Final summary with modern features and DFS
+ echo -e "${CYAN}🎯 === FINAL ANALYSIS SUMMARY ===${NC}"
+ echo ""
+
+ OVERALL_HEALTH="Excellent"
+ HEALTH_SCORE=100
+
+ # Calculate overall health including DFS impact
+ if [ "$WIFI_FUNCTIONAL" != "true" ]; then
+ OVERALL_HEALTH="Critical"
+ HEALTH_SCORE=0
+ elif [ "$SEVERE_ISSUES" -gt 0 ]; then
+ OVERALL_HEALTH="Poor"
+ HEALTH_SCORE=25
+ elif [ "$DFS_IMPACT_SCORE" -gt 75 ]; then
+ OVERALL_HEALTH="Poor (DFS interference)"
+ HEALTH_SCORE=30
+ elif [ "$DFS_IMPACT_SCORE" -gt 50 ]; then
+ OVERALL_HEALTH="Fair (DFS risk)"
+ HEALTH_SCORE=50
+ elif [ "$VPN_IMPACT_SCORE" -gt 50 ]; then
+ OVERALL_HEALTH="Fair"
+ HEALTH_SCORE=60
+ elif [ "$DFS_IMPACT_SCORE" -gt 25 ]; then
+ HEALTH_SCORE=85 # Minor DFS impact
+ fi
+
+ echo "📊 Overall WiFi Health: $OVERALL_HEALTH ($HEALTH_SCORE/100)"
+ echo "🔧 WiFi Status: $([ "$WIFI_FUNCTIONAL" = "true" ] && echo "✅ Working" || echo "❌ Broken")"
+ echo "🔒 VPN Status: $VPN_ACTIVE ($VPN_TYPE)"
+ echo "📡 Hardware: ${CHIP_VENDOR:-Unknown} ${CHIP_MODEL:-Unknown} (${CHIP_GENERATION:-Unknown})"
+ echo "🐧 System: $DISTRO_NAME"
+
+ # DFS status summary
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo "📡 DFS Status: ⚠️ Connected to DFS channel (radar risk)"
+ elif [ "$DFS_COUNT" -gt 0 ]; then
+ echo "📡 DFS Status: 📊 $DFS_COUNT DFS networks in area"
+ else
+ echo "📡 DFS Status: ✅ Clean environment (no DFS detected)"
+ fi
+
+ # Modern features summary
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo "🌟 WiFi 7 Features: $([ "$SUPPORTS_MLO" = true ] && echo "MLO capable" || echo "Standard WiFi 7")"
+ fi
+
+ if [ "$WIFI_FUNCTIONAL" = "true" ]; then
+ echo ""
+ echo -e "${GREEN}✅ SYSTEM STATUS: HEALTHY${NC}"
+ echo "🎉 Your modern WiFi system is functioning properly"
+ if [ "$VPN_ACTIVE" = "Yes" ]; then
+ echo "🔒 Modern VPN integration appears stable"
+ fi
+
+ # Modern optimization recommendations
+ if [ "$SUPPORTS_WIFI7" = true ] && [ "$CURRENT_BAND" != "6 GHz (WiFi 6E/7)" ]; then
+ echo "💡 Optimization: Consider upgrading to WiFi 7 router for 6GHz access"
+ echo "💡 6GHz Advantage: No DFS channels = no radar interference"
+ fi
+ if [ "$SUPPORTS_MLO" = true ]; then
+ echo "🔗 MLO available: Multi-link operation can improve performance"
+ fi
+
+ # DFS-specific recommendations
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo ""
+ echo -e "${YELLOW}⚠️ DFS RECOMMENDATION: Switch to non-DFS channel for stability${NC}"
+ echo "💡 Suggested channels: 36, 40, 44, 48 (low 5GHz) or 149+ (high 5GHz)"
+ elif [ "$DFS_IMPACT_SCORE" -gt 50 ]; then
+ echo ""
+ echo -e "${YELLOW}⚠️ DFS ENVIRONMENT: High radar activity detected${NC}"
+ echo "💡 Monitor for disconnection patterns and consider non-DFS channels"
+ fi
+
+ else
+ echo ""
+ echo -e "${RED}🚨 SYSTEM STATUS: NEEDS ATTENTION${NC}"
+ echo "💡 Run option 2 for detailed error analysis and troubleshooting"
+
+ if [ "$DFS_IMPACT_SCORE" -gt 50 ]; then
+ echo ""
+ echo -e "${MAGENTA}📡 DFS FACTOR: Radar interference may be contributing to issues${NC}"
+ echo "💡 Try non-DFS channels first before other troubleshooting"
+ fi
+ fi
+
+ echo ""
+}
+
+# Error analysis and troubleshooting with distribution awareness and DFS
+error_analysis_troubleshooting() {
+ echo -e "${BOLD}${RED}🚨 === ERROR ANALYSIS & TROUBLESHOOTING ===${NC}"
+ echo "🔍 Deep dive into system logs, errors, and failure patterns"
+ echo "🛠️ Distribution-aware troubleshooting recommendations with DFS analysis"
+ echo ""
+
+ # Quick system check first
+ gather_system_intelligence
+
+ # Run DFS analysis early to identify radar-related issues
+ analyze_dfs_channels >/dev/null 2>&1
+
+ # NEW: Enhanced authentication failure detection
+ AUTH_ISSUES=0
+ AUTH_FAILURES=$(journalctl --since "24 hours ago" --no-pager 2>/dev/null | \
+ grep -E "association took too long|ssid-not-found|authenticating.*disconnected" | \
+ wc -l)
+
+ if [ "$AUTH_FAILURES" -gt 0 ]; then
+ echo -e "${RED}🚨 FOUND $AUTH_FAILURES AUTHENTICATION FAILURES${NC}"
+ echo ""
+ echo "📋 Recent authentication failure patterns:"
+ journalctl --since "6 hours ago" --no-pager 2>/dev/null | \
+ grep -E "association took too long|ssid-not-found|authenticating.*disconnected" | \
+ tail -5 | while IFS= read -r line; do
+ echo " $line"
+ done
+ echo ""
+ AUTH_ISSUES=1
+ fi
+
+ # Check for DFS-related disconnection patterns in logs
+ DFS_LOG_ISSUES=0
+ if [ "$DFS_RADAR_EVENTS" -gt 0 ] || [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo -e "${MAGENTA}📡 DFS-RELATED ISSUE ANALYSIS${NC}"
+ echo ""
+
+ if [ "$DFS_RADAR_EVENTS" -gt 0 ]; then
+ echo -e "${RED}🚨 RADAR EVENTS DETECTED: $DFS_RADAR_EVENTS in last 24 hours${NC}"
+ echo "📋 Recent DFS/radar events:"
+ journalctl --since "6 hours ago" --no-pager 2>/dev/null | \
+ grep -iE "radar.*detect|dfs.*radar|channel.*blocked|cac.*complete|cac.*failed" | \
+ tail -5 | while IFS= read -r line; do
+ echo " $line"
+ done
+ DFS_LOG_ISSUES=1
+ fi
+
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo -e "${YELLOW}⚠️ DFS CHANNEL CONNECTION: High risk of radar-related disconnections${NC}"
+ echo " Current connection uses DFS channel - this explains intermittent drops"
+ DFS_LOG_ISSUES=1
+ fi
+ echo ""
+ fi
+
+ # Focus on problems if WiFi is broken OR authentication issues found OR DFS issues
+ if [ "$WIFI_FUNCTIONAL" != "true" ] || [ "$AUTH_ISSUES" -eq 1 ] || [ "$DFS_LOG_ISSUES" -eq 1 ]; then
+ echo -e "${RED}💥 WiFi SYSTEM ISSUES DETECTED - DIAGNOSTIC MODE${NC}"
+ echo ""
+
+ # DFS-specific fixes get priority if DFS issues detected
+ if [ "$DFS_LOG_ISSUES" -eq 1 ]; then
+ echo -e "${MAGENTA}🎯 PRIORITY: DFS FIXES (Radar interference detected)${NC}"
+ echo ""
+ echo "1. Immediate non-DFS channel switch:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " 🔧 Force 2.4GHz (no DFS): sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo " 🔧 Reconnect: sudo nmcli connection up \"$CURRENT_SSID\""
+ fi
+ echo ""
+ echo "2. Router configuration (CRITICAL):"
+ echo " 🔧 Set router primary channel to: 36, 40, 44, 48 (low 5GHz, no DFS)"
+ echo " 🔧 Alternative: 149, 153, 157, 161, 165 (high 5GHz, no DFS)"
+ echo " 🔧 Disable automatic channel selection"
+ echo " 🔧 Disable DFS channels entirely in router settings"
+ echo ""
+ echo "3. Test connectivity after DFS fix:"
+ echo " 🧪 Monitor: ping -c 10 8.8.8.8"
+ echo " 🧪 Verify channel: iw dev $IFACE link | grep freq"
+ echo ""
+ fi
+
+ detect_severe_wifi_issues
+
+ # NEW: Authentication-specific fixes - ONLY if WiFi is actually broken
+if [ "$AUTH_ISSUES" -eq 1 ]; then
+ if [ "$WIFI_FUNCTIONAL" = "true" ]; then
+ echo ""
+ echo -e "${GREEN}✅ HISTORICAL AUTHENTICATION EVENTS (System Currently Working)${NC}"
+ echo ""
+ echo "📋 Found $AUTH_FAILURES authentication entries in logs, but:"
+ echo " • WiFi is currently connected and functional"
+ echo " • Data flow is working properly"
+ echo " • These appear to be historical events (likely Tailscale/boot-time handshakes)"
+ echo ""
+ echo "💡 No immediate action required - monitor for actual disconnection issues"
+ echo ""
+ else
+ echo ""
+ echo -e "${YELLOW}🔧 AUTHENTICATION FAILURE FIXES:${NC}"
+ echo ""
+ echo "1. Delete and recreate connection profile:"
+ echo " 🔒 PERMANENT: sudo nmcli connection delete \"$CURRENT_SSID\""
+ echo " 🔒 PERMANENT: sudo nmcli device wifi connect \"$CURRENT_SSID\" password \"PASSWORD\""
+ echo ""
+ echo "2. Force specific band to avoid problematic radios:"
+ echo " 🔒 PERMANENT (5GHz only): sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band a"
+ echo " 🔒 PERMANENT (2.4GHz only): sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo ""
+ echo "3. Reset regulatory domain (6GHz issues):"
+ echo " ⏰ TEMPORARY: sudo iw reg set US"
+ echo " ⏰ TEMPORARY: sudo nmcli radio wifi off && sudo nmcli radio wifi on"
+ echo ""
+ echo "4. Router-specific fixes:"
+ echo " 💡 Disable band steering in router settings"
+ echo " 💡 Use non-DFS channels (36,40,44,149,153,157,161,165)"
+ echo " 💡 For Eero mesh: Check for firmware updates in Eero app"
+ echo ""
+ fi
+fi
+
+ echo ""
+ echo -e "${YELLOW}🔧 DISTRIBUTION-SPECIFIC TROUBLESHOOTING STEPS:${NC}"
+ echo ""
+ echo "🐧 Detected system: $DISTRO_NAME"
+ echo ""
+ echo "1. Basic restart sequence:"
+ echo " ⏰ IMMEDIATE (temporary fix): sudo systemctl restart NetworkManager"
+ echo " ⏰ IMMEDIATE (driver reload): sudo modprobe -r $DRIVER && sleep 3 && sudo modprobe $DRIVER"
+ echo ""
+ echo "2. Update system and firmware:"
+ echo " 🔒 PERMANENT: $(get_distro_command "firmware_update")"
+ echo ""
+ echo "3. Check for hardware issues:"
+ echo " 🧪 DIAGNOSTIC: lspci | grep -i wireless"
+ echo " 🧪 DIAGNOSTIC: dmesg | grep -i $DRIVER | tail -20"
+ echo ""
+ echo "4. Network configuration reset:"
+ echo " ⏰ IMMEDIATE: sudo nmcli device disconnect $IFACE"
+ echo " ⏰ IMMEDIATE: sudo nmcli device connect $IFACE"
+ echo ""
+
+ # Provide distribution-specific advanced fixes
+ provide_distribution_specific_workarounds "DRIVER_ERROR"
+
+ echo "📁 Detailed logs can be found in system journal"
+
+ else
+ echo -e "${GREEN}✅ WiFi WORKING - HEALTH CHECK MODE${NC}"
+ echo ""
+
+ # Even working systems can have underlying issues
+ echo "🔍 Checking for potential issues in working system..."
+
+ # Enhanced error filtering (remove harmless firmware loading attempts)
+ RECENT_ERRORS=$(journalctl --since "24 hours ago" --no-pager 2>/dev/null | \
+ grep -iE "$DRIVER.*error|wifi.*error|$IFACE.*error" | \
+ grep -v "Direct firmware load.*failed with error -2" | \
+ grep -v "firmware load.*failed.*error -2" | \
+ grep -v "WIFI_RAM_CODE.*failed" | \
+ grep -v "WIFI_MT.*patch.*failed" | \
+ grep -v "mediatek.*bin failed" | \
+ grep -v "firmware.*failed.*error -2" | \
+ wc -l)
+
+ if [ "$RECENT_ERRORS" -gt 0 ]; then
+ echo "⚠️ Found $RECENT_ERRORS recent WiFi-related errors"
+ echo "📋 Recent error patterns:"
+ journalctl --since "24 hours ago" --no-pager 2>/dev/null | \
+ grep -iE "$DRIVER.*error|wifi.*error" | \
+ grep -v "firmware.*failed.*error -2" | \
+ tail -5 | while IFS= read -r line; do
+ echo " $line"
+ done
+ else
+ echo "✅ No recent WiFi errors detected"
+ fi
+
+ # Modern VPN conflict check
+ if [ "$VPN_ACTIVE" = "Yes" ] && [ "$VPN_IMPACT_SCORE" -gt 30 ]; then
+ echo ""
+ echo "⚠️ Modern VPN may be impacting WiFi performance"
+ echo "💡 Consider testing WiFi without VPN periodically"
+ fi
+
+ # DFS health check even for working systems
+ if [ "$DFS_IMPACT_SCORE" -gt 25 ]; then
+ echo ""
+ echo "📡 DFS Analysis: Potential radar interference risk detected"
+ echo "💡 Monitor for disconnection patterns, especially 30+ second drops"
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo "⚠️ Current connection uses DFS channel - consider switching"
+ fi
+ fi
+
+ echo ""
+ echo "🎉 System appears healthy - no immediate action required"
+ fi
+
+ echo ""
+}
+
+# Interactive workaround generator with modern solutions and DFS fixes - COMPLETE
+interactive_workaround_generator() {
+ echo -e "${BOLD}${BLUE}🛠️ === INTERACTIVE WORKAROUND GENERATOR ===${NC}"
+ echo "🎯 Modern solutions for WiFi 7, 6GHz, MLO, DFS radar interference, and advanced VPN issues"
+ echo ""
+
+ echo "What WiFi issue are you experiencing?"
+ echo ""
+ echo "1) 📡 WiFi keeps disconnecting/dropping"
+ echo "2) 🐌 WiFi is very slow or unstable"
+ echo "3) ❌ WiFi won't connect at all"
+ echo "4) 🔒 Modern VPN causes WiFi problems (Tailscale/ZeroTier/etc)"
+ echo "5) 🔥 WiFi works but system gets hot/fans spin"
+ echo "6) ⚡ WiFi stops working after suspend/resume"
+ echo "7) 📶 Weak signal or poor range"
+ echo "8) 📡 DFS radar interference (sudden 30+ second disconnections)"
+ echo "9) 🆘 Emergency: Need immediate solution"
+ echo "10) 🔍 Run diagnostic first (recommended)"
+ echo ""
+ echo -n "Select your issue [1-10]: "
+
+ read -r issue_choice
+
+ case $issue_choice in
+ 1)
+ echo ""
+ echo "🔍 Analyzing modern disconnection patterns..."
+ # Re-run analysis and UPDATE global variables
+ gather_system_intelligence >/dev/null 2>&1
+ detect_vpn_configuration >/dev/null 2>&1
+ analyze_dfs_channels >/dev/null 2>&1
+
+ # Check if WiFi is actually having disconnection issues
+ if [ "$WIFI_FUNCTIONAL" = "true" ] && [ "$DFS_CURRENT_CONNECTION" != "true" ] && [ "$DFS_RADAR_EVENTS" -eq 0 ]; then
+ echo -e "${GREEN}✅ === WIFI CURRENTLY STABLE ===${NC}"
+ echo ""
+ echo "🎉 Analysis shows your WiFi is working excellently:"
+ echo " • Connected to 6GHz at high speeds"
+ echo " • No DFS radar interference"
+ echo " • No current disconnection issues detected"
+ echo ""
+ echo "❓ Are you experiencing actual disconnections right now? [y/N]"
+ read -r experiencing_issues
+
+ if [[ ! "$experiencing_issues" =~ ^[Yy]$ ]]; then
+ echo ""
+ echo "💡 Your WiFi appears stable. Consider monitoring with:"
+ echo " • Real-time connection: watch -n 2 'iw dev $IFACE link | grep -E \"Connected|signal\"'"
+ echo " • Network stability: ping -i 1 8.8.8.8"
+ echo ""
+ echo "💡 If disconnections occur later, re-run this tool for targeted fixes."
+ return 0
+ fi
+ echo ""
+ echo "🔍 Proceeding with disconnection analysis since you're experiencing issues..."
+ fi
+
+ echo -e "${CYAN}🛠️ === MODERN DISCONNECTION FIXES ===${NC}"
+ echo ""
+ echo "🐧 Distribution: $DISTRO_NAME"
+ echo ""
+
+ # DFS gets priority for disconnection issues
+ if [ "$DFS_CURRENT_CONNECTION" = true ] || [ "$DFS_RADAR_EVENTS" -gt 0 ]; then
+ echo -e "${MAGENTA}🎯 PRIORITY: DFS Radar Interference (likely cause)${NC}"
+ echo ""
+ echo "1. Immediate non-DFS channel switch:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " ⏰ IMMEDIATE: sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo " ⏰ IMMEDIATE: sudo nmcli connection up \"$CURRENT_SSID\""
+ echo " 💡 This forces 2.4GHz (no DFS channels exist in 2.4GHz)"
+ fi
+ echo ""
+ echo "2. Router configuration (CRITICAL):"
+ echo " 🔒 PERMANENT: Set router to channels 36, 40, 44, 48 (low 5GHz, no DFS)"
+ echo " 🔒 PERMANENT: Alternative: 149, 153, 157, 161, 165 (high 5GHz, no DFS)"
+ echo " 🔒 PERMANENT: Disable automatic channel selection"
+ echo ""
+ echo "3. Test after DFS fix:"
+ echo " 🧪 Monitor: ping -c 20 8.8.8.8 # Should show no 30+ second gaps"
+ echo " 🧪 Verify: iw dev $IFACE link | grep freq # Confirm non-DFS frequency"
+ echo ""
+ echo "4. 6GHz upgrade path (NO DFS):"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " ✅ Your hardware supports 6GHz"
+ echo " 🔒 PERMANENT: Upgrade to WiFi 6E/7 router for DFS-free operation"
+ echo " 🌟 6GHz advantage: Zero radar interference possible"
+ fi
+ echo ""
+ elif [ "$WIFI_FUNCTIONAL" != "true" ]; then
+ echo "1. Modern driver reset:"
+ echo " ⏰ IMMEDIATE (driver reload): sudo modprobe -r $DRIVER && sleep 5 && sudo modprobe $DRIVER"
+ echo ""
+ echo "2. Distribution-specific firmware update:"
+ echo " 🔒 PERMANENT (system upgrade): $(get_distro_command "firmware_update")"
+ echo ""
+ provide_distribution_specific_workarounds "FIRMWARE_CRASH"
+ elif [ "$VPN_ACTIVE" = "Yes" ] && [ "$VPN_IMPACT_SCORE" -gt 30 ]; then
+ echo "🔒 Modern VPN optimization detected:"
+ echo ""
+ echo "1. Optimize modern VPN MTU:"
+ echo " ⏰ TEMPORARY (until VPN restart): sudo ip link set $VPN_INTERFACE mtu 1200"
+ echo " 🔒 PERMANENT: Configure in VPN client settings"
+ echo ""
+ echo "2. For Tailscale specifically:"
+ echo " 🔒 PERMANENT (Tailscale config): tailscale up --accept-routes=false"
+ echo " 💡 Enables split tunneling - setting persists"
+ echo ""
+ echo "3. For ZeroTier:"
+ echo " 💡 Check ZeroTier controller settings for route conflicts"
+ echo " 🔒 PERMANENT: Configure managed routes in ZeroTier Central"
+ echo ""
+ echo "4. Test without VPN:"
+ echo " 🧪 TESTING (diagnostic): Temporarily disconnect modern VPN to test WiFi stability"
+ echo " ⏰ TEMPORARY: Changes revert when VPN reconnects"
+ else
+ echo "1. Modern power management fix:"
+ echo " ⏰ TEMPORARY (until reboot): sudo iw dev $IFACE set power_save off"
+ echo " 🔒 PERMANENT (module config): echo 'options mt7921e power_save=0' | sudo tee /etc/modprobe.d/mt7921e.conf"
+ echo " 💡 Try temporary first to test, then apply permanent if it works"
+ echo ""
+ echo "3. WiFi 7/6E specific optimizations:"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " 🔒 PERMANENT (system upgrade): $(get_distro_command "firmware_update")"
+ echo " ⚠️ REQUIRES REBOOT: System update needs restart to take effect"
+ echo " 💡 Your WiFi 7 hardware may need newer firmware for stability"
+ else
+ echo " 💡 Standard WiFi 6E optimizations"
+ echo " 💡 Consider hardware upgrade for WiFi 7 features"
+ fi
+ echo ""
+ echo "4. Distribution-specific module tuning:"
+ provide_distribution_specific_workarounds "DRIVER_ERROR"
+ fi
+ ;;
+ 2)
+ echo ""
+ echo "🔍 Analyzing modern performance issues..."
+ gather_system_intelligence >/dev/null 2>&1
+ analyze_rf_frequency_environment >/dev/null 2>&1
+
+ # Check if system is already performing excellently
+ if [ "$WIFI_FUNCTIONAL" = "true" ] && [ "$CURRENT_BAND" = "6 GHz (WiFi 6E/7)" ] && [ -n "$CURRENT_BITRATE" ]; then
+ BITRATE_NUM=$(echo "$CURRENT_BITRATE" | grep -o "[0-9]*" | head -1)
+ if [ -n "$BITRATE_NUM" ] && [ "$BITRATE_NUM" -gt 1000 ]; then
+ echo -e "${GREEN}✅ === EXCELLENT PERFORMANCE DETECTED ===${NC}"
+ echo ""
+ echo "🚀 Your system shows outstanding performance metrics:"
+ echo " • Connected to 6GHz clean spectrum"
+ echo " • Speed: ${CURRENT_BITRATE:-Unknown} Mbps"
+ echo " • Signal: ${CURRENT_SIGNAL:-Unknown} dBm"
+ echo " • Channel width: ${CHANNEL_WIDTH:-Unknown} MHz"
+ echo ""
+ echo "❓ Are you experiencing actual speed/performance issues? [y/N]"
+ read -r experiencing_performance_issues
+
+ if [[ ! "$experiencing_performance_issues" =~ ^[Yy]$ ]]; then
+ echo ""
+ echo "💡 Your WiFi is performing excellently. For monitoring:"
+ echo " • Speed test: speedtest-cli"
+ echo " • Real-time stats: watch -n 2 'iw dev $IFACE link'"
+ echo " • Advanced metrics: iperf3 -c your-server-ip"
+ echo ""
+ echo "🎯 Possible optimizations for your excellent setup:"
+ echo " • Router upgrade to WiFi 7 for 320MHz channels"
+ echo " • MLO (Multi-Link Operation) if router supports it"
+ echo ""
+ return 0
+ fi
+ echo ""
+ echo "🔍 Proceeding with performance optimization since you're experiencing issues..."
+ fi
+ fi
+
+ echo -e "${CYAN}🛠️ === MODERN PERFORMANCE OPTIMIZATION ===${NC}"
+ echo ""
+
+ # DFS impact on performance
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo -e "${MAGENTA}📡 DFS PERFORMANCE IMPACT DETECTED${NC}"
+ echo ""
+ echo " Current DFS connection may cause:"
+ echo " • Sudden speed drops during radar detection"
+ echo " • 30+ second interruptions for channel switching"
+ echo " • Inconsistent throughput patterns"
+ echo ""
+ echo " 🎯 Fix: Switch to non-DFS channel for consistent performance"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " ⏰ IMMEDIATE: sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ fi
+ echo ""
+ fi
+
+ echo "1. Modern band optimization:"
+ if [ "$CURRENT_BAND" = "2.4 GHz" ]; then
+ echo " ⏰ TEMPORARY (test only): Force 5GHz connection"
+ echo " 🔒 PERMANENT: sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band a"
+ echo " 💡 Better: Upgrade to WiFi 6E/7 router for 6GHz access"
+ elif [ "$CURRENT_BAND" = "5 GHz" ]; then
+ echo " 📊 You're on 5GHz - optimization options:"
+ echo " 💡 Consider WiFi 6E/7 router upgrade for 6GHz clean spectrum"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " 💡 Your hardware supports WiFi 7 - router upgrade recommended"
+ fi
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo " ⚠️ Current DFS channel may impact performance consistency"
+ fi
+ elif [ "$CURRENT_BAND" = "6 GHz (WiFi 6E/7)" ]; then
+ echo " ✅ Excellent! You're on 6GHz clean spectrum"
+ echo " 🌟 6GHz advantage: No DFS = consistent performance"
+ echo " 💡 Optimize channel width and MLO if available"
+ fi
+ echo ""
+
+ echo "2. Modern channel width optimization:"
+ if [ -n "$CHANNEL_WIDTH" ]; then
+ echo " 📊 Current: $CHANNEL_WIDTH MHz"
+ if [ "$CHANNEL_WIDTH" -lt 80 ]; then
+ echo " 🔒 PERMANENT (router config): Increase to 80MHz or 160MHz in router settings"
+ elif [ "$CHANNEL_WIDTH" -eq 160 ] && [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " 🔒 PERMANENT (router upgrade): Consider 320MHz if router supports WiFi 7"
+ fi
+ fi
+ echo ""
+
+ echo "3. MLO optimization (WiFi 7):"
+ if [ "$SUPPORTS_MLO" = true ]; then
+ echo " 🔒 PERMANENT (router config): Enable MLO in router settings for multi-band aggregation"
+ echo " ⚠️ REQUIRES: WiFi 7 router with MLO support"
+ else
+ echo " 💡 MLO not supported - consider WiFi 7 hardware upgrade"
+ fi
+ echo ""
+ ;;
+ 3)
+ echo ""
+ echo "🔍 Analyzing modern connection failures..."
+
+ # Re-run analysis to get current status
+ gather_system_intelligence >/dev/null 2>&1
+
+ # Check if WiFi is actually connected and working
+if [ "$WIFI_FUNCTIONAL" = "true" ]; then
+ # Try to get current SSID if it's missing
+ if [ -z "$CURRENT_SSID" ]; then
+ CURRENT_SSID=$(nmcli -t -f active,ssid dev wifi | grep '^yes' | cut -d: -f2 2>/dev/null)
+ if [ -z "$CURRENT_SSID" ]; then
+ CURRENT_SSID=$(iw dev "$IFACE" link 2>/dev/null | grep "SSID:" | awk '{print $2}')
+ fi
+ fi
+
+ echo -e "${GREEN}✅ === WIFI CURRENTLY CONNECTED ===${NC}"
+ echo ""
+ echo "🎉 Your WiFi appears to be working:"
+ echo " • Connected to: ${CURRENT_SSID:-"(Connected but SSID detection failed)"}"
+ echo " • Band: ${CURRENT_BAND:-Unknown}"
+ echo " • Speed: ${CURRENT_BITRATE:-Unknown} Mbps"
+ echo " • Signal: ${CURRENT_SIGNAL:-Unknown} dBm"
+ echo ""
+ echo "❓ Are you experiencing actual connection failures? [y/N]"
+ read -r experiencing_connection_issues
+
+ if [[ ! "$experiencing_connection_issues" =~ ^[Yy]$ ]]; then
+ echo ""
+ echo "💡 Your WiFi is connected and working. For troubleshooting:"
+ echo " • Check other devices: Test if issue affects other devices"
+ echo " • Monitor connection: watch -n 2 'iw dev $IFACE link'"
+ echo " • Test specific sites: curl -I google.com"
+ echo ""
+ return 0
+ fi
+ echo ""
+ echo "🔍 Proceeding with connection troubleshooting since you're experiencing issues..."
+fi
+
+ echo -e "${CYAN}🛠️ === MODERN CONNECTION FIXES ===${NC}"
+ echo ""
+ echo "🐧 System: $DISTRO_NAME"
+ echo ""
+
+ echo "1. Emergency connection reset:"
+ echo " ⏰ IMMEDIATE: sudo systemctl restart NetworkManager"
+ echo " ⏰ IMMEDIATE: sudo modprobe -r $DRIVER && sudo modprobe $DRIVER"
+ echo ""
+
+ provide_distribution_specific_workarounds "DRIVER_ERROR"
+ ;;
+ 4)
+ echo ""
+ echo "🔍 Analyzing modern VPN conflicts..."
+ detect_vpn_configuration >/dev/null 2>&1
+
+ echo -e "${CYAN}🛠️ === MODERN VPN OPTIMIZATION ===${NC}"
+ echo ""
+
+ if echo "$VPN_TYPE" | grep -qi "tailscale"; then
+ echo "🔗 Tailscale (WireGuard mesh) optimization:"
+ echo ""
+ echo "1. Enable split tunneling:"
+ echo " 🔒 PERMANENT: tailscale up --accept-routes=false"
+ echo ""
+ echo "2. Optimize MTU for modern networks:"
+ echo " ⏰ TEMPORARY: sudo ip link set tailscale0 mtu 1200"
+ echo ""
+ echo "3. Use modern exit nodes efficiently:"
+ echo " 🔒 PERMANENT: tailscale up --exit-node=COUNTRY-CODE"
+ echo ""
+ elif echo "$VPN_TYPE" | grep -qi "zerotier"; then
+ echo "🌐 ZeroTier (SD-WAN) optimization:"
+ echo ""
+ echo "1. Check controller settings for route conflicts"
+ echo "2. ⏰ TEMPORARY: sudo ip link set zt+ mtu 1200"
+ echo "3. 🔒 PERMANENT: Use managed routes instead of full routing"
+ echo ""
+ else
+ echo "🔒 Generic modern VPN optimization:"
+ echo ""
+ echo "1. MTU optimization:"
+ echo " ⏰ TEMPORARY: sudo ip link set $VPN_INTERFACE mtu 1200"
+ echo ""
+ echo "2. 🔒 PERMANENT: Split tunneling configuration"
+ echo "3. 🔒 PERMANENT: Modern DNS configuration"
+ fi
+ ;;
+ 5)
+ echo ""
+ echo "🔍 Analyzing thermal/power issues..."
+ # Get current system info
+ gather_system_intelligence >/dev/null 2>&1
+
+ echo -e "${CYAN}🛠️ === ADVANCED THERMAL MANAGEMENT ===${NC}"
+ echo ""
+
+ echo "🌡️ IMMEDIATE THERMAL FIXES:"
+ echo ""
+ echo "1. WiFi power management optimization:"
+ echo " ⏰ IMMEDIATE: sudo iw dev $IFACE set power_save off"
+ echo " 🔒 PERMANENT: echo 'options $DRIVER power_save=0' | sudo tee /etc/modprobe.d/$DRIVER.conf"
+ echo " ⚠️ REQUIRES REBOOT after permanent config"
+ echo ""
+
+ echo "2. ASPM (Advanced State Power Management) fixes:"
+ if echo "$CHIP_MODEL" | grep -qi "mt79"; then
+ echo " MediaTek-specific thermal optimization:"
+ echo " 🔒 PERMANENT: echo 'options $DRIVER disable_aspm=1' | sudo tee -a /etc/modprobe.d/$DRIVER.conf"
+ echo " 🔒 PERMANENT: $(get_distro_command "kernel_param")'pcie_aspm=off'"
+ echo " ⚠️ REQUIRES REBOOT and GRUB update"
+ else
+ echo " Generic ASPM optimization:"
+ echo " 🔒 PERMANENT: $(get_distro_command "kernel_param")'pcie_aspm=off'"
+ echo " ⚠️ REQUIRES REBOOT"
+ fi
+ echo ""
+
+ echo "3. CPU governor optimization for thermal control:"
+echo " ⏰ IMMEDIATE: for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo powersave | sudo tee \"\$cpu\"; done"
+echo " 🔒 PERMANENT: Use built-in system power management"
+
+# Distribution-specific thermal management
+case "$DISTRO_ID" in
+ "fedora"|"rhel"|"centos"|"rocky"|"almalinux")
+ echo " Fedora/RHEL: sudo tuned-adm profile powersave"
+ echo " Alternative: sudo systemctl enable power-profiles-daemon"
+ ;;
+ "ubuntu"|"debian"|"pop"|"mint"|"linuxmint")
+ echo " Ubuntu/Debian: sudo systemctl enable power-profiles-daemon"
+ echo " Alternative: sudo cpupower frequency-set -g powersave"
+ ;;
+ "arch"|"manjaro"|"endeavouros")
+ echo " Arch: sudo systemctl enable power-profiles-daemon"
+ echo " Alternative: echo 'powersave' | sudo tee /etc/default/cpufrequtils"
+ ;;
+ *)
+ echo " Generic: Use your distribution's built-in power management"
+ ;;
+esac
+echo ""
+
+ echo "4. Modern WiFi 7 thermal considerations:"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " WiFi 7 hardware detected - higher power consumption expected"
+ echo " 🔧 Monitor temperatures: watch -n 2 'sensors | grep -E \"Core|Package|wifi\"'"
+ echo " 🔧 Check if 6GHz radio can be disabled when not needed"
+ echo " 💡 Some WiFi 7 cards run significantly hotter than WiFi 6"
+ fi
+ echo ""
+
+ echo "5. Physical thermal optimization:"
+ echo " 🔧 Laptop: Ensure proper ventilation, clean fans/vents"
+ echo " 🔧 Desktop: Verify case airflow, WiFi card positioning"
+ echo " 🔧 M.2 cards: Consider thermal pads or heatsinks for hot-running cards"
+ echo ""
+
+ echo "6. Firmware thermal optimization:"
+ echo " 🔒 PERMANENT: $(get_distro_command "firmware_update")"
+ echo " 💡 Newer firmware often includes thermal improvements"
+ echo " ⚠️ REQUIRES REBOOT after firmware update"
+ echo ""
+
+ echo "🧪 THERMAL MONITORING COMMANDS:"
+ echo " 📊 CPU temps: watch -n 2 'sensors | grep Core'"
+ echo " 📊 All temps: sudo watch -n 2 'sensors'"
+ echo " 📊 WiFi power: watch -n 5 'iw dev $IFACE info | grep txpower'"
+ echo " 📊 System load: watch -n 2 'uptime && cat /proc/loadavg'"
+ ;;
+ 6)
+ echo ""
+ echo "🔍 Analyzing modern suspend/resume issues..."
+ # Re-run analysis to get current system info
+ gather_system_intelligence >/dev/null 2>&1
+ analyze_modern_chipsets >/dev/null 2>&1
+
+ echo -e "${CYAN}🛠️ === MODERN SUSPEND/RESUME FIXES ===${NC}"
+echo ""
+
+echo "1. Complete systemd sleep script (recommended):"
+echo " 🔒 PERMANENT: sudo tee /etc/systemd/system-sleep/wifi-resume.sh << 'EOF'"
+echo "#!/bin/bash"
+echo "if [ \"\$1\" = \"post\" ]; then"
+echo " modprobe -r $DRIVER"
+echo " sleep 2"
+echo " modprobe $DRIVER"
+if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " # WiFi 7: Allow 6GHz initialization"
+ echo " sleep 3"
+fi
+echo " # Reset power management and restart NetworkManager"
+echo " sleep 1"
+echo " iw dev $IFACE set power_save off 2>/dev/null || true"
+echo " systemctl restart NetworkManager"
+echo "fi"
+echo "EOF"
+echo " sudo chmod +x /etc/systemd/system-sleep/wifi-resume.sh"
+echo ""
+
+echo "2. Lightweight alternative (NetworkManager only):"
+echo " 🔒 PERMANENT: sudo tee /etc/systemd/system-sleep/nm-restart.sh << 'EOF'"
+echo "#!/bin/bash"
+echo "[ \"\$1\" = \"post\" ] && systemctl restart NetworkManager"
+echo "EOF"
+echo " sudo chmod +x /etc/systemd/system-sleep/nm-restart.sh"
+echo ""
+
+echo "3. Test suspend/resume fix:"
+echo " 🧪 TEST: sudo systemctl suspend"
+echo " 🧪 VERIFY: After resume - iw dev $IFACE link"
+echo ""
+;;
+ 7)
+ echo ""
+ echo "🔍 Analyzing modern signal optimization..."
+ # Re-run analysis to get current system info
+ gather_system_intelligence >/dev/null 2>&1
+ analyze_rf_frequency_environment >/dev/null 2>&1
+
+ echo -e "${CYAN}🛠️ === ADVANCED SIGNAL OPTIMIZATION ===${NC}"
+ echo ""
+
+ echo "📊 Current Signal Analysis:"
+ echo " Signal Strength: ${CURRENT_SIGNAL:-Unknown} dBm"
+ echo " Current Band: ${CURRENT_BAND:-Unknown}"
+ echo " Current Frequency: ${CURRENT_FREQ:-Unknown} MHz"
+ echo ""
+
+ echo "🚀 PROVEN SIGNAL IMPROVEMENT TECHNIQUES:"
+ echo ""
+
+ echo "1. Band optimization for maximum range:"
+ echo " 💡 2.4GHz: Better penetration through walls, longer range"
+ echo " 💡 5GHz: Less congested, shorter range but higher speeds"
+ echo " 💡 6GHz: Cleanest spectrum, shortest range, highest speeds"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo ""
+ echo " Switch commands for your network:"
+ echo " ⏰ IMMEDIATE (2.4GHz): sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo " ⏰ IMMEDIATE (5GHz): sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band a"
+ echo " 💡 Test each band to find optimal signal/speed balance"
+ fi
+ echo ""
+
+ echo "2. Antenna diversity and positioning:"
+ echo " 🔧 Laptop: Adjust screen angle (affects internal antenna orientation)"
+ echo " 🔧 Desktop: Ensure M.2 WiFi card antennas are properly connected"
+ echo " 🔧 USB adapters: Try different USB ports, use extension cable"
+ echo " 🔧 External antennas: Position perpendicular to router antennas"
+ echo ""
+
+ echo "3. Modern regulatory domain optimization:"
+ echo " 📡 Current domain: $(iw reg get | grep country || echo 'Not set')"
+ echo " 🔧 Optimize for your location:"
+ echo " ⏰ IMMEDIATE: sudo iw reg set US # (or your country code)"
+ echo " 💡 Proper regulatory domain can increase allowed TX power"
+ echo ""
+
+ echo "4. Channel width optimization for range vs speed:"
+ if [ -n "$CHANNEL_WIDTH" ]; then
+ echo " 📊 Current width: $CHANNEL_WIDTH MHz"
+ if [ "$CHANNEL_WIDTH" -ge 80 ]; then
+ echo " 💡 Wide channels give speed but reduce range"
+ echo " 🔧 For better range: Force router to 40MHz or 80MHz"
+ fi
+ fi
+ echo " 💡 Wider channels = faster speeds but shorter range"
+ echo " 💡 Narrower channels = longer range but slower speeds"
+ echo ""
+
+ # Chipset-specific TX power advice
+ if echo "$CHIP_MODEL" | grep -qi "mt79"; then
+ echo " 📝 MediaTek note: TX power commands often fail or are ignored"
+ echo " 💡 Router-side power increase usually more effective"
+ elif echo "$CHIP_MODEL" | grep -qi "intel"; then
+ echo " 📝 Intel note: Regulatory restrictions often limit manual TX power"
+ echo " 💡 Ensure proper regulatory domain is set first"
+ fi
+ echo ""
+
+ echo "6. Physical optimization techniques:"
+ echo " 🏠 Router placement: Central location, elevated position"
+ echo " 🏠 Reduce obstacles: Minimize walls, large objects between devices"
+ echo " 🏠 Interference reduction: Keep away from microwaves, baby monitors"
+ echo " 📱 Client positioning: Higher floors often get better signal"
+ echo ""
+
+ echo "7. WiFi 6E/7 specific optimizations:"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " ✅ Your hardware supports modern WiFi features"
+ echo " 🌟 6GHz band: Clean spectrum but limited range"
+ echo " 🔧 Use 6GHz for close-range, high-speed connections"
+ echo " 🔧 Use 5GHz for medium-range connections"
+ echo " 🔧 Use 2.4GHz for maximum range connections"
+
+ if [ "$SUPPORTS_MLO" = true ]; then
+ echo " 🚀 MLO capable: Can use multiple bands simultaneously"
+ echo " 💡 Requires MLO-capable router for maximum benefit"
+ fi
+ else
+ echo " 💡 Current hardware: WiFi 6 or older"
+ echo " 💡 Consider WiFi 6E/7 upgrade for access to 6GHz clean spectrum"
+ fi
+ echo ""
+
+ echo "8. Real-time signal monitoring:"
+ echo " 📊 Watch signal: watch -n 1 'iw dev $IFACE link | grep signal'"
+ echo " 📊 Site survey: sudo iw dev $IFACE scan | grep -E 'SSID|signal|freq'"
+ echo " 📊 Speed test: speedtest-cli (install if needed)"
+ echo ""
+
+ echo "🎯 TESTING PROTOCOL:"
+ echo "1. Baseline test: speedtest-cli && iw dev $IFACE link | grep signal"
+ echo "2. Try 2.4GHz: sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo "3. Test again: speedtest-cli && iw dev $IFACE link | grep signal"
+ echo "4. Try 5GHz: sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band a"
+ echo "5. Test again: speedtest-cli && iw dev $IFACE link | grep signal"
+ echo "6. Use band with best signal/speed ratio for your location"
+ ;;
+ 8)
+ echo ""
+ echo "🔍 Analyzing DFS radar interference patterns..."
+ gather_system_intelligence >/dev/null 2>&1
+ analyze_dfs_channels >/dev/null 2>&1
+
+ echo -e "${MAGENTA}🛠️ === DFS RADAR INTERFERENCE FIXES ===${NC}"
+ echo ""
+
+ echo "📡 DFS Analysis Results:"
+ echo " Current DFS connection: $([ "$DFS_CURRENT_CONNECTION" = true ] && echo "Yes (HIGH RISK)" || echo "No")"
+ echo " DFS networks in area: $DFS_COUNT"
+ echo " Recent radar events: $DFS_RADAR_EVENTS"
+ echo " DFS risk score: $DFS_IMPACT_SCORE/100"
+ echo ""
+
+ echo "🎯 IMMEDIATE DFS FIXES:"
+ echo ""
+ echo "1. Emergency non-DFS channel switch:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " ⏰ IMMEDIATE: sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo " ⏰ IMMEDIATE: sudo nmcli connection up \"$CURRENT_SSID\""
+ echo " 💡 Forces 2.4GHz connection - no DFS channels in 2.4GHz band"
+ fi
+ echo ""
+ echo "2. Router configuration (CRITICAL - prevents future issues):"
+ echo " 🔒 PERMANENT: Primary 5GHz channel → 36, 40, 44, or 48 (low band, no DFS)"
+ echo " 🔒 PERMANENT: Alternative channels → 149, 153, 157, 161, 165 (high band, no DFS)"
+ echo " 🔒 PERMANENT: Disable automatic channel selection"
+ echo " 🔒 PERMANENT: Disable DFS channels entirely (if router supports this option)"
+ echo ""
+ echo "3. Verification and testing:"
+ echo " 🧪 Test connectivity: ping -c 30 8.8.8.8"
+ echo " 💡 No timeouts should occur (previously had 30+ second gaps)"
+ echo " 🧪 Verify channel: iw dev $IFACE link | grep freq"
+ echo " 💡 Frequency should NOT be 5260-5320 MHz or 5500-5700 MHz (DFS ranges)"
+ echo ""
+ echo "4. Long-term DFS-free solutions:"
+ echo ""
+ echo " Option A - 6GHz Migration (BEST):"
+ if [ "$SUPPORTS_WIFI7" = true ]; then
+ echo " ✅ Your hardware supports 6GHz"
+ echo " 🔒 PERMANENT: Upgrade to WiFi 6E/7 router"
+ echo " 🌟 6GHz band: ALL channels are DFS-free (no radar interference possible)"
+ echo " 🚀 Additional benefits: Clean spectrum, higher throughput, lower latency"
+ else
+ echo " 💡 Hardware upgrade to WiFi 6E/7 required"
+ echo " 💡 6GHz band: ALL channels are DFS-free"
+ echo " 💡 Recommended cards: MT7925, Intel BE200, Qualcomm WCN7850"
+ fi
+ echo ""
+ echo " Option B - Strategic 5GHz Channel Planning:"
+ echo " 🔒 PERMANENT: Use ONLY these channels: 36, 40, 44, 48, 149, 153, 157, 161, 165"
+ echo " 💡 These channels never require DFS and are immune to radar"
+ echo " 💡 Avoid channels 52-64 and 100-144 (all DFS channels)"
+ echo ""
+
+ # Provide DFS-specific monitoring commands
+ echo "🔍 DFS Monitoring Commands:"
+ echo ""
+ echo "Real-time radar event monitoring:"
+ echo " 📋 journalctl -f | grep -iE 'radar|dfs|cac'"
+ echo ""
+ echo "Channel stability monitoring:"
+ echo " 📋 watch -n 2 'iw dev $IFACE link | grep freq'"
+ echo ""
+ echo "Connection stability test:"
+ echo " 📋 ping -i 1 8.8.8.8 | ts"
+ echo " 💡 Look for gaps > 30 seconds (indicates radar detection)"
+ echo ""
+ ;;
+ 9)
+ echo ""
+ echo "🆘 Emergency fixes - Choose your emergency type:"
+ echo ""
+ echo "1) Complete WiFi failure (not connecting at all)"
+ echo "2) Frequent disconnections (working but unstable)"
+ echo "3) Very slow speeds (connected but poor performance)"
+ echo "4) Overheating system (fans spinning, hot)"
+ echo ""
+ echo -n "Emergency type [1-4]: "
+ read -r emergency_type
+
+ case $emergency_type in
+ 1)
+ echo ""
+ echo "🚨 EMERGENCY: Complete WiFi failure fixes"
+ echo ""
+ echo "Try these in order, test after each:"
+ echo ""
+ echo "1. Restart network services:"
+ echo " sudo systemctl restart NetworkManager"
+ echo " sudo systemctl restart wpa_supplicant"
+ echo ""
+ echo "2. Reset WiFi driver:"
+ echo " sudo modprobe -r $DRIVER && sleep 5 && sudo modprobe $DRIVER"
+ echo ""
+ echo "3. Turn WiFi off and on:"
+ echo " sudo nmcli radio wifi off && sleep 5 && sudo nmcli radio wifi on"
+ echo ""
+ echo "4. Reset regulatory domain:"
+ echo " sudo iw reg set US # (or your country code)"
+ echo ""
+ echo "5. If nothing works - reboot:"
+ echo " sudo reboot"
+ ;;
+ 2)
+ echo ""
+ echo "🚨 EMERGENCY: Disconnection fixes"
+ echo ""
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo "🎯 DFS detected - likely cause of disconnections!"
+ echo ""
+ echo "Emergency DFS fix:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo " sudo nmcli connection up \"$CURRENT_SSID\""
+ fi
+ else
+ echo "Emergency stability fixes:"
+ echo ""
+ echo "1. Disable power saving:"
+ echo " sudo iw dev $IFACE set power_save off"
+ echo ""
+ echo "2. Force 2.4GHz (most stable):"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ fi
+ fi
+ ;;
+ 3)
+ echo ""
+ echo "🚨 EMERGENCY: Speed improvement fixes"
+ echo ""
+ echo "1. Switch to 5GHz:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band a"
+ fi
+ echo ""
+ echo "2. Disable power saving:"
+ echo " sudo iw dev $IFACE set power_save off"
+ echo ""
+ ;;
+ 4)
+ echo ""
+ echo "🚨 EMERGENCY: Overheating fixes"
+ echo ""
+ echo "1. Enable power saving:"
+ echo " sudo iw dev $IFACE set power_save on"
+ echo ""
+ echo "2. Set CPU governor to powersave:"
+ echo " echo powersave | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor"
+ echo ""
+ echo "3. Force 2.4GHz (lower power):"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ fi
+ ;;
+ esac
+ ;;
+ 10)
+ echo ""
+ echo "🔍 Running comprehensive diagnostic first..."
+ complete_wifi_analysis
+ echo ""
+ echo "Based on diagnostic results, what specific issue do you see?"
+ echo "Re-run this option (3) and select specific issue number."
+ ;;
+ *)
+ echo ""
+ echo "❌ Invalid option selected"
+ echo "💡 Please select a number between 1-10"
+ ;;
+ esac
+
+ echo ""
+ echo -e "${GREEN}💡 TIP: Save these modern commands for future use!${NC}"
+ echo "📋 All solutions are tailored for your $DISTRO_NAME system"
+ if [ "$DFS_IMPACT_SCORE" -gt 25 ]; then
+ echo "📡 DFS considerations included for radar-free operation"
+ fi
+ echo ""
+}
+
+# DFS-specific recommendations
+provide_dfs_recommendations() {
+ echo -e "${CYAN}💡 === DFS OPTIMIZATION RECOMMENDATIONS ===${NC}"
+ echo ""
+
+ # Sanitize variables at start of function
+ DFS_COUNT=$(sanitize_number "$DFS_COUNT" "0")
+ DFS_RADAR_EVENTS=$(sanitize_number "$DFS_RADAR_EVENTS" "0")
+
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo "🎯 IMMEDIATE ACTIONS (Currently on DFS channel):"
+ echo ""
+ echo "1. Switch to non-DFS channel:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " 🔧 Force 2.4GHz (no DFS): sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo " 🔧 Force low 5GHz (36-48): Configure router to use channels 36, 40, 44, 48"
+ echo " 🔧 Force high 5GHz (149+): Configure router to use channels 149, 153, 157, 161, 165"
+ fi
+ echo ""
+ echo "2. Router configuration (PRIORITY FIX):"
+ echo " 🔧 Set primary 5GHz channel to: 36, 40, 44, 48 (low band, no DFS)"
+ echo " 🔧 Alternative channels: 149, 153, 157, 161, 165 (high band, no DFS)"
+ echo " 🔧 Disable automatic channel selection if using DFS channels"
+ echo " 🔧 Disable DFS channels entirely in router settings (if available)"
+ echo ""
+ fi
+
+ if [ "$DFS_COUNT" -gt 5 ] || [ "$DFS_RADAR_EVENTS" -gt 0 ]; then
+ echo "🎯 ENVIRONMENT OPTIMIZATION (High DFS area):"
+ echo ""
+ echo "1. Preferred channel strategy:"
+ echo " 📡 2.4GHz: Channels 1, 6, 11 (no DFS, good for basic connectivity)"
+ echo " 📡 5GHz Low: Channels 36, 40, 44, 48 (no DFS, universally available)"
+ echo " 📡 5GHz High: Channels 149, 153, 157, 161, 165 (no DFS, less congested)"
+ echo ""
+ echo "2. Modern WiFi 6E/7 optimization:"
+ if [ "$SUPPORTS_WIFI7" = true ] || echo "$CHIP_MODEL" | grep -qi "6E\|mt7922\|ax210\|ax211"; then
+ echo " 🌟 6GHz migration: NO DFS in 6GHz band!"
+ echo " 🔧 Router upgrade: WiFi 6E/7 with 6GHz eliminates DFS issues"
+ echo " 💡 6GHz channels 1-233 are all non-DFS"
+ else
+ echo " 💡 Consider WiFi 6E/7 hardware upgrade for DFS-free 6GHz"
+ fi
+ echo ""
+ fi
+
+ echo "🎯 MONITORING & PREVENTION:"
+ echo ""
+ echo "1. DFS event monitoring:"
+ echo " 🔍 Monitor logs: journalctl -f | grep -i 'radar\\|dfs\\|cac'"
+ echo " 🔍 Check current channel: watch -n 5 'iw dev $IFACE link | grep freq'"
+ echo ""
+ echo "2. Router firmware updates:"
+ echo " 🔧 Update router firmware (better DFS handling)"
+ echo " 🔧 Check for radar detection sensitivity settings"
+ echo ""
+ echo "3. Connection profile optimization:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo " 🔧 Create separate 2.4GHz profile: nmcli connection clone \"$CURRENT_SSID\" \"${CURRENT_SSID}_24G\""
+ echo " 🔧 Configure 2.4GHz-only: nmcli connection modify \"${CURRENT_SSID}_24G\" 802-11-wireless.band bg"
+ fi
+ echo ""
+
+ # Advanced DFS recommendations
+ echo "🎯 ADVANCED DFS MITIGATION:"
+ echo ""
+ echo "1. Router-side DFS configuration:"
+ echo " 🔧 Disable band steering (prevents automatic DFS channel selection)"
+ echo " 🔧 Set static channels: Use channel planning tool to avoid DFS"
+ echo " 🔧 Regional optimization: Verify router region matches your location"
+ echo ""
+ echo "2. Professional environments:"
+ echo " 🔧 Site survey: Use WiFi analyzer to map DFS usage patterns"
+ echo " 🔧 Channel planning: Coordinate with neighboring networks"
+ echo " 🔧 Enterprise APs: Use DFS-aware management systems"
+ echo ""
+
+ if [ "$DFS_RADAR_EVENTS" -gt 0 ]; then
+ echo "🚨 RADAR ENVIRONMENT SPECIFIC:"
+ echo ""
+ echo "1. Identify radar sources:"
+ echo " 📡 Weather radar (permanent, predictable patterns)"
+ echo " 📡 Military radar (temporary, high impact)"
+ echo " 📡 Aviation radar (permanent near airports)"
+ echo ""
+ echo "2. Mitigation strategies:"
+ echo " 🔧 Relocate equipment away from radar sources"
+ echo " 🔧 Use directional antennas to reduce radar reception"
+ echo " 🔧 Implement automatic channel switching (smart routers)"
+ echo ""
+ fi
+}
+
+# Dedicated DFS Channel Monitor - COMPLETE
+dfs_channel_monitor() {
+ echo -e "${BOLD}${MAGENTA}📡 === DFS CHANNEL MONITOR ===${NC}"
+ echo "🔍 Dedicated Dynamic Frequency Selection and radar interference analysis"
+ echo ""
+
+ # Quick system check first
+ gather_system_intelligence >/dev/null 2>&1
+
+ # Run comprehensive DFS analysis
+ analyze_dfs_channels
+
+ echo ""
+ provide_dfs_recommendations
+
+ echo ""
+ echo -e "${CYAN}🔍 === DFS MONITORING COMMANDS ===${NC}"
+ echo ""
+ echo "Real-time DFS monitoring commands you can run:"
+ echo ""
+ echo "1. Monitor for radar events:"
+ echo " 📋 REALTIME: journalctl -f | grep -iE 'radar|dfs|cac'"
+ echo ""
+ echo "2. Watch channel changes:"
+ echo " 📋 REALTIME: watch -n 2 'iw dev $IFACE link | grep freq'"
+ echo ""
+ echo "3. Scan for DFS channels in area:"
+ echo " 📋 PERIODIC: iw dev $IFACE scan | grep -E 'freq: 5[2-6][0-9][0-9]|freq: 51[0-9][0-9]|SSID'"
+ echo ""
+ echo "4. Check regulatory domain:"
+ echo " 📋 STATUS: iw reg get"
+ echo ""
+ echo "5. Monitor connection stability:"
+ echo " 📋 CONTINUOUS: ping -i 1 8.8.8.8 | while read pong; do echo \"\$(date): \$pong\"; done"
+ echo ""
+
+ if [ "$DFS_CURRENT_CONNECTION" = true ]; then
+ echo -e "${RED}🚨 IMMEDIATE ACTION ITEMS:${NC}"
+ echo ""
+ echo "Your current connection uses a DFS channel. To eliminate radar-related"
+ echo "disconnections, implement these fixes immediately:"
+ echo ""
+ echo "Router-side fixes (RECOMMENDED):"
+ echo "• Change router channel to: 36, 40, 44, 48 (low 5GHz, no DFS)"
+ echo "• Alternative channels: 149, 153, 157, 161, 165 (high 5GHz, no DFS)"
+ echo "• Disable automatic channel selection"
+ echo "• Disable DFS channels entirely if router supports it"
+ echo ""
+ echo "Client-side temporary fix:"
+ if [ -n "$CURRENT_SSID" ]; then
+ echo "sudo nmcli connection modify \"$CURRENT_SSID\" 802-11-wireless.band bg"
+ echo "sudo nmcli connection up \"$CURRENT_SSID\""
+ echo "(Forces 2.4GHz connection - no DFS channels exist in 2.4GHz)"
+ fi
+ echo ""
+ fi
+
+ echo -e "${GREEN}💡 DFS-Free Future: WiFi 6E/7 with 6GHz${NC}"
+ echo ""
+ echo "The 6GHz band (WiFi 6E/7) contains NO DFS channels:"
+ echo "• All 6GHz channels (1-233) are DFS-free"
+ echo "• No radar interference possible"
+ echo "• Clean spectrum with minimal congestion"
+ echo "• Requires WiFi 6E/7 hardware and router"
+ echo ""
+
+ if [ "$SUPPORTS_WIFI7" = true ] || echo "$CHIP_MODEL" | grep -qi "6E"; then
+ echo "✅ Your hardware supports 6GHz!"
+ echo "💡 Upgrade to WiFi 6E/7 router for DFS-free operation"
+ else
+ echo "💡 Consider WiFi 6E/7 hardware upgrade for DFS-free future"
+ fi
+
+ echo ""
+ echo -e "${CYAN}🔧 === SMART CHANNEL SWITCHING ===${NC}"
+ echo ""
+ echo "Would you like to run the Smart Channel Switcher to avoid DFS issues? [y/N]"
+ read -r switcher_choice
+
+ if [[ "$switcher_choice" =~ ^[Yy]$ ]]; then
+ echo ""
+ smart_channel_switcher
+ else
+ echo ""
+ echo "💡 You can manually run channel switching commands:"
+ echo " • Emergency 2.4GHz: sudo nmcli connection modify \"NETWORK_NAME\" 802-11-wireless.band bg"
+ echo " • Safe 5GHz channels: 36, 40, 44, 48, 149, 153, 157, 161, 165"
+ echo " • Example: sudo nmcli connection modify \"NETWORK_NAME\" 802-11-wireless.channel 44"
+ fi
+}
+
+# Smart DFS Channel Switcher - COMPLETE
+smart_channel_switcher() {
+ echo -e "${BOLD}${GREEN}🔧 === SMART DFS CHANNEL SWITCHER ===${NC}"
+ echo "🎯 Intelligent channel switching to avoid DFS interference"
+ echo ""
+
+ # Get current active connection
+ ACTIVE_CONNECTION=$(nmcli -t connection show --active | grep -E "wifi|802-11-wireless" | head -1 | cut -d: -f1)
+
+ if [ -z "$ACTIVE_CONNECTION" ]; then
+ echo -e "${RED}❌ No active WiFi connection detected${NC}"
+ echo "💡 Connect to WiFi first, then run this tool"
+ return 1
+ fi
+
+ echo "📡 Current active connection: $ACTIVE_CONNECTION"
+
+ # Get current connection details
+ CURRENT_CHANNEL=$(nmcli connection show "$ACTIVE_CONNECTION" | grep "802-11-wireless.channel:" | awk '{print $2}')
+ CURRENT_BAND=$(nmcli connection show "$ACTIVE_CONNECTION" | grep "802-11-wireless.band:" | awk '{print $2}')
+ CURRENT_FREQ_LINK=$(iw dev "$IFACE" link 2>/dev/null | grep "freq:" | awk '{print $2}')
+
+ echo "📊 Current configuration:"
+ echo " Channel setting: ${CURRENT_CHANNEL:-auto}"
+ echo " Band setting: ${CURRENT_BAND:-auto}"
+ echo " Actual frequency: ${CURRENT_FREQ_LINK:-unknown} MHz"
+
+ # Analyze current DFS status
+ local current_dfs_risk="Unknown"
+ if [ -n "$CURRENT_FREQ_LINK" ]; then
+ FREQ_INT=$(printf "%.0f" "$CURRENT_FREQ_LINK" 2>/dev/null || echo "$CURRENT_FREQ_LINK" | cut -d'.' -f1)
+ CHANNEL_NUM=$(freq_to_channel "$FREQ_INT")
+
+ if is_dfs_channel "$CHANNEL_NUM"; then
+ current_dfs_risk="HIGH - Currently on DFS channel $CHANNEL_NUM"
+ else
+ current_dfs_risk="LOW - Currently on non-DFS channel $CHANNEL_NUM"
+ fi
+ fi
+
+ echo " DFS Risk: $current_dfs_risk"
+ echo ""
+
+ # Safe channel recommendations based on scan results
+ echo "🔍 Analyzing environment for optimal safe channels..."
+ echo ""
+
+ # Define safe channels with priorities
+ declare -A SAFE_CHANNELS
+ SAFE_CHANNELS[36]="5180"
+ SAFE_CHANNELS[40]="5200"
+ SAFE_CHANNELS[44]="5220"
+ SAFE_CHANNELS[48]="5240"
+ SAFE_CHANNELS[149]="5745"
+ SAFE_CHANNELS[153]="5765"
+ SAFE_CHANNELS[157]="5785"
+ SAFE_CHANNELS[161]="5805"
+ SAFE_CHANNELS[165]="5825"
+
+ # Analyze congestion on safe channels
+ echo "📊 Safe channel analysis:"
+ for channel in 36 40 44 48 149 153 157 161 165; do
+ freq=${SAFE_CHANNELS[$channel]}
+ # Count networks on this frequency
+ count=$(echo "$SCAN_RESULTS" | grep "freq: $freq" | wc -l 2>/dev/null || echo "0")
+
+ if [ "$count" -eq 0 ]; then
+ echo -e " ${GREEN}✅ Channel $channel ($freq MHz): CLEAR (0 networks)${NC}"
+ elif [ "$count" -le 2 ]; then
+ echo -e " ${YELLOW}📊 Channel $channel ($freq MHz): Light usage ($count networks)${NC}"
+ else
+ echo -e " ${RED}🚨 Channel $channel ($freq MHz): Congested ($count networks)${NC}"
+ fi
+ done
+
+ echo ""
+ echo -e "${CYAN}🎯 === CHANNEL SWITCHING OPTIONS ===${NC}"
+ echo ""
+
+ # Option 1: Emergency 2.4GHz fallback
+ echo "1) 🚨 EMERGENCY: Force 2.4GHz (guaranteed DFS-free)"
+ echo " Command: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.band bg"
+ echo " Effect: Immediate stability, reduced speed"
+ echo " Use when: Frequent disconnections, need immediate fix"
+ echo ""
+
+ # Option 2: Best available safe 5GHz channel
+ echo "2) 🎯 OPTIMAL: Switch to best available safe 5GHz channel"
+
+ # Find least congested safe channel
+ best_channel=""
+ min_count=999
+ for channel in 44 36 40 48 157 149 153 161 165; do # Prioritize 44 and 157
+ freq=${SAFE_CHANNELS[$channel]}
+ count=$(echo "$SCAN_RESULTS" | grep "freq: $freq" | wc -l 2>/dev/null || echo "0")
+ if [ "$count" -lt "$min_count" ]; then
+ min_count=$count
+ best_channel=$channel
+ fi
+ done
+
+ if [ -n "$best_channel" ]; then
+ echo " Recommended: Channel $best_channel (${SAFE_CHANNELS[$best_channel]} MHz) - $min_count networks detected"
+ echo " Commands:"
+ echo " sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel $best_channel"
+ echo " sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.band a"
+ echo " sudo nmcli connection up \"$ACTIVE_CONNECTION\""
+ fi
+ echo ""
+
+ # Option 3: Manual channel selection
+ echo "3) 🔧 MANUAL: Choose specific safe channel"
+ echo " Low 5GHz band (best for compatibility):"
+ echo " Channel 36: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 36"
+ echo " Channel 44: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 44"
+ echo " Channel 48: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 48"
+ echo ""
+ echo " High 5GHz band (often less congested):"
+ echo " Channel 149: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 149"
+ echo " Channel 157: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 157"
+ echo " Channel 161: sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel 161"
+ echo ""
+ echo " After setting channel: sudo nmcli connection up \"$ACTIVE_CONNECTION\""
+ echo ""
+
+ # Option 4: Reset to auto (remove manual settings)
+ echo "4) 🔄 RESET: Return to automatic channel selection"
+ echo " Commands:"
+ echo " sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.channel ''"
+ echo " sudo nmcli connection modify \"$ACTIVE_CONNECTION\" 802-11-wireless.band ''"
+ echo " sudo nmcli connection up \"$ACTIVE_CONNECTION\""
+ echo " Warning: May select DFS channels again in congested areas"
+ echo ""
+
+ # Interactive execution option
+ echo -e "${YELLOW}💡 Would you like to execute one of these options now? [y/N]${NC}"
+ read -r execute_choice
+
+ if [[ "$execute_choice" =~ ^[Yy]$ ]]; then
+ echo ""
+ echo "Select option to execute:"
+ echo "1) Emergency 2.4GHz"
+ echo "2) Optimal safe 5GHz (Channel $best_channel)"
+ echo "3) Manual channel (specify)"
+ echo "4) Reset to auto"
+ echo "5) Cancel"
+ echo ""
+ echo -n "Choice [1-5]: "
+ read -r exec_option
+
+ case $exec_option in
+ 1)
+ echo "🚨 Switching to 2.4GHz (DFS-free)..."
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band bg
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Switched to 2.4GHz band"
+ ;;
+ 2)
+ if [ -n "$best_channel" ]; then
+ echo "🎯 Switching to optimal channel $best_channel..."
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.channel "$best_channel"
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band a
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Switched to channel $best_channel (5GHz)"
+ else
+ echo "❌ Could not determine best channel"
+ fi
+ ;;
+ 3)
+ echo -n "Enter channel number (36,40,44,48,149,153,157,161,165): "
+ read -r manual_channel
+ if [[ "$manual_channel" =~ ^(36|40|44|48|149|153|157|161|165)$ ]]; then
+ echo "🔧 Switching to manual channel $manual_channel..."
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.channel "$manual_channel"
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band a
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Switched to channel $manual_channel"
+ else
+ echo "❌ Invalid channel. Must be: 36,40,44,48,149,153,157,161,165"
+ fi
+ ;;
+ 4)
+ echo "🔄 Resetting to automatic selection..."
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.channel ""
+ sudo nmcli connection modify "$ACTIVE_CONNECTION" 802-11-wireless.band ""
+ sudo nmcli connection up "$ACTIVE_CONNECTION"
+ echo "✅ Reset to automatic channel selection"
+ ;;
+ 5)
+ echo "Cancelled - no changes made"
+ ;;
+ *)
+ echo "❌ Invalid option"
+ ;;
+ esac
+
+ if [[ "$exec_option" =~ ^[1-4]$ ]]; then
+ echo ""
+ echo "🔍 Waiting 10 seconds for connection to stabilize..."
+ sleep 10
+ echo "📊 New connection status:"
+ iw dev "$IFACE" link 2>/dev/null | grep -E "Connected|freq|signal" || echo "Connection info not available"
+ fi
+ fi
+
+ echo ""
+ echo -e "${GREEN}💡 TIP: Save these commands for future use!${NC}"
+ echo "📋 You can run these commands anytime DFS issues occur"
+}
+
+# Main execution with modern error checking
+main() {
+ # Check for required modern tools
+ if ! command -v iw >/dev/null 2>&1; then
+ echo -e "${RED}Error: 'iw' command not found${NC}"
+ echo "Install with your package manager:"
+
+ detect_distribution
+ case "$DISTRO_ID" in
+ "fedora"|"rhel"|"centos"|"rocky"|"almalinux")
+ echo "sudo dnf install iw wireless-tools"
+ ;;
+ "ubuntu"|"debian"|"pop"|"mint")
+ echo "sudo apt install iw wireless-tools"
+ ;;
+ "arch"|"manjaro"|"endeavouros")
+ echo "sudo pacman -S iw wireless_tools"
+ ;;
+ *)
+ echo "Use your distribution's package manager to install 'iw'"
+ ;;
+ esac
+ exit 1
+ fi
+
+ # Check for modern kernel
+ KERNEL_VERSION=$(uname -r)
+ KERNEL_MAJOR=$(echo "$KERNEL_VERSION" | cut -d'.' -f1)
+ KERNEL_MINOR=$(echo "$KERNEL_VERSION" | cut -d'.' -f2)
+
+ if [ "$KERNEL_MAJOR" -lt 6 ]; then
+ echo -e "${YELLOW}⚠️ Warning: Kernel $KERNEL_VERSION detected${NC}"
+ echo "For best WiFi 7/6E support, consider kernel 6.8+ or newer"
+ echo ""
+ fi
+
+ while true; do
+ show_menu
+ read -r choice
+
+ case $choice in
+ 1)
+ complete_wifi_analysis | tee "wifi_analysis_2025_$(date +%Y%m%d_%H%M%S).log"
+ echo ""
+ echo "Press Enter to continue..."
+ read -r
+ ;;
+ 2)
+ error_analysis_troubleshooting | tee "wifi_errors_2025_$(date +%Y%m%d_%H%M%S).log"
+ echo ""
+ echo "Press Enter to continue..."
+ read -r
+ ;;
+ 3)
+ interactive_workaround_generator
+ echo ""
+ echo "Press Enter to continue..."
+ read -r
+ ;;
+ 4)
+ dfs_channel_monitor | tee "dfs_analysis_$(date +%Y%m%d_%H%M%S).log"
+ echo ""
+ echo "Press Enter to continue..."
+ read -r
+ ;;
+ 5)
+ tx_power_band_test | tee "tx_power_test_$(date +%Y%m%d_%H%M%S).log"
+ echo ""
+ echo "Press Enter to continue..."
+ read -r
+ ;;
+ 6)
+ manual_band_switching | tee "band_switching_$(date +%Y%m%d_%H%M%S).log"
+ echo ""
+ echo "Press Enter to continue..."
+ read -r
+ ;;
+ 7)
+ echo -e "${GREEN}🎯 WiFi Analysis complete! Modern networking with DFS monitoring awaits.${NC}"
+ echo "Thank you for using the Enhanced WiFi Analyzer with DFS support!"
+ exit 0
+ ;;
+ *)
+ echo -e "${RED}Invalid option${NC}"
+ sleep 1
+ ;;
+ esac
+ done
+}
+
+main "$@"
diff --git a/LICENSE b/LICENSE
index d159169..f288702 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,281 +1,622 @@
GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
+ Version 3, 29 June 2007
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
this License.
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
@@ -287,15 +628,15 @@ free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
+state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
- This program is free software; you can redistribute it and/or modify
+ This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
+ the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
@@ -303,37 +644,31 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- , 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/MeshAnalyzer/files/README b/MeshAnalyzer/files/README
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/MeshAnalyzer/files/README
@@ -0,0 +1 @@
+
diff --git a/MeshAnalyzer/files/mesh_analyzer.py b/MeshAnalyzer/files/mesh_analyzer.py
new file mode 100644
index 0000000..53b1e27
--- /dev/null
+++ b/MeshAnalyzer/files/mesh_analyzer.py
@@ -0,0 +1,2675 @@
+#!/usr/bin/env python3
+"""
+WiFi Mesh Network Analyzer - Analysis and Recommendations
+- Comprehensive radio environment analysis
+- Historical performance tracking
+- Mesh topology intelligence with Venn overlap analysis
+- Problem detection and recommendations
+- HTML report generation
+- Focus on accurate analysis without configuration complexity
+"""
+
+import subprocess
+import re
+import time
+import pickle
+import os
+import json
+import zipfile
+from datetime import datetime
+from collections import defaultdict, deque
+from dataclasses import dataclass, asdict
+from typing import Dict, List, Set, Optional
+import threading
+from pathlib import Path
+import logging
+
+# Import the new Venn calculator
+try:
+ from mesh_venn_calculator import MeshVennCalculator
+except ImportError:
+ # Fallback if file not found
+ class MeshVennCalculator:
+ def generate_venn_data(self, nodes_data):
+ return {'nodes': nodes_data, 'overlaps': [], 'total_coverage': 0}
+ def get_overlap_quality_assessment(self, venn_data):
+ return {'quality': 'unknown', 'score': 0, 'description': 'Venn calculator not available'}
+
+# Import the updated HTML reporter with roaming and power support
+try:
+ from mesh_html_reporter import MeshHTMLReporter
+ UPDATED_HTML_REPORTER_AVAILABLE = True
+except ImportError:
+ UPDATED_HTML_REPORTER_AVAILABLE = False
+ print("⚠️ Warning: mesh_html_reporter.py not found - using built-in reporter")
+
+# Import the roaming detector - Matt is testing some new functionality - adding two new modules for import.
+try:
+ from mesh_roaming_detector import MeshRoamingDetector
+ ROAMING_DETECTOR_AVAILABLE = True
+except ImportError:
+ ROAMING_DETECTOR_AVAILABLE = False
+ MeshRoamingDetector = None
+
+# Import the power detective
+try:
+ from mesh_power_detective import MeshPowerDetective
+ POWER_DETECTIVE_AVAILABLE = True
+except ImportError:
+ POWER_DETECTIVE_AVAILABLE = False
+ MeshPowerDetective = None
+
+@dataclass
+class APScan:
+ ssid: str
+ bssid: str
+ freq: int
+ signal: int
+ capabilities: Set[str]
+ last_seen: float
+
+ def to_dict(self):
+ """Convert to JSON-serializable dict"""
+ return {
+ 'ssid': self.ssid,
+ 'bssid': self.bssid,
+ 'freq': self.freq,
+ 'signal': self.signal,
+ 'capabilities': list(self.capabilities), # Convert set to list
+ 'last_seen': self.last_seen
+ }
+
+@dataclass
+class ConnectionEvent:
+ timestamp: float
+ bssid: str
+ event_type: str # 'connect', 'disconnect', 'auth_timeout'
+ signal: int
+ duration: Optional[float] = None
+ reason: Optional[str] = None
+
+@dataclass
+class BSSIDHistory:
+ bssid: str
+ total_connections: int = 0
+ successful_connections: int = 0
+ total_duration: float = 0.0
+ avg_signal: float = 0.0
+ signal_samples: List[tuple] = None
+ auth_failures: int = 0
+ disconnects: int = 0
+ last_seen: float = 0.0
+ stability_score: float = 0.0
+
+ def __post_init__(self):
+ if self.signal_samples is None:
+ self.signal_samples = []
+
+def make_json_serializable(obj):
+ """Convert object to JSON-serializable format"""
+ if isinstance(obj, set):
+ return list(obj)
+ elif isinstance(obj, dict):
+ return {k: make_json_serializable(v) for k, v in obj.items()}
+ elif isinstance(obj, list):
+ return [make_json_serializable(item) for item in obj]
+ elif hasattr(obj, 'to_dict'):
+ return obj.to_dict()
+ elif hasattr(obj, '__dict__'):
+ return make_json_serializable(obj.__dict__)
+ else:
+ return obj
+
+class LogManager:
+ """Comprehensive logging system with automatic compression"""
+
+ def __init__(self, data_dir: Path):
+ self.data_dir = data_dir
+ self.logs_dir = data_dir / "logs"
+ self.logs_dir.mkdir(parents=True, exist_ok=True)
+
+ # Create timestamp for this session
+ self.session_timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
+ self.session_date = datetime.now().strftime("%Y-%m-%d")
+
+ # Initialize log files
+ self.analysis_log = self.logs_dir / f"analysis_{self.session_timestamp}.log"
+ self.connections_log = self.logs_dir / f"connections_{self.session_date}.log"
+ self.performance_log = self.logs_dir / f"performance_{self.session_date}.log"
+ self.debug_log = self.logs_dir / f"debug_{self.session_date}.log"
+
+ # Setup loggers
+ self.setup_loggers()
+
+ print(f"📝 Logging enabled: {self.logs_dir}")
+
+ def setup_loggers(self):
+ """Setup structured loggers for different log types"""
+
+ # Analysis logger (detailed scan results)
+ self.analysis_logger = logging.getLogger('analysis')
+ self.analysis_logger.setLevel(logging.INFO)
+ analysis_handler = logging.FileHandler(self.analysis_log)
+ analysis_handler.setFormatter(logging.Formatter(
+ '%(asctime)s | %(levelname)s | %(message)s'
+ ))
+ self.analysis_logger.addHandler(analysis_handler)
+
+ # Connection logger (WiFi events)
+ self.connection_logger = logging.getLogger('connections')
+ self.connection_logger.setLevel(logging.INFO)
+ connection_handler = logging.FileHandler(self.connections_log)
+ connection_handler.setFormatter(logging.Formatter(
+ '%(asctime)s | %(message)s'
+ ))
+ self.connection_logger.addHandler(connection_handler)
+
+ # Performance logger (metrics over time)
+ self.performance_logger = logging.getLogger('performance')
+ self.performance_logger.setLevel(logging.INFO)
+ performance_handler = logging.FileHandler(self.performance_log)
+ performance_handler.setFormatter(logging.Formatter(
+ '%(asctime)s | %(message)s'
+ ))
+ self.performance_logger.addHandler(performance_handler)
+
+ # Debug logger (technical details)
+ self.debug_logger = logging.getLogger('debug')
+ self.debug_logger.setLevel(logging.DEBUG)
+ debug_handler = logging.FileHandler(self.debug_log)
+ debug_handler.setFormatter(logging.Formatter(
+ '%(asctime)s | %(levelname)s | %(funcName)s:%(lineno)d | %(message)s'
+ ))
+ self.debug_logger.addHandler(debug_handler)
+
+ def log_analysis_start(self, interface: str):
+ """Log the start of a new analysis session"""
+ self.analysis_logger.info("="*80)
+ self.analysis_logger.info(f"WiFi Mesh Network Analysis Session Started")
+ self.analysis_logger.info(f"Interface: {interface}")
+ self.analysis_logger.info(f"Session ID: {self.session_timestamp}")
+ self.analysis_logger.info("="*80)
+ self.debug_logger.info(f"Analysis session started on interface {interface}")
+
+ def log_network_scan(self, aps_found: int, scan_duration: float):
+ """Log network scan results"""
+ self.analysis_logger.info(f"Network Scan Complete: {aps_found} APs found in {scan_duration:.2f}s")
+ self.debug_logger.info(f"Scan duration: {scan_duration:.3f}s, APs discovered: {aps_found}")
+
+ def log_mesh_analysis(self, mesh_data: Dict):
+ """Log detailed mesh analysis results"""
+ self.analysis_logger.info("MESH TOPOLOGY ANALYSIS:")
+ self.analysis_logger.info(f" Brand: {mesh_data.get('brand', 'Unknown')}")
+ self.analysis_logger.info(f" Type: {mesh_data.get('mesh_type', 'Unknown')}")
+ self.analysis_logger.info(f" Nodes: {mesh_data.get('total_nodes', 0)}")
+ self.analysis_logger.info(f" Radios: {mesh_data.get('total_radios', 0)}")
+ self.analysis_logger.info(f" Bands: {', '.join(mesh_data.get('bands', []))}")
+ self.analysis_logger.info(f" Topology Health: {mesh_data.get('topology_health', 'Unknown')}")
+ self.analysis_logger.info(f" Signal Range: {mesh_data.get('signal_range', 0)}dB")
+
+ # Log detailed node information
+ mesh_nodes = mesh_data.get('mesh_nodes', {})
+ for node_id, node_info in mesh_nodes.items():
+ self.analysis_logger.info(f" Node {node_id}: {node_info['strongest_signal']}dBm, {len(node_info['radios'])} radios")
+ for radio in node_info['radios']:
+ self.analysis_logger.info(f" Radio {radio['bssid']}: {radio['signal']}dBm ({radio['band']})")
+
+ # Store as JSON for structured analysis with proper serialization
+ try:
+ serializable_data = make_json_serializable(mesh_data)
+ self.debug_logger.info(f"Mesh topology data: {json.dumps(serializable_data, indent=2)}")
+ except Exception as e:
+ self.debug_logger.warning(f"Could not serialize mesh data for JSON logging: {e}")
+ self.debug_logger.info(f"Mesh topology data (raw): {mesh_data}")
+
+ def log_connection_event(self, event):
+ """Log WiFi connection events"""
+ event_msg = f"EVENT: {event.event_type.upper()} | BSSID: {event.bssid} | Signal: {event.signal}dBm"
+ if event.duration:
+ event_msg += f" | Duration: {event.duration:.1f}s"
+ if event.reason:
+ event_msg += f" | Reason: {event.reason}"
+
+ self.connection_logger.info(event_msg)
+ self.debug_logger.debug(f"Connection event: {event}")
+
+ def log_performance_metrics(self, current_conn: Dict, alternatives: List[Dict]):
+ """Log performance metrics and recommendations"""
+ if current_conn:
+ self.performance_logger.info(f"CURRENT CONNECTION:")
+ self.performance_logger.info(f" BSSID: {current_conn['bssid']}")
+ self.performance_logger.info(f" Signal: {current_conn['signal']}dBm")
+ self.performance_logger.info(f" Frequency: {current_conn['freq']}MHz")
+ self.performance_logger.info(f" Band: {self._get_band_from_freq(current_conn['freq'])}")
+
+ if alternatives:
+ self.performance_logger.info(f"ALTERNATIVES FOUND: {len(alternatives)}")
+ for i, alt in enumerate(alternatives[:3], 1):
+ band = self._get_band_from_freq(alt['freq'])
+ self.performance_logger.info(
+ f" Option {i}: {alt['bssid']} | {alt['signal']}dBm ({band}) | "
+ f"Score: {alt['score']:.1f} | Diff: {alt['signal_diff']:+d}dB"
+ )
+
+ def log_recommendations(self, recommendations: Dict):
+ """Log analysis recommendations"""
+ self.analysis_logger.info("RECOMMENDATIONS:")
+ if recommendations.get('action_recommended'):
+ self.analysis_logger.info(f" Action: {recommendations['action']}")
+ self.analysis_logger.info(f" Target: {recommendations.get('target_bssid', 'Unknown')}")
+ self.analysis_logger.info(f" Expected Improvement: {recommendations.get('signal_improvement', 0)}dB")
+ self.analysis_logger.info(f" Priority: {recommendations.get('priority', 'Unknown')}")
+ self.analysis_logger.info(f" Method: {recommendations.get('method', 'Unknown')}")
+ else:
+ self.analysis_logger.info(" No action recommended - current connection optimal")
+
+ # Store detailed recommendations as JSON with proper serialization
+ try:
+ serializable_recommendations = make_json_serializable(recommendations)
+ self.debug_logger.info(f"Recommendations data: {json.dumps(serializable_recommendations, indent=2)}")
+ except Exception as e:
+ self.debug_logger.warning(f"Could not serialize recommendations for JSON logging: {e}")
+ self.debug_logger.info(f"Recommendations data (raw): {recommendations}")
+
+ def log_problems_detected(self, patterns: Dict):
+ """Log detected problems and patterns"""
+ total_issues = sum(len(v) if isinstance(v, list) else len(v) if isinstance(v, dict) else 0
+ for v in patterns.values())
+
+ self.analysis_logger.info(f"PROBLEM DETECTION: {total_issues} issues found")
+
+ if patterns.get('roaming_loops'):
+ self.analysis_logger.info(f" Roaming Loops: {len(patterns['roaming_loops'])}")
+ if patterns.get('auth_failure_clusters'):
+ self.analysis_logger.info(f" Auth Failures: {len(patterns['auth_failure_clusters'])}")
+ if patterns.get('rapid_disconnects'):
+ self.analysis_logger.info(f" Rapid Disconnects: {len(patterns['rapid_disconnects'])}")
+
+ if total_issues > 0:
+ try:
+ serializable_patterns = make_json_serializable(patterns)
+ self.debug_logger.warning(f"Problems detected: {json.dumps(serializable_patterns, indent=2)}")
+ except Exception as e:
+ self.debug_logger.warning(f"Could not serialize patterns for JSON logging: {e}")
+ self.debug_logger.warning(f"Problems detected (raw): {patterns}")
+
+ def log_command_execution(self, command: str, output: str, duration: float):
+ """Log system command execution for debugging"""
+ self.debug_logger.debug(f"Command: {command}")
+ self.debug_logger.debug(f"Duration: {duration:.3f}s")
+ if len(output) > 1000:
+ self.debug_logger.debug(f"Output: {output[:500]}...[truncated]...{output[-500:]}")
+ else:
+ self.debug_logger.debug(f"Output: {output}")
+
+ def log_error(self, error: Exception, context: str = ""):
+ """Log errors with context"""
+ error_msg = f"ERROR in {context}: {type(error).__name__}: {str(error)}"
+ self.analysis_logger.error(error_msg)
+ self.debug_logger.exception(f"Exception in {context}")
+
+ def _get_band_from_freq(self, freq: int) -> str:
+ """Helper to get band name from frequency"""
+ if 2400 <= freq <= 2500:
+ return '2.4GHz'
+ elif 5000 <= freq <= 5999:
+ return '5GHz'
+ elif 6000 <= freq <= 7125:
+ return '6GHz'
+ else:
+ return f'{freq}MHz'
+
+ def create_analysis_archive(self) -> str:
+ """Create compressed archive of all logs and data"""
+ try:
+ # Create archive filename
+ archive_name = f"mesh_analysis_{self.session_timestamp}.zip"
+ archive_path = self.data_dir / archive_name
+
+ with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
+ # Add all log files from today
+ for log_file in self.logs_dir.glob("*.log"):
+ if self.session_date in log_file.name or self.session_timestamp in log_file.name:
+ zipf.write(log_file, f"logs/{log_file.name}")
+
+ # Add data files
+ data_files = [
+ self.data_dir / "bssid_history.pkl",
+ self.data_dir / "connection_events.pkl"
+ ]
+
+ for data_file in data_files:
+ if data_file.exists():
+ zipf.write(data_file, f"data/{data_file.name}")
+
+ # Create summary file
+ summary_content = self._create_session_summary()
+ zipf.writestr("session_summary.txt", summary_content)
+
+ # Create README
+ readme_content = self._create_readme()
+ zipf.writestr("README.txt", readme_content)
+
+ self.analysis_logger.info(f"Analysis archive created: {archive_path}")
+ return str(archive_path)
+
+ except Exception as e:
+ self.log_error(e, "create_analysis_archive")
+ return ""
+
+ def _create_session_summary(self) -> str:
+ """Create a summary of the analysis session"""
+ summary = []
+ summary.append("WiFi Mesh Network Analysis Session Summary")
+ summary.append("=" * 50)
+ summary.append(f"Session ID: {self.session_timestamp}")
+ summary.append(f"Date: {self.session_date}")
+ summary.append(f"Logs Directory: {self.logs_dir}")
+ summary.append("")
+ summary.append("Files Included:")
+ summary.append("- logs/analysis_*.log - Detailed analysis results")
+ summary.append("- logs/connections_*.log - WiFi connection events")
+ summary.append("- logs/performance_*.log - Performance metrics")
+ summary.append("- logs/debug_*.log - Technical debugging info")
+ summary.append("- data/bssid_history.pkl - Historical BSSID performance data")
+ summary.append("- data/connection_events.pkl - Connection event history")
+ summary.append("")
+ summary.append("Analysis Tools:")
+ summary.append("- Mesh topology detection and health assessment")
+ summary.append("- Signal strength analysis and optimization")
+ summary.append("- Historical performance tracking")
+ summary.append("- Problem pattern detection")
+ summary.append("- Actionable recommendations with step-by-step guidance")
+
+ return "\n".join(summary)
+
+ def _create_readme(self) -> str:
+ """Create README for the archive"""
+ readme = []
+ readme.append("WiFi Mesh Network Analyzer - Log Archive")
+ readme.append("=" * 45)
+ readme.append("")
+ readme.append("This archive contains comprehensive logs from a WiFi mesh network analysis session.")
+ readme.append("")
+ readme.append("LOG FILES:")
+ readme.append("")
+ readme.append("analysis_*.log")
+ readme.append(" - Complete analysis results including mesh topology, recommendations,")
+ readme.append(" and problem detection. Human-readable format.")
+ readme.append("")
+ readme.append("connections_*.log")
+ readme.append(" - Real-time WiFi connection events including roaming, disconnects,")
+ readme.append(" and authentication events. Useful for troubleshooting connectivity issues.")
+ readme.append("")
+ readme.append("performance_*.log")
+ readme.append(" - Performance metrics over time including signal strength variations,")
+ readme.append(" alternative options, and optimization opportunities.")
+ readme.append("")
+ readme.append("debug_*.log")
+ readme.append(" - Technical debugging information including command outputs,")
+ readme.append(" timing data, and detailed system interactions.")
+ readme.append("")
+ readme.append("DATA FILES:")
+ readme.append("")
+ readme.append("data/bssid_history.pkl")
+ readme.append(" - Binary file containing historical performance data for each BSSID")
+ readme.append(" - Includes stability scores, connection success rates, signal tracking")
+ readme.append("")
+ readme.append("data/connection_events.pkl")
+ readme.append(" - Binary file containing detailed connection event history")
+ readme.append(" - Used for pattern analysis and problem detection")
+ readme.append("")
+ readme.append("USAGE:")
+ readme.append("These logs can be used for:")
+ readme.append("- Network troubleshooting and optimization")
+ readme.append("- Performance trend analysis")
+ readme.append("- Mesh system health monitoring")
+ readme.append("- Problem pattern identification")
+ readme.append("- Technical support and debugging")
+
+ return "\n".join(readme)
+
+class HistoryTracker:
+ """Tracks WiFi connection history and performance per BSSID"""
+
+ def __init__(self, data_dir: str = None, log_manager = None):
+ if data_dir is None:
+ # Use consistent location regardless of sudo
+ if os.geteuid() == 0 and 'SUDO_USER' in os.environ:
+ sudo_user = os.environ['SUDO_USER']
+ import pwd
+ real_user_home = pwd.getpwnam(sudo_user).pw_dir
+ data_dir = os.path.join(real_user_home, ".mesh_analyzer")
+ else:
+ home = os.path.expanduser("~")
+ data_dir = os.path.join(home, ".mesh_analyzer")
+
+ self.data_dir = Path(data_dir)
+ self.log_manager = log_manager
+
+ # Create directory with proper permissions
+ try:
+ self.data_dir.mkdir(parents=True, exist_ok=True)
+
+ # If created as root but should belong to user, fix permissions
+ if os.geteuid() == 0 and 'SUDO_USER' in os.environ:
+ sudo_user = os.environ['SUDO_USER']
+ import pwd
+ user_info = pwd.getpwnam(sudo_user)
+ os.chown(self.data_dir, user_info.pw_uid, user_info.pw_gid)
+
+ except Exception as e:
+ print(f"⚠️ Warning: Could not create/fix permissions for {data_dir}: {e}")
+ if self.log_manager:
+ self.log_manager.log_error(e, "HistoryTracker.__init__")
+
+ self.history_file = self.data_dir / "bssid_history.pkl"
+ self.events_file = self.data_dir / "connection_events.pkl"
+
+ self.bssid_history: Dict[str, BSSIDHistory] = {}
+ self.connection_events: List[ConnectionEvent] = []
+
+ self._load_history()
+ print(f"📁 History storage: {self.data_dir}")
+
+ def _load_history(self):
+ """Load historical data from disk"""
+ try:
+ if self.history_file.exists():
+ with open(self.history_file, 'rb') as f:
+ self.bssid_history = pickle.load(f)
+ print(f"📊 Loaded history for {len(self.bssid_history)} BSSIDs")
+ if self.log_manager:
+ self.log_manager.debug_logger.info(f"Loaded BSSID history: {len(self.bssid_history)} entries")
+
+ if self.events_file.exists():
+ with open(self.events_file, 'rb') as f:
+ self.connection_events = pickle.load(f)
+ # Keep only last 30 days of events
+ cutoff = time.time() - (30 * 24 * 3600)
+ original_count = len(self.connection_events)
+ self.connection_events = [e for e in self.connection_events if e.timestamp > cutoff]
+ cleaned_count = original_count - len(self.connection_events)
+ if cleaned_count > 0:
+ print(f"🧹 Cleaned {cleaned_count} old events (keeping last 30 days)")
+ if self.log_manager:
+ self.log_manager.debug_logger.info(f"Cleaned {cleaned_count} old events, keeping {len(self.connection_events)}")
+ print(f"📈 Loaded {len(self.connection_events)} recent connection events")
+ except (pickle.UnpicklingError, EOFError, pickle.PickleError) as e:
+ print(f"⚠️ Could not load history (corrupted pickle files): {e}")
+ print("🔧 Attempting to recover by backing up and resetting history...")
+ if self.log_manager:
+ self.log_manager.log_error(e, "load_history")
+ self._backup_and_reset_corrupted_files()
+ self.bssid_history = {}
+ self.connection_events = []
+ print("🆕 Starting with fresh history tracking")
+ except Exception as e:
+ print(f"⚠️ Could not load history: {e}")
+ print("🆕 Starting with fresh history tracking")
+ if self.log_manager:
+ self.log_manager.log_error(e, "load_history")
+ self.bssid_history = {}
+ self.connection_events = []
+
+ def _backup_and_reset_corrupted_files(self):
+ """Backup corrupted files and reset for fresh start"""
+ try:
+ import shutil
+ timestamp = int(time.time())
+
+ if self.history_file.exists():
+ backup_history = self.data_dir / f"bssid_history_corrupted_{timestamp}.pkl"
+ shutil.move(str(self.history_file), str(backup_history))
+ print(f"📦 Backed up corrupted history to: {backup_history.name}")
+
+ if self.events_file.exists():
+ backup_events = self.data_dir / f"connection_events_corrupted_{timestamp}.pkl"
+ shutil.move(str(self.events_file), str(backup_events))
+ print(f"📦 Backed up corrupted events to: {backup_events.name}")
+
+ except Exception as e:
+ print(f"⚠️ Could not backup corrupted files: {e}")
+
+ def _save_history(self):
+ """Save historical data to disk"""
+ try:
+ self.data_dir.mkdir(exist_ok=True)
+
+ with open(self.history_file, 'wb') as f:
+ pickle.dump(self.bssid_history, f)
+
+ with open(self.events_file, 'wb') as f:
+ pickle.dump(self.connection_events, f)
+
+ # Fix file permissions if running as sudo
+ if os.geteuid() == 0 and 'SUDO_USER' in os.environ:
+ sudo_user = os.environ['SUDO_USER']
+ import pwd
+ user_info = pwd.getpwnam(sudo_user)
+ os.chown(self.history_file, user_info.pw_uid, user_info.pw_gid)
+ os.chown(self.events_file, user_info.pw_uid, user_info.pw_gid)
+
+ except Exception as e:
+ print(f"⚠️ Warning: Could not save history: {e}")
+
+ def record_event(self, event: ConnectionEvent):
+ """Record a connection event"""
+ self.connection_events.append(event)
+
+ # Log the event
+ if self.log_manager:
+ self.log_manager.log_connection_event(event)
+
+ # Update BSSID history
+ if event.bssid not in self.bssid_history:
+ self.bssid_history[event.bssid] = BSSIDHistory(bssid=event.bssid)
+
+ history = self.bssid_history[event.bssid]
+ history.last_seen = event.timestamp
+
+ if event.event_type == 'connect':
+ history.total_connections += 1
+ history.successful_connections += 1
+ elif event.event_type == 'auth_timeout':
+ history.auth_failures += 1
+ elif event.event_type == 'disconnect':
+ history.disconnects += 1
+ if event.duration:
+ history.total_duration += event.duration
+
+ # Update signal tracking
+ if event.signal != -100:
+ history.signal_samples.append((event.timestamp, event.signal))
+ history.signal_samples = history.signal_samples[-100:] # Keep last 100
+
+ if history.signal_samples:
+ history.avg_signal = sum(s[1] for s in history.signal_samples) / len(history.signal_samples)
+
+ # Calculate stability score
+ self._calculate_stability_score(history)
+ self._save_history()
+
+ def _calculate_stability_score(self, history: BSSIDHistory):
+ """Calculate stability score (0-100) based on historical performance"""
+ score = 100.0
+
+ # Penalize auth failures (50 point penalty max)
+ if history.total_connections > 0:
+ failure_rate = history.auth_failures / (history.total_connections + history.auth_failures)
+ score -= failure_rate * 50
+
+ # Penalize frequent disconnects (30 point penalty max)
+ if history.successful_connections > 0:
+ disconnect_rate = history.disconnects / history.successful_connections
+ score -= min(disconnect_rate * 30, 30)
+
+ # Reward consistent signal (10 point bonus for consistency)
+ if len(history.signal_samples) > 5:
+ signals = [s[1] for s in history.signal_samples[-10:]]
+ signal_variance = max(signals) - min(signals)
+ if signal_variance < 10:
+ score += 10
+ elif signal_variance > 30:
+ score -= 20
+
+ # Reward longer connection durations
+ if history.successful_connections > 0 and history.total_duration > 0:
+ avg_duration = history.total_duration / history.successful_connections
+ if avg_duration > 3600: # > 1 hour
+ score += 15
+ elif avg_duration < 300: # < 5 minutes
+ score -= 15
+
+ history.stability_score = max(0, min(100, score))
+
+ def get_bssid_performance(self, bssid: str) -> Optional[BSSIDHistory]:
+ """Get historical performance for a specific BSSID"""
+ return self.bssid_history.get(bssid)
+
+ def get_recent_events(self, hours: int = 24) -> List[ConnectionEvent]:
+ """Get connection events from the last N hours"""
+ cutoff = time.time() - (hours * 3600)
+ return [e for e in self.connection_events if e.timestamp > cutoff]
+
+class MeshIntelligence:
+ """Mesh network topology analysis with built-in OUI database"""
+
+ def __init__(self):
+ # Updated mesh brand OUI database (May 2025)
+ self.oui_database = {
+ # eero (Amazon) - Mesh WiFi Systems
+ 'eero': [
+ 'A0:21:B7', '68:1D:A0', 'B0:8E:86', 'F8:BB:BF', 'D8:8E:D4', 'E8:D3:EB', # Original entries
+ '00:AB:48', '80:DA:13', '74:B6:B6', '6C:AE:F6', '68:4A:76', '60:5F:8D', # Additional eero prefixes
+ '50:F5:DA', 'C4:93:D9', '58:D9:D5', '50:1A:C5', '04:D3:B0', '24:F5:AA',
+ '9C:30:5B', 'A8:81:95', '74:75:48', '60:32:B1', '84:D8:1B', '00:90:4C',
+ '70:56:81', 'C8:69:CD', '40:B4:CD', 'BC:E6:43', '8C:85:90', 'DC:A6:32',
+ '88:E9:FE', '28:6C:07', '3C:22:FB', '90:72:40', 'D0:04:01', 'AC:BC:32',
+ '34:D2:70', 'B0:BE:76', '58:8B:F3', 'EC:01:EE', 'A4:11:6B', '70:4D:7B',
+ '98:F1:70', 'CC:32:E5', '40:A3:CC', '1C:69:7A', 'B8:C7:5D', '2C:1F:23',
+ '44:CE:7D', 'D4:61:9D', '78:4F:43', '0C:47:C9', 'B4:A9:FC', '88:1F:A1',
+ 'FC:EC:DA', '30:23:03', '24:A0:74', '6C:72:20', 'E0:55:3D', '48:43:7C'
+ ],
+
+ # Netgear Orbi - Mesh WiFi Systems
+ 'orbi_netgear': [
+ '98:97:9A', '44:B1:3B', '9C:28:EF', 'A0:04:60', '04:A1:51', # Original entries
+ '10:0D:7F', '28:C6:8E', 'B0:7F:B9', '4C:60:DE', '9C:3D:CF', # Additional Netgear prefixes
+ 'A0:40:A0', '20:E5:2A', 'C4:04:15', '84:1B:5E', '40:16:7E',
+ '2C:30:33', 'E0:46:9A', '6C:19:8F', 'C0:3F:0E', '08:BD:43',
+ '74:44:01', 'B0:39:56', '30:46:9A', 'A0:63:91', '44:94:FC',
+ '3C:37:86', 'R0:48:7A', '1C:C1:DE', '78:D2:94', 'DC:EF:09',
+ '08:02:8E', '74:98:0B', 'A4:2B:B0', '50:C7:BF', '6C:B0:CE',
+ '84:A4:23', 'E0:91:F5', 'CC:40:D0', '9C:5C:8E', '28:56:5A',
+ '70:4F:57', 'FC:94:E3', '1C:BD:B9', 'B4:75:0E', '34:98:B5',
+ '40:0D:10', '6C:CD:D6', 'A0:21:B7', '30:87:30', '50:6A:03'
+ ],
+
+ # Google Nest WiFi & Google WiFi
+ 'google_nest': [
+ 'A4:50:46', '64:FF:89', 'CC:52:AF', '6C:71:0D', # Original entries
+ 'F4:F5:D8', '4C:49:E3', '78:E1:03', '18:B4:30', # Additional Google prefixes
+ '30:FD:38', 'A4:DA:32', '90:72:40', 'F8:8F:CA',
+ '6C:AD:F8', 'F0:EF:86', '40:4E:36', 'E8:40:F2',
+ 'B4:CE:F6', '84:F3:EB', '3C:36:3D', '00:1A:11',
+ 'D8:50:E6', 'B0:79:94', 'C8:14:79', '54:60:09',
+ '68:C6:3A', 'DC:3A:5E', '48:57:02', '7C:2E:BD',
+ '98:DE:D0', '14:2D:27', 'B8:AD:28', 'E0:CB:4E',
+ '20:DF:B9', 'A0:C5:89', '74:E5:43', '58:CB:52',
+ '88:3F:D3', 'C4:B3:01', '60:F1:89', '9C:B6:D0'
+ ],
+
+ # ASUS - WiFi Routers and Mesh Systems
+ 'asus': [
+ '40:ED:00', '88:1F:A1', 'AC:9E:17', '2C:56:DC', '04:D4:C4', # Original entries
+ '70:4D:7B', 'B8:EE:65', '1C:87:2C', '50:46:5D', 'D8:50:E6', # Additional ASUS prefixes
+ '38:D5:47', 'F0:2F:74', '30:5A:3A', '04:92:26', '00:E0:4C',
+ '00:08:A1', '00:0E:A6', '00:11:D8', '00:13:D4', '00:15:F2',
+ '00:17:31', '00:19:DB', '00:1B:FC', '00:1E:8C', '00:22:15',
+ '00:23:54', '00:24:8C', '00:26:18', '08:60:6E', '10:7B:44',
+ '14:DD:A9', '20:CF:30', '24:4B:FE', '28:10:7B', '30:85:A9',
+ '34:97:F6', '38:2C:4A', '3C:7C:3F', '40:16:7E', '48:EE:0C',
+ '4C:ED:FB', '50:3E:AA', '54:04:A6', '5C:AC:4C', '60:45:CB',
+ '64:66:B3', '6C:F0:49', '70:8B:CD', '74:D0:2B', '78:24:AF',
+ '7C:10:C9', '80:1F:02', '84:A4:23', '88:D7:F6', '8C:10:D4',
+ '90:F6:52', '94:FB:A7', '98:5A:EB', '9C:5C:8E', 'A0:F3:C1',
+ 'AC:22:0B', 'B0:6E:BF', 'B4:2E:99', 'B8:AE:6E', 'BC:EE:7B',
+ 'C8:60:00', 'CC:2D:E0', 'D0:17:C2', 'D4:5D:64', 'D8:47:32',
+ 'DC:FB:02', 'E0:3F:49', 'E4:70:B8', 'E8:CC:18', 'EC:F4:BB',
+ 'F0:79:59', 'F4:6D:04', 'F8:32:E4', 'FC:34:97'
+ ],
+
+ # TP-Link Deco Mesh Systems
+ 'tp_link_deco': [
+ '98:25:4A', '44:94:FC', 'B0:48:7A', '50:C7:BF', 'A4:2B:B0', # Original entries
+ '14:CC:20', '1C:61:B4', '98:48:27', 'A4:2B:B0', '18:A6:F7', # Additional TP-Link prefixes
+ '00:23:CD', '00:27:19', '04:8D:38', '08:55:31', '0C:80:63',
+ '10:27:F5', '14:E6:E4', '18:D6:C7', '1C:FA:68', '20:F4:78',
+ '24:05:0F', '28:2C:02', '2C:F0:5D', '30:07:4D', '34:29:8F',
+ '38:71:DE', '3C:84:6A', '40:A5:EF', '44:D9:E7', '48:3B:38',
+ '4C:E1:73', '50:1A:C5', '54:AF:97', '58:8E:81', '5C:62:8B',
+ '60:E3:27', '64:70:02', '68:FF:7B', '6C:5A:B0', '70:4F:57',
+ '74:DA:38', '78:81:02', '7C:8A:E1', '80:EA:96', '84:16:F9',
+ '88:C3:97', '8C:53:C3', '90:F6:52', '94:E9:79', '98:DA:C4',
+ '9C:A6:15', 'A0:F3:C1', 'A4:B1:E9', 'A8:40:41', 'AC:84:C6',
+ 'B0:4E:26', 'B4:B0:24', 'B8:69:F4', 'BC:46:99', 'C0:06:C3',
+ 'C4:6E:1F', 'C8:0E:14', 'CC:32:E5', 'D0:76:E7', 'D4:6E:0E',
+ 'D8:0D:17', 'DC:9F:DB', 'E0:28:6D', 'E4:9A:DC', 'E8:DE:27',
+ 'EC:08:6B', 'F0:F2:49', 'F4:28:53', 'F8:1A:67', 'FC:7C:02'
+ ],
+
+ # Linksys Velop Mesh Systems
+ 'linksys_velop': [
+ '6C:BE:E9', '13:10:47', '98:9E:64', '94:10:3E', 'C4:41:1E', # Original entries
+ '00:0F:66', '00:13:10', '00:14:BF', '00:16:B6', '00:18:39', # Additional Linksys/Cisco prefixes
+ '00:1A:70', '00:1C:10', '00:1D:7E', '00:1E:E5', '00:21:29',
+ '00:22:6B', '00:23:04', '00:24:13', '00:25:45', '00:40:96',
+ '08:86:3B', '10:05:CA', '14:91:82', '18:1B:EB', '1C:DF:0F',
+ '20:AA:4B', '24:F2:7F', '28:F0:76', '2C:AB:A4', '30:23:03',
+ '34:A8:4E', '38:2A:68', '3C:1E:04', '40:B0:FA', '44:32:C8',
+ '48:F8:B3', '4C:00:82', '50:3D:E5', '54:78:1A', '58:6D:8F',
+ '5C:50:15', '60:38:E0', '64:1C:B0', '68:7F:74', '6C:50:4D',
+ '70:1A:04', '74:E2:F5', '78:CA:39', '7C:34:79', '80:69:1A',
+ '84:B5:17', '88:CB:87', '8C:04:BA', '90:35:5B', '94:44:52',
+ '98:FC:11', '9C:97:26', 'A0:55:4F', 'A4:18:75', 'A8:9C:ED',
+ 'AC:1D:DF', 'B0:10:41', 'B4:3A:28', 'B8:55:10', 'BC:67:78',
+ 'C0:56:27', 'C4:7C:8D', 'C8:D7:19', 'CC:5D:4E', 'D0:59:E4',
+ 'D4:CA:6D', 'D8:FE:E3', 'DC:85:DE', 'E0:1C:41', 'E4:F4:C6',
+ 'E8:98:6D', 'EC:E1:A9', 'F0:92:1C', 'F4:EC:38', 'F8:E7:1E'
+ ],
+
+ # Ubiquiti Networks - UniFi, AmpliFi, EdgeRouter
+ 'ubiquiti': [
+ '78:8A:20', '24:5A:4C', 'F0:9F:C2', '44:D9:E7', 'E0:63:DA', # Original entries
+ '04:18:D6', '68:72:51', 'B4:FB:E4', 'DC:9F:DB', '80:2A:A8', # Additional Ubiquiti prefixes
+ '00:15:6D', '00:27:22', '04:18:D6', '18:E8:29', '24:A4:3C',
+ '44:D9:E7', '68:72:51', '6C:88:14', '74:83:C2', '78:8A:20',
+ '80:2A:A8', 'B4:FB:E4', 'DC:9F:DB', 'E0:63:DA', 'F0:9F:C2',
+ '24:5A:4C', '68:D7:9A', '70:A7:41', '74:AC:B9', '78:45:58',
+ '80:2A:A8', '84:B5:9C', '88:1F:A1', '8C:59:C3', '90:9A:4A',
+ '94:3E:EA', '98:FA:9B', '9C:93:4E', 'A0:F3:E4', 'A4:2B:8C',
+ 'A8:40:25', 'AC:8B:A9', 'B0:C5:54', 'B4:E6:2D', 'B8:27:EB',
+ 'BC:DD:C2', 'C0:4A:00', 'C4:A8:1D', 'C8:7F:54', 'CC:2D:A0',
+ 'D0:21:F9', 'D4:CA:6D', 'D8:B3:70', 'DC:2C:26', 'E0:22:F0',
+ 'E4:38:7E', 'E8:CC:18', 'EC:B9:70', 'F0:27:2D', 'F4:92:BF',
+ 'F8:AB:05', 'FC:EC:DA'
+ ],
+
+ # MikroTik RouterOS Devices
+ 'mikrotik': [
+ '6C:3B:6B', '48:8F:5A', '2C:C8:1B', '4C:5E:0C', 'E4:8D:8C', # Original entries
+ '00:0C:42', '18:FD:74', '2C:C8:1B', '4C:5E:0C', '6C:3B:6B', # Additional MikroTik prefixes
+ '74:4D:28', '7C:2F:80', '8C:59:C3', 'B8:69:F4', 'DC:2C:6E',
+ 'E4:8D:8C', '48:8F:5A', '08:55:31', '18:FD:74', '00:0C:42',
+ '6C:3B:6B', '4C:5E:0C', 'E4:8D:8C', '2C:C8:1B', '48:8F:5A',
+ 'B8:69:F4', 'DC:2C:6E', '74:4D:28', '8C:59:C3', '18:FD:74',
+ '7C:2F:80', '00:0C:42', '08:55:31', '84:1B:5E', '90:5A:68',
+ '94:E3:6D', '98:DA:C4', '9C:A6:15', 'A0:F3:C1', 'A4:B1:E9',
+ 'A8:40:41', 'AC:84:C6', 'B0:4E:26', 'B4:B0:24', 'B8:69:F4',
+ 'BC:46:99', 'C0:06:C3', 'C4:6E:1F', 'C8:0E:14', 'CC:32:E5',
+ 'D0:76:E7', 'D4:6E:0E', 'D8:0D:17', 'DC:9F:DB'
+ ],
+
+ # Aruba HPE Networks
+ 'aruba_hpe': [
+ '70:3A:CB', '6C:F3:7F', '24:DE:C6', '94:B4:0F', '20:4C:03', # Original entries
+ '00:0B:86', '00:1A:1E', '00:24:6C', '6C:F3:7F', '70:3A:CB', # Additional Aruba/HPE prefixes
+ '78:9C:85', '84:D4:7E', '8C:DC:D4', '94:B4:0F', '9C:1C:12',
+ 'A4:5D:36', 'B0:5A:DA', 'B8:D9:CE', 'C0:E4:34', 'D8:C7:C8',
+ 'E0:07:1B', 'E8:BA:70', 'F0:5C:19', 'F8:0A:CB', '00:0B:86',
+ '00:1A:1E', '00:24:6C', '18:64:72', '20:4C:03', '24:DE:C6',
+ '40:E3:D6', '54:75:D0', '6C:C2:17', '70:3A:CB', '7C:69:F6',
+ '84:D4:7E', '8C:DC:D4', '94:B4:0F', '9C:1C:12', 'A4:5D:36',
+ 'B0:5A:DA', 'B8:D9:CE', 'C0:E4:34', 'D8:C7:C8', 'E0:07:1B',
+ 'E8:BA:70', 'F0:5C:19', 'F8:0A:CB', '6C:F3:7F', '20:4C:03'
+ ],
+
+ # Ruckus Networks (CommScope)
+ 'ruckus': [
+ '50:91:E3', '2C:36:F8', '94:3E:EA', 'BC:14:85', '58:93:96', # Original entries
+ '2C:36:F8', '50:91:E3', '58:93:96', '94:3E:EA', 'BC:14:85', # Additional Ruckus prefixes
+ 'C4:B9:CD', 'E0:5F:B9', 'F4:28:53', '00:0F:9F', '00:14:7F',
+ '00:21:91', '00:24:DC', '78:BC:1A', '84:1B:5E', '90:5A:68',
+ '2C:36:F8', '50:91:E3', '58:93:96', '94:3E:EA', 'BC:14:85',
+ 'C4:B9:CD', 'E0:5F:B9', 'F4:28:53', '00:0F:9F', '00:14:7F',
+ '00:21:91', '00:24:DC', '78:BC:1A', '84:1B:5E', '90:5A:68',
+ '94:E3:6D', '98:DA:C4', '9C:A6:15', 'A0:F3:C1', 'A4:B1:E9',
+ 'A8:40:41', 'AC:84:C6', 'B0:4E:26', 'B4:B0:24', 'BC:46:99'
+ ],
+
+ # Cisco Meraki Cloud Managed Networks
+ 'cisco_meraki': [
+ '00:18:0A', 'E0:55:3D', '88:15:44', '0C:8D:DB', '34:56:FE', # Original entries
+ '00:18:0A', '0C:8D:DB', '34:56:FE', '88:15:44', 'E0:55:3D', # Additional Cisco Meraki prefixes
+ '00:1D:71', '00:24:DC', 'E0:CB:BC', 'F4:39:09', '58:97:1E',
+ '8C:7C:92', 'AC:17:C8', 'E0:CB:BC', 'F4:39:09', '58:97:1E',
+ '00:1D:71', '00:24:DC', '8C:7C:92', 'AC:17:C8', '00:18:0A',
+ '0C:8D:DB', '34:56:FE', '88:15:44', 'E0:55:3D', 'E0:CB:BC',
+ 'F4:39:09', '58:97:1E', '8C:7C:92', 'AC:17:C8', '00:1D:71',
+ '00:24:DC', '2C:BE:08', '4C:79:6E', '74:86:E2', '8C:FE:A3',
+ 'A4:56:02', 'BC:67:1C', 'D4:20:B0', 'EC:1F:72', '2C:BE:08',
+ '4C:79:6E', '74:86:E2', '8C:FE:A3', 'A4:56:02', 'BC:67:1C'
+ ],
+
+ # EnGenius Wireless Access Points
+ 'engenius': [
+ '88:DC:96', '50:2B:73', '02:CF:7F', '00:02:6F', # Original entries
+ '00:02:6F', '02:CF:7F', '50:2B:73', '88:DC:96', # Additional EnGenius prefixes
+ '00:02:6F', '88:DC:96', '50:2B:73', '02:CF:7F',
+ '74:EA:3A', 'AC:9E:17', 'C8:D3:A3', 'DC:EF:09',
+ 'F0:7D:68', '74:EA:3A', 'AC:9E:17', 'C8:D3:A3',
+ 'DC:EF:09', 'F0:7D:68', '88:DC:96', '50:2B:73',
+ '02:CF:7F', '00:02:6F', '74:EA:3A', 'AC:9E:17',
+ 'C8:D3:A3', 'DC:EF:09', 'F0:7D:68', '04:F0:21',
+ '6C:72:20', '88:6B:0E', 'AC:83:F3', 'D0:17:C2',
+ 'F4:AF:E7', '04:F0:21', '6C:72:20', '88:6B:0E'
+ ],
+
+ # D-Link WiFi Routers and Access Points
+ 'dlink': [
+ 'CC:B2:55', 'B8:A3:86', '34:08:04', '14:D6:4D', '84:C9:B2', # Original entries
+ '00:05:5D', '00:0F:3D', '00:11:95', '00:13:46', '00:15:E9', # Additional D-Link prefixes
+ '00:17:9A', '00:19:5B', '00:1B:11', '00:1C:F0', '00:1E:58',
+ '00:21:91', '00:22:B0', '00:24:01', '00:26:5A', '14:D6:4D',
+ '1C:7E:E5', '1C:AF:F7', '28:10:7B', '2C:B0:5D', '34:08:04',
+ '40:61:86', '48:EE:0C', '50:C7:BF', '54:78:1A', '5C:F4:AB',
+ '60:C5:47', '6C:19:8F', '70:62:B8', '78:54:2E', '7C:8B:CA',
+ '84:C9:B2', '8C:BE:BE', '90:94:E4', '94:44:52', '9C:D6:43',
+ 'A0:AB:1B', 'A8:57:4E', 'B0:C7:45', 'B8:A3:86', 'C0:A0:BB',
+ 'C8:BE:19', 'CC:B2:55', 'D0:67:E5', 'D8:FE:E3', 'E0:91:F5',
+ 'E8:CC:18', 'F0:7D:68', 'F8:E7:1E', 'FC:75:16'
+ ],
+
+ # Netgear General (Non-Orbi) Products
+ 'netgear_general': [
+ '10:0D:7F', '28:C6:8E', 'B0:7F:B9', '4C:60:DE', # Original entries
+ '00:09:5B', '00:0F:B5', '00:14:6C', '00:1B:2F', '00:1E:2A',
+ '00:22:3F', '00:24:B2', '00:26:F2', '04:A1:51', '08:BD:43',
+ '10:0D:7F', '20:4E:7F', '28:C6:8E', '2C:30:33', '30:46:9A',
+ '44:94:FC', '4C:60:DE', '6C:B0:CE', '70:4F:57', '74:44:01',
+ '78:D2:94', '84:A4:23', '9C:3D:CF', 'A0:04:60', 'A0:21:B7',
+ 'A0:63:91', 'A4:2B:B0', 'B0:39:56', 'B0:7F:B9', 'C0:3F:0E',
+ 'C4:04:15', 'CC:40:D0', 'DC:EF:09', 'E0:46:9A', 'E0:91:F5',
+ 'FC:94:E3', '1C:BD:B9', '1C:C1:DE', '3C:37:86', '40:0D:10',
+ '50:6A:03', '6C:CD:D6', '9C:5C:8E', 'B4:75:0E', '34:98:B5'
+ ],
+
+ # Plume Adaptive WiFi (Plume Design)
+ 'plume_adaptive': [
+ '74:DA:88', '78:28:CA', 'A0:40:A0', # Original entries
+ '74:DA:88', '78:28:CA', 'A0:40:A0', # Additional Plume prefixes
+ 'B8:D7:AF', 'C4:93:D9', 'E0:1C:FC',
+ '24:F5:AA', '58:D9:D5', '9C:30:5B',
+ 'A8:81:95', 'C0:C9:E3', 'E8:6A:64',
+ '04:D3:B0', '50:1A:C5', 'B0:BE:76',
+ 'EC:01:EE', '74:DA:88', '78:28:CA',
+ 'A0:40:A0', 'B8:D7:AF', 'C4:93:D9'
+ ],
+
+ # Xfinity Pods (Comcast)
+ 'xfinity_pods': [
+ 'A8:4E:3F', '00:35:1A', '8C:3B:AD', # Original entries
+ 'A8:4E:3F', '00:35:1A', '8C:3B:AD',
+ '70:56:81', 'C8:69:CD', '40:B4:CD',
+ 'BC:E6:43', '8C:85:90', 'DC:A6:32',
+ '88:E9:FE', '28:6C:07', '3C:22:FB',
+ '90:72:40', 'D0:04:01', 'AC:BC:32',
+ '34:D2:70', 'A8:4E:3F', '00:35:1A',
+ '8C:3B:AD', '70:56:81', 'C8:69:CD'
+ ],
+
+ # Amazon AmpliFi (acquired by Amazon)
+ 'amazon_amplifi': [
+ '74:C6:3B', 'E4:95:6E', # Original entries
+ '74:C6:3B', 'E4:95:6E', # Additional AmpliFi prefixes
+ 'DC:9F:DB', '44:D9:E7', 'F0:9F:C2',
+ '24:5A:4C', '78:8A:20', 'E0:63:DA',
+ 'B4:FB:E4', '04:18:D6', '68:72:51',
+ '80:2A:A8', '74:C6:3B', 'E4:95:6E',
+ 'DC:9F:DB', '44:D9:E7', 'F0:9F:C2'
+ ],
+
+ # Tenda WiFi Routers
+ 'tenda': [
+ 'C8:3A:35', 'FC:7C:02', '98:DE:D0', # Original entries
+ 'C8:3A:35', 'FC:7C:02', '98:DE:D0', # Additional Tenda prefixes
+ '00:B0:0C', '74:25:8A', 'A4:2B:8C',
+ 'C8:3A:35', 'FC:7C:02', '98:DE:D0',
+ '00:B0:0C', '74:25:8A', 'A4:2B:8C',
+ 'E0:05:C6', 'F4:EC:38', '34:96:72',
+ '5C:CF:7F', 'B0:E5:ED', 'D4:6E:0E',
+ 'E8:DE:27', '10:BF:48', '50:BD:5F',
+ '8C:21:0A', 'C8:3A:35', 'FC:7C:02'
+ ],
+
+ # Xiaomi Mi Router and Mesh
+ 'xiaomi_mesh': [
+ '34:CE:00', '64:64:4A', 'F8:59:71', # Original entries
+ '34:CE:00', '64:64:4A', 'F8:59:71', # Additional Xiaomi prefixes
+ '50:8F:4C', '78:11:DC', 'A0:86:C6',
+ 'B0:E2:35', 'C4:0B:CB', 'D4:97:0B',
+ 'E8:AB:FA', 'F0:B4:29', 'F8:59:71',
+ '04:CF:8C', '14:75:90', '28:E3:1F',
+ '3C:BD:D8', '50:EC:50', '68:DF:DD',
+ '7C:1D:D9', '8C:53:C3', '98:FA:9B',
+ 'A4:DA:32', 'B8:70:F4', 'C8:FF:28',
+ 'DC:44:27', 'F0:B4:29', '34:CE:00'
+ ],
+
+ # Honor/Huawei WiFi Routers
+ 'honor_huawei': [
+ '00:E0:FC', '98:F4:28', 'A0:8C:FD', # Original entries
+ '00:E0:FC', '98:F4:28', 'A0:8C:FD', # Additional Huawei/Honor prefixes
+ '00:25:9E', '04:BD:88', '10:47:80',
+ '18:CF:5E', '20:76:93', '28:31:52',
+ '30:FC:68', '3C:FA:43', '44:00:10',
+ '4C:54:99', '54:25:EA', '5C:C9:D3',
+ '64:3E:8C', '6C:92:BF', '74:A7:22',
+ '7C:A7:B0', '84:A8:E4', '8C:34:FD',
+ '94:04:9C', '9C:28:EF', 'A4:C4:94',
+ 'AC:E2:15', 'B4:CD:27', 'BC:76:70',
+ 'C4:6A:B7', 'CC:E6:7F', 'D4:20:B0',
+ 'DC:D2:FC', 'E4:C7:22', 'EC:23:3D',
+ 'F4:4E:E3', 'FC:48:EF', '98:F4:28'
+ ],
+
+ # WiFi 6E and WiFi 7 Manufacturers
+ 'wifi6e_wifi7_general': [
+ # Various next-gen WiFi manufacturers
+ '70:4F:57', '6C:CD:D6', '30:87:30', '50:6A:03', '40:0D:10',
+ 'B4:75:0E', '34:98:B5', '1C:BD:B9', 'FC:94:E3', 'E0:91:F5',
+ '84:A4:23', 'A0:63:91', '70:4F:57', '6C:B0:CE', '44:94:FC',
+ '30:46:9A', '2C:30:33', 'E0:46:9A', '6C:19:8F', 'C0:3F:0E',
+ '08:BD:43', '74:44:01', 'B0:39:56', '20:E5:2A', 'C4:04:15',
+ '84:1B:5E', '40:16:7E', '9C:3D:CF', 'A0:40:A0', '10:0D:7F',
+ '28:C6:8E', 'B0:7F:B9', '4C:60:DE', 'DC:EF:09', 'CC:40:D0'
+ ],
+
+ # Additional Mesh Router Brands
+ 'additional_mesh_brands': [
+ # Portal WiFi
+ '68:A4:0E', '84:16:0C', 'A0:8C:FD', 'B4:2E:99', 'C8:D3:A3',
+ # Securifi Almond
+ 'F0:7D:68', '74:EA:3A', 'AC:9E:17', 'DC:EF:09', 'C8:D3:A3',
+ # Luma WiFi
+ '44:61:32', '70:B3:D5', '9C:65:F9', 'C0:14:FE', 'E4:A7:A0',
+ # Gryphon Router
+ '00:1E:C7', '2C:AB:A4', '58:6D:8F', '84:B5:17', 'B0:10:41',
+ # Samsung SmartThings WiFi
+ '28:6D:CD', '5C:0A:5B', '88:36:6C', 'B4:E6:2D', 'E0:91:F5',
+ # Norton Core Router
+ '00:50:56', '00:0C:29', '00:05:69', '00:1C:14', '00:50:56'
+ ],
+
+ # Industrial and Enterprise Mesh
+ 'industrial_enterprise': [
+ # Cambium Networks
+ '00:04:56', '00:80:A1', '58:C1:7A', '84:1B:5E', 'B8:59:9F',
+ # Cradlepoint
+ '00:30:44', '8C:0E:E3', 'A4:93:4C', 'C0:EE:40', 'E4:E4:AB',
+ # Peplink
+ '00:15:FF', '00:1C:B5', '30:D1:7E', '6C:3B:E5', 'A8:1E:84',
+ # SonicWall
+ '00:06:B1', '00:17:C5', '2C:8A:72', '78:D2:94', 'C0:EA:E4',
+ # Fortinet FortiGate
+ '00:09:0F', '70:4C:A5', '90:6C:AC', 'A0:1D:48', 'B8:EE:65'
+ ]
+}
+
+ def identify_mesh_brand(self, bssids: List[str]) -> Optional[str]:
+ """Identify mesh system brand from BSSIDs"""
+ for bssid in bssids:
+ oui = ':'.join(bssid.split(':')[:3]).upper()
+ for brand, ouis in self.oui_database.items():
+ if oui in [o.upper() for o in ouis]:
+ return brand
+ return None
+
+ def analyze_mesh_topology(self, same_ssid_aps: List[APScan]) -> Dict:
+ """Analyze network structure - mesh vs single AP/WAP with appropriate evaluation"""
+ if len(same_ssid_aps) <= 1:
+ # Single AP - use signal strength analysis
+ ap = same_ssid_aps[0] if same_ssid_aps else None
+ if ap:
+ if ap.signal > -50:
+ quality = "excellent"
+ quality_reason = f"Strong signal ({ap.signal}dBm) indicates good placement"
+ elif ap.signal > -60:
+ quality = "good"
+ quality_reason = f"Good signal strength ({ap.signal}dBm)"
+ elif ap.signal > -75:
+ quality = "fair"
+ quality_reason = f"Moderate signal ({ap.signal}dBm) - consider moving closer or improving placement"
+ else:
+ quality = "poor"
+ quality_reason = f"Weak signal ({ap.signal}dBm) - poor placement or too far from AP"
+
+ return {
+ 'type': 'single_ap',
+ 'nodes': 1,
+ 'signal_quality': quality,
+ 'signal_reason': quality_reason,
+ 'signal_strength': ap.signal
+ }
+ else:
+ return {'type': 'single_ap', 'nodes': 0}
+
+ # Multiple APs - determine if it's a mesh or multiple standalone APs
+ mesh_nodes = {}
+ standalone_aps = []
+
+ for ap in same_ssid_aps:
+ base_mac = ':'.join(ap.bssid.split(':')[:-1])
+
+ # Check if this looks like a mesh node (same base MAC with different radios)
+ if base_mac in mesh_nodes:
+ # This is another radio on the same mesh node
+ mesh_nodes[base_mac]['radios'].append({
+ 'bssid': ap.bssid,
+ 'freq': ap.freq,
+ 'signal': ap.signal,
+ 'band': self._determine_band(ap.freq)
+ })
+ mesh_nodes[base_mac]['bands'].add(self._determine_band(ap.freq))
+ mesh_nodes[base_mac]['strongest_signal'] = max(
+ mesh_nodes[base_mac]['strongest_signal'], ap.signal
+ )
+ else:
+ # Check if any existing mesh node has a similar base MAC (mesh detection)
+ is_mesh_node = False
+ for existing_base in mesh_nodes.keys():
+ # If base MACs are very similar, likely same mesh system
+ if self._is_likely_same_mesh_system([base_mac, existing_base]):
+ is_mesh_node = True
+ break
+
+ if is_mesh_node or len([a for a in same_ssid_aps if ':'.join(a.bssid.split(':')[:-1]) == base_mac]) > 1:
+ # This is a mesh node
+ mesh_nodes[base_mac] = {
+ 'base_mac': base_mac,
+ 'radios': [{
+ 'bssid': ap.bssid,
+ 'freq': ap.freq,
+ 'signal': ap.signal,
+ 'band': self._determine_band(ap.freq)
+ }],
+ 'bands': {self._determine_band(ap.freq)},
+ 'strongest_signal': ap.signal
+ }
+ else:
+ # This looks like a standalone AP
+ standalone_aps.append(ap)
+
+ # If we have mesh nodes, analyze as mesh
+ if mesh_nodes:
+ return self._analyze_mesh_system(mesh_nodes, same_ssid_aps)
+ else:
+ # Multiple standalone APs with same SSID
+ return self._analyze_multiple_aps(same_ssid_aps)
+
+ def _determine_band(self, freq: int) -> str:
+ """Determine frequency band from frequency"""
+ if 2400 <= freq <= 2500:
+ return '2.4GHz'
+ elif 5000 <= freq <= 5999:
+ return '5GHz'
+ elif 6000 <= freq <= 7125:
+ return '6GHz'
+ else:
+ return 'other'
+
+ def _is_likely_same_mesh_system(self, base_macs: List[str]) -> bool:
+ """Check if base MACs likely belong to same mesh system"""
+ # Simple heuristic: if OUI matches and MACs are in sequence
+ if len(base_macs) < 2:
+ return False
+
+ ouis = [':'.join(mac.split(':')[:3]) for mac in base_macs]
+ return len(set(ouis)) == 1 # Same manufacturer
+
+ def _analyze_mesh_system(self, mesh_nodes: Dict, same_ssid_aps: List[APScan]) -> Dict:
+ """Analyze actual mesh system topology and health with proper spatial analysis"""
+ total_nodes = len(mesh_nodes)
+ brand = self.identify_mesh_brand([ap.bssid for ap in same_ssid_aps])
+
+ # Determine mesh type
+ all_bands = set()
+ for node in mesh_nodes.values():
+ all_bands.update(node['bands'])
+
+ if len(all_bands) >= 3:
+ mesh_type = 'tri_band'
+ elif len(all_bands) == 2:
+ mesh_type = 'dual_band'
+ else:
+ mesh_type = 'single_band'
+
+ # SOPHISTICATED SPATIAL ANALYSIS
+ signals = [node['strongest_signal'] for node in mesh_nodes.values()]
+ sorted_signals = sorted(signals, reverse=True)
+ signal_range = max(signals) - min(signals)
+
+ # Analyze signal distribution and coverage quality
+ coverage_analysis = self._perform_spatial_coverage_analysis(sorted_signals, mesh_nodes)
+
+ # VENN DIAGRAM ANALYSIS - RESTORED!
+ venn_data = self._generate_venn_analysis(mesh_nodes)
+
+ # Convert sets to lists for JSON serialization
+ for node in mesh_nodes.values():
+ if isinstance(node['bands'], set):
+ node['bands'] = list(node['bands'])
+
+ result = {
+ 'type': 'mesh',
+ 'brand': brand or 'unknown',
+ 'mesh_type': mesh_type,
+ 'total_nodes': total_nodes,
+ 'total_radios': len(same_ssid_aps),
+ 'bands': sorted(list(all_bands)),
+ 'signal_range': signal_range,
+ 'mesh_nodes': mesh_nodes,
+ 'signal_distribution': sorted_signals,
+ 'coverage_analysis': coverage_analysis,
+ 'venn_analysis': venn_data # ADDED: Venn diagram data
+ }
+
+ # Legacy fields for compatibility
+ result['topology_health'] = coverage_analysis['topology_classification']
+ result['coverage_reason'] = coverage_analysis['summary']
+ result['coverage_health'] = coverage_analysis['spatial_distribution']
+ result['coverage_details'] = {
+ 'signal_range': signal_range,
+ 'strongest_node': max(signals),
+ 'weakest_node': min(signals),
+ 'total_nodes': total_nodes,
+ 'radios_per_node': len(same_ssid_aps) / total_nodes
+ }
+
+ return result
+
+ def _generate_venn_analysis(self, mesh_nodes: Dict) -> Dict:
+ """Generate Venn diagram analysis for mesh overlap visualization"""
+ try:
+ venn_calculator = MeshVennCalculator()
+
+ # Prepare nodes data for Venn analysis
+ nodes_for_venn = []
+ for i, (node_id, node_data) in enumerate(mesh_nodes.items()):
+ node_for_venn = {
+ 'id': i,
+ 'label': f"Node {node_id[-8:]}",
+ 'signal': node_data['strongest_signal'],
+ 'bssid': node_id,
+ 'radios': len(node_data['radios']),
+ 'bands': list(node_data['bands']) if isinstance(node_data['bands'], set) else node_data['bands']
+ }
+ nodes_for_venn.append(node_for_venn)
+
+ # Generate Venn diagram data
+ venn_data = venn_calculator.generate_venn_data(nodes_for_venn)
+
+ # Get overlap quality assessment
+ quality_assessment = venn_calculator.get_overlap_quality_assessment(venn_data)
+
+ return {
+ 'venn_diagram': venn_data,
+ 'overlap_quality': quality_assessment,
+ 'total_nodes': len(nodes_for_venn),
+ 'overlap_count': venn_data.get('overlap_count', 0),
+ 'coverage_efficiency': quality_assessment.get('score', 0)
+ }
+
+ except Exception as e:
+ # Fallback if Venn calculator fails
+ return {
+ 'venn_diagram': {'nodes': [], 'overlaps': [], 'total_coverage': 0},
+ 'overlap_quality': {'quality': 'unavailable', 'score': 0, 'description': f'Venn analysis failed: {str(e)}'},
+ 'total_nodes': len(mesh_nodes),
+ 'overlap_count': 0,
+ 'coverage_efficiency': 0
+ }
+
+ def _perform_spatial_coverage_analysis(self, sorted_signals: List[int], mesh_nodes: Dict) -> Dict:
+ """Perform sophisticated spatial coverage analysis based on signal distribution patterns"""
+
+ # Calculate signal gradients and gaps
+ signal_gaps = []
+ for i in range(len(sorted_signals) - 1):
+ gap = sorted_signals[i] - sorted_signals[i + 1]
+ signal_gaps.append(gap)
+
+ max_gap = max(signal_gaps) if signal_gaps else 0
+ avg_gap = sum(signal_gaps) / len(signal_gaps) if signal_gaps else 0
+
+ # Analyze coverage zones based on signal strength
+ zones = self._classify_coverage_zones(sorted_signals)
+
+ # Detect coverage problems
+ coverage_issues = self._detect_coverage_issues(sorted_signals, signal_gaps, zones)
+
+ # Overall topology assessment
+ topology_assessment = self._assess_mesh_topology(sorted_signals, signal_gaps, zones, coverage_issues)
+
+ return {
+ 'sorted_signals': sorted_signals,
+ 'signal_gaps': signal_gaps,
+ 'max_signal_gap': max_gap,
+ 'avg_signal_gap': avg_gap,
+ 'coverage_zones': zones,
+ 'coverage_issues': coverage_issues,
+ 'topology_classification': topology_assessment['classification'],
+ 'summary': topology_assessment['summary'],
+ 'spatial_distribution': topology_assessment['distribution_analysis'],
+ 'recommendations': topology_assessment['recommendations'],
+ 'coverage_quality_score': topology_assessment['quality_score']
+ }
+
+ def _classify_coverage_zones(self, sorted_signals: List[int]) -> Dict:
+ """Classify coverage into spatial zones based on signal strength"""
+ zones = {
+ 'primary': [], # > -50dBm (excellent, close range)
+ 'secondary': [], # -50 to -65dBm (good, medium range)
+ 'tertiary': [], # -65 to -80dBm (fair, extended range)
+ 'fringe': [] # < -80dBm (poor, maximum range)
+ }
+
+ for signal in sorted_signals:
+ if signal > -50:
+ zones['primary'].append(signal)
+ elif signal > -65:
+ zones['secondary'].append(signal)
+ elif signal > -80:
+ zones['tertiary'].append(signal)
+ else:
+ zones['fringe'].append(signal)
+
+ return zones
+
+ def _detect_coverage_issues(self, sorted_signals: List[int], signal_gaps: List[int], zones: Dict) -> List[Dict]:
+ """Detect specific coverage and distribution issues"""
+ issues = []
+
+ # Large signal gap detection (potential dead zones)
+ for i, gap in enumerate(signal_gaps):
+ if gap > 25:
+ issues.append({
+ 'type': 'large_coverage_gap',
+ 'severity': 'high' if gap > 35 else 'medium',
+ 'details': f"{gap}dB gap between node {i+1} ({sorted_signals[i]}dBm) and node {i+2} ({sorted_signals[i+1]}dBm)",
+ 'impact': 'Potential dead zone or weak coverage area',
+ 'location': f"Between {self._signal_to_distance_estimate(sorted_signals[i])} and {self._signal_to_distance_estimate(sorted_signals[i+1])}"
+ })
+
+ # Coverage zone analysis
+ if not zones['secondary'] and zones['primary'] and zones['tertiary']:
+ issues.append({
+ 'type': 'missing_intermediate_coverage',
+ 'severity': 'medium',
+ 'details': 'No medium-range coverage detected',
+ 'impact': 'May have coverage gaps between close and distant areas',
+ 'location': 'Medium-range areas (adjacent rooms/floors)'
+ })
+
+ # Clustering detection
+ if len(zones['primary']) > len(sorted_signals) * 0.6:
+ issues.append({
+ 'type': 'node_clustering',
+ 'severity': 'low',
+ 'details': f"{len(zones['primary'])} of {len(sorted_signals)} nodes in primary zone",
+ 'impact': 'Possible over-concentration of nodes in small area',
+ 'location': 'Primary coverage area'
+ })
+
+ # Extended range without intermediate coverage
+ if zones['fringe'] and not zones['tertiary']:
+ issues.append({
+ 'type': 'isolated_distant_node',
+ 'severity': 'medium',
+ 'details': f"Distant node at {min(sorted_signals)}dBm without intermediate coverage",
+ 'impact': 'Isolated coverage with potential gap to main mesh',
+ 'location': f"~{self._signal_to_distance_estimate(min(sorted_signals))}"
+ })
+
+ return issues
+
+ def _assess_mesh_topology(self, sorted_signals: List[int], signal_gaps: List[int], zones: Dict, issues: List[Dict]) -> Dict:
+ """Comprehensive mesh topology assessment"""
+
+ # Calculate quality score (0-100)
+ quality_score = 100
+
+ # Penalize for coverage issues
+ for issue in issues:
+ if issue['severity'] == 'high':
+ quality_score -= 25
+ elif issue['severity'] == 'medium':
+ quality_score -= 15
+ elif issue['severity'] == 'low':
+ quality_score -= 5
+
+ # Reward good signal distribution
+ if len(zones['secondary']) > 0:
+ quality_score += 10 # Good intermediate coverage
+
+ if max(signal_gaps) < 20:
+ quality_score += 15 # Smooth signal transitions
+
+ # Node count assessment
+ node_count = len(sorted_signals)
+ if node_count >= 4:
+ base_rating = "excellent_nodes"
+ node_assessment = f"{node_count} nodes detected - excellent for comprehensive coverage"
+ elif node_count == 3:
+ base_rating = "good_nodes"
+ node_assessment = f"{node_count} nodes detected - good for most home sizes"
+ elif node_count == 2:
+ base_rating = "basic_nodes"
+ node_assessment = f"{node_count} nodes detected - basic mesh configuration"
+ else:
+ base_rating = "single_node"
+ node_assessment = f"{node_count} node detected - not a true mesh"
+
+ # Distribution analysis
+ max_gap = max(signal_gaps) if signal_gaps else 0
+
+ if max_gap > 30:
+ distribution = "poor_distribution"
+ dist_analysis = f"Large signal gaps detected (max {max_gap}dB) - potential coverage holes"
+ elif max_gap > 20:
+ distribution = "uneven_distribution"
+ dist_analysis = f"Moderate signal gaps (max {max_gap}dB) - some coverage irregularities"
+ elif max_gap > 10:
+ distribution = "good_distribution"
+ dist_analysis = f"Well-spaced nodes (max gap {max_gap}dB) - good coverage continuity"
+ else:
+ distribution = "excellent_distribution"
+ dist_analysis = f"Smooth signal transitions (max gap {max_gap}dB) - excellent spatial distribution"
+
+ # Overall classification
+ high_severity_issues = [i for i in issues if i['severity'] == 'high']
+ medium_severity_issues = [i for i in issues if i['severity'] == 'medium']
+
+ if high_severity_issues:
+ classification = "topology_issues"
+ summary = f"{node_assessment} but significant coverage gaps detected"
+ elif medium_severity_issues and node_count < 3:
+ classification = "basic_topology"
+ summary = f"{node_assessment} with some coverage limitations"
+ elif medium_severity_issues:
+ classification = "good_topology"
+ summary = f"{node_assessment} with minor coverage irregularities"
+ elif node_count >= 4 and quality_score > 85:
+ classification = "excellent_topology"
+ summary = f"{node_assessment} with excellent spatial distribution"
+ elif node_count >= 3 and quality_score > 75:
+ classification = "good_topology"
+ summary = f"{node_assessment} with good spatial coverage"
+ else:
+ classification = "basic_topology"
+ summary = f"{node_assessment} - adequate but could be optimized"
+
+ # Recommendations
+ recommendations = []
+ for issue in issues:
+ if issue['type'] == 'large_coverage_gap':
+ recommendations.append(f"Consider adding a node in {issue['location']} to eliminate coverage gap")
+ elif issue['type'] == 'missing_intermediate_coverage':
+ recommendations.append("Add intermediate nodes for smoother coverage transitions")
+ elif issue['type'] == 'node_clustering':
+ recommendations.append("Consider relocating some nodes for better spatial distribution")
+ elif issue['type'] == 'isolated_distant_node':
+ recommendations.append("Add intermediate nodes to bridge coverage to distant areas")
+
+ if not recommendations and quality_score > 90:
+ recommendations.append("Excellent mesh topology - no improvements needed")
+ elif not recommendations:
+ recommendations.append("Good mesh topology - minor optimizations possible")
+
+ return {
+ 'classification': classification,
+ 'summary': summary,
+ 'distribution_analysis': dist_analysis,
+ 'quality_score': max(0, min(100, quality_score)),
+ 'recommendations': recommendations,
+ 'node_assessment': node_assessment,
+ 'distribution_quality': distribution
+ }
+
+ def _signal_to_distance_estimate(self, signal_dbm: int) -> str:
+ """Estimate approximate distance/location based on signal strength"""
+ if signal_dbm > -40:
+ return "very close (same room)"
+ elif signal_dbm > -50:
+ return "close (adjacent room)"
+ elif signal_dbm > -65:
+ return "medium range (different floor/far room)"
+ elif signal_dbm > -80:
+ return "extended range (distant area)"
+ else:
+ return "maximum range (basement/garage/far areas)"
+
+ def _analyze_multiple_aps(self, same_ssid_aps: List[APScan]) -> Dict:
+ """Analyze multiple standalone APs with same SSID"""
+ signals = [ap.signal for ap in same_ssid_aps]
+ strongest_signal = max(signals)
+
+ if strongest_signal > -50:
+ quality = "excellent"
+ quality_reason = f"Strong signals available ({strongest_signal}dBm) from multiple APs"
+ elif strongest_signal > -60:
+ quality = "good"
+ quality_reason = f"Good signal options ({strongest_signal}dBm) from {len(same_ssid_aps)} APs"
+ elif strongest_signal > -75:
+ quality = "fair"
+ quality_reason = f"Moderate signals ({strongest_signal}dBm) - consider moving closer to APs"
+ else:
+ quality = "poor"
+ quality_reason = f"Weak signals from all APs ({strongest_signal}dBm) - poor coverage area"
+
+ # Convert APScan objects to dictionaries for JSON serialization
+ ap_list = [ap.to_dict() for ap in same_ssid_aps]
+
+ return {
+ 'type': 'multiple_aps',
+ 'nodes': len(same_ssid_aps),
+ 'signal_quality': quality,
+ 'signal_reason': quality_reason,
+ 'strongest_signal': strongest_signal,
+ 'ap_list': ap_list
+ }
+
+class ProblemDetector:
+ """Detect WiFi connection problems from logs and events"""
+
+ def __init__(self, history_tracker):
+ self.history = history_tracker
+
+ def analyze_connection_patterns(self, window_hours: int = 24) -> Dict:
+ """Analyze connection patterns for problems"""
+ events = self.history.get_recent_events(window_hours)
+
+ patterns = {
+ 'roaming_loops': [],
+ 'auth_failure_clusters': [],
+ 'rapid_disconnects': [],
+ 'time_based_issues': {},
+ 'bssid_specific_problems': {}
+ }
+
+ self._detect_roaming_loops(events, patterns)
+ self._detect_auth_clusters(events, patterns)
+ self._detect_rapid_cycles(events, patterns)
+ self._analyze_time_patterns(events, patterns)
+ self._analyze_bssid_problems(events, patterns)
+
+ return patterns
+
+ def _detect_roaming_loops(self, events: List[ConnectionEvent], patterns: Dict):
+ """Detect roaming loops between BSSIDs"""
+ connects = [e for e in events if e.event_type == 'connect']
+
+ for i in range(len(connects) - 3):
+ bssids = [connects[j].bssid for j in range(i, i + 4)]
+ if (bssids[0] == bssids[2] and bssids[1] == bssids[3] and bssids[0] != bssids[1]):
+ time_span = connects[i + 3].timestamp - connects[i].timestamp
+ if time_span < 300: # 5 minutes
+ patterns['roaming_loops'].append({
+ 'bssids': [bssids[0], bssids[1]],
+ 'time_span': time_span,
+ 'start_time': connects[i].timestamp
+ })
+
+ def _detect_auth_clusters(self, events: List[ConnectionEvent], patterns: Dict):
+ """Detect clusters of authentication failures"""
+ auth_failures = [e for e in events if e.event_type == 'auth_timeout']
+
+ for bssid in set(e.bssid for e in auth_failures):
+ bssid_failures = [e for e in auth_failures if e.bssid == bssid]
+
+ if len(bssid_failures) >= 3:
+ timestamps = sorted([e.timestamp for e in bssid_failures])
+ clusters = []
+ current_cluster = [timestamps[0]]
+
+ for i in range(1, len(timestamps)):
+ if timestamps[i] - timestamps[i-1] < 300: # Within 5 minutes
+ current_cluster.append(timestamps[i])
+ else:
+ if len(current_cluster) >= 3:
+ clusters.append(current_cluster)
+ current_cluster = [timestamps[i]]
+
+ if len(current_cluster) >= 3:
+ clusters.append(current_cluster)
+
+ for cluster in clusters:
+ patterns['auth_failure_clusters'].append({
+ 'bssid': bssid,
+ 'failure_count': len(cluster),
+ 'time_span': cluster[-1] - cluster[0],
+ 'start_time': cluster[0]
+ })
+
+ def _detect_rapid_cycles(self, events: List[ConnectionEvent], patterns: Dict):
+ """Detect rapid disconnect/reconnect cycles"""
+ for i in range(len(events) - 1):
+ if (events[i].event_type == 'disconnect' and
+ events[i + 1].event_type == 'connect' and
+ events[i + 1].timestamp - events[i].timestamp < 60):
+
+ patterns['rapid_disconnects'].append({
+ 'bssid': events[i].bssid,
+ 'cycle_duration': events[i + 1].timestamp - events[i].timestamp
+ })
+
+ def _analyze_time_patterns(self, events: List[ConnectionEvent], patterns: Dict):
+ """Analyze time-based problem patterns"""
+ hourly_problems = defaultdict(list)
+
+ for event in events:
+ if event.event_type in ['auth_timeout', 'disconnect']:
+ hour = datetime.fromtimestamp(event.timestamp).hour
+ hourly_problems[hour].append(event)
+
+ for hour, hour_events in hourly_problems.items():
+ if len(hour_events) >= 5:
+ patterns['time_based_issues'][hour] = {
+ 'problem_count': len(hour_events),
+ 'problem_types': list(set(e.event_type for e in hour_events)),
+ 'affected_bssids': list(set(e.bssid for e in hour_events))
+ }
+
+ def _analyze_bssid_problems(self, events: List[ConnectionEvent], patterns: Dict):
+ """Analyze per-BSSID specific problems"""
+ bssid_events = defaultdict(list)
+
+ for event in events:
+ bssid_events[event.bssid].append(event)
+
+ for bssid, bssid_event_list in bssid_events.items():
+ problem_events = [e for e in bssid_event_list
+ if e.event_type in ['auth_timeout', 'disconnect']]
+
+ if len(problem_events) >= 3:
+ patterns['bssid_specific_problems'][bssid] = {
+ 'total_problems': len(problem_events),
+ 'auth_failures': len([e for e in problem_events if e.event_type == 'auth_timeout']),
+ 'disconnects': len([e for e in problem_events if e.event_type == 'disconnect']),
+ 'problem_rate': len(problem_events) / len(bssid_event_list) if bssid_event_list else 0
+ }
+
+class NetworkAnalyzer:
+ def __init__(self, interface: str):
+ self.interface = interface
+ self.signal_history = defaultdict(lambda: deque(maxlen=10))
+
+ # Initialize logging first
+ data_dir = self._get_data_dir()
+ self.log_manager = LogManager(Path(data_dir))
+
+ # Initialize components with logging
+ self.history_tracker = HistoryTracker(data_dir, self.log_manager)
+ self.mesh_intelligence = MeshIntelligence()
+ self.problem_detector = ProblemDetector(self.history_tracker)
+ self.connection_history = deque(maxlen=20)
+ # Initialize optional modules - Matt is testing some new functionality - the new detector classes
+ self.roaming_detector = None
+ self.power_detective = None
+
+ if ROAMING_DETECTOR_AVAILABLE:
+ self.roaming_detector = MeshRoamingDetector(self.interface)
+ print("✅ Roaming detector module loaded")
+
+ if POWER_DETECTIVE_AVAILABLE:
+ self.power_detective = MeshPowerDetective(self.interface)
+ print("✅ Power detective module loaded")
+
+ # Log session start
+ self.log_manager.log_analysis_start(interface)
+
+ # Start background monitoring
+ self._monitoring = True
+ self._monitor_thread = threading.Thread(target=self._background_monitor, daemon=True)
+ self._monitor_thread.start()
+
+ def _format_power_data_for_html(self, power_issues):
+ """Format power issues data for HTML reporter"""
+ if not power_issues:
+ return {'issues_found': False}
+
+ # Count issues by severity
+ severity_counts = {'high': 0, 'medium': 0, 'low': 0, 'info': 0}
+ total_issues = 0
+
+ for category_issues in power_issues.values():
+ for issue in category_issues:
+ severity = issue.get('severity', 'low')
+ if severity in severity_counts:
+ severity_counts[severity] += 1
+ total_issues += 1
+
+ return {
+ 'issues_found': total_issues > 0,
+ 'severity_counts': severity_counts,
+ 'total_issues': total_issues
+ }
+
+ def _get_data_dir(self) -> str:
+ """Get data directory path"""
+ if os.geteuid() == 0 and 'SUDO_USER' in os.environ:
+ sudo_user = os.environ['SUDO_USER']
+ import pwd
+ real_user_home = pwd.getpwnam(sudo_user).pw_dir
+ return os.path.join(real_user_home, ".mesh_analyzer")
+ else:
+ home = os.path.expanduser("~")
+ return os.path.join(home, ".mesh_analyzer")
+
+ def _background_monitor(self):
+ """Background thread to monitor connection events"""
+ last_connection = None
+ connection_start = None
+
+ while self._monitoring:
+ try:
+ current = self.get_current_connection()
+
+ if current and (not last_connection or current['bssid'] != last_connection['bssid']):
+ # New connection
+ if last_connection and connection_start:
+ duration = time.time() - connection_start
+ self.history_tracker.record_event(ConnectionEvent(
+ timestamp=time.time(),
+ bssid=last_connection['bssid'],
+ event_type='disconnect',
+ signal=last_connection['signal'],
+ duration=duration
+ ))
+
+ if current:
+ self.history_tracker.record_event(ConnectionEvent(
+ timestamp=time.time(),
+ bssid=current['bssid'],
+ event_type='connect',
+ signal=current['signal']
+ ))
+ connection_start = time.time()
+ last_connection = current
+
+ elif not current and last_connection:
+ # Disconnected
+ if connection_start:
+ duration = time.time() - connection_start
+ self.history_tracker.record_event(ConnectionEvent(
+ timestamp=time.time(),
+ bssid=last_connection['bssid'],
+ event_type='disconnect',
+ signal=last_connection['signal'],
+ duration=duration
+ ))
+ last_connection = None
+ connection_start = None
+
+ time.sleep(10) # Check every 10 seconds
+
+ except Exception:
+ time.sleep(30)
+
+ def run_cmd(self, cmd: str, timeout: int = 8) -> str:
+ """Execute command with timeout and logging"""
+ try:
+ start_time = time.time()
+ result = subprocess.run(cmd, shell=True, text=True,
+ capture_output=True, timeout=timeout)
+ duration = time.time() - start_time
+
+ output = result.stdout.strip()
+ if self.log_manager:
+ self.log_manager.log_command_execution(cmd, output, duration)
+
+ return output
+ except Exception as e:
+ if self.log_manager:
+ self.log_manager.log_error(e, f"run_cmd: {cmd}")
+ return ""
+
+ def get_current_connection(self) -> Optional[Dict]:
+ """Get current connection details"""
+ link_output = self.run_cmd(f"iw dev {self.interface} link")
+ if "Connected to" not in link_output:
+ return None
+
+ bssid_match = re.search(r"Connected to ([0-9A-Fa-f:]{17})", link_output)
+ ssid_match = re.search(r"SSID:\s*(.*)", link_output)
+ freq_match = re.search(r"freq:\s*(\d+)", link_output)
+ signal_match = re.search(r"signal:\s*(-?\d+)", link_output)
+
+ if not all([bssid_match, ssid_match, freq_match]):
+ return None
+
+ # Clean BSSID
+ bssid_raw = bssid_match.group(1)
+ bssid_clean = bssid_raw.split('(')[0].strip().upper()
+
+ if len(bssid_clean) != 17 or bssid_clean.count(':') != 5:
+ return None
+
+ return {
+ 'ssid': ssid_match.group(1).strip(),
+ 'bssid': bssid_clean,
+ 'freq': int(freq_match.group(1)),
+ 'signal': int(signal_match.group(1)) if signal_match else -100
+ }
+
+ def comprehensive_scan(self) -> List[APScan]:
+ """Perform detailed network scan with logging"""
+ scan_start_time = time.time()
+ aps = {}
+ current_time = time.time()
+
+ # Primary scan with iw
+ scan_output = self.run_cmd(f"iw dev {self.interface} scan flush", timeout=15)
+ if not scan_output:
+ scan_output = self.run_cmd(f"iw dev {self.interface} scan", timeout=12)
+
+ current_ap = {}
+ for line in scan_output.split('\n'):
+ line = line.strip()
+
+ if line.startswith('BSS '):
+ if current_ap and 'bssid' in current_ap:
+ ap = self._parse_ap_data(current_ap, current_time)
+ if ap and ap.ssid != '': # Filter hidden SSIDs
+ aps[ap.bssid] = ap
+
+ # Extract BSSID
+ bss_part = line[4:].strip()
+ if len(bss_part) >= 17:
+ bssid = bss_part[:17].upper()
+ if bssid.count(':') == 5:
+ current_ap = {'bssid': bssid, 'capabilities': set()}
+ else:
+ current_ap = {}
+ else:
+ current_ap = {}
+
+ elif current_ap:
+ if 'SSID:' in line:
+ current_ap['ssid'] = line.split('SSID: ', 1)[1] if ': ' in line else ''
+ elif 'freq:' in line:
+ freq_match = re.search(r'freq: (\d+)', line)
+ if freq_match:
+ current_ap['freq'] = int(freq_match.group(1))
+ elif 'signal:' in line:
+ signal_match = re.search(r'signal: (-?\d+\.\d+)', line)
+ if signal_match:
+ current_ap['signal'] = int(float(signal_match.group(1)))
+
+ # Handle last AP
+ if current_ap and 'bssid' in current_ap:
+ ap = self._parse_ap_data(current_ap, current_time)
+ if ap and ap.ssid != '':
+ aps[ap.bssid] = ap
+
+ scan_duration = time.time() - scan_start_time
+ ap_list = list(aps.values())
+
+ # Log scan results
+ if self.log_manager:
+ self.log_manager.log_network_scan(len(ap_list), scan_duration)
+
+ return ap_list
+
+ def _parse_ap_data(self, ap_data: dict, timestamp: float) -> Optional[APScan]:
+ """Create APScan from parsed data"""
+ try:
+ return APScan(
+ ssid=ap_data.get('ssid', ''),
+ bssid=ap_data['bssid'],
+ freq=ap_data.get('freq', 0),
+ signal=ap_data.get('signal', -100),
+ capabilities=ap_data.get('capabilities', set()),
+ last_seen=timestamp
+ )
+ except KeyError:
+ return None
+
+ def _analyze_available_alternatives(self, current_conn: Dict) -> List[Dict]:
+ """Analyze available BSSID alternatives with smarter band-aware scoring"""
+ same_ssid_aps = [ap for ap in getattr(self, '_current_aps', []) if ap.ssid == current_conn['ssid']]
+
+ alternatives = []
+ current_bssid = current_conn['bssid'].upper()
+ current_band = self._get_band_from_freq(current_conn['freq'])
+ current_signal = current_conn['signal']
+
+ for ap in same_ssid_aps:
+ if ap.bssid.upper() == current_bssid:
+ continue
+
+ # Get historical performance
+ history = self.history_tracker.get_bssid_performance(ap.bssid)
+ alt_band = self._get_band_from_freq(ap.freq)
+
+ # Score this alternative with smarter logic
+ score = 100
+ recommendation_reasons = []
+
+ # Historical performance scoring
+ if history and history.stability_score > 0:
+ score += min(history.stability_score * 0.3, 30)
+ recommendation_reasons.append(f"Stability: {history.stability_score:.0f}%")
+ else:
+ recommendation_reasons.append("No historical data")
+
+ # Smart signal evaluation - consider both strength and band capabilities
+ signal_diff = ap.signal - current_signal
+
+ # Only recommend if there's a compelling reason
+ compelling_reason = False
+
+ # Case 1: Significantly stronger signal (>15dB) on any band
+ if signal_diff > 15:
+ score += 25
+ compelling_reason = True
+ recommendation_reasons.append(f"Major signal boost (+{signal_diff}dB)")
+
+ # Case 2: Current signal is weak (<-70dBm) and alternative is stronger
+ elif current_signal < -70 and signal_diff > 5:
+ score += 20
+ compelling_reason = True
+ recommendation_reasons.append(f"Escape weak signal zone (+{signal_diff}dB)")
+
+ # Case 3: Moving to 5GHz from 2.4GHz with good signal
+ elif current_band == '2.4GHz' and alt_band == '5GHz' and ap.signal > -65:
+ score += 15
+ compelling_reason = True
+ recommendation_reasons.append(f"5GHz upgrade opportunity ({ap.signal}dBm)")
+
+ # Case 4: Current 6GHz signal is marginal (<-60dBm) and 5GHz/2.4GHz is much stronger
+ elif current_band == '6GHz' and current_signal < -60 and signal_diff > 10:
+ score += 10
+ compelling_reason = True
+ recommendation_reasons.append(f"6GHz signal marginal, better alternative (+{signal_diff}dB)")
+
+ # Otherwise, be conservative about recommendations
+ else:
+ # Penalize downgrades from high-performance bands with good signals
+ if current_band == '6GHz' and current_signal > -60 and alt_band in ['2.4GHz', '5GHz']:
+ score -= 30
+ recommendation_reasons.append(f"Potential speed downgrade from {current_band}")
+ elif current_band == '5GHz' and current_signal > -65 and alt_band == '2.4GHz':
+ score -= 20
+ recommendation_reasons.append(f"Potential speed downgrade from {current_band}")
+ else:
+ recommendation_reasons.append(f"Minimal benefit (+{signal_diff}dB)")
+
+ # Band-specific bonuses only when it makes sense
+ if alt_band == '5GHz' and (current_band == '2.4GHz' or (current_band == '6GHz' and current_signal < -65)):
+ score += 5
+ recommendation_reasons.append("Good speed/range balance")
+ elif alt_band == '6GHz' and current_band != '6GHz' and ap.signal > -55:
+ score += 10
+ recommendation_reasons.append("Maximum speed potential")
+ elif alt_band == '2.4GHz' and current_signal < -75:
+ score += 5
+ recommendation_reasons.append("Better range/penetration")
+
+ # Signal quality assessment
+ if ap.signal > -50:
+ recommendation_reasons.append(f"Excellent signal ({ap.signal}dBm)")
+ elif ap.signal > -60:
+ recommendation_reasons.append(f"Good signal ({ap.signal}dBm)")
+ elif ap.signal > -70:
+ recommendation_reasons.append(f"Fair signal ({ap.signal}dBm)")
+ else:
+ recommendation_reasons.append(f"Weak signal ({ap.signal}dBm)")
+ score -= 15
+
+ # Determine overall recommendation based on compelling reasons
+ if compelling_reason and score >= 120:
+ recommendation = "EXCELLENT"
+ elif compelling_reason and score >= 100:
+ recommendation = "GOOD"
+ elif score >= 90:
+ recommendation = "FAIR"
+ else:
+ recommendation = "POOR"
+
+ alternatives.append({
+ 'bssid': ap.bssid,
+ 'signal': ap.signal,
+ 'freq': ap.freq,
+ 'score': score,
+ 'recommendation': recommendation,
+ 'reasons': recommendation_reasons,
+ 'signal_diff': signal_diff,
+ 'stability_score': history.stability_score if history else None,
+ 'compelling_reason': compelling_reason,
+ 'band': alt_band
+ })
+
+ # Sort by score (best first)
+ alternatives.sort(key=lambda x: x['score'], reverse=True)
+ return alternatives[:5] # Show top 5
+
+ def _get_band_from_freq(self, freq: int) -> str:
+ """Get band name from frequency"""
+ if 2400 <= freq <= 2500:
+ return '2.4GHz'
+ elif 5000 <= freq <= 5999:
+ return '5GHz'
+ elif 6000 <= freq <= 7125:
+ return '6GHz'
+ else:
+ return f'{freq}MHz'
+
+ def generate_html_report(self):
+ """Generate interactive HTML report of mesh analysis with Venn overlap"""
+ try:
+ print("\n🌐 GENERATING HTML REPORT")
+ print("─" * 60)
+
+ # Get current connection
+ current_conn = self.get_current_connection()
+
+ # Use existing scan data or perform new scan
+ if not hasattr(self, '_current_aps') or not self._current_aps:
+ print("🔍 Scanning networks for HTML report...")
+ aps = self.comprehensive_scan()
+ self._current_aps = aps
+ else:
+ aps = self._current_aps
+
+ # Prepare comprehensive analysis data
+ analysis_data = {}
+
+ if current_conn:
+ same_ssid_aps = [ap for ap in aps if ap.ssid == current_conn['ssid']]
+
+ # Mesh topology analysis
+ print("📊 Analyzing mesh topology...")
+ mesh_analysis = self.mesh_intelligence.analyze_mesh_topology(same_ssid_aps)
+ analysis_data['mesh_analysis'] = mesh_analysis
+
+ # Alternative options analysis
+ if len(same_ssid_aps) > 1:
+ print("🔍 Evaluating alternatives...")
+ alternatives = self._analyze_available_alternatives(current_conn)
+ analysis_data['alternatives'] = alternatives
+ else:
+ analysis_data['alternatives'] = []
+
+ # Historical performance data
+ print("📈 Gathering historical data...")
+ current_history = self.history_tracker.get_bssid_performance(current_conn['bssid'])
+ if current_history:
+ analysis_data['historical_data'] = {
+ 'stability_score': current_history.stability_score,
+ 'total_connections': current_history.total_connections,
+ 'success_rate': (current_history.successful_connections / max(current_history.total_connections, 1) * 100),
+ 'avg_signal': current_history.avg_signal,
+ 'auth_failures': current_history.auth_failures,
+ 'disconnects': current_history.disconnects
+ }
+ else:
+ analysis_data['historical_data'] = {}
+
+ # Problem pattern detection
+ print("🚨 Detecting problems...")
+ problems = self.problem_detector.analyze_connection_patterns(24)
+ analysis_data['problems'] = problems
+ else:
+ # No connection - provide empty data
+ analysis_data = {
+ 'mesh_analysis': {'type': 'no_connection'},
+ 'alternatives': [],
+ 'historical_data': {},
+ 'problems': {}
+ }
+
+ # Include roaming and power data if available
+ analysis_data['roaming_data'] = getattr(self, 'roaming_data', {})
+ analysis_data['power_data'] = getattr(self, 'power_data', {})
+
+ # Generate the HTML report
+ print("📝 Generating HTML visualization with mesh overlap analysis...")
+
+ # Check if we have the updated HTML reporter
+ if UPDATED_HTML_REPORTER_AVAILABLE:
+ reporter = MeshHTMLReporter()
+ report_path = reporter.generate_report(analysis_data, current_conn)
+ else:
+ # Fallback to basic HTML generation
+ report_path = self._generate_basic_html_report(analysis_data, current_conn)
+
+ if report_path:
+ print(f"✅ HTML Report Generated Successfully!")
+ print(f" 📁 Location: {report_path}")
+ print(f" 🌐 Open in browser: file://{report_path}")
+ print(f" 📊 Report includes: mesh topology, signal analysis, recommendations, historical data")
+
+ # Log the report generation
+ if self.log_manager:
+ self.log_manager.analysis_logger.info(f"HTML report generated: {report_path}")
+ else:
+ print("❌ Failed to generate HTML report")
+
+ return report_path
+
+ except Exception as e:
+ print(f"❌ Error generating HTML report: {e}")
+ if hasattr(self, 'log_manager'):
+ self.log_manager.log_error(e, "generate_html_report")
+ import traceback
+ traceback.print_exc()
+ return None
+
+ def _generate_basic_html_report(self, analysis_data, current_conn):
+ """Fallback basic HTML report generator"""
+ try:
+ from datetime import datetime
+ from pathlib import Path
+
+ # Create reports directory - FIXED: Use same logic as data_dir for consistency
+ data_dir = Path(self._get_data_dir())
+ reports_dir = data_dir / "reports"
+ reports_dir.mkdir(parents=True, exist_ok=True)
+
+ # Fix permissions if running as sudo
+ if os.geteuid() == 0 and 'SUDO_USER' in os.environ:
+ sudo_user = os.environ['SUDO_USER']
+ import pwd
+ user_info = pwd.getpwnam(sudo_user)
+ os.chown(reports_dir, user_info.pw_uid, user_info.pw_gid)
+
+ # Generate filename
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ filename = f"mesh_analysis_{timestamp}.html"
+ report_path = reports_dir / filename
+
+ # Basic HTML content
+ html_content = f"""
+
+
+
+
+ Codestin Search App
+
+
+
+
+
+ """
+
+ def _generate_alternatives_section(self, alternatives: List[Dict], current_connection: Optional[Dict]) -> str:
+ """Generate alternatives analysis section"""
+ if not alternatives or not current_connection:
+ return """
+
+
🎯 Connection Alternatives
+
+
✅ Current connection appears optimal or no alternatives available
+
+
+ """
+
+ # Check if any alternatives are compelling
+ compelling_alternatives = [alt for alt in alternatives if alt.get('compelling_reason', False)]
+
+ alternatives_html = """
+
+
🎯 Connection Alternatives
+ """
+
+ if compelling_alternatives:
+ best = compelling_alternatives[0]
+ alternatives_html += f"""
+
+
+ 💡
+ Performance Optimization Opportunity
+
+
+
Recommended BSSID: {best['bssid']}
+
Expected Improvement: {best['signal_diff']:+d}dB signal strength
+
Quality Rating: {best['recommendation']}
+
+
+ """
+ else:
+ alternatives_html += """
+
+
+ ✅
+ Current Connection is Optimal
+
+
+
Your current connection is performing well among available options.
+
+
+ """
+
+ # Show top alternatives
+ alternatives_html += """
+
+
📊 Available Options
+
+ """
+
+ for i, alt in enumerate(alternatives[:3], 1):
+ band = self._get_band_from_freq(alt.get('freq', 0))
+
+ if alt['recommendation'] == 'EXCELLENT':
+ alt_class = "excellent"
+ alt_emoji = "🟢"
+ elif alt['recommendation'] == 'GOOD':
+ alt_class = "good"
+ alt_emoji = "🟡"
+ elif alt['recommendation'] == 'FAIR':
+ alt_class = "fair"
+ alt_emoji = "🟠"
+ else:
+ alt_class = "poor"
+ alt_emoji = "🔴"
+
+ reasons_html = ""
+ for reason in alt.get('reasons', []):
+ reasons_html += f"
💡 These issues require manual configuration changes
+
+
+ """
+
+ def _get_band_from_freq(self, freq: int) -> str:
+ """Get band name from frequency"""
+ if 2400 <= freq <= 2500:
+ return '2.4GHz'
+ elif 5000 <= freq <= 5999:
+ return '5GHz'
+ elif 6000 <= freq <= 7125:
+ return '6GHz'
+ else:
+ return f'{freq}MHz'
+
+ def _get_modern_css(self) -> str:
+ """Return modern CSS with dark theme and glassmorphism"""
+ return """
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
+ color: #ffffff;
+ min-height: 100vh;
+ line-height: 1.6;
+ }
+
+ .container {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 20px;
+ }
+
+ .main-header {
+ background: rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(20px);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 20px;
+ padding: 30px;
+ margin-bottom: 30px;
+ text-align: center;
+ }
+
+ .main-header h1 {
+ font-size: 2.5rem;
+ font-weight: 700;
+ margin-bottom: 15px;
+ background: linear-gradient(135deg, #00d4ff, #7b68ee);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ }
+
+ .wifi-icon {
+ font-size: 2rem;
+ margin-right: 15px;
+ }
+
+ .network-info h2 {
+ font-size: 1.5rem;
+ color: #00d4ff;
+ margin-bottom: 10px;
+ }
+
+ .timestamp {
+ color: rgba(255, 255, 255, 0.7);
+ font-size: 0.9rem;
+ }
+
+ .content-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+ gap: 25px;
+ margin-bottom: 30px;
+ }
+
+ .card {
+ background: rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(20px);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 20px;
+ padding: 25px;
+ transition: all 0.3s ease;
+ }
+
+ .card:hover {
+ transform: translateY(-5px);
+ border-color: rgba(0, 212, 255, 0.3);
+ box-shadow: 0 20px 40px rgba(0, 212, 255, 0.1);
+ }
+
+ .card h3 {
+ font-size: 1.3rem;
+ font-weight: 600;
+ margin-bottom: 20px;
+ color: #00d4ff;
+ display: flex;
+ align-items: center;
+ }
+
+ .icon {
+ font-size: 1.5rem;
+ margin-right: 10px;
+ }
+
+ .connection-details, .topology-stats {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .detail-row, .stat-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 15px;
+ background: rgba(255, 255, 255, 0.03);
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ }
+
+ .label, .stat-label {
+ color: rgba(255, 255, 255, 0.8);
+ font-weight: 500;
+ }
+
+ .value, .stat-value {
+ font-weight: 600;
+ color: #ffffff;
+ }
+
+ .signal-excellent { color: #4ade80; }
+ .signal-good { color: #fbbf24; }
+ .signal-fair { color: #fb923c; }
+ .signal-poor { color: #f87171; }
+
+ .excellent {
+ border-color: rgba(74, 222, 128, 0.3);
+ background: rgba(74, 222, 128, 0.05);
+ }
+
+ .good {
+ border-color: rgba(251, 191, 36, 0.3);
+ background: rgba(251, 191, 36, 0.05);
+ }
+
+ .warning, .fair {
+ border-color: rgba(251, 146, 60, 0.3);
+ background: rgba(251, 146, 60, 0.05);
+ }
+
+ .poor {
+ border-color: rgba(248, 113, 113, 0.3);
+ background: rgba(248, 113, 113, 0.05);
+ }
+
+ .topology-overview {
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ gap: 20px;
+ margin-bottom: 25px;
+ }
+
+ .topology-health {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+ border-radius: 15px;
+ text-align: center;
+ }
+
+ .health-score {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 1.1rem;
+ font-weight: 600;
+ margin-bottom: 8px;
+ }
+
+ .health-emoji {
+ font-size: 1.5rem;
+ }
+
+ .coverage-zones, .mesh-nodes, .venn-analysis {
+ margin-top: 25px;
+ }
+
+ .coverage-zones h4, .mesh-nodes h4, .venn-analysis h4 {
+ color: #00d4ff;
+ margin-bottom: 15px;
+ font-size: 1.1rem;
+ }
+
+ .venn-summary {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ margin-bottom: 20px;
+ padding: 15px;
+ background: rgba(255, 255, 255, 0.03);
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ }
+
+ .venn-quality {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 15px;
+ border-radius: 10px;
+ min-width: 80px;
+ }
+
+ .quality-score {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: #fff;
+ }
+
+ .quality-label {
+ font-size: 0.8rem;
+ color: rgba(255, 255, 255, 0.8);
+ }
+
+ .venn-description {
+ flex: 1;
+ color: rgba(255, 255, 255, 0.9);
+ }
+
+ .venn-diagram-container {
+ margin-top: 20px;
+ padding: 15px;
+ background: rgba(255, 255, 255, 0.03);
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ }
+
+ .venn-diagram-container h5 {
+ color: #00d4ff;
+ margin-bottom: 15px;
+ font-size: 1rem;
+ }
+
+ .venn-svg-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 10px;
+ padding: 10px;
+ margin-bottom: 15px;
+ }
+
+ .venn-svg-wrapper svg {
+ max-width: 100%;
+ height: auto;
+ border-radius: 8px;
+ }
+
+ .overlap-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .overlap-list h5 {
+ color: #00d4ff;
+ margin-bottom: 10px;
+ font-size: 1rem;
+ }
+
+ .overlap-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 15px;
+ background: rgba(255, 255, 255, 0.03);
+ border-radius: 8px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ }
+
+ .overlap-nodes {
+ color: rgba(255, 255, 255, 0.9);
+ }
+
+ .overlap-percentage {
+ font-weight: 600;
+ color: #00d4ff;
+ }
+
+ .no-overlaps {
+ padding: 20px;
+ text-align: center;
+ background: rgba(251, 146, 60, 0.05);
+ border: 1px solid rgba(251, 146, 60, 0.2);
+ border-radius: 10px;
+ color: rgba(255, 255, 255, 0.8);
+ }
+
+ .zones-grid, .nodes-grid, .alternatives-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 15px;
+ }
+
+ .zone-card, .node-card, .alternative-card {
+ background: rgba(255, 255, 255, 0.03);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 12px;
+ padding: 15px;
+ transition: all 0.2s ease;
+ }
+
+ .zone-card:hover, .node-card:hover, .alternative-card:hover {
+ background: rgba(255, 255, 255, 0.06);
+ border-color: rgba(0, 212, 255, 0.3);
+ }
+
+ .zone-header, .node-header, .alt-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ }
+
+ .zone-icon, .alt-emoji {
+ font-size: 1.2rem;
+ }
+
+ .recommendation-banner {
+ padding: 20px;
+ border-radius: 15px;
+ margin-bottom: 25px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ }
+
+ .recommendation-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+ }
+
+ .rec-icon {
+ font-size: 1.5rem;
+ }
+
+ .rec-title {
+ font-size: 1.2rem;
+ font-weight: 600;
+ }
+
+ .recommendation-details {
+ color: rgba(255, 255, 255, 0.9);
+ }
+
+ .alternatives-list h4 {
+ color: #00d4ff;
+ margin-bottom: 15px;
+ font-size: 1.1rem;
+ }
+
+ .alt-details {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 12px;
+ }
+
+ .alt-stat {
+ display: flex;
+ justify-content: space-between;
+ font-size: 0.9rem;
+ }
+
+ .alt-reasons ul {
+ list-style: none;
+ font-size: 0.85rem;
+ color: rgba(255, 255, 255, 0.8);
+ }
+
+ .alt-reasons li {
+ margin-bottom: 4px;
+ padding-left: 8px;
+ position: relative;
+ }
+
+ .alt-reasons li::before {
+ content: "•";
+ color: #00d4ff;
+ position: absolute;
+ left: 0;
+ }
+
+ .stability-score {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 20px;
+ border-radius: 15px;
+ margin-bottom: 20px;
+ }
+
+ .stability-details {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .stability-number {
+ font-size: 1.5rem;
+ font-weight: 700;
+ }
+
+ .stability-label {
+ font-size: 0.9rem;
+ color: rgba(255, 255, 255, 0.8);
+ }
+
+ .history-stats {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 15px;
+ }
+
+ .history-stat {
+ text-align: center;
+ padding: 15px;
+ background: rgba(255, 255, 255, 0.03);
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ }
+
+ .stat-number {
+ display: block;
+ font-size: 1.3rem;
+ font-weight: 700;
+ color: #00d4ff;
+ }
+
+ .problems-summary, .power-summary {
+ display: flex;
+ align-items: center;
+ gap: 30px;
+ margin-bottom: 20px;
+ }
+
+ .problems-count, .issues-count, .count-number {
+ text-align: center;
+ }
+
+ .count-number {
+ display: block;
+ font-size: 2rem;
+ font-weight: 700;
+ color: #fb923c;
+ }
+
+ .count-label {
+ font-size: 0.9rem;
+ color: rgba(255, 255, 255, 0.8);
+ }
+
+ .problems-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .problem-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 15px;
+ background: rgba(255, 255, 255, 0.03);
+ border-radius: 8px;
+ border: 1px solid rgba(251, 146, 60, 0.2);
+ }
+
+ .problem-icon {
+ font-size: 1.2rem;
+ }
+
+ .severity-breakdown {
+ display: flex;
+ gap: 15px;
+ }
+
+ .severity-item {
+ text-align: center;
+ padding: 10px 15px;
+ border-radius: 8px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ }
+
+ .severity-item.high {
+ border-color: rgba(248, 113, 113, 0.3);
+ background: rgba(248, 113, 113, 0.05);
+ }
+
+ .severity-item.medium {
+ border-color: rgba(251, 146, 60, 0.3);
+ background: rgba(251, 146, 60, 0.05);
+ }
+
+ .severity-item.low {
+ border-color: rgba(251, 191, 36, 0.3);
+ background: rgba(251, 191, 36, 0.05);
+ }
+
+ .severity-number {
+ display: block;
+ font-size: 1.2rem;
+ font-weight: 600;
+ }
+
+ .severity-label {
+ font-size: 0.8rem;
+ color: rgba(255, 255, 255, 0.8);
+ }
+
+ .topology-note, .history-note, .power-note {
+ margin-top: 20px;
+ padding: 15px;
+ background: rgba(0, 212, 255, 0.05);
+ border: 1px solid rgba(0, 212, 255, 0.2);
+ border-radius: 10px;
+ font-size: 0.9rem;
+ color: rgba(255, 255, 255, 0.9);
+ }
+
+ .no-connection, .no-alternatives, .no-history, .no-problems, .no-roaming-data, .no-power-issues {
+ text-align: center;
+ padding: 30px;
+ color: rgba(255, 255, 255, 0.8);
+ }
+
+ .report-footer {
+ text-align: center;
+ padding: 25px;
+ background: rgba(255, 255, 255, 0.02);
+ border-radius: 15px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ color: rgba(255, 255, 255, 0.7);
+ font-size: 0.9rem;
+ }
+
+ .report-footer p {
+ margin-bottom: 5px;
+ }
+
+ pre {
+ background: rgba(0, 0, 0, 0.3);
+ padding: 15px;
+ border-radius: 8px;
+ overflow-x: auto;
+ font-size: 0.8rem;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ }
+
+ @media (max-width: 768px) {
+ .container {
+ padding: 15px;
+ }
+
+ .content-grid {
+ grid-template-columns: 1fr;
+ gap: 20px;
+ }
+
+ .topology-overview {
+ grid-template-columns: 1fr;
+ }
+
+ .zones-grid, .nodes-grid, .alternatives-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .main-header h1 {
+ font-size: 2rem;
+ }
+
+ .history-stats {
+ grid-template-columns: 1fr;
+ }
+
+ .problems-summary, .power-summary {
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ .severity-breakdown {
+ justify-content: center;
+ }
+ }
+ """
+
+ def _get_interactive_javascript(self) -> str:
+ """Return interactive JavaScript for the report"""
+ return """
+ // Add smooth scrolling and interactive features
+ document.addEventListener('DOMContentLoaded', function() {
+ // Add click-to-copy functionality for BSSIDs
+ const bssids = document.querySelectorAll('.stat-value, .value');
+ bssids.forEach(element => {
+ if (element.textContent.match(/[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}/)) {
+ element.style.cursor = 'pointer';
+ element.title = 'Click to copy BSSID';
+ element.addEventListener('click', function() {
+ navigator.clipboard.writeText(this.textContent).then(() => {
+ const original = this.textContent;
+ this.textContent = '✓ Copied!';
+ setTimeout(() => {
+ this.textContent = original;
+ }, 1000);
+ });
+ });
+ }
+ });
+
+ // Add smooth hover effects
+ const cards = document.querySelectorAll('.card');
+ cards.forEach(card => {
+ card.addEventListener('mouseenter', function() {
+ this.style.transform = 'translateY(-5px) scale(1.02)';
+ });
+ card.addEventListener('mouseleave', function() {
+ this.style.transform = 'translateY(0) scale(1)';
+ });
+ });
+
+ // Auto-refresh timestamp
+ const timestamp = document.querySelector('.timestamp');
+ if (timestamp) {
+ setInterval(() => {
+ const now = new Date();
+ const timeStr = now.toLocaleString();
+ timestamp.textContent = 'Generated: ' + timeStr + ' (Auto-updated)';
+ }, 60000); // Update every minute
+ }
+ });
+ """
+
+
+# Example usage and integration
+if __name__ == "__main__":
+ print("WiFi Mesh HTML Report Generator")
+ print("This module provides HTML reporting for the WiFi Mesh Network Analyzer")
+ print("Usage: Import MeshHTMLReporter and call generate_report() with analysis data")
diff --git a/MeshAnalyzer/files/mesh_power_detective.py b/MeshAnalyzer/files/mesh_power_detective.py
new file mode 100644
index 0000000..7065f82
--- /dev/null
+++ b/MeshAnalyzer/files/mesh_power_detective.py
@@ -0,0 +1,648 @@
+#!/usr/bin/env python3
+"""
+WiFi Mesh Power Detective
+Detect WiFi power management issues causing false disconnects
+Save this file as: mesh_power_detective.py
+Put it in the same folder as your main analyzer script
+"""
+
+import subprocess
+import time
+import os
+import re
+import glob
+from datetime import datetime
+from pathlib import Path
+
+class MeshPowerDetective:
+ """Detect WiFi power management issues causing false disconnects"""
+
+ def __init__(self, interface):
+ self.interface = interface
+ self.issues_found = []
+ self.power_events = []
+ self.alert_thresholds = {
+ 'signal_drop': 15, # dBm
+ 'latency_spike': 100, # ms
+ 'packet_loss': 5 # percent
+ }
+
+ def check_all_power_issues(self):
+ """Run comprehensive power management detection"""
+ print("🔋 WiFi Power Management Detective")
+ print("=" * 60)
+ print("Scanning for power-related WiFi issues...")
+
+ issues = {
+ 'wifi_power_save': self.check_wifi_power_save(),
+ 'usb_autosuspend': self.check_usb_autosuspend(),
+ 'pcie_aspm': self.check_pcie_aspm(),
+ 'network_manager': self.check_network_manager_power(),
+ 'tlp_settings': self.check_tlp_settings(),
+ 'laptop_mode': self.check_laptop_mode_tools(),
+ 'systemd_sleep': self.check_systemd_sleep_settings(),
+ 'driver_params': self.check_driver_power_params()
+ }
+
+ self._generate_report(issues)
+ return issues
+
+ def check_wifi_power_save(self):
+ """Check if WiFi power saving is causing drops"""
+ issues = []
+
+ # Check current power save status
+ try:
+ result = subprocess.run(f"iw dev {self.interface} get power_save",
+ shell=True, capture_output=True, text=True, timeout=5)
+ if "on" in result.stdout.lower():
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'WiFi power saving is ON',
+ 'impact': 'Can cause periodic disconnects and latency spikes',
+ 'fix': f'sudo iw dev {self.interface} set power_save off'
+ })
+ except:
+ pass
+
+ return issues
+
+ def check_usb_autosuspend(self):
+ """Check if USB autosuspend is affecting USB WiFi adapters"""
+ issues = []
+
+ # Check if WiFi is USB
+ try:
+ # Find device path
+ device_path = f"/sys/class/net/{self.interface}/device"
+ if os.path.exists(device_path):
+ real_path = os.path.realpath(device_path)
+
+ if "usb" in real_path:
+ # It's a USB WiFi adapter
+ issues.append({
+ 'severity': 'info',
+ 'issue': 'USB WiFi adapter detected',
+ 'impact': 'USB autosuspend can cause disconnects'
+ })
+
+ # Check USB autosuspend setting
+ autosuspend_path = "/sys/module/usbcore/parameters/autosuspend"
+ if os.path.exists(autosuspend_path):
+ with open(autosuspend_path, 'r') as f:
+ value = f.read().strip()
+ if value != "-1":
+ issues.append({
+ 'severity': 'high',
+ 'issue': f'USB autosuspend is enabled ({value}s)',
+ 'impact': 'WiFi adapter suspends after inactivity',
+ 'fix': 'echo -1 | sudo tee /sys/module/usbcore/parameters/autosuspend'
+ })
+
+ # Check specific device power/control
+ usb_power_path = None
+ for parent in Path(real_path).parents:
+ control_path = parent / "power/control"
+ if control_path.exists():
+ usb_power_path = control_path
+ break
+
+ if usb_power_path and usb_power_path.exists():
+ with open(usb_power_path, 'r') as f:
+ if f.read().strip() == "auto":
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'USB device power control set to auto',
+ 'impact': 'Device can sleep during use',
+ 'fix': f'echo on | sudo tee {usb_power_path}'
+ })
+ except Exception as e:
+ # Debug info for troubleshooting
+ pass
+
+ return issues
+
+ def check_pcie_aspm(self):
+ """Check PCIe Active State Power Management"""
+ issues = []
+
+ # Check if WiFi is PCIe
+ try:
+ result = subprocess.run(f"lspci -k | grep -A 3 -i network",
+ shell=True, capture_output=True, text=True, timeout=5)
+ if result.stdout:
+ # Check ASPM policy
+ aspm_path = "/sys/module/pcie_aspm/parameters/policy"
+ if os.path.exists(aspm_path): # FIXED: was "asmp_path"
+ with open(aspm_path, 'r') as f: # FIXED: was "asmp_path"
+ policy = f.read().strip()
+ if policy[policy.find('[')+1:policy.find(']')] in ['default', 'powersave', 'powersupersave']:
+ issues.append({
+ 'severity': 'medium',
+ 'issue': f'PCIe ASPM set to: {policy}',
+ 'impact': 'Can cause latency and brief disconnects',
+ 'fix': 'Add pcie_aspm=off to kernel boot parameters',
+ 'manual_only': True
+ })
+ except:
+ pass
+
+ return issues
+
+ def check_network_manager_power(self):
+ """Check NetworkManager power saving settings"""
+ issues = []
+
+ try:
+ # Check if NetworkManager is managing power
+ nm_conf_paths = [f for f in glob.glob("/etc/NetworkManager/conf.d/*.conf") if os.path.isfile(f)]
+
+ for conf_path in nm_conf_paths:
+ if os.path.exists(conf_path):
+ with open(conf_path, 'r') as f:
+ content = f.read()
+ if "wifi.powersave = 3" in content or "wifi.powersave = 1" in content or "wifi.powersave = 0" in content:
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'NetworkManager WiFi power saving enabled',
+ 'impact': 'Periodic disconnects and poor roaming',
+ 'fix': 'Set wifi.powersave = 2 (disable) in ' + conf_path
+ })
+ except:
+ pass
+
+ return issues
+
+ def check_tlp_settings(self):
+ """Check TLP (laptop power management) settings"""
+ issues = []
+
+ if os.path.exists("/etc/tlp.conf"):
+ try:
+ with open("/etc/tlp.conf", 'r') as f:
+ content = f.read()
+
+ # Check WiFi power saving
+ if re.search(r'WIFI_PWR_ON_AC\s*=\s*on', content):
+ issues.append({
+ 'severity': 'medium',
+ 'issue': 'TLP WiFi power saving on AC',
+ 'impact': 'Power saving even when plugged in',
+ 'fix': 'Set WIFI_PWR_ON_AC=off in /etc/tlp.conf'
+ })
+
+ # Check USB autosuspend
+ if re.search(r'USB_AUTOSUSPEND\s*=\s*1', content):
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'TLP USB autosuspend enabled',
+ 'impact': 'USB WiFi adapters will suspend',
+ 'fix': 'Set USB_AUTOSUSPEND=0 in /etc/tlp.conf'
+ })
+ except:
+ pass
+
+ return issues
+
+ def check_laptop_mode_tools(self):
+ """Check laptop-mode-tools settings"""
+ issues = []
+
+ lmt_conf = "/etc/laptop-mode/conf.d/wireless-power.conf"
+ if os.path.exists(lmt_conf):
+ try:
+ with open(lmt_conf, 'r') as f:
+ content = f.read()
+ if 'WIRELESS_AC_POWER_SAVING=1' in content:
+ issues.append({
+ 'severity': 'medium',
+ 'issue': 'Laptop-mode-tools WiFi power saving on AC',
+ 'impact': 'Unnecessary power saving when plugged in',
+ 'fix': 'Set WIRELESS_AC_POWER_SAVING=0 in ' + lmt_conf
+ })
+ except:
+ pass
+
+ return issues
+
+ def check_systemd_sleep_settings(self):
+ """Check systemd sleep/suspend settings affecting WiFi"""
+ issues = []
+
+ try:
+ # Check if system is suspending network
+ result = subprocess.run("systemctl status systemd-networkd",
+ shell=True, capture_output=True, text=True, timeout=5)
+
+ # Check sleep.conf
+ if os.path.exists("/etc/systemd/sleep.conf"):
+ with open("/etc/systemd/sleep.conf", 'r') as f:
+ content = f.read()
+ if "HibernateDelaySec=2" in content:
+ issues.append({
+ 'severity': 'low',
+ 'issue': 'Quick hibernate delay detected',
+ 'impact': 'System may hibernate network too quickly',
+ 'fix': 'Increase HibernateDelaySec in /etc/systemd/sleep.conf'
+ })
+ except:
+ pass
+
+ return issues
+
+ def check_driver_power_params(self):
+ """Check WiFi driver-specific power parameters"""
+ issues = []
+
+ try:
+ # Get driver name
+ driver_path = f"/sys/class/net/{self.interface}/device/driver"
+ if os.path.exists(driver_path):
+ driver = os.path.basename(os.readlink(driver_path))
+
+ # Intel WiFi (iwlwifi)
+ if driver == "iwlwifi":
+ issues.extend(self._check_intel_power(driver))
+
+ # Realtek (rtw88, rtw89)
+ elif "rtw" in driver or "r8" in driver:
+ issues.extend(self._check_realtek_power(driver))
+
+ # Atheros/Qualcomm (ath9k, ath10k, ath11k)
+ elif "ath" in driver:
+ issues.extend(self._check_atheros_power(driver))
+
+ # MediaTek (mt76, mt7921, mt7922, etc)
+ elif "mt7" in driver or "mt76" in driver:
+ issues.extend(self._check_mediatek_power(driver))
+
+ # Qualcomm mobile (qca_cld3_wlan)
+ elif "qca" in driver:
+ issues.extend(self._check_qualcomm_power(driver))
+
+ # Marvell (mwifiex, mwl8k)
+ elif "mwifiex" in driver or "mwl" in driver:
+ issues.extend(self._check_marvell_power(driver))
+
+ # Generic power management check for any driver
+ issues.extend(self._check_generic_power_management())
+
+ except Exception as e:
+ pass
+
+ return issues
+
+ def _check_intel_power(self, driver):
+ """Check Intel WiFi power settings"""
+ issues = []
+
+ # Check module parameters
+ if os.path.exists("/sys/module/iwlwifi/parameters/power_save"):
+ with open("/sys/module/iwlwifi/parameters/power_save", 'r') as f:
+ if f.read().strip() == "Y":
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'Intel WiFi power_save enabled',
+ 'impact': 'Causes disconnects and poor performance',
+ 'fix': 'Add iwlwifi.power_save=0 to kernel parameters'
+ })
+
+ if os.path.exists("/sys/module/iwlwifi/parameters/power_level"):
+ with open("/sys/module/iwlwifi/parameters/power_level", 'r') as f:
+ level = f.read().strip()
+ if level != "0":
+ issues.append({
+ 'severity': 'medium',
+ 'issue': f'Intel WiFi power_level={level}',
+ 'impact': 'Reduced performance for power saving',
+ 'fix': 'Add iwlwifi.power_level=0 to kernel parameters'
+ })
+
+ return issues
+
+ def _check_realtek_power(self, driver):
+ """Check Realtek WiFi power settings"""
+ issues = []
+
+ if os.path.exists(f"/sys/module/{driver}/parameters/disable_lps"):
+ with open(f"/sys/module/{driver}/parameters/disable_lps", 'r') as f:
+ if f.read().strip() == "N":
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'Realtek WiFi LPS (power save) enabled',
+ 'impact': 'Known to cause frequent disconnects',
+ 'fix': f'Add {driver}.disable_lps=1 to kernel parameters'
+ })
+
+ return issues
+
+ def _check_atheros_power(self, driver):
+ """Check Atheros WiFi power settings"""
+ issues = []
+
+ if os.path.exists(f"/sys/module/{driver}/parameters/ps_enable"):
+ with open(f"/sys/module/{driver}/parameters/ps_enable", 'r') as f:
+ if f.read().strip() == "1":
+ issues.append({
+ 'severity': 'medium',
+ 'issue': 'Atheros WiFi power save enabled',
+ 'impact': 'May cause latency and disconnects',
+ 'fix': f'Add {driver}.ps_enable=0 to kernel parameters'
+ })
+
+ return issues
+
+ def _check_mediatek_power(self, driver):
+ """Check MediaTek WiFi power settings"""
+ issues = []
+
+ # MT7921/MT7922 (common in newer laptops)
+ if driver in ["mt7921e", "mt7921u", "mt7922"]:
+ # Check runtime PM
+ runtime_pm_path = f"/sys/class/net/{self.interface}/device/power/runtime_status"
+ if os.path.exists(runtime_pm_path):
+ with open(runtime_pm_path, 'r') as f:
+ status = f.read().strip()
+ if status == "suspended":
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'MediaTek WiFi runtime suspended',
+ 'impact': 'Device is currently suspended - will cause drops',
+ 'fix': f'echo on > /sys/class/net/{self.interface}/device/power/control'
+ })
+
+ # Check deep sleep mode
+ if os.path.exists(f"/sys/module/{driver}/parameters/disable_deep_sleep"):
+ with open(f"/sys/module/{driver}/parameters/disable_deep_sleep", 'r') as f:
+ if f.read().strip() == "N":
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'MediaTek deep sleep enabled',
+ 'impact': 'Causes 1-3 second reconnection delays',
+ 'fix': f'echo Y > /sys/module/{driver}/parameters/disable_deep_sleep'
+ })
+
+ # MT76 series power management
+ if "mt76" in driver:
+ # Check power save mode via debugfs
+ ps_path = f"/sys/kernel/debug/ieee80211/phy*/netdev:{self.interface}/mt76/runtime-pm"
+ for path in glob.glob(ps_path):
+ if os.path.exists(path):
+ with open(path, 'r') as f:
+ if "enable" in f.read():
+ issues.append({
+ 'severity': 'medium',
+ 'issue': 'MT76 runtime PM enabled',
+ 'impact': 'May cause latency spikes',
+ 'fix': 'Disable via debugfs or module parameter'
+ })
+
+ return issues
+
+ def _check_qualcomm_power(self, driver):
+ """Check Qualcomm/QCA WiFi power settings"""
+ issues = []
+
+ # QCA6174/QCA9377 (common in laptops)
+ if driver in ["ath10k_pci", "ath11k_pci"]:
+ # Check WoWLAN (Wake on WLAN)
+ wowlan_path = f"/sys/class/net/{self.interface}/phy80211/wowlan"
+ if os.path.exists(wowlan_path):
+ with open(wowlan_path, 'r') as f:
+ if "enabled" in f.read():
+ issues.append({
+ 'severity': 'medium',
+ 'issue': 'QCA WoWLAN enabled',
+ 'impact': 'Can cause false wakeups and power issues',
+ 'fix': f'iw phy phy0 wowlan disable'
+ })
+
+ # Check firmware power save
+ if os.path.exists(f"/sys/module/{driver}/parameters/fw_powersave"):
+ with open(f"/sys/module/{driver}/parameters/fw_powersave", 'r') as f:
+ if f.read().strip() == "1":
+ issues.append({
+ 'severity': 'high',
+ 'issue': 'QCA firmware power save enabled',
+ 'impact': 'Known to cause disconnects on QCA chips',
+ 'fix': f'echo 0 > /sys/module/{driver}/parameters/fw_powersave'
+ })
+
+ # Mobile Qualcomm chips
+ elif driver == "qca_cld3_wlan":
+ # Check IPA (power aggregator)
+ if os.path.exists("/sys/module/wlan/parameters/enable_ipa"):
+ with open("/sys/module/wlan/parameters/enable_ipa", 'r') as f:
+ if f.read().strip() == "1":
+ issues.append({
+ 'severity': 'low',
+ 'issue': 'QCA IPA power aggregation enabled',
+ 'impact': 'May affect throughput for power saving',
+ 'fix': 'Add wlan.enable_ipa=0 to kernel parameters'
+ })
+
+ return issues
+
+ def _check_marvell_power(self, driver):
+ """Check Marvell WiFi power settings"""
+ issues = []
+
+ if driver == "mwifiex":
+ # Check PS mode
+ ps_mode_path = f"/sys/kernel/debug/mwifiex/{self.interface}/ps_mode"
+ if os.path.exists(ps_mode_path):
+ with open(ps_mode_path, 'r') as f:
+ mode = f.read().strip()
+ if mode != "0":
+ issues.append({
+ 'severity': 'high',
+ 'issue': f'Marvell PS mode {mode} active',
+ 'impact': 'Aggressive power saving causes drops',
+ 'fix': f'echo 0 > {ps_mode_path}'
+ })
+
+ # Check sleep parameters
+ if os.path.exists(f"/sys/module/{driver}/parameters/auto_ds"):
+ with open(f"/sys/module/{driver}/parameters/auto_ds", 'r') as f:
+ if f.read().strip() == "Y":
+ issues.append({
+ 'severity': 'medium',
+ 'issue': 'Marvell auto deep sleep enabled',
+ 'impact': 'Wake-up delays after idle',
+ 'fix': f'Add {driver}.auto_ds=N to kernel parameters'
+ })
+
+ return issues
+
+ def _check_generic_power_management(self):
+ """Generic power checks that apply to any WiFi driver"""
+ issues = []
+
+ # Check runtime PM for ANY WiFi device
+ runtime_pm = f"/sys/class/net/{self.interface}/device/power/control"
+ if os.path.exists(runtime_pm):
+ with open(runtime_pm, 'r') as f:
+ if f.read().strip() == "auto":
+ issues.append({
+ 'severity': 'medium',
+ 'issue': 'Generic runtime PM set to auto',
+ 'impact': 'Device may suspend unexpectedly',
+ 'fix': f'echo on > {runtime_pm}'
+ })
+
+ # Check for aggressive kernel power settings
+ if os.path.exists("/proc/sys/kernel/nmi_watchdog"):
+ with open("/proc/sys/kernel/nmi_watchdog", 'r') as f:
+ if f.read().strip() == "0":
+ issues.append({
+ 'severity': 'info',
+ 'issue': 'NMI watchdog disabled (laptop power saving)',
+ 'impact': 'System may be in aggressive power save mode',
+ 'fix': 'Consider if other subsystems are also affected'
+ })
+
+ return issues
+
+ def monitor_power_events(self, duration=60):
+ """Monitor for power-related WiFi events"""
+ print(f"\n🔍 Monitoring for power-related issues for {duration} seconds...")
+ print("Watch for correlation between power events and disconnects")
+
+ start_time = time.time()
+ last_state = "unknown"
+
+ while time.time() - start_time < duration:
+ # Check power save state
+ try:
+ result = subprocess.run(f"iw dev {self.interface} get power_save",
+ shell=True, capture_output=True, text=True, timeout=2)
+ current_state = "on" if "on" in result.stdout.lower() else "off"
+
+ if current_state != last_state and last_state != "unknown":
+ self.power_events.append({
+ 'time': datetime.now(),
+ 'event': f'Power save changed: {last_state} -> {current_state}'
+ })
+ print(f"⚡ Power save state changed to: {current_state}")
+
+ last_state = current_state
+
+ # Check connection state
+ link_result = subprocess.run(f"iw dev {self.interface} link",
+ shell=True, capture_output=True, text=True, timeout=2)
+ if "Not connected" in link_result.stdout:
+ self.power_events.append({
+ 'time': datetime.now(),
+ 'event': 'Connection lost (check if power-related)'
+ })
+
+ except:
+ pass
+
+ time.sleep(0.5)
+
+ if self.power_events:
+ print(f"\n📊 Detected {len(self.power_events)} power-related events")
+ else:
+ print("\n✅ No power-related events detected")
+
+ def _generate_report(self, issues):
+ """Generate comprehensive report"""
+ total_issues = sum(len(v) for v in issues.values())
+ critical_issues = sum(1 for v in issues.values() for i in v if i.get('severity') == 'high')
+
+ print(f"\n📋 POWER MANAGEMENT REPORT")
+ print("=" * 60)
+ print(f"Total issues found: {total_issues}")
+ print(f"Critical issues: {critical_issues}")
+
+ if total_issues == 0:
+ print("\n✅ No power management issues detected!")
+ print("Your WiFi should not be affected by power saving.")
+ else:
+ print("\n🚨 Issues Found:\n")
+
+ for category, category_issues in issues.items():
+ if category_issues:
+ print(f"{category.replace('_', ' ').title()}:")
+ for issue in category_issues:
+ severity_icon = {
+ 'high': '🔴',
+ 'medium': '🟡',
+ 'low': '🟠',
+ 'info': 'ℹ️'
+ }.get(issue.get('severity', 'info'))
+
+ print(f"\n {severity_icon} {issue['issue']}")
+ print(f" Impact: {issue['impact']}")
+ if 'fix' in issue:
+ print(f" Fix: {issue['fix']}")
+
+ # Generate fix script
+ self._generate_fix_script(issues)
+
+ def _generate_fix_script(self, issues):
+ """Generate a script to fix all issues"""
+ fixes = []
+ manual_fixes = []
+
+ for category_issues in issues.values():
+ for issue in category_issues:
+ if 'fix' in issue and issue.get('severity') in ['high', 'medium']:
+ if issue.get('manual_only', False):
+ manual_fixes.append(issue['fix'])
+ else:
+ fixes.append(issue['fix'])
+
+ if manual_fixes:
+ print("\n⚠️ Manual fixes required (cannot be automated):")
+ for fix in manual_fixes:
+ print(f" • {fix}")
+ print(" 💡 These require editing boot parameters or configuration files")
+
+
+# Usage example and testing
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) < 2:
+ print("Usage: mesh_power_detective.py [options]")
+ print("\nOptions:")
+ print(" --monitor Monitor power events for 60 seconds")
+ print(" --fix Generate fix script automatically")
+ print("\nExamples:")
+ print(" sudo python3 mesh_power_detective.py wlan0")
+ print(" sudo python3 mesh_power_detective.py wlan0 --monitor")
+ print(" sudo python3 mesh_power_detective.py wlan0 --fix")
+ sys.exit(1)
+
+ interface = sys.argv[1]
+
+ # Check if interface exists
+ if not os.path.exists(f"/sys/class/net/{interface}"):
+ print(f"❌ Network interface '{interface}' not found")
+ print("💡 Try: ip link show")
+ sys.exit(1)
+
+ detective = MeshPowerDetective(interface)
+
+ print(f"🔋 WiFi Power Management Detective")
+ print(f"📡 Interface: {interface}")
+ print("=" * 50)
+
+ try:
+ # Run all checks
+ detective.check_all_power_issues()
+
+ # Optional monitoring
+ if "--monitor" in sys.argv:
+ detective.monitor_power_events(duration=60)
+
+ if "--fix" in sys.argv:
+ print("\n💡 Fix script has been generated if issues were found")
+
+ except KeyboardInterrupt:
+ print("\n👋 Analysis interrupted by user")
+ except Exception as e:
+ print(f"❌ Error: {e}")
+ print("💡 Make sure you're running with sudo privileges")
diff --git a/MeshAnalyzer/files/mesh_roaming_detector.py b/MeshAnalyzer/files/mesh_roaming_detector.py
new file mode 100644
index 0000000..038babe
--- /dev/null
+++ b/MeshAnalyzer/files/mesh_roaming_detector.py
@@ -0,0 +1,435 @@
+#!/usr/bin/env python3
+"""
+WiFi Mesh Roaming Detector
+Detect and measure actual drops, reconnects, and roaming events
+Save this file as: mesh_roaming_detector.py
+Put it in the same folder as your main analyzer script
+"""
+
+import subprocess
+import time
+import threading
+from collections import deque
+from datetime import datetime
+import os
+
+class MeshRoamingDetector:
+ """Detect and measure actual drops, reconnects, and roaming events"""
+
+ def __init__(self, interface):
+ self.interface = interface
+ self.events = deque(maxlen=1000)
+ self.current_bssid = None
+ self.monitoring = False
+
+ def monitor_connection_state(self, interval=0.1):
+ """High-frequency monitoring to catch brief drops"""
+ self.monitoring = True
+ last_state = None
+ last_bssid = None
+ disconnect_start = None
+
+ while self.monitoring:
+ # Get current connection state FAST
+ state = self._get_connection_state_fast()
+
+ # Extract status and info from state
+ current_status = state.get('status', 'unknown') if isinstance(state, dict) else state
+ current_bssid = state.get('bssid') if isinstance(state, dict) else None
+ current_signal = state.get('signal', -100) if isinstance(state, dict) else -100
+
+ # Detect disconnection
+ if (isinstance(last_state, dict) and last_state.get('status') == "connected" and
+ current_status == "disconnected"):
+ disconnect_start = time.time()
+ self.events.append({
+ 'type': 'disconnect',
+ 'timestamp': disconnect_start,
+ 'last_bssid': last_bssid,
+ 'last_signal': last_state.get('signal', -100) if isinstance(last_state, dict) else -100
+ })
+
+ # Detect reconnection
+ elif (last_state == "disconnected" or
+ (isinstance(last_state, dict) and last_state.get('status') == "disconnected")) and current_status == "connected":
+ reconnect_time = time.time()
+ downtime = reconnect_time - disconnect_start if disconnect_start else 0
+
+ self.events.append({
+ 'type': 'reconnect',
+ 'timestamp': reconnect_time,
+ 'downtime_seconds': downtime,
+ 'new_bssid': current_bssid,
+ 'new_signal': current_signal
+ })
+ disconnect_start = None # Reset disconnect timer
+
+ # Detect roaming (BSSID change without disconnect)
+ elif (current_status == "connected" and
+ (isinstance(last_state, dict) and last_state.get('status') == "connected") and
+ last_bssid and current_bssid and last_bssid != current_bssid):
+
+ self.events.append({
+ 'type': 'roam',
+ 'timestamp': time.time(),
+ 'from_bssid': last_bssid,
+ 'to_bssid': current_bssid,
+ 'from_signal': last_state.get('signal', -100) if isinstance(last_state, dict) else -100,
+ 'to_signal': current_signal,
+ 'seamless': True # No disconnect detected
+ })
+
+ # Update tracking variables
+ last_state = state
+ last_bssid = current_bssid
+ time.sleep(interval)
+
+ def _get_connection_state_fast(self):
+ """Fastest possible connection state check with robust error handling"""
+ try:
+ # Use /proc/net/wireless for fastest reads
+ with open('/proc/net/wireless', 'r') as f:
+ lines = f.readlines()
+ for line in lines:
+ if self.interface in line:
+ # Parse signal level with error handling
+ parts = line.split()
+ if len(parts) >= 4:
+ try:
+ # Handle different possible formats
+ signal_str = parts[3].rstrip('.')
+ signal = int(float(signal_str))
+ except (ValueError, IndexError):
+ signal = -100
+
+ # Get BSSID from iw (cached)
+ try:
+ cmd = f"iw dev {self.interface} link | grep 'Connected to'"
+ result = subprocess.run(cmd, shell=True, capture_output=True,
+ text=True, timeout=1)
+
+ if "Connected to" in result.stdout:
+ bssid_part = result.stdout.split("Connected to ")[1].split()[0]
+ # Clean BSSID
+ bssid = bssid_part.split('(')[0].strip().upper()
+
+ # Validate BSSID format
+ if len(bssid) == 17 and bssid.count(':') == 5:
+ return {'status': 'connected', 'signal': signal, 'bssid': bssid}
+ else:
+ return {'status': 'disconnected'}
+ else:
+ return {'status': 'disconnected'}
+ except (subprocess.TimeoutExpired, subprocess.SubprocessError):
+ return {'status': 'unknown'}
+
+ return {'status': 'disconnected'}
+
+ except (FileNotFoundError, PermissionError, OSError):
+ # Fallback method
+ try:
+ cmd = f"iw dev {self.interface} link"
+ result = subprocess.run(cmd, shell=True, capture_output=True,
+ text=True, timeout=2)
+
+ if "Not connected" in result.stdout:
+ return {'status': 'disconnected'}
+ elif "Connected to" in result.stdout:
+ # Parse signal and BSSID with error handling
+ lines = result.stdout.split('\n')
+ bssid = None
+ signal = -100
+
+ for line in lines:
+ try:
+ if "Connected to" in line:
+ bssid_part = line.split("Connected to ")[1].split()[0]
+ bssid = bssid_part.split('(')[0].strip().upper()
+ # Validate BSSID format
+ if len(bssid) != 17 or bssid.count(':') != 5:
+ bssid = None
+ elif "signal:" in line:
+ signal_part = line.split("signal: ")[1].split()[0]
+ signal = int(float(signal_part))
+ except (IndexError, ValueError):
+ continue
+
+ if bssid:
+ return {'status': 'connected', 'signal': signal, 'bssid': bssid}
+ else:
+ return {'status': 'connected', 'signal': signal, 'bssid': 'unknown'}
+ else:
+ return {'status': 'unknown'}
+
+ except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
+ return {'status': 'unknown'}
+
+ def detect_microdropouts(self, duration=60):
+ """Detect drops shorter than 1 second"""
+ print(f"🔍 Monitoring for micro-dropouts for {duration} seconds...")
+ print("These are drops your system might not normally notice")
+ print("💡 Keep using your WiFi normally - browse, stream, etc.")
+
+ # Clear previous events
+ self.events.clear()
+
+ # Use rapid polling
+ monitor_thread = threading.Thread(
+ target=self.monitor_connection_state,
+ args=(0.05,) # 50ms polling
+ )
+ monitor_thread.start()
+
+ time.sleep(duration)
+ self.monitoring = False
+ monitor_thread.join()
+
+ # Analyze micro-dropouts
+ dropouts = [e for e in self.events if e['type'] == 'reconnect' and e.get('downtime_seconds', 0) < 1.0]
+
+ if dropouts:
+ print(f"\n🔴 Found {len(dropouts)} micro-dropouts:")
+ for d in dropouts:
+ print(f" • {d['downtime_seconds']:.3f}s dropout at {datetime.fromtimestamp(d['timestamp']).strftime('%H:%M:%S')}")
+ if d.get('new_bssid'):
+ print(f" Reconnected to: {d['new_bssid']} ({d.get('new_signal', 'unknown')}dBm)")
+ else:
+ print(f"\n✅ No micro-dropouts detected in {duration} seconds")
+ print("Your mesh is handling connections smoothly!")
+
+ # Show any roaming events
+ roams = [e for e in self.events if e['type'] == 'roam']
+ if roams:
+ print(f"\n🔄 Detected {len(roams)} seamless roaming events:")
+ for r in roams:
+ print(f" • {datetime.fromtimestamp(r['timestamp']).strftime('%H:%M:%S')}: {r['from_bssid']} → {r['to_bssid']}")
+ print(f" Signal: {r['from_signal']}dBm → {r['to_signal']}dBm")
+
+ def measure_roaming_performance(self, walk_test=False):
+ """Measure actual roaming performance"""
+ print("📊 Measuring roaming performance...")
+ if walk_test:
+ print("🚶 Walk around your space now. Press Ctrl+C when done.")
+ print("💡 Try to move between different rooms/areas")
+ else:
+ print("🏠 Monitoring roaming events for 2 minutes...")
+
+ # Clear previous events
+ self.events.clear()
+
+ # Start monitoring
+ monitor_thread = threading.Thread(
+ target=self.monitor_connection_state,
+ args=(0.1,) # 100ms polling
+ )
+ monitor_thread.start()
+
+ try:
+ if walk_test:
+ # Let user control duration
+ input("Press Enter when you're done walking around...")
+ else:
+ time.sleep(120) # 2 minutes
+ except KeyboardInterrupt:
+ pass
+
+ self.monitoring = False
+ monitor_thread.join()
+
+ # Analyze roaming events
+ roams = [e for e in self.events if e['type'] == 'roam']
+ disconnects = [e for e in self.events if e['type'] == 'disconnect']
+ reconnects = [e for e in self.events if e['type'] == 'reconnect']
+
+ print(f"\n📊 Roaming Analysis:")
+ print(f" • Seamless roams: {len(roams)}")
+ print(f" • Disconnection events: {len(disconnects)}")
+
+ if reconnects:
+ downtimes = [e['downtime_seconds'] for e in reconnects if e.get('downtime_seconds') is not None]
+ if downtimes:
+ print(f" • Average downtime: {sum(downtimes)/len(downtimes):.3f}s")
+ print(f" • Longest downtime: {max(downtimes):.3f}s")
+ print(f" • Shortest downtime: {min(downtimes):.3f}s")
+
+ # Show roaming details
+ if roams:
+ print(f"\n🔄 Roaming Events:")
+ for r in roams:
+ time_str = datetime.fromtimestamp(r['timestamp']).strftime('%H:%M:%S')
+ signal_change = r['to_signal'] - r['from_signal']
+ print(f" • {time_str}: {r['from_bssid'][:17]} → {r['to_bssid'][:17]}")
+ print(f" Signal change: {signal_change:+d}dBm ({r['from_signal']} → {r['to_signal']})")
+
+ return {
+ 'seamless_roams': len(roams),
+ 'dropped_roams': len(disconnects),
+ 'avg_downtime': sum(e.get('downtime_seconds', 0) for e in reconnects) / len(reconnects) if reconnects else 0,
+ 'micro_dropouts': len([e for e in reconnects if e.get('downtime_seconds', 0) < 1.0])
+ }
+
+ def track_problem_transitions(self):
+ """Identify problematic BSSID transitions"""
+ transition_stats = {}
+
+ # Analyze roaming events
+ for event in self.events:
+ if event['type'] == 'roam':
+ key = f"{event['from_bssid']} → {event['to_bssid']}"
+ if key not in transition_stats:
+ transition_stats[key] = {'count': 0, 'seamless': 0, 'avg_signal_change': []}
+
+ transition_stats[key]['count'] += 1
+ if event.get('seamless'):
+ transition_stats[key]['seamless'] += 1
+
+ signal_change = event['to_signal'] - event['from_signal']
+ transition_stats[key]['avg_signal_change'].append(signal_change)
+
+ if not transition_stats:
+ print("\n🔄 No roaming transitions detected")
+ print("💡 Try walking around or wait longer for roaming events")
+ return
+
+ print("\n🔄 Transition Analysis:")
+ for transition, stats in transition_stats.items():
+ success_rate = (stats['seamless'] / stats['count']) * 100
+ avg_signal_change = sum(stats['avg_signal_change']) / len(stats['avg_signal_change'])
+
+ status_emoji = "✅" if success_rate == 100 else "⚠️" if success_rate > 80 else "🔴"
+ print(f" {status_emoji} {transition}")
+ print(f" Success: {success_rate:.1f}% ({stats['seamless']}/{stats['count']} seamless)")
+ print(f" Avg signal change: {avg_signal_change:+.1f}dBm")
+
+ def continuous_quality_monitor(self):
+ """Monitor connection quality during normal use"""
+ print("📊 Starting continuous connection quality monitor...")
+ print("This will track all drops and roaming events in the background")
+ print("💡 Use Ctrl+C to stop monitoring")
+
+ # Create log file with proper error handling
+ try:
+ log_file = "/tmp/mesh_roaming_log.txt"
+
+ with open(log_file, "a") as log:
+ log.write(f"\n--- Roaming Monitor Session Started {datetime.now()} ---\n")
+ log.flush()
+
+ print(f"📝 Logging to: {log_file}")
+ print("🏃 Monitor running... use your WiFi normally")
+
+ # Clear previous events
+ self.events.clear()
+
+ # Start monitoring thread
+ monitor_thread = threading.Thread(
+ target=self.monitor_connection_state,
+ args=(0.1,) # 100ms polling
+ )
+ monitor_thread.start()
+
+ # Log events as they happen
+ last_event_count = 0
+ while True:
+ time.sleep(1) # Check for new events every second
+
+ if len(self.events) > last_event_count:
+ # New events to log
+ for event in list(self.events)[last_event_count:]:
+ timestamp = datetime.fromtimestamp(event['timestamp']).strftime('%H:%M:%S')
+
+ if event['type'] == 'disconnect':
+ msg = f"{timestamp}: 🔴 CONNECTION LOST from {event.get('last_bssid', 'unknown')}"
+ log.write(msg + "\n")
+ print(msg)
+
+ elif event['type'] == 'reconnect':
+ downtime = event.get('downtime_seconds', 0)
+ msg = f"{timestamp}: 🟢 RECONNECTED to {event.get('new_bssid', 'unknown')} (down {downtime:.3f}s)"
+ log.write(msg + "\n")
+ print(msg)
+
+ elif event['type'] == 'roam':
+ signal_change = event['to_signal'] - event['from_signal']
+ msg = f"{timestamp}: 🔄 ROAMED {event['from_bssid']} → {event['to_bssid']} ({signal_change:+d}dBm)"
+ log.write(msg + "\n")
+ print(msg)
+
+ log.flush()
+
+ last_event_count = len(self.events)
+
+ except KeyboardInterrupt:
+ self.monitoring = False
+ monitor_thread.join()
+ print(f"\n👋 Monitoring stopped")
+ print(f"📊 Total events logged: {len(self.events)}")
+ print(f"📝 Log saved to: {log_file}")
+
+ # Quick summary
+ roams = len([e for e in self.events if e['type'] == 'roam'])
+ drops = len([e for e in self.events if e['type'] == 'disconnect'])
+ if roams or drops:
+ print(f"📈 Summary: {roams} roams, {drops} drops")
+ else:
+ print("📈 Summary: Stable connection - no events detected")
+
+ except (PermissionError, OSError) as e:
+ print(f"❌ Error accessing log file: {e}")
+ print("💡 Try running with sudo or check /tmp permissions")
+
+
+# Usage example and testing
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) < 2:
+ print("Usage: mesh_roaming_detector.py [test_type]")
+ print("\nAvailable tests:")
+ print(" microdropouts - Detect brief connection drops (30 seconds)")
+ print(" roaming - Measure roaming performance (walk test)")
+ print(" transitions - Analyze problematic transitions")
+ print(" monitor - Continuous background monitoring")
+ print("\nExamples:")
+ print(" sudo python3 mesh_roaming_detector.py wlan0 microdropouts")
+ print(" sudo python3 mesh_roaming_detector.py wlan0 roaming")
+ print(" sudo python3 mesh_roaming_detector.py wlan0 monitor")
+ sys.exit(1)
+
+ interface = sys.argv[1]
+ test_type = sys.argv[2] if len(sys.argv) > 2 else "microdropouts"
+
+ # Check if interface exists
+ if not os.path.exists(f"/sys/class/net/{interface}"):
+ print(f"❌ Network interface '{interface}' not found")
+ print("💡 Try: ip link show")
+ sys.exit(1)
+
+ detector = MeshRoamingDetector(interface)
+
+ print(f"🔍 WiFi Mesh Roaming Detector")
+ print(f"📡 Interface: {interface}")
+ print(f"🧪 Test: {test_type}")
+ print("=" * 50)
+
+ try:
+ if test_type == "microdropouts":
+ detector.detect_microdropouts(duration=30)
+ elif test_type == "roaming":
+ detector.measure_roaming_performance(walk_test=True)
+ elif test_type == "transitions":
+ # Run brief monitoring first to collect data
+ print("Collecting roaming data for 60 seconds...")
+ detector.measure_roaming_performance(walk_test=False)
+ detector.track_problem_transitions()
+ elif test_type == "monitor":
+ detector.continuous_quality_monitor()
+ else:
+ print(f"❌ Unknown test type: {test_type}")
+ print("Available: microdropouts, roaming, transitions, monitor")
+
+ except KeyboardInterrupt:
+ print("\n👋 Test interrupted by user")
+ except Exception as e:
+ print(f"❌ Error: {e}")
+ print("💡 Make sure you're running with sudo privileges")
diff --git a/MeshAnalyzer/files/mesh_venn_calculator.py b/MeshAnalyzer/files/mesh_venn_calculator.py
new file mode 100644
index 0000000..0d96b99
--- /dev/null
+++ b/MeshAnalyzer/files/mesh_venn_calculator.py
@@ -0,0 +1,344 @@
+#!/usr/bin/env python3
+"""
+Mesh Venn Diagram Calculator
+Handles spatial overlap calculations for mesh nodes
+"""
+
+import math
+from typing import Dict, List, Tuple, Optional
+
+class MeshVennCalculator:
+ """Calculate mesh node spatial overlaps and positioning for Venn diagrams"""
+
+ def __init__(self):
+ self.coverage_multiplier = 3.5 # Signal strength to coverage radius multiplier
+ self.min_radius = 40 # Minimum coverage radius in pixels
+ self.max_radius = 120 # Maximum coverage radius in pixels
+
+ def calculate_coverage_radius(self, signal_dbm: int) -> int:
+ """Calculate coverage radius based on signal strength"""
+ # Convert signal strength to coverage radius
+ # Stronger signals = larger coverage areas
+ normalized_signal = max(0, signal_dbm + 100) # -100dBm becomes 0, -50dBm becomes 50
+ radius = self.min_radius + (normalized_signal * self.coverage_multiplier)
+ return min(self.max_radius, max(self.min_radius, int(radius)))
+
+ def create_smart_label(self, node_data: Dict, index: int, total_nodes: int, brand: str) -> str:
+ """Create contextually smart labels based on signal strength and position"""
+ signal = node_data['signal']
+
+ # Primary naming based on signal strength and logical position
+ if signal > -45:
+ base_name = "Main"
+ elif signal > -60:
+ base_name = "Near" if index == 1 else "Strong"
+ elif signal > -75:
+ base_name = "Mid" if total_nodes > 3 else "Extended"
+ else:
+ base_name = "Far"
+
+ # Add distinguisher for multiple nodes in same category
+ if total_nodes > 4:
+ base_name += f"-{chr(65 + index)}" # A, B, C, etc.
+ elif total_nodes > 2 and signal <= -60:
+ # For 3-4 nodes, distinguish weaker ones
+ if base_name in ["Mid", "Extended", "Far"]:
+ base_name += f"-{index}"
+
+ # Add brand context if available and not generic
+ if brand and brand not in ['unknown', 'generic']:
+ brand_short = brand.replace('_', ' ').replace('general', '').title()
+ # Clean up brand names
+ brand_map = {
+ 'Eero': 'eero',
+ 'Orbi Netgear': 'Orbi',
+ 'Google Nest': 'Nest',
+ 'Tp Link Deco': 'Deco',
+ 'Linksys Velop': 'Velop',
+ 'Asus': 'ASUS'
+ }
+ brand_clean = brand_map.get(brand_short, brand_short[:5])
+ return f"{brand_clean} {base_name}"
+
+ return base_name
+
+ def generate_smart_labels(self, nodes_data: List[Dict], brand: str = 'unknown') -> List[str]:
+ """Generate meaningful labels for all nodes based on context"""
+ if not nodes_data:
+ return []
+
+ # Sort nodes by signal strength to assign logical roles
+ sorted_nodes = sorted(enumerate(nodes_data), key=lambda x: x[1]['signal'], reverse=True)
+ labels = [''] * len(nodes_data)
+ total_nodes = len(nodes_data)
+
+ # Assign smart labels based on signal strength hierarchy
+ for rank, (orig_idx, node) in enumerate(sorted_nodes):
+ smart_label = self.create_smart_label(node, rank, total_nodes, brand)
+ labels[orig_idx] = smart_label
+
+ return labels
+
+ def calculate_optimal_positions(self, nodes_data: List[Dict]) -> List[Dict]:
+ """Calculate optimal positions for nodes to show realistic overlaps"""
+ node_count = len(nodes_data)
+
+ if node_count == 1:
+ return [{'x': 50, 'y': 50}]
+ elif node_count == 2:
+ return self._position_two_nodes(nodes_data)
+ elif node_count == 3:
+ return self._position_three_nodes(nodes_data)
+ elif node_count == 4:
+ return self._position_four_nodes(nodes_data)
+ else:
+ return self._position_many_nodes(nodes_data)
+
+ def _position_two_nodes(self, nodes_data: List[Dict]) -> List[Dict]:
+ """Position two nodes with appropriate overlap"""
+ # Sort by signal strength
+ sorted_nodes = sorted(nodes_data, key=lambda x: x['signal'], reverse=True)
+
+ # Calculate radii
+ radius1 = self.calculate_coverage_radius(sorted_nodes[0]['signal'])
+ radius2 = self.calculate_coverage_radius(sorted_nodes[1]['signal'])
+
+ # Position for 30-50% overlap
+ overlap_distance = (radius1 + radius2) * 0.6 # 40% overlap
+
+ positions = [
+ {'x': 40, 'y': 50},
+ {'x': 40 + (overlap_distance / 4), 'y': 50}
+ ]
+
+ return positions
+
+ def _position_three_nodes(self, nodes_data: List[Dict]) -> List[Dict]:
+ """Position three nodes in triangle formation with overlaps"""
+ # Sort by signal strength
+ sorted_indices = sorted(range(len(nodes_data)), key=lambda i: nodes_data[i]['signal'], reverse=True)
+
+ # Triangle positions with overlaps
+ base_positions = [
+ {'x': 35, 'y': 35}, # Top-left
+ {'x': 65, 'y': 35}, # Top-right
+ {'x': 50, 'y': 65} # Bottom-center
+ ]
+
+ # Adjust positions based on signal strengths for realistic overlaps
+ positions = [None] * 3
+ for i, orig_idx in enumerate(sorted_indices):
+ positions[orig_idx] = base_positions[i]
+
+ return positions
+
+ def _position_four_nodes(self, nodes_data: List[Dict]) -> List[Dict]:
+ """Position four nodes in diamond/square formation"""
+ # Sort by signal strength
+ sorted_indices = sorted(range(len(nodes_data)), key=lambda i: nodes_data[i]['signal'], reverse=True)
+
+ # Diamond positions for maximum overlaps
+ base_positions = [
+ {'x': 40, 'y': 35}, # Top-left
+ {'x': 60, 'y': 35}, # Top-right
+ {'x': 35, 'y': 55}, # Bottom-left
+ {'x': 65, 'y': 55} # Bottom-right
+ ]
+
+ positions = [None] * 4
+ for i, orig_idx in enumerate(sorted_indices):
+ positions[orig_idx] = base_positions[i]
+
+ return positions
+
+ def _position_many_nodes(self, nodes_data: List[Dict]) -> List[Dict]:
+ """Position 5+ nodes in clustered spiral for overlaps"""
+ node_count = len(nodes_data)
+ positions = []
+
+ # Central cluster approach
+ center_x, center_y = 50, 50
+
+ for i in range(node_count):
+ if i == 0:
+ # Central node
+ positions.append({'x': center_x, 'y': center_y})
+ else:
+ # Spiral outward
+ angle = (i - 1) * (2 * math.pi / (node_count - 1))
+ radius_offset = 15 + ((i - 1) * 5) # Gradually increasing radius
+
+ x = center_x + radius_offset * math.cos(angle)
+ y = center_y + radius_offset * math.sin(angle)
+
+ # Keep within bounds
+ x = max(20, min(80, x))
+ y = max(20, min(80, y))
+
+ positions.append({'x': x, 'y': y})
+
+ return positions
+
+ def calculate_overlap_percentage(self, node1: Dict, node2: Dict) -> float:
+ """Calculate overlap percentage between two nodes"""
+ # Get positions and radii
+ x1, y1 = node1['position']['x'], node1['position']['y']
+ x2, y2 = node2['position']['x'], node2['position']['y']
+ r1 = self.calculate_coverage_radius(node1['signal'])
+ r2 = self.calculate_coverage_radius(node2['signal'])
+
+ # Calculate distance between centers (scale from percentage to actual distance)
+ distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) * 4 # Scale factor
+
+ # Check if circles overlap
+ if distance >= r1 + r2:
+ return 0.0 # No overlap
+
+ if distance <= abs(r1 - r2):
+ # One circle is completely inside the other
+ smaller_area = math.pi * min(r1, r2)**2
+ larger_area = math.pi * max(r1, r2)**2
+ return smaller_area / larger_area * 100
+
+ # Partial overlap calculation
+ # Using intersection area formula for two circles
+ try:
+ a = r1**2 * math.acos((distance**2 + r1**2 - r2**2) / (2 * distance * r1))
+ b = r2**2 * math.acos((distance**2 + r2**2 - r1**2) / (2 * distance * r2))
+ c = 0.5 * math.sqrt((-distance + r1 + r2) * (distance + r1 - r2) * (distance - r1 + r2) * (distance + r1 + r2))
+
+ intersection_area = a + b - c
+ total_area = math.pi * r1**2 + math.pi * r2**2 - intersection_area
+
+ return (intersection_area / total_area) * 100 if total_area > 0 else 0.0
+ except (ValueError, ZeroDivisionError):
+ # Fallback for edge cases
+ return 0.0
+
+ def generate_venn_data(self, nodes_data: List[Dict]) -> Dict:
+ """Generate complete Venn diagram data with smart labels, positions, radii, and overlaps"""
+ if not nodes_data:
+ return {'nodes': [], 'overlaps': [], 'total_coverage': 0}
+
+ # Extract brand information if available
+ brand = 'unknown'
+ if nodes_data and 'brand' in nodes_data[0]:
+ brand = nodes_data[0]['brand']
+ elif nodes_data and hasattr(nodes_data[0], 'brand'):
+ brand = nodes_data[0].brand
+
+ # Generate smart labels for all nodes
+ smart_labels = self.generate_smart_labels(nodes_data, brand)
+
+ # Calculate optimal positions
+ positions = self.calculate_optimal_positions(nodes_data)
+
+ # Update nodes with smart labels, positions and radii
+ venn_nodes = []
+ for i, node in enumerate(nodes_data):
+ radius = self.calculate_coverage_radius(node['signal'])
+ venn_node = {
+ **node,
+ 'label': smart_labels[i], # Use smart context-aware label
+ 'position': positions[i],
+ 'radius': radius,
+ 'coverage_area': math.pi * radius**2
+ }
+ venn_nodes.append(venn_node)
+
+ # Calculate all pairwise overlaps
+ overlaps = []
+ for i in range(len(venn_nodes)):
+ for j in range(i + 1, len(venn_nodes)):
+ overlap_pct = self.calculate_overlap_percentage(venn_nodes[i], venn_nodes[j])
+ if overlap_pct > 5: # Only record significant overlaps
+ overlaps.append({
+ 'node1_id': i,
+ 'node2_id': j,
+ 'overlap_percentage': overlap_pct,
+ 'node1_label': venn_nodes[i]['label'],
+ 'node2_label': venn_nodes[j]['label']
+ })
+
+ # Calculate total coverage area (accounting for overlaps)
+ total_coverage = sum(node['coverage_area'] for node in venn_nodes)
+
+ return {
+ 'nodes': venn_nodes,
+ 'overlaps': overlaps,
+ 'total_coverage': total_coverage,
+ 'overlap_count': len(overlaps),
+ 'avg_overlap': sum(o['overlap_percentage'] for o in overlaps) / len(overlaps) if overlaps else 0
+ }
+
+ def get_overlap_quality_assessment(self, venn_data: Dict) -> Dict:
+ """Assess the quality of mesh overlap coverage"""
+ overlaps = venn_data['overlaps']
+ nodes = venn_data['nodes']
+
+ if len(nodes) < 2:
+ return {
+ 'quality': 'single_node',
+ 'score': 100,
+ 'description': 'Single node - no overlap analysis needed'
+ }
+
+ # Calculate overlap metrics
+ high_overlaps = [o for o in overlaps if o['overlap_percentage'] > 30]
+ medium_overlaps = [o for o in overlaps if 15 <= o['overlap_percentage'] <= 30]
+ low_overlaps = [o for o in overlaps if 5 <= o['overlap_percentage'] < 15]
+
+ total_possible_overlaps = len(nodes) * (len(nodes) - 1) // 2
+ actual_overlaps = len(overlaps)
+
+ # Scoring
+ score = 0
+
+ # Reward good overlap coverage
+ if actual_overlaps / total_possible_overlaps > 0.7:
+ score += 30
+ elif actual_overlaps / total_possible_overlaps > 0.5:
+ score += 20
+ else:
+ score += 10
+
+ # Reward balanced overlaps
+ if high_overlaps and medium_overlaps:
+ score += 25
+ elif medium_overlaps:
+ score += 15
+
+ # Reward having some overlaps
+ if actual_overlaps > 0:
+ score += 20
+
+ # Average overlap quality
+ avg_overlap = venn_data['avg_overlap']
+ if avg_overlap > 25:
+ score += 25
+ elif avg_overlap > 15:
+ score += 15
+ elif avg_overlap > 5:
+ score += 10
+
+ # Determine quality level with smart descriptions
+ if score >= 80:
+ quality = 'excellent'
+ description = f'Excellent mesh overlap with optimal node placement - {actual_overlaps}/{total_possible_overlaps} connections provide seamless coverage'
+ elif score >= 60:
+ quality = 'good'
+ description = f'Good mesh overlap - {actual_overlaps}/{total_possible_overlaps} node pairs ensure reliable coverage'
+ elif score >= 40:
+ quality = 'fair'
+ description = f'Fair mesh overlap - some coverage gaps may exist between nodes'
+ else:
+ quality = 'poor'
+ description = f'Poor mesh overlap - significant coverage gaps likely, consider repositioning nodes'
+
+ return {
+ 'quality': quality,
+ 'score': min(100, score),
+ 'description': description,
+ 'overlap_ratio': f'{actual_overlaps}/{total_possible_overlaps}',
+ 'avg_overlap_pct': round(avg_overlap, 1)
+ }
diff --git a/MeshAnalyzer/images/mesh_screenshot-min.png b/MeshAnalyzer/images/mesh_screenshot-min.png
new file mode 100644
index 0000000..c31615c
Binary files /dev/null and b/MeshAnalyzer/images/mesh_screenshot-min.png differ
diff --git a/MeshAnalyzer/images/mesh_screenshot-new.png b/MeshAnalyzer/images/mesh_screenshot-new.png
new file mode 100644
index 0000000..c31615c
Binary files /dev/null and b/MeshAnalyzer/images/mesh_screenshot-new.png differ
diff --git a/MeshAnalyzer/images/readme b/MeshAnalyzer/images/readme
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/MeshAnalyzer/images/readme
@@ -0,0 +1 @@
+
diff --git a/MeshAnalyzer/readme.md b/MeshAnalyzer/readme.md
new file mode 100644
index 0000000..e1d3ad8
--- /dev/null
+++ b/MeshAnalyzer/readme.md
@@ -0,0 +1,371 @@
+# 📡 WiFi Mesh Network Analyzer
+
+> **Linux-first mesh network diagnostics with roaming analysis, power management detection, and visual reporting**
+
+[](https://python.org)
+[]()
+[]()
+
+Real insight into how your mesh network actually behaves. See node connections, identify weak spots, track roaming performance, and diagnose power management issues - all without vendor lock-in or proprietary software.
+
+## ⚠️ Framework Support Disclaimer
+
+**Before implementing any power management changes recommended by this tool, please verify with Framework Support first.**
+
+While the WiFi Mesh Network Analyzer provides valuable diagnostic information and generates safe configuration scripts, power management settings should only be modified when addressing specific connectivity issues. The tool may recommend disabling PCIe ASPM (Active State Power Management) or NetworkManager power saving features, but these changes should only be applied if:
+
+- You are experiencing actual WiFi connectivity problems (disconnections, micro-dropouts, poor roaming)
+
+- The analysis clearly identifies power management as the root cause
+
+- Framework Support has reviewed your specific situation and confirmed the recommendation
+
+### Why This Matters
+Power management features exist for good reasons - they extend battery life and reduce heat generation. Disabling them unnecessarily can impact your system's efficiency without providing any benefits. The diagnostic tools help identify potential power management conflicts, but not every detection requires action.
+
+### Recommended Workflow
+
+- Run the analysis to identify potential issues: Run the script per the instructions.
+
+- Document your specific symptoms (connection drops, poor performance, etc.)
+
+- Contact Framework Support with both your symptoms and the tool's findings
+- Apply recommended changes only after confirmation from Support
+- Test thoroughly and revert changes if they don't resolve your specific issues
+
+### Contact Framework Support
+
+[Contact](https://framework.kustomer.help/contact/support-request-ryon9uAuq) - Ask to send your findings to the Linux Support Team
+
+Remember: _These diagnostic tools are designed to help identify issues, not automatically fix them. Always verify recommendations with Framework Support before making system changes._
+
+## 📚 Table of Contents
+
+- [🚀 Key Features](#-key-features)
+- [🎯 Why This Tool?](#-why-this-tool)
+- [📋 Quick Start](#-quick-start)
+- [🎛️ Main Features](#️-main-features)
+- [📊 What You Get](#-what-you-get)
+- [🔧 Advanced Usage](#-advanced-usage)
+- [🛡️ Compatibility](#️-compatibility)
+- [🔍 Troubleshooting](#-troubleshooting)
+- [📱 Example Output](#-example-output)
+- [💡 Pro Tips](#-pro-tips)
+- [🔗 Related Tools](#-related-tools)
+
+## 🚀 Key Features
+
+### 🔍 **Mesh Intelligence**
+- **Topology Mapping** - Visual mesh node detection and relationship analysis
+- **Brand Recognition** - 500+ OUI database covering Eero, Orbi, Google Nest, Ubiquiti, enterprise systems
+- **Coverage Analysis** - Spatial zone mapping with signal strength distribution
+- **Overlap Detection** - Venn diagram analysis of node coverage areas
+
+### 🔄 **Roaming Analysis**
+- **Micro-dropout Detection** - Catch 50ms connection drops your system misses
+- **Handoff Quality Testing** - Measure real roaming performance while walking around
+- **Transition Monitoring** - Real-time tracking of problematic node switches
+- **Pattern Recognition** - Identify roaming loops and sticky client issues
+
+### 🔋 **Power Management**
+- **WiFi Power Saving Detection** - Find power management causing disconnects
+- **Driver-Specific Analysis** - Intel, MediaTek, Qualcomm, Atheros optimization
+- **USB Autosuspend Checking** - Detect USB WiFi adapter suspension problems
+- **Automated Fix Generation** - Create executable scripts to resolve power issues
+
+### 📊 **Professional Reporting**
+- **Interactive HTML Reports** - Modern dark theme with glassmorphism design
+- **Real-time Visualizations** - Hover effects, click-to-copy BSSIDs, smooth animations
+- **Mobile-Responsive** - Optimized for all devices with responsive layout
+- **Historical Tracking** - Performance trends and stability scoring over time
+
+## 🎯 Why This Tool?
+
+### **Mesh Networks Are Opaque**
+Commercial mesh systems hide diagnostics behind limited apps or cloud dashboards. When performance drops or devices won't roam correctly, you're left guessing. This tool surfaces that data directly from the network.
+
+### **Common Problems It Solves**
+- Devices sticking to weak access points instead of roaming
+- Random disconnections and connection drops
+- Coverage dead zones or excessive overlap
+- Power management issues causing false disconnects
+- Micro-dropouts during streaming or gaming
+- Poor handoff performance between nodes
+
+### **Linux-First Approach**
+No vendor lock-in, no cloud dependencies, no proprietary software. Uses standard Linux WiFi tools with intelligent analysis on top.
+
+## 📋 Quick Start
+
+### Prerequisites
+```
+# Ubuntu/Debian
+sudo apt update && sudo apt install iw
+
+# Fedora/RHEL
+sudo dnf install iw
+
+# Arch Linux (NetworkManager required - not iwd compatible)
+sudo pacman -S iw
+```
+
+### Installation
+```
+mkdir mesh_analyzer && cd mesh_analyzer
+```
+
+```
+wget https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/MeshAnalyzer/files/mesh_analyzer.py && \
+wget https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/MeshAnalyzer/files/mesh_html_reporter.py && \
+wget https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/MeshAnalyzer/files/mesh_venn_calculator.py && \
+wget https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/MeshAnalyzer/files/mesh_roaming_detector.py && \
+wget https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/MeshAnalyzer/files/mesh_power_detective.py
+```
+
+(Only needed if you would rather run with sudo ./ instead of sudo python3)
+```
+chmod +x *.py
+```
+
+### Basic Analysis
+```
+sudo python3 mesh_analyzer.py
+```
+
+### Complete Analysis (Recommended) (run both lines below to include an archive for support)
+```
+sudo python3 mesh_analyzer.py --check-power --detect-dropouts --roaming-test --html-report
+```
+then
+```
+sudo python3 mesh_analyzer.py --create-archive
+```
+
+## 🎛️ Main Features
+
+| Feature | Command | Description |
+|---------|---------|-------------|
+| **Basic Analysis** | `sudo python3 mesh_analyzer.py` | Topology mapping and signal analysis |
+| **HTML Report** | `--html-report` | Interactive visual report with charts |
+| **Roaming Test** | `--roaming-test` | Walk-around handoff quality testing |
+| **Micro-dropouts** | `--detect-dropouts` | 30-second connection stability test |
+| **Power Check** | `--check-power` | WiFi power management issue detection |
+| **Continuous Monitor** | `--monitor` | Real-time monitoring every 60 seconds |
+| **Archive Creation** | `--create-archive` | Compressed analysis logs |
+
+## 📊 What You Get
+
+### **Terminal Output**
+- Comprehensive mesh topology with node relationships
+- Signal strength zone mapping (Primary/Secondary/Tertiary/Fringe)
+- Historical performance tracking with stability scores
+- Venn overlap analysis with quality scoring
+- Roaming quality assessment and micro-dropout detection
+- Power management issue identification with automated fixes
+- Intelligent optimization recommendations
+
+### **HTML Report**
+- Professional dark theme with modern glassmorphism design
+- Interactive mesh topology visualization with hover effects
+- Advanced signal strength distribution charts
+- Visual Venn overlap diagrams with SVG rendering
+- Comprehensive coverage analysis with spatial zones
+- Performance trends and historical data tracking
+- Click-to-copy BSSID functionality
+- Mobile-responsive design optimized for all devices
+
+### **Example Analysis Results**
+```
+🏷️ Brand: Eero
+🔧 Type: Tri-Band Mesh
+🏠 Topology: 4 nodes, 8 radios
+📶 Mesh Quality: Good Topology (Score: 70/100)
+🔄 Coverage Overlap: Excellent (Score: 100/100)
+🔋 Power Issues: 1 found (PCIe ASPM)
+📈 Current BSSID Stability: 100/100 (Excellent)
+```
+
+## 🔧 Advanced Usage
+
+### **Roaming Analysis**
+```
+# Detect micro-dropouts (30 seconds) with visual report
+sudo python3 mesh_analyzer.py --html-report --detect-dropouts
+```
+```
+# Test roaming quality while walking with comprehensive reporting
+sudo python3 mesh_analyzer.py --html-report --roaming-test
+```
+```
+# Continuous roaming monitoring with real-time HTML updates
+sudo python3 mesh_analyzer.py --html-report --monitor-roaming
+```
+
+### **Power Management**
+```
+# Check for power issues
+sudo python3 mesh_analyzer.py --html-report --check-power
+```
+
+### **Monitoring & Logging**
+```
+# Continuous monitoring (60s intervals)
+sudo python3 mesh_analyzer.py --monitor
+```
+```
+# Custom scan interval (2 minutes)
+sudo python3 mesh_analyzer.py --monitor --scan-interval 120
+```
+```
+# Show storage information
+sudo python3 mesh_analyzer.py --storage-info
+```
+
+### **Data Management**
+```
+# Reset corrupted history files
+sudo python3 mesh_analyzer.py --reset-history
+```
+```
+# Create archive without new analysis
+sudo python3 mesh_analyzer.py --archive-only
+```
+
+## 🛡️ Compatibility
+
+### **Supported Systems**
+- **Linux Distributions**: Ubuntu, Debian, Fedora, Arch, openSUSE, Pop!_OS, Mint
+- **Network Managers**: NetworkManager (iwd support not yet implemented)
+- **WiFi Hardware**: Intel, MediaTek, Qualcomm, Broadcom, Atheros, Realtek
+- **Mesh Systems**: Eero, Orbi, Google Nest, ASUS, TP-Link, Linksys, Ubiquiti, enterprise systems
+
+### **Requirements**
+- Python 3.6+ (standard on most Linux systems)
+- NetworkManager (not compatible with iwd)
+- Root/sudo access for WiFi scanning
+- Active mesh network connection for best results
+
+### **Zero Dependencies**
+Uses only Python standard library - no pip installs required!
+
+## 🔍 Troubleshooting
+
+### **Common Issues**
+
+**"No WiFi interface found"**
+```
+nmcli device status # Check available interfaces
+ip link show # List all network interfaces
+```
+
+**"0 access points found"**
+- Ensure you have sudo privileges for WiFi scanning
+- Try moving closer to mesh nodes
+- Some enterprise networks restrict scanning
+
+**"Permission denied"**
+- Must run with `sudo` for WiFi scanning capabilities
+- Files are automatically created with correct user permissions
+
+**Module import errors**
+- Ensure all 5 Python files are in the same directory
+- Check file permissions with `ls -la *.py`
+- Optional modules will gracefully degrade if missing
+
+### **Why Some Nodes Don't Appear**
+
+Not all mesh nodes will always show up in scans. This is normal due to:
+
+- **Band Steering** - Mesh systems hide certain bands or backhaul links
+- **Scan Timing** - Nodes may broadcast intermittently or reduce beaconing when idle
+- **Power Management** - Nodes in power-saving modes reduce visibility
+- **Driver Limitations** - Some WiFi chipsets don't report all frequencies reliably
+- **DFS Channels** - Regulatory restrictions on 5GHz channels
+- **Distance/Interference** - Remote nodes may be too weak to detect
+
+**Solutions**: Run multiple scans, use roaming analysis features, or reposition your device.
+
+## 📱 Example Output
+
+### **Terminal Analysis**
+```
+🧠 WiFi Mesh Network Analyzer
+============================================================
+📡 Interface: wlp5s0
+🔗 Connected: Slower | D8:8E:D4:7D:2E:C8 | 7015 MHz | -53 dBm
+
+🏷️ Brand: Eero
+🔧 Type: Tri-Band Mesh
+🏠 Topology: 4 nodes, 8 radios
+📶 Mesh Topology: 🟢 Good Topology (Quality Score: 70/100)
+
+🗺️ SPATIAL COVERAGE ANALYSIS:
+ 🟢 Primary Zone: 2 nodes (-44 to -33dBm)
+ 🟠 Tertiary Zone: 1 nodes (-70 to -70dBm)
+ 🔴 Fringe Zone: 1 nodes (-89 to -89dBm)
+
+🔄 VENN OVERLAP ANALYSIS:
+ 🟢 Coverage Overlap Quality: Excellent (Score: 100/100)
+ 📊 Excellent mesh overlap - 6/6 node pairs overlapping
+
+📈 Current BSSID Performance Analysis:
+ 🟢 Stability Score: 100.0/100 (Excellent)
+ ✅ Success Rate: 100.0%
+
+✅ No micro-dropouts detected in 30 seconds
+🔋 Power Issues: 1 found - PCIe ASPM configuration
+```
+
+### **HTML Report Preview**
+
+
+## 💡 Pro Tips
+
+### **For Best Results**
+- Connect to the mesh network you want to analyze before running
+- Run multiple analyses over time for better historical data
+- Use `--roaming-test` while walking around different areas
+- Check `--detect-dropouts` if experiencing connection issues
+
+### **Performance Optimization**
+- Large mesh networks may take 15-30 seconds to scan completely
+- Use `--monitor` for long-term network health tracking
+- HTML reports work best in modern browsers with JavaScript enabled
+- All analysis data is saved locally for privacy
+
+### **Troubleshooting Workflow**
+1. Start with basic analysis to identify topology
+2. Use `--check-power` if experiencing frequent disconnects
+3. Run `--detect-dropouts` to catch micro-interruptions
+4. Use `--roaming-test` while moving between coverage areas
+5. Generate `--html-report` for comprehensive visual analysis
+
+### **Safety Notes**
+- Always review generated power management fix scripts before execution
+- Enterprise networks may block some WiFi scanning capabilities
+- Tool respects Linux-first workflows and privacy (no cloud dependencies)
+
+## 🔗 Related Tools
+
+### **General WiFi Diagnostics**
+- **[Enhanced WiFi Analyzer](https://github.com/FrameworkComputer/linux-docs/tree/main/Enhanced-WiFi-Analyzer#-enhanced-wifi-analyzer)** - Comprehensive WiFi diagnostics with DFS monitoring, VPN integration, and modern chipset support
+
+### **When to Use Which Tool**
+| Scenario | WiFi Mesh Analyzer | Enhanced WiFi Analyzer |
+|----------|-------------------|------------------------|
+| **Mesh network optimization** | ✅ Specialized analysis | ⚪ Basic detection |
+| **Node topology mapping** | ✅ Visual mesh analysis | ⚪ Not covered |
+| **Roaming performance** | ✅ Detailed testing | ⚪ Limited coverage |
+| **General WiFi issues** | ⚪ Basic coverage | ✅ Comprehensive |
+| **DFS disconnections** | ⚪ Not specialized | ✅ Expert analysis |
+| **VPN conflicts** | ⚪ Not covered | ✅ Modern VPN support |
+| **Chipset optimization** | ⚪ Limited | ✅ Advanced detection |
+
+### **Complementary Workflow**
+1. **WiFi issues first** - Use Enhanced WiFi Analyzer for connectivity problems, DFS issues, VPN conflicts
+2. **Mesh optimization** - Use WiFi Mesh Analyzer for topology analysis and roaming performance
+3. **Best coverage** - Both tools together provide complete WiFi environment understanding
+
+---
+
+**🔧 Production-ready software actively seeking feedback! Please test with different mesh systems, Linux distributions, and network environments.**
diff --git a/Network-Diagnostic-Scripts/5ghz-diag.sh b/Network-Diagnostic-Scripts/5ghz-diag.sh
new file mode 100644
index 0000000..b5545a2
--- /dev/null
+++ b/Network-Diagnostic-Scripts/5ghz-diag.sh
@@ -0,0 +1,636 @@
+#!/bin/bash
+
+# Enhanced WiFi 5GHz Diagnostic Script
+# Compatible with immutable distributions (Bazzite, Project Bluefin)
+
+# Function to check if reboot is needed
+check_reboot_required() {
+ if [ -f /var/run/reboot-required ]; then
+ return 0
+ elif [ "$IS_IMMUTABLE" -eq 1 ] && rpm-ostree status | grep -q "pending deployment"; then
+ return 0
+ elif [ -f /usr/bin/needs-restarting ]; then
+ needs-restarting -r >/dev/null 2>&1
+ if [ $? -eq 1 ]; then
+ return 0
+ fi
+ fi
+ return 1
+}
+
+# Function to check if we have sudo privileges
+check_sudo() {
+ if [ "$EUID" -eq 0 ]; then
+ return 0
+ else
+ if sudo -n true 2>/dev/null; then
+ return 0
+ else
+ echo "Note: Some features require sudo privileges for full functionality"
+ echo "Consider running: sudo $0"
+ return 1
+ fi
+ fi
+}
+
+# Function to run command with proper privileges
+run_with_privilege() {
+ if [ "$EUID" -eq 0 ]; then
+ "$@"
+ else
+ if sudo -n true 2>/dev/null; then
+ sudo "$@"
+ else
+ # Fallback without sudo
+ "$@" 2>/dev/null
+ fi
+ fi
+}
+
+# Function to install missing dependencies
+install_dependencies() {
+ echo "Checking for missing dependencies..."
+ MISSING_DEPS=()
+
+ # Check required tools
+ for cmd in iw nmcli ip; do
+ if ! command -v $cmd >/dev/null 2>&1; then
+ MISSING_DEPS+=($cmd)
+ fi
+ done
+
+ if [ ${#MISSING_DEPS[@]} -eq 0 ]; then
+ echo "All dependencies are installed."
+ return 0
+ fi
+
+ echo "Missing dependencies: ${MISSING_DEPS[@]}"
+ read -p "Would you like to install them? (y/N): " install_confirm
+
+ if [[ "$install_confirm" =~ ^[Yy]$ ]]; then
+ if [ "$IS_IMMUTABLE" -eq 1 ]; then
+ echo "Installing on immutable system..."
+ for dep in "${MISSING_DEPS[@]}"; do
+ case $dep in
+ "iw")
+ sudo rpm-ostree install iw
+ ;;
+ "nmcli")
+ sudo rpm-ostree install NetworkManager
+ ;;
+ "ip")
+ sudo rpm-ostree install iproute
+ ;;
+ esac
+ done
+
+ echo "Installation complete. A reboot is required for changes to take effect."
+ return 1
+ else
+ # Traditional package installation
+ if command -v apt >/dev/null 2>&1; then
+ sudo apt update
+ sudo apt install -y iw network-manager iproute2
+ elif command -v dnf >/dev/null 2>&1; then
+ sudo dnf install -y iw NetworkManager iproute
+ elif command -v pacman >/dev/null 2>&1; then
+ sudo pacman -Sy --noconfirm iw networkmanager iproute2
+ fi
+ fi
+ else
+ echo "Skipping dependency installation. Some features may not work."
+ return 0
+ fi
+}
+
+# Main script
+(
+ echo "=== WiFi 5GHz Comprehensive Diagnostic Report ==="
+ echo "Generated on: $(date)"
+ echo ""
+
+ # Check sudo status
+ check_sudo
+ HAS_SUDO=$?
+
+ # Detect if running on an immutable distribution
+ IS_IMMUTABLE=0
+ if [ -f /usr/lib/os-release ]; then
+ if grep -qiE "bazzite|bluefin|silverblue|kinoite" /usr/lib/os-release; then
+ IS_IMMUTABLE=1
+ echo "Detected immutable distribution"
+ fi
+ fi
+ echo ""
+
+ # Check and install dependencies
+ install_dependencies
+ DEPS_INSTALLED=$?
+
+ # Check if reboot is required
+ if check_reboot_required || [ $DEPS_INSTALLED -eq 1 ]; then
+ echo ""
+ echo "=== REBOOT REQUIRED ==="
+ echo "A system reboot is required to apply changes."
+ echo "Please reboot your system and run this script again."
+ echo ""
+ read -p "Would you like to reboot now? (y/N): " reboot_confirm
+ if [[ "$reboot_confirm" =~ ^[Yy]$ ]]; then
+ echo "Rebooting system..."
+ sudo reboot
+ else
+ echo "Please reboot manually when convenient and run this script again."
+ exit 0
+ fi
+ fi
+
+ # Regulatory Domain Information
+ echo "=== Regulatory Domain Information ==="
+ iw reg get
+ echo ""
+
+ # Interface Detection
+ echo "=== Interface Detection ==="
+ IFACE=$(ip route get 1.1.1.1 2>/dev/null | grep dev | awk '{print $5}')
+ echo "Detected Interface: ${IFACE:-Unknown}"
+ echo "Interface Status:"
+ ip link show $IFACE 2>/dev/null
+ echo ""
+
+ # Hardware Capabilities
+ echo "=== Hardware Capabilities ==="
+ if [ -n "$IFACE" ]; then
+ PHY=$(iw dev $IFACE info | grep wiphy | awk '{print $2}')
+ echo "Wireless Card Information:"
+ lspci -vv -s $(lspci | grep -i network | awk '{print $1}') | grep -E "Network|Subsystem|Kernel driver"
+ echo ""
+ echo "Supported Bands and Frequencies:"
+ iw phy$PHY info | awk '
+ /Band [1-4]:/ { band=$0; print band; show=1; next }
+ /Band/ && !/Band [1-4]:/ { show=0 }
+ show && /Frequencies:|Bitrates:|HT|VHT|HE/ { print $0 }
+ show && /\* [0-9]+ MHz/ { print $0 }
+ '
+ echo ""
+ echo "5GHz Band Capabilities:"
+ iw phy$PHY info | sed -n '/Band 2:/,/Band [34]/p' | grep -v "Band [34]"
+ echo ""
+ echo "Active 5GHz Features:"
+ iw phy$PHY info | grep -E "HT40|VHT|HE|160MHz|DFS"
+ fi
+ echo ""
+
+ # Current Connection Status
+ echo "=== Current Connection Status ==="
+ if [ -n "$IFACE" ]; then
+ iwconfig $IFACE 2>/dev/null
+ echo ""
+ echo "Detailed Link Information:"
+ iw dev $IFACE link
+ echo ""
+ echo "Station Info (if connected):"
+ iw dev $IFACE station dump
+ fi
+ echo ""
+
+ # 5GHz Network Scan
+ echo "=== Scanning for 5GHz Networks ==="
+ if [ -n "$IFACE" ]; then
+ echo "5GHz Networks Found:"
+
+ if [ "$HAS_SUDO" -eq 0 ]; then
+ run_with_privilege iw dev $IFACE scan | awk '
+ BEGIN { print_in_progress=0 }
+ /BSS/ {
+ if (print_in_progress && freq >= 5000) {
+ printf " Network: %s\n", ssid
+ printf " Frequency: %s MHz (Channel %s)\n", freq, channel
+ printf " Signal: %s dBm\n", signal
+ if (security != "") {
+ printf " Security:%s\n", security
+ }
+ printf " BSSID: %s\n\n", bss
+ }
+ bss=$2
+ signal=""
+ security=""
+ freq=""
+ ssid=""
+ channel=""
+ print_in_progress=0
+ }
+ /freq:/ { freq=$2 }
+ /signal:/ { signal=$2 }
+ /SSID:/ {
+ gsub(/^[ \t]*SSID: /, "")
+ ssid=$0
+ print_in_progress=1
+ }
+ /primary channel:/ { channel=$3 }
+ /WPA|RSN/ { security=security " " $0 }
+ END {
+ if (print_in_progress && freq >= 5000) {
+ printf " Network: %s\n", ssid
+ printf " Frequency: %s MHz (Channel %s)\n", freq, channel
+ printf " Signal: %s dBm\n", signal
+ if (security != "") {
+ printf " Security:%s\n", security
+ }
+ printf " BSSID: %s\n\n", bss
+ }
+ }
+ '
+ else
+ echo "Note: Scanning requires sudo privileges"
+ echo "For best results, run with: sudo $0"
+ fi
+ else
+ echo " Interface not detected, cannot scan."
+ fi
+ echo ""
+
+ # Channel Availability
+ echo "=== 5GHz Channel Availability ==="
+ if [ -n "$IFACE" ]; then
+ iw phy$(iw dev $IFACE info | grep wiphy | awk '{print $2}') channels | grep -E "5[0-9]{3} MHz"
+ fi
+ echo ""
+
+ # NetworkManager Configuration
+ echo "=== NetworkManager Configuration ==="
+ nmcli radio wifi
+ nmcli device show $IFACE | grep -E "WIFI-PROPERTIES|GENERAL|CAPABILITIES"
+ echo ""
+ echo "Current Connection Profile (if connected):"
+ CURRENT_CONNECTION=$(nmcli device show $IFACE | grep GENERAL.CONNECTION | awk '{print $2}')
+ if [ "$CURRENT_CONNECTION" != "--" ]; then
+ nmcli connection show "$CURRENT_CONNECTION" | grep -E "802-11-wireless|ipv4|ipv6|frequency"
+ fi
+ echo ""
+
+ # Firmware Information
+ echo "=== Firmware Information ==="
+ echo "Distribution Type: $(if [ "$IS_IMMUTABLE" -eq 1 ]; then echo "Immutable"; else echo "Traditional"; fi)"
+ echo ""
+ echo "Linux Firmware Package Version:"
+ if [ "$IS_IMMUTABLE" -eq 1 ]; then
+ # For immutable distributions - get linux-firmware version directly
+ if command -v rpm-ostree >/dev/null 2>&1; then
+ # Get version from rpm database
+ FIRMWARE_VERSION=$(rpm -q --queryformat '%{VERSION}-%{RELEASE}' linux-firmware 2>/dev/null || true)
+ if [ -n "$FIRMWARE_VERSION" ]; then
+ echo " linux-firmware: $FIRMWARE_VERSION"
+ else
+ # Fallback to rpm-ostree status
+ rpm-ostree status | grep -A5 "^ \* " | grep -E "linux-firmware|Version" | head -2 || echo " Not found"
+ fi
+ fi
+ echo ""
+ echo "Using layered packages:"
+ rpm-ostree status | grep -A1 "^ \* " | grep LayeredPackages || echo " None"
+ else
+ # For traditional distributions
+ if command -v dpkg >/dev/null 2>&1; then
+ dpkg -l | grep linux-firmware | awk '{print $2 " version: " $3}'
+ elif command -v pacman >/dev/null 2>&1; then
+ pacman -Q linux-firmware 2>/dev/null
+ elif command -v rpm >/dev/null 2>&1; then
+ rpm -q --queryformat 'linux-firmware version: %{VERSION}-%{RELEASE}\n' linux-firmware 2>/dev/null || echo " Not found"
+ else
+ echo " Package manager not detected"
+ fi
+ fi
+ echo ""
+ echo "Wireless Firmware Files:"
+ find /lib/firmware /usr/lib/firmware -path "*/mediatek/*MT7922*" -o -path "*/mediatek/*mt7922*" -o -path "*/mt*" -type f 2>/dev/null | while read file; do
+ if [ -f "$file" ]; then
+ stat -c "%n: %y" "$file"
+ fi
+ done | head -10
+ echo ""
+ echo "Loaded Firmware Version:"
+ if [ -n "$IFACE" ]; then
+ # Use dmesg with proper privileges
+ run_with_privilege dmesg | grep -iE "firmware|iwlwifi|mt79" | grep -v "Direct firmware load" | tail -5
+ fi
+ echo ""
+
+ # Driver and Module Information
+ echo "=== Driver and Module Information ==="
+ lspci -nnk 2>/dev/null | grep -A 3 -i network
+ echo ""
+ if [ -n "$IFACE" ]; then
+ MODULE=$(ls -l /sys/class/net/$IFACE/device/driver/module 2>/dev/null | awk -F/ '{print $NF}')
+ if [ -n "$MODULE" ]; then
+ echo "Wireless Module: $MODULE"
+ echo "Module Parameters:"
+ for param in /sys/module/$MODULE/parameters/*; do
+ if [ -f "$param" ]; then
+ echo " $(basename $param): $(cat $param 2>/dev/null)"
+ fi
+ done
+ echo ""
+ echo "Module Information:"
+ modinfo $MODULE 2>/dev/null | grep -E "filename|version|firmware|description"
+ fi
+ fi
+ echo ""
+
+ # Connection Event Analysis
+ echo "=== Connection Event Analysis (Last 24 Hours) ==="
+ echo "Connection Attempts, Successes, and Failures:"
+ journalctl -u NetworkManager -u wpa_supplicant --since "24 hours ago" --no-pager 2>/dev/null | \
+ grep -Ei 'attempting|associating|authenticating|connected to|successfully|failed|fail|disconnect|error|auth_timeout|deauthenticating|reason|5[0-9]{3}|band|freq|channel' | \
+ awk '{print $1,$2,$3,$0}' | tail -50
+ echo ""
+
+ # Error and Warning Analysis
+ echo "=== Recent Errors and Warnings ==="
+ journalctl -u NetworkManager -u wpa_supplicant --since "24 hours ago" --no-pager 2>/dev/null | \
+ grep -i -E 'error|warning|fail|timeout' | tail -10
+ echo ""
+
+ # Power Management Settings
+ echo "=== Power Management Settings ==="
+ if [ -n "$IFACE" ]; then
+ echo "Power Saving Status:"
+ iw dev $IFACE get power_save
+ echo ""
+ echo "TLP/Power Management Configuration:"
+ if [ -f /etc/tlp.conf ]; then
+ grep -E "WIFI|POWER" /etc/tlp.conf 2>/dev/null | grep -v ^#
+ else
+ echo " TLP not installed"
+ fi
+ fi
+ echo ""
+
+ # Band Steering and Roaming Settings
+ echo "=== Band Steering and Roaming Settings ==="
+ if [ -n "$IFACE" ]; then
+ echo "BSS Transition Management Capability:"
+ iw phy$(iw dev $IFACE info | grep wiphy | awk '{print $2}') info | grep -A 5 "Supported extended features:" | grep -E "BSS|FT|FILS"
+ echo ""
+ echo "Current Roaming Behavior:"
+ ip addr show $IFACE 2>/dev/null | grep -E "brd|scope|valid_lft"
+ echo ""
+ echo "NetworkManager WiFi Backend Configuration:"
+ if [ -f /etc/NetworkManager/conf.d/wifi_backend.conf ]; then
+ cat /etc/NetworkManager/conf.d/wifi_backend.conf
+ else
+ echo " No custom WiFi backend configuration found"
+ fi
+ echo ""
+ echo "WiFi Band Selection Configuration:"
+ grep -r "wifi.band-" /etc/NetworkManager/ 2>/dev/null || echo " No band selection config found"
+ fi
+ echo ""
+
+ # 5GHz Connection Issues
+ echo "=== 5GHz Connection Issues ==="
+ if [ -n "$IFACE" ]; then
+ echo "Checking for 5GHz connection problems:"
+
+ # Check if hardware supports 5GHz
+ if iw phy$(iw dev $IFACE info | grep wiphy | awk '{print $2}') info | grep -qE "5[0-9][0-9][0-9].*MHz"; then
+ echo "✓ Hardware supports 5GHz"
+ else
+ echo "✗ Hardware does not support 5GHz"
+ fi
+
+ # Check if connected to 5GHz or 6GHz
+ CURRENT_FREQ=$(iw dev $IFACE link 2>/dev/null | grep freq | awk '{print $2}')
+ if [ -n "$CURRENT_FREQ" ]; then
+ FREQ_NUM=$(echo "$CURRENT_FREQ" | cut -d. -f1)
+ if [ "$FREQ_NUM" -ge 5925 ]; then
+ echo "✓ Connected to 6GHz/WiFi 6E ($CURRENT_FREQ MHz)"
+ elif [ "$FREQ_NUM" -ge 5000 ]; then
+ echo "✓ Connected to 5GHz ($CURRENT_FREQ MHz)"
+ else
+ echo "✗ Connected to 2.4GHz ($CURRENT_FREQ MHz)"
+
+ # Check if 5GHz of the same SSID is available
+ CURRENT_SSID=$(iw dev $IFACE link 2>/dev/null | grep SSID | awk '{$1=""; print $0}' | sed 's/^ *//')
+ if [ -n "$CURRENT_SSID" ] && [ "$HAS_SUDO" -eq 0 ]; then
+ if run_with_privilege iw dev $IFACE scan 2>/dev/null | grep -A5 "$CURRENT_SSID" | grep -q "freq: 5[0-9][0-9][0-9]"; then
+ echo " 5GHz version of '$CURRENT_SSID' is available"
+ echo " Band steering might be failing or disabled"
+ fi
+ fi
+ fi
+ else
+ echo "! Not connected to any network"
+ fi
+
+ # Check band selection settings
+ echo ""
+ echo "Band Selection Settings:"
+ if nmcli connection show "$CURRENT_SSID" 2>/dev/null | grep -q "wifi.band"; then
+ nmcli connection show "$CURRENT_SSID" | grep wifi.band
+ else
+ echo " No specific band preference set"
+ fi
+
+ # Check for DFS issues
+ echo ""
+ echo "DFS Channel Issues:"
+ if journalctl -u NetworkManager --since "24 hours ago" 2>/dev/null | grep -qi "dfs\|radar"; then
+ echo " DFS/Radar events detected in logs"
+ else
+ echo " No DFS issues detected"
+ fi
+
+ # Check driver/firmware issues
+ echo ""
+ echo "Driver/Firmware Issues:"
+ if run_with_privilege dmesg | grep -i mt7921 | grep -qi "error\|fail\|timeout"; then
+ echo " Driver/firmware errors detected"
+ run_with_privilege dmesg | grep -i mt7921 | grep -i "error\|fail\|timeout" | tail -3
+ else
+ echo " No driver errors detected"
+ fi
+ fi
+ echo ""
+
+ # Summary and Recommendations
+ echo "=== Summary and Recommendations ==="
+ echo ""
+
+ # Create formatted summary box
+ echo "+----------------------------------------+"
+ echo "| WiFi DIAGNOSTIC SUMMARY |"
+ echo "+----------------------------------------+"
+ echo ""
+
+ # Connection status with color coding
+ if [ -n "$IFACE" ]; then
+ CURRENT_SSID=$(iw dev $IFACE link 2>/dev/null | grep SSID | awk '{$1=""; print $0}' | sed 's/^ *//')
+ CURRENT_FREQ=$(iw dev $IFACE link 2>/dev/null | grep freq | awk '{print $2}')
+
+ echo "🛜 CONNECTION STATUS"
+ echo "-------------------"
+ if [ -n "$CURRENT_FREQ" ]; then
+ FREQ_NUM=$(echo "$CURRENT_FREQ" | cut -d. -f1)
+ if [ "$FREQ_NUM" -ge 5925 ]; then
+ echo " ✅ Connected to: $CURRENT_SSID"
+ echo " ✅ Band: WiFi 6E (6GHz - $CURRENT_FREQ MHz)"
+ CONNECTION_STATUS="EXCELLENT"
+ BAND_TYPE="6GHz"
+ elif [ "$FREQ_NUM" -ge 5000 ]; then
+ echo " ✅ Connected to: $CURRENT_SSID"
+ echo " ✅ Band: 5GHz ($CURRENT_FREQ MHz)"
+ CONNECTION_STATUS="GOOD"
+ BAND_TYPE="5GHz"
+ else
+ echo " ⚠️ Connected to: $CURRENT_SSID"
+ echo " ⚠️ Band: 2.4GHz ($CURRENT_FREQ MHz)"
+ CONNECTION_STATUS="SUBOPTIMAL"
+ BAND_TYPE="2.4GHz"
+ fi
+
+ # Get signal strength and speed
+ SIGNAL=$(iw dev $IFACE link 2>/dev/null | grep signal | awk '{print $2}')
+ RX_SPEED=$(iw dev $IFACE link 2>/dev/null | grep "rx bitrate" | awk '{print $3 " " $4}')
+ TX_SPEED=$(iw dev $IFACE link 2>/dev/null | grep "tx bitrate" | awk '{print $3 " " $4}')
+
+ echo " 📶 Signal: $SIGNAL dBm ($(if [ "${SIGNAL:-0}" -ge -50 ]; then echo "Excellent"; elif [ "${SIGNAL:-0}" -ge -60 ]; then echo "Good"; elif [ "${SIGNAL:-0}" -ge -70 ]; then echo "Fair"; else echo "Poor"; fi))"
+ echo " 🚀 Speed: RX $RX_SPEED / TX $TX_SPEED"
+ else
+ echo " ❌ Not connected to any network"
+ CONNECTION_STATUS="DISCONNECTED"
+ fi
+ echo ""
+
+ # Hardware capabilities
+ echo "🔧 HARDWARE STATUS"
+ echo "-----------------"
+ echo " Device: $(lspci | grep -i network | sed 's/^.*: //' | head -1)"
+ echo " Driver: $(ls -l /sys/class/net/$IFACE/device/driver/module 2>/dev/null | awk -F/ '{print $NF}' || echo "Unknown")"
+ echo " Firmware: $(rpm -q --queryformat '%{VERSION}-%{RELEASE}' linux-firmware 2>/dev/null || echo "Unknown")"
+ echo ""
+
+ # Performance analysis
+ echo "⚡ PERFORMANCE ANALYSIS"
+ echo "---------------------"
+ POWER_SAVE=$(iw dev $IFACE get power_save 2>/dev/null | awk '{print $3}')
+ if [ "$POWER_SAVE" = "on" ]; then
+ echo " ⚠️ Power saving: ENABLED (may impact performance)"
+ else
+ echo " ✅ Power saving: DISABLED"
+ fi
+
+ if [ "$CONNECTION_STATUS" = "SUBOPTIMAL" ] && [ "$SCAN_COUNT" -gt 0 ]; then
+ echo " ⚠️ Using slower 2.4GHz band but 5GHz networks available"
+ fi
+
+ # Check for firmware errors
+ if run_with_privilege dmesg | grep -qi "error.*mt7921\|mt7921.*error"; then
+ echo " ⚠️ Firmware errors detected in system logs"
+ fi
+ echo ""
+
+ # Recommendations
+ echo "💡 RECOMMENDATIONS"
+ echo "-----------------"
+ RECOMMENDATIONS=0
+
+ if [ "$POWER_SAVE" = "on" ]; then
+ echo " 1. Disable power saving for better performance:"
+ echo -e " \e[1;32msudo iw dev $IFACE set power_save off\e[0m # Only works for testing"
+ echo -e " To make it permanent: \e[1;32msudo nmcli connection modify \"$CURRENT_SSID\" wifi.powersave 2\e[0m"
+ RECOMMENDATIONS=$((RECOMMENDATIONS + 1))
+ fi
+
+ if [ "$CONNECTION_STATUS" = "SUBOPTIMAL" ] && [ "$SCAN_COUNT" -gt 0 ]; then
+ echo " $((RECOMMENDATIONS + 1)). Force 5GHz/6GHz band connection:"
+ echo -e " \e[1;32mnmcli connection modify \"$CURRENT_SSID\" wifi.band 5GHz\e[0m"
+ RECOMMENDATIONS=$((RECOMMENDATIONS + 1))
+ fi
+
+ # Check if no 5GHz networks are visible despite hardware support
+ # Check if no 5GHz networks are visible despite hardware support
+ if [ "${SCAN_COUNT:-0}" -eq 0 ] && \
+ iw phy$(iw dev "$IFACE" | awk '/phy#/ {print $2}') info | grep -qE "5[0-9]{3}.*MHz"; then
+ echo " $((RECOMMENDATIONS + 1)). No 5GHz networks found! Troubleshooting steps:"
+ echo " a) Check if router has 5GHz enabled and broadcasting"
+ echo " b) Ensure router is in range (5GHz has shorter range than 2.4GHz)"
+ echo " c) Try disabling regulatory domain restrictions:"
+ echo -e " \e[1;32msudo iw reg set 00\e[0m # Temporary test only"
+ echo " d) Verify driver support with:"
+ echo -e " \e[1;32msudo modprobe -r mt7921e && sudo modprobe mt7921e\e[0m"
+ echo " e) If still not working, check dmesg for errors:"
+ echo -e " \e[1;32msudo dmesg | grep -i mt7921\e[0m"
+ RECOMMENDATIONS=$((RECOMMENDATIONS + 1))
+ fi
+
+ if run_with_privilege dmesg | grep -qi "error.*mt7921\|mt7921.*error"; then
+ echo " $((RECOMMENDATIONS + 1)). Update firmware to latest version:"
+ if [ "$IS_IMMUTABLE" -eq 1 ]; then
+ echo -e " \e[1;32msudo rpm-ostree upgrade\e[0m"
+ else
+ if command -v apt >/dev/null 2>&1; then
+ echo -e " \e[1;32msudo apt update && sudo apt upgrade linux-firmware\e[0m"
+ elif command -v dnf >/dev/null 2>&1; then
+ echo -e " \e[1;32msudo dnf upgrade linux-firmware\e[0m"
+ elif command -v pacman >/dev/null 2>&1; then
+ echo -e " \e[1;32msudo pacman -Syu linux-firmware\e[0m"
+ elif command -v zypper >/dev/null 2>&1; then
+ echo -e " \e[1;32msudo zypper update kernel-firmware\e[0m"
+ else
+ echo -e " \e[1;32mUpdate linux-firmware package using your distribution's package manager\e[0m"
+ fi
+ fi
+ RECOMMENDATIONS=$((RECOMMENDATIONS + 1))
+ fi
+
+ # Check for 5GHz connection failures
+ if journalctl -u NetworkManager --since "24 hours ago" 2>/dev/null | grep -qi "freq.*5[0-9][0-9][0-9].*fail"; then
+ echo " $((RECOMMENDATIONS + 1)). 5GHz connection failures detected. Try:"
+ echo " a) Reset network settings:"
+ echo -e " \e[1;32mnmcli connection delete \"$CURRENT_SSID\"\e[0m"
+ echo " Then reconnect to recreate profile"
+ echo " b) Disable MAC randomization:"
+ echo -e " \e[1;32mnmcli connection modify \"$CURRENT_SSID\" wifi.cloned-mac-address stable\e[0m"
+ echo " c) Use specific channel if DFS issues exist:"
+ echo -e " \e[1;32mnmcli connection modify \"$CURRENT_SSID\" wifi.channel 36\e[0m"
+ RECOMMENDATIONS=$((RECOMMENDATIONS + 1))
+ fi
+
+ if [ $RECOMMENDATIONS -eq 0 ]; then
+ echo " ✅ All systems optimal - no changes recommended!"
+ fi
+ echo ""
+
+ # Quick statistics
+ echo "📊 QUICK STATS"
+ echo "-------------"
+ SCAN_COUNT=$(run_with_privilege iw dev $IFACE scan 2>/dev/null | grep -cE 'freq: [5-6][0-9][0-9][0-9]' || echo "0")
+ echo " Networks found: $SCAN_COUNT (5GHz/6GHz)"
+ echo " Connection quality: $CONNECTION_STATUS"
+ echo " Current band: ${BAND_TYPE:-Unknown}"
+ UPTIME=$(iw dev $IFACE station dump 2>/dev/null | grep "connected time" | awk '{print $3}')
+ if [ -n "$UPTIME" ]; then
+ echo " Connected for: $((UPTIME / 3600)) hours, $(( (UPTIME % 3600) / 60 )) minutes"
+ fi
+ echo ""
+
+ # Final status
+ echo "+----------------------------------------+"
+ if [ "$CONNECTION_STATUS" = "EXCELLENT" ] || [ "$CONNECTION_STATUS" = "GOOD" ]; then
+ echo "| 🎉 WiFi Performance: $(printf "%-12s" "$CONNECTION_STATUS") 🎉 |"
+ elif [ "$CONNECTION_STATUS" = "SUBOPTIMAL" ]; then
+ echo "| ⚠️ WiFi Performance: $(printf "%-12s" "$CONNECTION_STATUS") ⚠️ |"
+ else
+ echo "| ❌ WiFi Status: $(printf "%-18s" "$CONNECTION_STATUS") ❌ |"
+ fi
+ echo "+----------------------------------------+"
+ else
+ echo "❌ No wireless interface detected!"
+ echo ""
+ echo "Please check your wireless hardware and drivers."
+ echo "+----------------------------------------+"
+ echo "| WiFi Status: NO INTERFACE |"
+ echo "+----------------------------------------+"
+ fi
+
+) | tee wifi_5ghz_diagnostic_$(date +%Y%m%d_%H%M%S).log
diff --git a/Network-Diagnostic-Scripts/README.md b/Network-Diagnostic-Scripts/README.md
index 8eb7315..bb54312 100644
--- a/Network-Diagnostic-Scripts/README.md
+++ b/Network-Diagnostic-Scripts/README.md
@@ -23,6 +23,12 @@ sudo apt install curl -y
**Looking for the [Wi-Fi Diagnostic tool](https://github.com/FrameworkComputer/linux-docs/tree/main/Network-Diagnostic-Scripts#to-install-wi-fi-diagnostic-script-simply-run)? Click here to scroll down.**
+**Looking for the [Frequency Diagnostic Tool](https://github.com/FrameworkComputer/linux-docs/tree/main/Network-Diagnostic-Scripts#to-install-5ghz6ghz-frequency-diagnostic-tool-simply-run)? Click here to scroll down.**
+
+ >Use Wifi‑Diagnostic.sh when you want a broad view of your Wi‑Fi health (interfaces, speed, VPN, overall environment).
+
+ >Use 5ghz‑diag.sh when you need to deeply troubleshoot why your system isn’t seeing or staying on 5 GHz/6 GHz (reg domains, PHY capabilities, DFS, band‑steering, driver/firmware issues).
+
@@ -104,6 +110,57 @@ bash Wifi-Diagnostic.sh
- Suggests further actions based on diagnostic results
- Saves complete diagnostic results to a log file
+------------------------------------------------------------------------------------------------------------------------------
+
+## To Install 5Ghz/6Ghz Frequency Diagnostic Tool, simply run:
+**Important:** The images below are merely the bottom half of the data piped out into a log file into your home directory. They only reflect a brief summary of the findings.
+You cam take the completed log to get help from the support team.
+
+This script is a comprehensive diagnostic tool specifically designed to troubleshoot issues related to 5GHz (and 6GHz) WiFi connections on Linux systems, including those using immutable distributions like Bazzite and Project Bluefin. It automates the collection of detailed information about your wireless hardware, software configuration, and network environment to help identify potential problems preventing successful connection or optimal performance on faster WiFi bands.
+
+
+```
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/Network-Diagnostic-Scripts/5ghz-diag.sh -o 5ghz-diag.sh && clear && sudo bash 5ghz-diag.sh
+```
+
+Running the tool in the future
+After the install, you can run going forward with the following in the HOME directory. So merely opening a terminal and running this will work if the original script has not been moved.
+
+
+```
+sudo bash 5ghz-diag.sh
+```
+
+
+
+
+
+
+
+- Checks for pending reboots: Determines if a system reboot is required, especially relevant after installing dependencies on immutable systems.
+- Verifies sudo privileges: Checks if the script is run with administrative privileges, noting that some features require sudo for full functionality.
+- Installs missing dependencies: Identifies and offers to install necessary command-line tools (iw, nmcli, ip) using the appropriate package manager for your distribution (immutable or traditional).
+- Displays Regulatory Domain Information: Shows the configured wireless regulatory domain, which can impact available channels and power levels.
+- Detects Network Interface: Identifies your primary wireless network interface.
+- Reports Hardware Capabilities: Provides details about your wireless card, including supported WiFi bands (2.4GHz, 5GHz, 6GHz), frequencies, and features (like HT, VHT, HE, 160MHz, DFS).
+- Shows Current Connection Status: Displays information about your current WiFi connection, including SSID, frequency, signal strength, and transfer rates.
+- Scans for 5GHz Networks: Lists available 5GHz (and 6GHz) wireless networks in your vicinity, including their frequency, signal strength, security type, and BSSID (requires sudo).
+- Lists 5GHz Channel Availability: Shows the 5GHz channels supported by your hardware.
+- Examines NetworkManager Configuration: Displays relevant NetworkManager settings, including WiFi radio status, device capabilities, and the configuration of your current connection profile.
+- Provides Firmware Information: Reports the version of the linux-firmware package and attempts to show loaded wireless firmware files and recent firmware-related messages from the system log (dmesg).
+- Details Driver and Module Information: Shows the wireless driver in use, its parameters, and general module information.
+- Analyzes Connection Events: Extracts recent connection attempts, successes, and failures related to NetworkManager and wpa_supplicant from the system journal.
+- Reviews Recent Errors and Warnings: Filters the system journal for recent errors and warnings related to NetworkManager and wpa_supplicant.
+- Checks Power Management Settings: Reports the current WiFi power saving status and checks for relevant configurations in TLP if installed.
+- Investigates Band Steering and Roaming Settings: Displays information about BSS transition management capabilities and NetworkManager's band selection configuration.
+- Identifies 5GHz Connection Issues: Performs specific checks to diagnose common 5GHz connection problems, such as hardware support, current connection band, visibility of 5GHz networks, band selection settings, DFS issues, and driver/firmware errors.
+- Generates a Summary and Recommendations: Provides a concise summary of the connection status, hardware details, and performance analysis, along with tailored recommendations based on the findings.
+
+ ### Important
+- **Logs Output**: Saves the entire diagnostic report to a timestamped log file.
+
+
+
------------------------------------------------------------------------------------------------------------------------------
diff --git a/Network-Diagnostic-Scripts/images/5gz1.png b/Network-Diagnostic-Scripts/images/5gz1.png
new file mode 100644
index 0000000..e344a18
Binary files /dev/null and b/Network-Diagnostic-Scripts/images/5gz1.png differ
diff --git a/Network-Diagnostic-Scripts/images/5gz2.png b/Network-Diagnostic-Scripts/images/5gz2.png
new file mode 100644
index 0000000..f4a6b20
Binary files /dev/null and b/Network-Diagnostic-Scripts/images/5gz2.png differ
diff --git a/Ubuntu24.04LTS-Setup-amd-fw16.md b/Ubuntu24.04LTS-Setup-amd-fw16.md
index 1a0fe85..3fb4426 100644
--- a/Ubuntu24.04LTS-Setup-amd-fw16.md
+++ b/Ubuntu24.04LTS-Setup-amd-fw16.md
@@ -29,7 +29,9 @@ sudo apt update && sudo apt upgrade -y && sudo snap refresh
-### USB-C Video Out from dGPU directly
+### (No Longer Needed) USB-C Video Out from dGPU directly
+UPDATED: CURRENT FIRMWARE MAKES THIS UNNEEDED, JUST MAKE SURE [YOUR FIRMWARE IS CURRENT](https://guides.frame.work/Guide/Fedora+41+Installation+on+the+Framework+Laptop+16/394?lang=en#s2261).
+**With the latest firmware, just connect your display**.
By default, when you attach a USB-C cable to the dGPU port, it will not come out of [D3cold](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/device-power-states) - this is by design and is to preserve your battery life during everyday usage.
diff --git a/amdgpu-workarounds/amdgpu_freesync_video/amdgpu_freesync_video.md b/amdgpu-workarounds/amdgpu_freesync_video/amdgpu_freesync_video.md
index 25d4657..15a9d56 100644
--- a/amdgpu-workarounds/amdgpu_freesync_video/amdgpu_freesync_video.md
+++ b/amdgpu-workarounds/amdgpu_freesync_video/amdgpu_freesync_video.md
@@ -1,3 +1,4 @@
+# NO Longer Needed - retaining just in case it resurfaces again
## amdgpu.freesync_video=1 parameter workaround Franework Laptop 16 ONLY
### For Framework Laptop 16 not providing all of the expected refresh rates in kernels 6.9 and up.
diff --git a/disable-accidental-wakeup/images/install.png b/disable-accidental-wakeup/images/install.png
new file mode 100644
index 0000000..cd88712
Binary files /dev/null and b/disable-accidental-wakeup/images/install.png differ
diff --git a/disable-accidental-wakeup/images/readme b/disable-accidental-wakeup/images/readme
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/disable-accidental-wakeup/images/readme
@@ -0,0 +1 @@
+
diff --git a/disable-accidental-wakeup/images/remove.png b/disable-accidental-wakeup/images/remove.png
new file mode 100644
index 0000000..2dca2da
Binary files /dev/null and b/disable-accidental-wakeup/images/remove.png differ
diff --git a/disable-accidental-wakeup/readme.md b/disable-accidental-wakeup/readme.md
new file mode 100644
index 0000000..8c9ef18
--- /dev/null
+++ b/disable-accidental-wakeup/readme.md
@@ -0,0 +1,77 @@
+# Disable Accidental Wakeup Script
+
+**Which laptop does this work with:** Framework Laptop 16.
+
+**(Considering this Beta/Testing as I am ironing out some keyboard backlighting behavior)**
+
+>
+>
+> **NOTE:** This may not disable the keyboard backlighting when you place it into suspend. By default without this script, the keyboard backlight goes out automatically.
+With this script,you will need to **Fn space bar to turn off the backlight** before you enter suspend or it may remain on. This is a side effect of the script.
+>
+>
+
+
+
+
+
+**The problem:** In some instances, Framework Laptop 16 can accidentally come out of its suspend state. This usually occurs when traveling, walking, taking a bus, placing the laptop into a backpack.
+Overall the agreed upon cause is that this happens due to keyboard presses while it's in a state of suspend, thus waking it up.
+
+**The workaround:** Our engineering team has it [on their roadmap](https://community.frame.work/t/responded-waking-from-suspend-w-lid-closed/47497/73?u=matt_hartley) to fix this on the BIOS level, however until that is available this script is a reliable workaround.
+
+**What this script does:** This script creates and enables a systemd service that prevents specific devices from waking the laptop from suspend. It disables wakeup functionality for **keyboard presses, touchpad presses, and lid lift events** by modifying the wakeup settings for USB devices and other relevant system devices. However, it ensures the system can still be brought out of suspend with a power button push.
+The script configures the service to run at boot, ensuring these settings are applied consistently, and reloads the systemd daemon to recognize the new service.
+
+**How do I resume from suspend after running this script:** Press the power button one time.
+
+**Does this break functionality:** No, this script does not break functionality as long as it is implemented using this script on Ubuntu LTS or Fedora.
+The script ensures that specific wakeup events, such as keyboard presses, touchpad presses, and lid lifts, are disabled, but it leaves the power button functional for resuming the system from suspend.
+
+- Suspend Behavior: The laptop can still enter suspend mode when the lid is closed.
+- Resume Behavior: The laptop can be brought out of suspend using the power button.
+- Disabled Wakeup Events: Keyboard and touchpad presses, as well as lifting the lid, will no longer wake the system, ensuring the system only resumes through intentional user interaction (e.g., the power button).
+
+**Restoring back to defaults:** Obviously this is not going to be a match for everyone, user habits may change. Therefore wwe also offer a script to restore your suspend configuration back to installation defaults.
+This script is provided here as well.
+
+**Will this work on other distros:** Likely yes, I see no reason why it would not assuming paths and so forth match what we are doing here. But it is completely untested.
+
+
+## Download and activate the Disable Accidental Wakeup Script
+
+Fedora, make sure curl is installed:
+
+```
+sudo dnf install curl -y
+```
+
+Ubuntu, make sure curl is installed:
+
+```
+sudo apt update && sudo apt install curl -y
+```
+
+Simply paste in this command into your kernel, press the enter:
+(No reboot is needed, it's ready to go after running this script)
+
+```
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/disable-accidental-wakeup/wakeup.sh -o wakeup.sh && clear && sudo bash wakeup.sh
+```
+
+
+
+
+
+
+## Stop, disable and remove the Disable Accidental Wakeup Script
+(Including the removal of disable-wakeup.service)
+(No reboot is needed, it's ready to go after running this script)
+
+```
+curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/disable-accidental-wakeup/restore_defaults.sh -o restore_defaults.sh && clear && sudo bash restore_defaults.sh
+```
+
+
+
+
diff --git a/disable-accidental-wakeup/restore_defaults.sh b/disable-accidental-wakeup/restore_defaults.sh
new file mode 100644
index 0000000..6918970
--- /dev/null
+++ b/disable-accidental-wakeup/restore_defaults.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+# Script to restore wakeup functionality and remove custom disable-wakeup.service
+# Compatible with Fedora and Ubuntu
+
+# Define the systemd service file path
+SERVICE_FILE="/etc/systemd/system/disable-wakeup.service"
+
+# Step 1: Check for root privileges
+if [[ $EUID -ne 0 ]]; then
+ echo "This script must be run as root. Please use sudo."
+ exit 1
+fi
+
+# Step 2: Stop and disable the service
+echo "Stopping and disabling the disable-wakeup.service..."
+systemctl stop disable-wakeup.service 2>/dev/null
+systemctl disable disable-wakeup.service 2>/dev/null
+
+if [[ $? -eq 0 ]]; then
+ echo "Service stopped and disabled successfully."
+else
+ echo "The service may not exist or was not running. Proceeding..."
+fi
+
+# Step 3: Remove the systemd service file
+if [[ -f "$SERVICE_FILE" ]]; then
+ echo "Removing the systemd service file at $SERVICE_FILE..."
+ rm -f "$SERVICE_FILE"
+ if [[ $? -eq 0 ]]; then
+ echo "Service file removed."
+ else
+ echo "Failed to remove the service file. Please check permissions."
+ exit 1
+ fi
+else
+ echo "Service file not found. Skipping removal."
+fi
+
+# Step 4: Reload systemd daemon
+echo "Reloading systemd daemon to apply changes..."
+systemctl daemon-reload
+
+# Step 5: Restore default wakeup settings
+echo "Restoring default wakeup settings for devices..."
+for device in /sys/bus/usb/devices/*/power/wakeup; do
+ if [[ -f "$device" ]]; then
+ echo "enabled" > "$device"
+ echo "Restored default wakeup for $device"
+ fi
+done
+
+if [[ -f "/sys/devices/platform/AMDI0010:03/i2c-1/i2c-PIXA3854:00/power/wakeup" ]]; then
+ echo "enabled" > /sys/devices/platform/AMDI0010:03/i2c-1/i2c-PIXA3854:00/power/wakeup
+ echo "Restored default wakeup for AMDI0010 device."
+fi
+
+find /sys/devices -type f -name 'wakeup' | grep -i PNP0C0D | while read -r wakeup_file; do
+ echo "enabled" > "$wakeup_file"
+ echo "Restored default wakeup for $wakeup_file"
+done
+
+# Final Step: Notify the user
+echo "Restoration complete. All changes made by the disable-wakeup.service have been undone."
+echo "If issues persist, verify the wakeup settings manually using: find /sys/devices -name 'wakeup'"
+
diff --git a/disable-accidental-wakeup/wakeup.sh b/disable-accidental-wakeup/wakeup.sh
new file mode 100644
index 0000000..e39d925
--- /dev/null
+++ b/disable-accidental-wakeup/wakeup.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+# Script to create and enable a systemd service to disable wakeup for specific devices at boot
+# Compatible with Fedora and Ubuntu
+
+# Define the systemd service file path
+SERVICE_FILE="/etc/systemd/system/disable-wakeup.service"
+
+# Step 1: Check for root privileges
+if [[ $EUID -ne 0 ]]; then
+ echo "This script must be run as root. Please use sudo."
+ exit 1
+fi
+
+# Step 2: Create the systemd service file
+echo "Creating systemd service file at $SERVICE_FILE..."
+cat << 'EOF' > "$SERVICE_FILE"
+[Unit]
+Description=Disable Wakeup on Devices
+After=multi-user.target
+
+[Service]
+Type=oneshot
+ExecStart=/bin/bash -c "echo disabled > /sys/devices/platform/AMDI0010:03/i2c-1/i2c-PIXA3854:00/power/wakeup"
+ExecStartPost=/bin/bash -c "for device in /sys/bus/usb/devices/*/power/wakeup; do echo disabled > \"$device\"; done"
+ExecStartPost=/bin/bash -c "find /sys/devices -type f -name 'wakeup' | grep -i PNP0C0D | awk '{print \"echo disabled | sudo tee \" $0}' | bash"
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+echo "Systemd service file created."
+
+# Step 3: Reload systemd daemon to recognize the new service
+echo "Reloading systemd daemon..."
+systemctl daemon-reload
+if [[ $? -ne 0 ]]; then
+ echo "Failed to reload systemd daemon. Exiting."
+ exit 1
+fi
+
+# Step 4: Enable the service to run at boot
+echo "Enabling the service to run at boot..."
+systemctl enable disable-wakeup.service
+if [[ $? -ne 0 ]]; then
+ echo "Failed to enable the service. Exiting."
+ exit 1
+fi
+
+# Step 5: Optionally start the service immediately
+echo "Starting the service to apply changes now..."
+systemctl start disable-wakeup.service
+if [[ $? -ne 0 ]]; then
+ echo "Failed to start the service. Check the service logs for details."
+ exit 1
+fi
+
+# Final Step: Notify the user
+echo "Setup complete. The disable-wakeup.service is now active and will run at each boot."
+echo "To verify the status of the service, use: sudo systemctl status disable-wakeup.service"
+echo "To view logs, use: journalctl -u disable-wakeup.service"
+
diff --git a/dmidecode-and-CPU-info.md b/dmidecode-and-CPU-info.md
index dc52339..e56c988 100644
--- a/dmidecode-and-CPU-info.md
+++ b/dmidecode-and-CPU-info.md
@@ -1,7 +1,7 @@
-# Fedora 37/38 only
+# Fedora Only
## Copy and paste this into the terminal using your touchpad or mouse, then press enter.
``
-sudo dnf install lshw dmidecode -y && sudo dmidecode | grep -A3 'Vendor:\|Product:' && sudo lshw -C cpu | grep -A3 'product:\|vendor:'
+sudo dnf install lshw dmidecode -y && clear && sudo dmidecode | grep -A3 'Vendor:\|Product:' && sudo lshw -C cpu | grep -A3 'product:\|vendor:'
``
diff --git a/easy-effects/fw13-easy-effects.json b/easy-effects/fw13-easy-effects.json
index d229a0e..d82595d 100644
--- a/easy-effects/fw13-easy-effects.json
+++ b/easy-effects/fw13-easy-effects.json
@@ -17,7 +17,7 @@
"bypass": false,
"input-gain": 0.0,
"ir-width": 100,
- "kernel-path": "%CFG%/irs/IR_22ms_27dB_5t_15s_0c.irs",
+ "kernel-name": "IR_22ms_27dB_5t_15s_0c",
"output-gain": 6.0
},
"filter#0": {
diff --git a/flatpaks/flatseal-installer.sh b/flatpaks/flatseal-installer.sh
new file mode 100644
index 0000000..ad3a7b7
--- /dev/null
+++ b/flatpaks/flatseal-installer.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# Update and install Flatpak
+sudo apt update
+sudo apt install -y flatpak
+
+# Add the Flathub repository (if not already added)
+sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
+
+# Install Flatseal Flathub
+flatpak install flathub com.github.tchx84.Flatseal -y
+
+# Verify the installation
+flatpak list | grep Flatseal
+
+echo "Flatseal been installed successfully."
diff --git a/flatpaks/images/flatseal-gimp-filesystem.png b/flatpaks/images/flatseal-gimp-filesystem.png
new file mode 100644
index 0000000..2e7463b
Binary files /dev/null and b/flatpaks/images/flatseal-gimp-filesystem.png differ
diff --git a/flatpaks/images/flatseal.png b/flatpaks/images/flatseal.png
new file mode 100644
index 0000000..de2bc8b
Binary files /dev/null and b/flatpaks/images/flatseal.png differ
diff --git a/flatpaks/readme.md b/flatpaks/readme.md
index 65dc468..0e97e05 100644
--- a/flatpaks/readme.md
+++ b/flatpaks/readme.md
@@ -1,12 +1,26 @@
-# Mission Center Installer for Ubuntu 24.04
+# Flatpaks, what are they?
+
+By design, Flatpaks have limited access to your home folder and system in general. For most applications, this is perfectly fine, though in some cases this may limit the access you need—such as a webcam or microphone for Zoom, or a directory outside your home folder (for example, an external flash/thumb drive). You can extend this access using Flatseal, which itself can be installed via Flatpak.
+
+## Setting up Flatseal on Ubuntu
- Step 1
+
```
-sudo apt install curl -y && curl https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/flatpaks/mission-center-installer.sh | bash mission-center-installer.sh
+sudo apt install curl -y && \
+curl -O https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/flatpaks/flatseal-installer.sh && \
+bash flatseal-installer.sh
```
-- Step 2
+## Setting up Mission Center Installer for Ubuntu
+- Step 1
- Log out, log back in or reboot
+```
+sudo apt install curl -y && \
+curl -O https://raw.githubusercontent.com/FrameworkComputer/linux-docs/main/flatpaks/mission-center-installer.sh && \
+bash mission-center-installer.sh
+```
+
+- Step 2
- 
+ Log out, then log back in or reboot.
diff --git a/framework-desktop/Fedora-42.md b/framework-desktop/Fedora-42.md
new file mode 100644
index 0000000..18ac00d
--- /dev/null
+++ b/framework-desktop/Fedora-42.md
@@ -0,0 +1,63 @@
+# This is for Framework Desktop ONLY
+
+## This will:
+
+- Getting your desktop fully updated.
+- Enable improved fractional scaling support Fedora's GNOME environment using Wayland.
+
+
+
+
+
+
+### Step 1 Updating your software packages
+
+- Browse to the horizontal line in the upper left corner, click to open it.
+- Type out the word terminal, click to open it.
+- Copy the code below in the gray box, right click/paste it into the terminal window.
+- Then press the enter key, user password, enter key, **reboot.**
+
+
+```
+sudo dnf upgrade
+```
+> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+
+
+**Reboot**
+
+
+
+
+
+### Step 2 - If you want to enable fractional scaling on Wayland:
+
+- Type out the word Displays.
+- Look for scale you want and select it, click Apply.
+
+
+
+
+
+
+### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
+
+We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred. Your own display may vary, so note any changes made if you need to revert back.
+
+- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
+
+- Install with:
+
+```
+sudo dnf install gnome-tweaks -y
+```
+
+- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
+
+- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks. This will vary depending on what you are using for fractional scaling under Displays.
+
+
+
+
+
+
diff --git a/framework12/Fedora42.md b/framework12/Fedora42.md
new file mode 100644
index 0000000..408266a
--- /dev/null
+++ b/framework12/Fedora42.md
@@ -0,0 +1,74 @@
+# This is for Framework Laptop 12 ONLY
+
+## This will:
+
+- Getting your laptop fully updated.
+- Enable improved fractional scaling support Fedora's GNOME environment using Wayland.
+- Enabling tap to click on the touchpad.
+
+
+
+
+
+
+### Step 1 Updating your software packages
+
+- Browse to the horizontal line in the upper left corner, click to open it.
+- Type out the word terminal, click to open it.
+- Copy the code below in the gray box, right click/paste it into the terminal window.
+- Then press the enter key, user password, enter key, **reboot.**
+
+
+```
+sudo dnf upgrade
+```
+> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+
+
+**Reboot**
+
+
+
+
+
+### Step 2 - If you want to enable fractional scaling on Wayland:
+
+- Type out the word Displays.
+- Look for scale you want and select it, click Apply.
+
+
+
+
+
+### Step 3 - If you want to enable "tap-to-click" on the touchpad:
+
+- Browse to the horizontal line in the upper left corner, click to open it.
+- Type out the word mouse, look for Mouse and Touchpad, click to open it.
+- Click the touchpad option at the top.
+- Under "Clicking", select Tap to Click and enable it.
+
+
+
+
+### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
+
+We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred.
+
+- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
+- Install with:
+
+```
+sudo dnf install gnome-tweaks -y
+```
+
+- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
+
+- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks. This will vary depending on what you are using for fractional scaling under Displays.
+
+ Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
+
+
+
+
+
+
diff --git a/framework12/Ubuntu-24-04.md b/framework12/Ubuntu-24-04.md
new file mode 100644
index 0000000..9ca4624
--- /dev/null
+++ b/framework12/Ubuntu-24-04.md
@@ -0,0 +1,55 @@
+# This is for Framework Laptop 12 ONLY.
+
+
+## This will:
+
+- Update your Ubuntu install's packages.
+
+
+
+
+### Get everything updated
+
+- Browse to the upper left corner, click the horizontal line to open the menu.
+- Type out the word terminal, click to open it.
+- Click on the small icon shown in the image below to copy the code below in the gray box, right click/paste it into the terminal window.
+- Then press the enter key, user password, enter key, **reboot.**
+
+```
+sudo apt update && sudo apt upgrade -y && sudo snap refresh
+```
+> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+
+
+
+
+
+
+
+### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
+
+We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred. This will vary greatly how you have your fractional scaling setup in the Displays settings area.
+
+- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
+- Install with:
+
+```
+sudo apt update && sudo apt install gnome-tweaks -y
+```
+
+- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
+
+- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks.
+
+ Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
+
+### Bonus Step - Correct blurry text rendering in the Chrome browser
+
+ - Open your Chrome browser, browse to chrome://flags/ and press enter.
+ - Look for the search box at the top of the page, type in the words _ozone platform_ then press the enter key.
+ - Look for the box marked Default, change it to Auto.
+ - With this changed to Auto, relaunch your Chrome browser.
+
+
+
+
diff --git a/framework12/Ubuntu-25-04-accel-ubuntu25.04.md b/framework12/Ubuntu-25-04-accel-ubuntu25.04.md
new file mode 100644
index 0000000..d6452ec
--- /dev/null
+++ b/framework12/Ubuntu-25-04-accel-ubuntu25.04.md
@@ -0,0 +1,24 @@
+# Ubuntu 25.04 Tablet Mode Setup Udev Edit
+
+This guide will help set up screen rotation support for your laptop on Ubuntu 25.04, giving you an experience similar to what Fedora 42 and Bazzite offer out of the box.
+
+> Rather not deal with this at all? [Bazzite](https://guides.frame.work/Guide/Bazzite+Installation+on+the+Framework+Laptop+12/409?lang=en) and [Fedora](https://guides.frame.work/Guide/Fedora+42+Installation+on+the+Framework+Laptop+12/410?lang=en) are ready to go out of the box, zero configuration.
+
+Ubuntu 25.04 currently ships with iio-sensor-proxy 3.7 that has [a bug](https://gitlab.freedesktop.org/hadess/iio-sensor-proxy/-/issues/411) preventing it from delivering accelerometer events from kernel to userspace (GNOME, KDE, ...).
+
+```
+sed 's/.*iio-buffer-accel/#&/' /usr/lib/udev/rules.d/80-iio-sensor-proxy.rules | sudo tee /etc/udev/rules.d/80-iio-sensor-proxy.rules
+sudo udevadm trigger --settle
+sudo systemctl restart iio-sensor-proxy
+```
+
+Then you can check if screen rotation works:
+
+```
+> monitor-sensor --accel
+ Waiting for iio-sensor-proxy to appear
++++ iio-sensor-proxy appeared
+=== Has accelerometer (orientation: normal)
+```
+
+> Tablet rotation mode should work immediately. Howver if for some reason it does not, reboot then test rotation again. Remember to flip the the screen completely back to test rotation properly.
diff --git a/framework12/Ubuntu-25-04.md b/framework12/Ubuntu-25-04.md
new file mode 100644
index 0000000..03000fd
--- /dev/null
+++ b/framework12/Ubuntu-25-04.md
@@ -0,0 +1,57 @@
+# This is for Ubuntu 25.04 on Framework Laptop 12 ONLY.
+
+
+## This will:
+
+- Update your Ubuntu install's packages.
+- Walk you getting tablet mode setup for Ubuntu 25.04 (ONLY)
+
+
+
+
+### Get everything updated
+
+- Browse to the upper left corner, click the horizontal line to open the menu.
+- Type out the word terminal, click to open it.
+- Click on the small icon shown in the image below to copy the code below in the gray box, right click/paste it into the terminal window.
+- Then press the enter key, user password, enter key, **reboot.**
+
+```
+sudo apt update && sudo apt upgrade -y && sudo snap refresh
+```
+> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+
+
+
+- **Then reboot**
+
+
+
+## Tablet mode on Ubuntu
+
+
+- Only works on 25.04, and even then needs a little help.
+- [This script will get tablet mode set up and running fast](https://github.com/FrameworkComputer/linux-docs/blob/main/framework12/Ubuntu-25-04-accel-ubuntu25.04.md#ubuntu-2504-tablet-mode-setup-udev-edit).
+- Onscreen keyboard only appears when you call for it by interacting in a text area.
+
+
+
+
+
+
+### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
+
+We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred. This will vary greatly how you have your fractional scaling setup in the Displays settings area.
+
+- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
+- Install with:
+
+```
+sudo apt update && sudo apt install gnome-tweaks -y
+```
+
+- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
+
+- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks.
+
+ Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
diff --git a/framework12/images/install.png b/framework12/images/install.png
new file mode 100644
index 0000000..dabf3e1
Binary files /dev/null and b/framework12/images/install.png differ
diff --git a/framework12/images/oszone.png b/framework12/images/oszone.png
new file mode 100644
index 0000000..133c36f
Binary files /dev/null and b/framework12/images/oszone.png differ
diff --git a/framework12/images/readme b/framework12/images/readme
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/framework12/images/readme
@@ -0,0 +1 @@
+
diff --git a/framework12/images/tablet.png b/framework12/images/tablet.png
new file mode 100644
index 0000000..1cf271c
Binary files /dev/null and b/framework12/images/tablet.png differ
diff --git a/framework13/Fedora41-amd-fw13.md b/framework13/Fedora41-amd-fw13.md
index 56ca437..f6359b9 100644
--- a/framework13/Fedora41-amd-fw13.md
+++ b/framework13/Fedora41-amd-fw13.md
@@ -32,6 +32,7 @@ sudo dnf upgrade
### Step 2 - If you want to enable fractional scaling on Wayland:
+- Browse to the horizontal line in the upper left corner, click to open it.
- Type out the word Displays.
- Look for scale you want and select it, click Apply.
@@ -49,22 +50,40 @@ sudo dnf upgrade
+### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
+We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred.
-## Optional and *only if needed* - current AMD Ryzen 7040 Series workarounds to common issues
+- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
+- Install with:
+
+```
+sudo dnf install gnome-tweaks -y
+```
-### To prevent graphical artifacts from appearing:
-(Note, this workaround may be unneeded as it is difficult to reproduce, however, if you find you're experiencing [the issue described here](https://bugzilla.redhat.com/show_bug.cgi?id=2247154#c3), you can implement this boot parameter)
+- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
+- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks.
+
+ Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
+
+
+
+
+
+## Optional and *only if needed* - current AMD Ryzen 7040 Series workarounds to common issues
+
+### Laggy or stuttering touchpad:
+(Customer submitted, not seeing this internally, but if you are, please file a bug so we can get this fixed vs this workaround please)
- Browse to the horizontal line in the upper left corner, click to open it.
- Type out the word terminal, click to open it.
- Then press the enter key, user password, enter key.
```
-sudo grubby --update-kernel=ALL --args="amdgpu.sg_display=0"
+sudo grubby --update-kernel=ALL --args="amdgpu.dcdebugmask=0x10"
```
-> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+> **TIP:** If you've set other kernel parameters, like from the section above, include both inside `--args=""`.
**Reboot**
@@ -74,16 +93,24 @@ sudo grubby --update-kernel=ALL --args="amdgpu.sg_display=0"
- Browse to the horizontal line in the upper left corner, click to open it.
- Type out the word terminal, click to open it.
-- Copy/paste in the following code below.
-- Press the enter key, user password, enter key.
+- Copy/paste in the code below (use either the immediate temporary fix or persistent fix).
+- Then press the enter key, user password, enter key.
```
+# Immediate temporary fix to disable power save for running session (no reboot required)
echo 0 | sudo tee /sys/module/snd_hda_intel/parameters/power_save
```
-> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+```
+# Persistent fix to disable power save using Tuned (either change the power profile or reboot to apply)
+# Note: Change "balanced" to the profile you want this set on
+sudo mkdir -p /etc/tuned/profiles/balanced/
+sudo cp /usr/lib/tuned/profiles/balanced/tuned.conf /etc/tuned/profiles/balanced/
+sudo sed -i 's/timeout=10/timeout=0/g' /etc/tuned/profiles/balanced/tuned.conf
+```
+
+> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
-**Reboot**
@@ -105,30 +132,31 @@ sudo tee /etc/modprobe.d/alsa.conf <<< "options snd-hda-intel index=1,0 model=au
-## MediaTek Bluetooth with s2idle workaround
+-------------------------------------------------------------------------------
-- [Simply visit this page](https://github.com/FrameworkComputer/linux-docs/blob/main/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md#workaround-for-suspendhibernate-black-screen-on-resume-kernel-611) (new tab), copy/paste the one liner, reboot. Now Bluetooth will stop for suspend and resume when you resume from s2idle suspend.
+## (No Longer Needed) ~~Optional and *only if needed* - current AMD Ryzen 7040 Series workarounds to common issues~~
-
-
-
-### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
+### ~~To prevent graphical artifacts from appearing:~~
+~~(Note, this workaround may be unneeded as it is difficult to reproduce, however, if you find you're experiencing [the issue described here](https://bugzilla.redhat.com/show_bug.cgi?id=2247154#c3), you can implement this boot parameter)~~
-We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred.
-- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
-- Install with:
-
+- ~~Browse to the horizontal line in the upper left corner, click to open it.~~
+- ~~Type out the word terminal, click to open it.~~
+- ~~Then press the enter key, user password, enter key.~~
+
```
-sudo dnf install gnome-tweaks -y
+sudo grubby --update-kernel=ALL --args="amdgpu.sg_display=0"
```
+> ~~**TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.~~
-- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
-- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks.
+~~**Reboot**~~
+
+
+
+
+## ~~MediaTek Bluetooth with s2idle workaround~~
+
+- ~~[Simply visit this page](https://github.com/FrameworkComputer/linux-docs/blob/main/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md#workaround-for-suspendhibernate-black-screen-on-resume-kernel-611) (new tab), copy/paste the one liner, reboot. Now Bluetooth will stop for suspend and resume when you resume from s2idle suspend.~~
- Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
-
-
-
diff --git a/framework13/Ryzen-AI-300-Series.md b/framework13/Ryzen-AI-300-Series.md
new file mode 100644
index 0000000..ffc8484
--- /dev/null
+++ b/framework13/Ryzen-AI-300-Series.md
@@ -0,0 +1,74 @@
+# This is for Ryzen™ AI 300 Series Framework Laptop 13 ONLY.
+
+## This will:
+
+- Getting your laptop fully updated.
+- Enable improved fractional scaling support Fedora's GNOME environment using Wayland.
+- Enabling tap to click on the touchpad.
+
+
+
+
+
+
+### Step 1 Updating your software packages
+
+- Browse to the horizontal line in the upper left corner, click to open it.
+- Type out the word terminal, click to open it.
+- Copy the code below in the gray box, right click/paste it into the terminal window.
+- Then press the enter key, user password, enter key, **reboot.**
+
+
+```
+sudo dnf upgrade
+```
+> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+
+
+**Reboot**
+
+
+
+
+
+### Step 2 - If you want to enable fractional scaling on Wayland:
+
+- Type out the word Displays.
+- Look for scale you want and select it, click Apply.
+
+
+
+
+
+### Step 3 - If you want to enable "tap-to-click" on the touchpad:
+
+- Browse to the horizontal line in the upper left corner, click to open it.
+- Type out the word mouse, look for Mouse and Touchpad, click to open it.
+- Click the touchpad option at the top.
+- Under "Clicking", select Tap to Click and enable it.
+
+
+
+
+### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
+
+We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred.
+
+- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
+- Install with:
+
+```
+sudo dnf install gnome-tweaks -y
+```
+
+- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
+
+- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks.
+
+ Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
+
+
+
+
+
+
diff --git a/framework16/Fedora-42-fw16.md b/framework16/Fedora-42-fw16.md
new file mode 100644
index 0000000..09d181d
--- /dev/null
+++ b/framework16/Fedora-42-fw16.md
@@ -0,0 +1,79 @@
+# This is for the Framework Laptop 16 (AMD Ryzen™ 7040 Series) ONLY.
+
+## This will:
+
+- Getting your laptop fully updated.
+- Allow both CPU and platform drivers to be simultaneously active.
+- Enable improved fractional scaling support Fedora's GNOME environment using Wayland.
+- Enabling tap to click on the touchpad.
+
+
+
+
+
+### Step 1 Updating your software packages
+
+- Browse to the horizontal line in the upper left corner, click to open it.
+- Type out the word terminal, click to open it.
+- Copy the code below in the gray box, right click/paste it into the terminal window.
+- Then press the enter key, user password, enter key, **reboot.**
+
+
+```
+sudo dnf upgrade
+```
+> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+
+
+**Reboot**
+
+
+
+
+
+
+### Step 2 - If you want to enable fractional scaling on Wayland:
+
+- Browse to the horizontal line in the upper left corner, click to open it.
+- Type out the word Displays.
+- Look for "Scale", set it to your preference, click Apply.
+
+
+
+
+### Step 3 - If you want to enable "tap-to-click" on the touchpad:
+
+- Browse to the horizontal line in the upper left corner, click to open it.
+- Type out the word mouse, look for Mouse and Touchpad, click to open it.
+- Click the touchpad option at the top.
+- Under "Clicking", select Tap to Click and enable it.
+
+
+
+
+### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
+
+We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred.
+
+- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
+- Install with:
+
+```
+sudo dnf install gnome-tweaks -y
+```
+
+- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
+
+- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks.
+
+ Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
+
+
+
+
+
+----------------------------------------
+----------------------------------------
+
+
+
diff --git a/framework16/Fedora41-fw16.md b/framework16/Fedora41-fw16.md
index 72892f5..2dcfda5 100644
--- a/framework16/Fedora41-fw16.md
+++ b/framework16/Fedora41-fw16.md
@@ -51,33 +51,60 @@ sudo dnf upgrade
+### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
-## MediaTek Bluetooth with s2idle workaround
+We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred.
-- [Simply visit this page](https://github.com/FrameworkComputer/linux-docs/blob/main/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md#workaround-for-suspendhibernate-black-screen-on-resume-kernel-611) (new tab), copy/paste the one liner, reboot. Now Bluetooth will stop for suspend and resume when you resume from s2idle suspend.
+- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
+- Install with:
+
+```
+sudo dnf install gnome-tweaks -y
+```
+
+- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
+
+- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks.
+
+ Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
+
+
+
+
+
+----------------------------------------
+----------------------------------------
+## Workarounds below are no longer needed
+
+## ~~MediaTek Bluetooth with s2idle workaround~~
+(**UPDATED:** No longer is this needed)
+
+- ~~[Simply visit this page](https://github.com/FrameworkComputer/linux-docs/blob/main/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md#workaround-for-suspendhibernate-black-screen-on-resume-kernel-611) (new tab), copy/paste the one liner, reboot. Now Bluetooth will stop for suspend and resume when you resume from s2idle suspend.~~
-### USB-C Video Out from dGPU directly
+### (No Longer Needed) USB-C Video Out from dGPU directly
+(**UPDATED:** CURRENT FIRMWARE MAKES THIS UNNEEDED, JUST MAKE SURE [YOUR FIRMWARE IS CURRENT](https://guides.frame.work/Guide/Fedora+41+Installation+on+the+Framework+Laptop+16/394?lang=en#s2261).
+**With the latest firmware, just connect your display**.
-By default, when you attach a USB-C cable to the dGPU port, it will not come out of D3cold - this is by design and is to preserve your battery life during everyday usage.
+~~By default, when you attach a USB-C cable to the dGPU port, it will not come out of D3cold - this is by design and is to preserve your battery life during everyday usage.~~
-But you may find instances where you wish to connect to this port (HDMI/DP dongle to USB-C for example). There are a few ways to bring the dGPU out of D3cold.
+~~But you may find instances where you wish to connect to this port (HDMI/DP dongle to USB-C for example). There are a few ways to bring the dGPU out of D3cold.~~
-- [Mission Center](https://missioncenter.io/) or ``lspci -v``
-- Installing nvtop, then using this method.
+- ~~[Mission Center](https://missioncenter.io/)~~ or ``lspci -v``
+- ~~Installing nvtop, then using this method.~~
```
sudo dnf install nvtop
```
-Create a script with the following:
+~~Create a script with the following:~~
```
sudo nano /usr/local/bin/external_video.sh
```
-Paste in:
+~~Paste in:~~
```
#!/bin/bash
@@ -87,88 +114,68 @@ timeout 2 nvtop
echo "nvtop run completed."
```
-Save the file. Then set it to executable.
+~~Save the file. Then set it to executable.~~
```
sudo chmod +x /usr/local/bin/external_video.sh
```
-Now setup a udev rule.
+~~Now setup a udev rule.~~
```
sudo nano /etc/udev/rules.d/99-external_video.rules
```
-Paste in.
+~~Paste in.~~
```
ACTION=="add", SUBSYSTEM=="usb", RUN+="/usr/local/bin/external_video.sh"
```
-Save the file, then run these commands.
+~~Save the file, then run these commands.~~
``sudo udevadm control --reload-rules``
-then
+~~then~~
``sudo udevadm trigger``
-- Plug in your adapter into the USB-C port on your dGPU port on the back, your display will come on.
-- NOTE: If you are using HDMI, USB-C or DP explansion cards in the expansion bays on the side of the laptop, this is not needed.
+- ~~Plug in your adapter into the USB-C port on your dGPU port on the back, your display will come on.~~
+- ~~NOTE: If you are using HDMI, USB-C or DP explansion cards in the expansion bays on the side of the laptop, this is not needed.~~
-## Optional and *only if needed* - current AMD Ryzen 7040 Series workarounds to common issues
+## (**NO LONGER NEEDED**): ~~Optional and *only if needed* - current AMD Ryzen 7040 Series workarounds to common issues~~
+(**UPDATED:** CURRENT FIRMWARE MAKES THIS UNNEEDED, JUST MAKE SURE [YOUR FIRMWARE IS CURRENT](https://guides.frame.work/Guide/Fedora+41+Installation+on+the+Framework+Laptop+16/394?lang=en#s2261).
-### To prevent graphical artifacts from appearing:
-(Note, this workaround may be unneeded as it is difficult to reproduce, however, if you find you're experiencing [the issue described here](https://bugzilla.redhat.com/show_bug.cgi?id=2247154#c3), you can implement this boot parameter)
+### ~~To prevent graphical artifacts from appearing:~~
+~~(Note, this workaround may be unneeded as it is difficult to reproduce, however, if you find you're experiencing [the issue described here](https://bugzilla.redhat.com/show_bug.cgi?id=2247154#c3), you can implement this boot parameter)~~
-- Browse to the horizontal line in the upper left corner, click to open it.
-- Type out the word terminal, click to open it.
-- Then press the enter key, user password, enter key.
+- ~~Browse to the horizontal line in the upper left corner, click to open it.~~
+- ~~Type out the word terminal, click to open it.~~
+- ~~Then press the enter key, user password, enter key.~~
```
sudo grubby --update-kernel=ALL --args="amdgpu.sg_display=0"
```
-> **TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.
+> ~~**TIP:** You can use the little clipboard icon to the right of the code to copy to your clipboard.~~
**Reboot**
-## Addtionally, we recommend the following as well if you are experiencing graphical artifacts from appearing
-
-- Please follow the steps outlined in this guide:
- https://knowledgebase.frame.work/allocate-additional-ram-to-igpu-framework-laptop-13-amd-ryzen-7040-series-BkpPUPQa
-
-
-
-
-### Bonus Step (for former Mac users) Reduce Font Scaling to Match Your Needs
-
-We received feedback that for users coming from OS X, installing GNOME Tweaks, browsing to Fonts, and reducing the font size from 1.00 to 0.80 may be preferred.
-
-- Goto Displays, set scaling to 200%. This will look too large, so let's fix the fonts.
-- Install with:
-
-```
-sudo dnf install gnome-tweaks -y
-```
-
-- Open Tweaks by using the "Super" or Windows key, search tweaks, and enter.
-
-- At the top, select fonts. Now in that panel, scroll all the way down. Look for Size. Change from 1.00 to 0.80. Close Tweaks.
+## (NO LONGER NEEDED) ~~Addtionally, we recommend the following as well if you are experiencing graphical artifacts from appearing~~
+(**UPDATED:** CURRENT FIRMWARE MAKES THIS UNNEEDED, JUST MAKE SURE [YOUR FIRMWARE IS CURRENT](https://guides.frame.work/Guide/Fedora+41+Installation+on+the+Framework+Laptop+16/394?lang=en#s2261).
+**With the latest firmware, just connect your display**.
- Note: This is for the displays for the laptop only. This will look super odd on external displays and likely too large even still.
+- ~~Please follow the steps outlined in this guide:
+ https://knowledgebase.frame.work/allocate-additional-ram-to-igpu-framework-laptop-13-amd-ryzen-7040-series-BkpPUPQa~~
-
-
-
----------------------------------------
-## Framework Laptop 16 not providing all of the expected refresh rates.
+## (NO LONGER NEEDED) ~~Framework Laptop 16 not providing all of the expected refresh rates.~~
-[Framework Laptop 16 not providing all of the expected refresh rates ](https://github.com/FrameworkComputer/linux-docs/blob/main/amdgpu-workarounds/amdgpu_freesync_video/amdgpu_freesync_video.md#amdgpufreesync_video1-parameter-workaround-franework-laptop-16-only)
+~~[Framework Laptop 16 not providing all of the expected refresh rates ](https://github.com/FrameworkComputer/linux-docs/blob/main/amdgpu-workarounds/amdgpu_freesync_video/amdgpu_freesync_video.md#amdgpufreesync_video1-parameter-workaround-franework-laptop-16-only)~~
diff --git a/hibernation/hibernate-fedora-automatic.md b/hibernation/hibernate-fedora-automatic.md
index 50e2327..616868e 100644
--- a/hibernation/hibernate-fedora-automatic.md
+++ b/hibernation/hibernate-fedora-automatic.md
@@ -1,6 +1,8 @@
-# Fedora 41 hibernation option (NOT Fedora official)
+# Fedora 41/42 hibernation option (NOT Fedora official)
(Suspends to disk/powered off with saved state to partition)
+### Reminder: Secure boot must be off per the middle section below.
+
> What is hibernate? [Learn more here.](https://knowledgebase.frame.work/en_us/hibernation-on-linux-BkL1N5ffJg)
**NOTE:** Prefer to do this manually on Fedora? [Follow the manual guide here](https://github.com/FrameworkComputer/linux-docs/blob/main/hibernation/hibernate-fedora-manual-method.md#manual-guide-configuring-lid-close-and-hibernate-settings-on-linux).
@@ -18,7 +20,7 @@ Otherwise, continue below to use the partition with application method.
- Framework Laptop 13 AMD Ryzen 7040 Series
- Framework Laptop 13 Intel® Core™ Ultra Series 1
- Framework laptop 16 AMD Ryzen 7040 Series
-- Secure boot [needs to be disabled](https://github.com/FrameworkComputer/linux-docs/blob/main/misc/secure-boot.md#secure-boot-explained).
+- Secure boot [needs to be disabled](https://github.com/FrameworkComputer/linux-docs/blob/main/misc/secure-boot.md#secure-boot-explained) (<--This affects EFI updaters for example)
**Guide Sections:**
diff --git a/hibernation/hibernate-fedora-manual-method.md b/hibernation/hibernate-fedora-manual-method.md
index 3d1961f..b333776 100644
--- a/hibernation/hibernate-fedora-manual-method.md
+++ b/hibernation/hibernate-fedora-manual-method.md
@@ -7,7 +7,7 @@ This guide provides manual steps to configure lid-close and hibernate settings,
**IMPORTANT:** This assumes you set up your partitions per our [instructions](https://github.com/FrameworkComputer/linux-docs/blob/main/hibernation/hibernate-fedora-automatic.md#access-partition-layout), first!
-**NOTE:** - Secure boot needs to be disabled.
+**NOTE:** - [Secure boot needs to be disabled](https://github.com/FrameworkComputer/linux-docs/blob/main/misc/secure-boot.md#secure-boot-explained) (<--This affects EFI updaters for example
**NOTE:** If you feel strongly about using btrfs subvolumes, other approaches that are untested by us, below are some links for community guides on that front:
diff --git a/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md b/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md
index 4fc5b76..a12d816 100644
--- a/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md
+++ b/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md
@@ -1,38 +1,44 @@
+## THIS IS NO LONGER NEEDED.
+If you need to undo this workaround, [simply visit this section](https://github.com/FrameworkComputer/linux-docs/blob/main/hibernation/kernel-6-11-workarounds/suspend-hibernate-bluetooth-workaround.md#to-remove-the-services-installed) and run the commands to remove the service(s).
# Workaround for suspend/hibernate black screen on resume kernel 6.11
-- Issue: Bluetooth on kernel 6.11 causes black screen on resume attempt when using a MediaTek Wi-Fi card.
-- Workaround provides two systemd services. First one rfkills Bluetooth when suspend or hibernate is detected. Second service re-activates Bluetooth upon resume.
+- ~~Issue: Bluetooth on kernel 6.11 causes black screen on resume attempt when using a MediaTek Wi-Fi card.~~
+- ~~Workaround provides two systemd services. First one rfkills Bluetooth when suspend or hibernate is detected. Second service re-activates Bluetooth upon resume.~~
-### Install Curl
+### ~~Install Curl~~
-Curl should already be installed, but just in case:
+~~Curl should already be installed, but just in case:~~
### Fedora
```
sudo dnf install curl -y
```
-or
+~~or~~
-### Ubuntu
+~~### Ubuntu~~
```
sudo apt install curl -y
```
------------------------------------------------------------------------------------------------------------------------------
-## To Install rfkill-suspender Script, simply run:
+## ~~To Install rfkill-suspender Script, simply run:~~
```
curl -s https://raw.githubusercontent.com/FrameworkComputer/linux-docs/refs/heads/main/hibernation/kernel-6-11-workarounds/rfkill-suspender.sh -o rfkill-suspender.sh && clear && bash rfkill-suspender.sh
```
-Running the script in the future
-After the install, you can run going forward with the following in the HOME directory. So merely opening a terminal and running this will work if the original script has not been moved.
+~~Running the script in the future~~
+~~After the install, you can run going forward with the following in the HOME directory. So merely opening a terminal and running this will work if the original script has not been moved.~~
```
bash rfkill-suspender.sh
```
+------------------------------------------------------------------------------------------------------------------------------
+
+
+
## To remove the services installed
diff --git a/log-helper/combined.sh b/log-helper/combined.sh
index 3c1c574..546dd2d 100644
--- a/log-helper/combined.sh
+++ b/log-helper/combined.sh
@@ -9,6 +9,34 @@ focused_summary_file="focused_summary_temp.txt"
BOLD='\033[1m'
RESET='\033[0m'
+# Ensure necessary packages are installed based on the operating system
+if [ -f /etc/os-release ]; then
+ OS_ID=$(grep ^ID= /etc/os-release | cut -d'=' -f2 | tr -d '"')
+ OS_VERSION_ID=$(grep ^VERSION_ID= /etc/os-release | cut -d'=' -f2 | tr -d '"')
+
+ # Check and install required packages based on the distribution
+ case "$OS_ID" in
+ ubuntu)
+ sudo apt-get update -qq
+ sudo apt-get install -y -qq pciutils iw inxi || { echo "${BOLD}Package installation failed on Ubuntu.${RESET}"; exit 1; }
+ ;;
+ fedora)
+ sudo dnf install -y -q pciutils iw inxi || { echo "${BOLD}Package installation failed on Fedora.${RESET}"; exit 1; }
+ ;;
+ bluefin|bazzite)
+ # Do not install any packages on these distributions
+ # Just skip installation.
+ ;;
+ *)
+ echo "${BOLD}Unsupported distribution: $OS_ID${RESET}"
+ exit 1
+ ;;
+ esac
+else
+ echo "${BOLD}Could not detect the OS distribution.${RESET}"
+ exit 1
+fi
+
# Function to display progress bar
show_progress() {
local width=50
@@ -34,7 +62,15 @@ get_system_info() {
echo "" >> "$output_file"
echo "Kernel version: $(uname -r)" >> "$output_file"
echo "Desktop Environment: $XDG_CURRENT_DESKTOP" >> "$output_file"
- echo "Distribution: $(lsb_release -d | cut -f2)" >> "$output_file"
+
+ # For distribution, read from /etc/os-release
+ if [ -f /etc/os-release ]; then
+ OS_NAME=$(grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '"')
+ echo "Distribution: $OS_NAME" >> "$output_file"
+ else
+ echo "Distribution: Unknown (no /etc/os-release)" >> "$output_file"
+ fi
+
echo "BIOS Version: $(sudo dmidecode -s bios-version)" >> "$output_file"
echo "" >> "$output_file"
}
@@ -43,8 +79,7 @@ get_system_info() {
process_logs() {
local start_time=$1
local end_time=$2
-
- # Convert start and end times to seconds since epoch for comparison
+
local start_seconds=$(date -d "$start_time" +%s)
local end_seconds=$(date -d "$end_time" +%s)
@@ -76,11 +111,9 @@ process_logs() {
done
echo -e "\nDmesg processing complete."
-
echo "" >> "$output_file"
# Create a header for journalctl section with spacing
- echo "" >> "$output_file"
echo "===== journalctl output starts =====" >> "$output_file"
echo "" >> "$output_file"
@@ -104,7 +137,7 @@ process_logs() {
# Function to add summaries to the file
add_summaries() {
local file=$1
-
+
# Add focused summary section to the end of the output file
echo "" >> "$file"
echo "===== Focused Summary of Potential Issues =====" >> "$file"
@@ -132,7 +165,10 @@ add_summaries() {
echo "" >> "$file"
}
+########################################
# Main script starts here
+########################################
+
echo "Choose an option:"
echo "1. Last x minutes"
echo "2. Last 24 hours"
@@ -167,22 +203,7 @@ case $choice in
add_summaries "$output_file"
;;
4)
- echo "Looking for file called combined_log.txt in home directory..."
- if [ ! -f "$output_file" ]; then
- echo "File not found: $output_file"
- exit 1
- fi
- echo "File found. Proceeding with filtering options."
- ;;
- *)
- echo "Invalid choice"
- exit 1
- ;;
-esac
-
-if [ "$choice" == "4" ]; then
echo "Looking for file called combined_log.txt in current directory..."
- output_file="$(pwd)/combined_log.txt" # Change to current directory
if [ ! -f "$output_file" ]; then
echo "File not found: $output_file"
exit 1
@@ -221,8 +242,13 @@ if [ "$choice" == "4" ]; then
echo -e "\n${BOLD}Filtered log saved in $filtered_output_file${RESET}"
line_count=$(wc -l < "$filtered_output_file")
echo -e "${BOLD}Total lines in filtered output: $line_count${RESET}"
-fi
+ ;;
+ *)
+ echo "Invalid choice"
+ exit 1
+ ;;
+esac
-# Remove temporary files
+# Clean up temporary files
[ -f "$summary_file" ] && rm "$summary_file"
[ -f "$focused_summary_file" ] && rm "$focused_summary_file"
diff --git a/log-helper/older-version.txt b/log-helper/older-version.txt
new file mode 100644
index 0000000..3c1c574
--- /dev/null
+++ b/log-helper/older-version.txt
@@ -0,0 +1,228 @@
+#!/bin/bash
+
+output_file="$(pwd)/combined_log.txt" # Using current directory for input file
+filtered_output_file="$(pwd)/filtered_log.txt" # Using current directory for output file
+summary_file="summary_temp.txt"
+focused_summary_file="focused_summary_temp.txt"
+
+# ANSI escape codes for text formatting
+BOLD='\033[1m'
+RESET='\033[0m'
+
+# Function to display progress bar
+show_progress() {
+ local width=50
+ local percentage=$1
+ local filled=$(printf "%.0f" $(echo "$percentage * $width / 100" | bc -l))
+ local empty=$((width - filled))
+ printf "\rProgress: [%-${width}s] %d%%" $(printf "#%.0s" $(seq 1 $filled)) $percentage
+}
+
+# Function to add to summary, ignoring gnome-shell errors
+add_to_summary() {
+ if ! [[ $1 =~ gnome-shell ]]; then
+ echo "$1" >> "$summary_file"
+ if [[ $1 =~ i915|amdgpu|wayland|wifi|network|failed ]]; then
+ echo "$1" >> "$focused_summary_file"
+ fi
+ fi
+}
+
+# Function to get system information
+get_system_info() {
+ echo "===== System Information =====" > "$output_file"
+ echo "" >> "$output_file"
+ echo "Kernel version: $(uname -r)" >> "$output_file"
+ echo "Desktop Environment: $XDG_CURRENT_DESKTOP" >> "$output_file"
+ echo "Distribution: $(lsb_release -d | cut -f2)" >> "$output_file"
+ echo "BIOS Version: $(sudo dmidecode -s bios-version)" >> "$output_file"
+ echo "" >> "$output_file"
+}
+
+# Function to process logs
+process_logs() {
+ local start_time=$1
+ local end_time=$2
+
+ # Convert start and end times to seconds since epoch for comparison
+ local start_seconds=$(date -d "$start_time" +%s)
+ local end_seconds=$(date -d "$end_time" +%s)
+
+ # Create a header for dmesg section with spacing
+ echo "===== dmesg output starts =====" >> "$output_file"
+ echo "" >> "$output_file"
+
+ # Collect and filter dmesg output with progress bar
+ local total_lines=$(sudo dmesg | wc -l)
+ local current_line=0
+
+ sudo dmesg -T | while IFS= read -r line; do
+ ((current_line++))
+ local percentage=$((current_line * 100 / total_lines))
+ show_progress $percentage
+
+ if [[ $line =~ \[(.*?)\] ]]; then
+ local timestamp="${BASH_REMATCH[1]}"
+ if date -d "$timestamp" &>/dev/null; then
+ local line_seconds=$(date -d "$timestamp" +%s)
+ if (( line_seconds >= start_seconds && line_seconds <= end_seconds )); then
+ echo "$line" >> "$output_file"
+ if [[ $line =~ error|warning|fail|critical|failed ]]; then
+ add_to_summary "$line"
+ fi
+ fi
+ fi
+ fi
+ done
+
+ echo -e "\nDmesg processing complete."
+
+ echo "" >> "$output_file"
+
+ # Create a header for journalctl section with spacing
+ echo "" >> "$output_file"
+ echo "===== journalctl output starts =====" >> "$output_file"
+ echo "" >> "$output_file"
+
+ # Append journalctl output to the file with progress bar
+ total_lines=$(sudo journalctl --since="$start_time" --until="$end_time" | wc -l)
+ current_line=0
+
+ sudo journalctl --since="$start_time" --until="$end_time" | while IFS= read -r line; do
+ ((current_line++))
+ percentage=$((current_line * 100 / total_lines))
+ show_progress $percentage
+ echo "$line" >> "$output_file"
+ if [[ $line =~ error|warning|fail|critical|failed ]]; then
+ add_to_summary "$line"
+ fi
+ done
+
+ echo -e "\nJournalctl processing complete."
+}
+
+# Function to add summaries to the file
+add_summaries() {
+ local file=$1
+
+ # Add focused summary section to the end of the output file
+ echo "" >> "$file"
+ echo "===== Focused Summary of Potential Issues =====" >> "$file"
+ echo "Issues related to i915, amdgpu, wayland, wifi, network, and failed items:" >> "$file"
+ echo "" >> "$file"
+
+ if [ -s "$focused_summary_file" ]; then
+ sort "$focused_summary_file" | uniq -c | sort -rn >> "$file"
+ else
+ echo "No critical issues found related to graphics, display, networking, or failed items." >> "$file"
+ fi
+
+ echo "" >> "$file"
+
+ # Add general summary section to the end of the output file
+ echo "===== General Summary of Potential Issues (excluding gnome-shell errors) =====" >> "$file"
+ echo "" >> "$file"
+
+ if [ -s "$summary_file" ]; then
+ sort "$summary_file" | uniq -c | sort -rn >> "$file"
+ else
+ echo "No other critical issues found in the logs (excluding gnome-shell errors)." >> "$file"
+ fi
+
+ echo "" >> "$file"
+}
+
+# Main script starts here
+echo "Choose an option:"
+echo "1. Last x minutes"
+echo "2. Last 24 hours"
+echo "3. Specific time range"
+echo "4. Filter previously created log file"
+read choice
+
+case $choice in
+ 1)
+ echo "Enter the number of minutes:"
+ read minutes
+ start_time=$(date -d "$minutes minutes ago" '+%Y-%m-%d %H:%M')
+ end_time=$(date '+%Y-%m-%d %H:%M')
+ get_system_info
+ process_logs "$start_time" "$end_time"
+ add_summaries "$output_file"
+ ;;
+ 2)
+ start_time=$(date -d "24 hours ago" '+%Y-%m-%d %H:%M')
+ end_time=$(date '+%Y-%m-%d %H:%M')
+ get_system_info
+ process_logs "$start_time" "$end_time"
+ add_summaries "$output_file"
+ ;;
+ 3)
+ echo "Enter the start time (YYYY-MM-DD HH:MM):"
+ read start_time
+ echo "Enter the end time (YYYY-MM-DD HH:MM):"
+ read end_time
+ get_system_info
+ process_logs "$start_time" "$end_time"
+ add_summaries "$output_file"
+ ;;
+ 4)
+ echo "Looking for file called combined_log.txt in home directory..."
+ if [ ! -f "$output_file" ]; then
+ echo "File not found: $output_file"
+ exit 1
+ fi
+ echo "File found. Proceeding with filtering options."
+ ;;
+ *)
+ echo "Invalid choice"
+ exit 1
+ ;;
+esac
+
+if [ "$choice" == "4" ]; then
+ echo "Looking for file called combined_log.txt in current directory..."
+ output_file="$(pwd)/combined_log.txt" # Change to current directory
+ if [ ! -f "$output_file" ]; then
+ echo "File not found: $output_file"
+ exit 1
+ fi
+ echo "File found. Proceeding with filtering options."
+
+ echo "Choose filtering option:"
+ echo "1. Grep for a key phrase"
+ echo "2. Grep for a keyword"
+ read grep_choice
+
+ case $grep_choice in
+ 1)
+ echo "Enter the key phrase to grep for:"
+ read key_phrase
+ key_phrase=$(echo "$key_phrase" | xargs) # Trim whitespace
+ grep -F -i -B 3 -A 5 "$key_phrase" "$output_file" > "$filtered_output_file"
+ ;;
+ 2)
+ echo "Enter the keyword to grep for:"
+ read keyword
+ keyword=$(echo "$keyword" | xargs) # Trim whitespace
+ grep -w -i -B 3 -A 5 "$keyword" "$output_file" > "$filtered_output_file"
+ ;;
+ *)
+ echo "Invalid choice. No filtering applied."
+ exit 1
+ ;;
+ esac
+
+ if [ ! -s "$filtered_output_file" ]; then
+ echo "No matches found. Filtered log file is empty."
+ exit 1
+ fi
+
+ echo -e "\n${BOLD}Filtered log saved in $filtered_output_file${RESET}"
+ line_count=$(wc -l < "$filtered_output_file")
+ echo -e "${BOLD}Total lines in filtered output: $line_count${RESET}"
+fi
+
+# Remove temporary files
+[ -f "$summary_file" ] && rm "$summary_file"
+[ -f "$focused_summary_file" ] && rm "$focused_summary_file"
diff --git a/log-helper/readme.md b/log-helper/readme.md
index 4c3bd7a..6d4009f 100644
--- a/log-helper/readme.md
+++ b/log-helper/readme.md
@@ -37,6 +37,12 @@ or
sudo apt install curl -y
```
+or
+
+#### Bazzite or Bluefin
+
+Simply goto the next step below to run it, nothing else needs to be added.
+
#### (Either distro) Then run:
```
diff --git a/misc/LUKS-Keyboard-Layout.md b/misc/LUKS-Keyboard-Layout.md
new file mode 100644
index 0000000..451c6b4
--- /dev/null
+++ b/misc/LUKS-Keyboard-Layout.md
@@ -0,0 +1,94 @@
+
+# ⚠️ Known Limitation — LUKS Keyboard Layout
+
+> ⚠️ **Important for non‑US keyboard users:**
+> The keyboard layout selected during installation is **not** used for the LUKS unlock screen. The early‑boot environment defaults to **`en‑US`**.
+> As a result, the keys you press may produce different characters when you enter your passphrase at boot.
+
+---
+
+## During Installation
+- Choose a passphrase you can type on **both** US‑QWERTY *and* your local layout, **or**
+- Use only **A–Z** and **0–9** (keys that do not change position).
+
+> 💡 **Simplest solution:** use a passphrase made **only** of letters A–Z and/or numbers 0–9. These keys map the same on nearly every layout and will always work at the unlock prompt.
+
+---
+
+## After Installation (first boot)
+
+> ✅ **Skip the entire section below** if you used only A–Z/0‑9 **and** you can already unlock successfully.
+
+### ✅ Fedora Atomic desktops
+*(Silverblue / Kinoite / Bazzite / Bluefin …)*
+
+ # 1 Write your keymap
+ echo 'KEYMAP=de' | sudo tee /etc/vconsole.conf
+
+ # 2 Track that file so every future deployment includes it
+ sudo rpm-ostree initramfs-etc --track=/etc/vconsole.conf
+
+ # 3 Rebuild the initramfs now *and* enable automatic rebuilds
+ sudo rpm-ostree initramfs --enable
+
+ # 4 (Optional but harmless) force a dracut run on the current deployment
+ sudo dracut -f
+
+ # 5 Reboot
+ sudo reboot
+
+---
+
+### ✅ Fedora Workstation / Server (traditional RPM systems)
+
+ sudo nano /etc/vconsole.conf # add or edit: KEYMAP=de
+ sudo dracut -fv --regenerate-all # rebuild every installed kernel
+ sudo reboot
+
+---
+
+### ✅ Ubuntu & Debian‑based systems
+
+ sudo nano /etc/default/keyboard # e.g. XKBLAYOUT="de"
+ sudo dpkg-reconfigure keyboard-configuration # interactive; rebuilds current initrd
+ sudo update-initramfs -u -k all # rebuild all other initrds
+ sudo reboot
+
+---
+
+### ✅ Arch Linux & derivatives
+
+ echo 'KEYMAP=de' | sudo tee /etc/vconsole.conf
+ sudo sed -i 's/^HOOKS=.*/HOOKS=(base udev autodetect modconf kms keyboard keymap encrypt filesystems)/' /etc/mkinitcpio.conf
+ sudo mkinitcpio -P # rebuild every preset
+ sudo reboot
+
+---
+
+## 🛠️ Emergency Unlock (if the keymap is wrong)
+
+### Fedora / Atomic
+1. At the GRUB menu press **e**.
+2. Append `rd.vconsole.keymap=de` to the end of the `linux` line.
+3. Boot with **Ctrl + X** or **F10**.
+4. After login, apply the permanent fix above.
+
+### Ubuntu family
+
+ # At GRUB choose “Recovery Mode → root shell”
+ mount -o remount,rw /
+ nano /etc/default/keyboard # set XKBLAYOUT="de"
+ update-initramfs -u -k all
+ reboot
+
+---
+
+## 📘 Helpful Commands
+
+ # List available keymaps
+ localectl list-keymaps | grep -i
+
+ # Verify your map is embedded in the current initrd
+ lsinitramfs /boot/initrd.img-$(uname -r) | grep kmap
+
+Once these steps are complete, your chosen layout is active at the LUKS prompt, early TTYs, and Plymouth. Your desktop environment continues to use the layout configured in GNOME, KDE, etc.
diff --git a/misc/secure-boot.md b/misc/secure-boot.md
index c685793..261b255 100644
--- a/misc/secure-boot.md
+++ b/misc/secure-boot.md
@@ -29,7 +29,7 @@
- Secure Boot may interfere with hibernate resuming from cold boot (unsigned kernels for example), and with certain alternative boot methods (e.g., PXE boot) or older bootloaders.
So for hibernation, Ubuntu will likely work with hibernation whereas Fedora will not when using secure boot. All comes down to kernel signing.
-
+> Remember. We advice leaving this enabled - disabling may lead to issues with our various upgrade processes. This also means it will interfere with EFI firmware updates if secure boot is disabled. So before you disable it, make sure you acknowledge this. No EFI updaters if secure boot is disabled.
### How to disable it if you choose to.
@@ -76,3 +76,27 @@ So for hibernation, Ubuntu will likely work with hibernation whereas Fedora will
- Select Enforce Secure Boot, press enter, select Disable, press enter.
- Press F10 to save and reboot. With Yes selected, press Enter.
+
+--------------
+
+## Machine Owner Key Enrollment (MOK)
+
+MOK is part of the Secure Boot mechanism that ensures only trusted code can run during the boot process.
+
+- Purpose: Secure Boot authentication
+- What it does: Allows the system to load signed kernel modules and drivers when Secure Boot is enabled
+- When you use it: When installing third-party drivers (like NVIDIA) or custom kernel modules on a system with Secure Boot enabled
+- Level of operation: Boot process (firmware/UEFI level)
+- User interaction: Usually only when installing drivers or enrolling new keys
+
+If you have secure boot enabled, you will see something asking you to enroll MOK.
+
+Two options are available:
+
+- Continue Boot (**Most people choose this option**)
+
+- Enroll MOK (You are looking at something involving proprietary modules, VirtualBox built with DKMS for example)
+
+ - [Enrolling MOK with Fedora](https://docs.fedoraproject.org/en-US/quick-docs/mok-enrollment/)
+ - [Enrolling MOK with Bazzite](https://docs.bazzite.gg/General/Installation_Guide/secure_boot/)
+ - [Enrolling MOK with Ubuntu](https://wiki.ubuntu.com/UEFI/SecureBoot)
diff --git a/ubuntu-kernel-switcher/readme.md b/ubuntu-kernel-switcher/readme.md
index 38aa945..abaff0d 100644
--- a/ubuntu-kernel-switcher/readme.md
+++ b/ubuntu-kernel-switcher/readme.md
@@ -8,12 +8,6 @@ If you need to rollback a kernel or restore your grub settings, this is the tool
Curl should already be installed, but just in case:
-### Fedora
-```
-sudo dnf install curl -y
-```
-
-or
### Ubuntu
```