package wifi import ( "errors" "fmt" "sort" "strings" "time" "github.com/gorilla/websocket" "github.com/nemunaire/repeater/internal/models" "github.com/nemunaire/repeater/internal/wifi/backend" ) var ( wifiBackend backend.WiFiBackend wifiBroadcaster *WifiBroadcaster ) // errWifiDisabled is returned by every wifi.* wrapper when no backend has // been initialized. This happens when the application chose not to start // wpa_supplicant because the Ethernet uplink is already providing // connectivity — touching the D-Bus interface in that mode would re-activate // the daemon via dbus-activation, defeating the intent. var errWifiDisabled = errors.New("wifi backend disabled (Ethernet uplink active)") // Initialize initializes the WiFi service with the specified backend func Initialize(interfaceName string, backendName string) error { // Create the appropriate backend using the factory var err error wifiBackend, err = createBackend(backendName) if err != nil { return err } // Initialize the backend return wifiBackend.Initialize(interfaceName) } // Close closes the backend connection func Close() { if wifiBackend != nil { wifiBackend.Close() } } // GetCachedNetworks returns previously discovered networks without triggering a scan func GetCachedNetworks() ([]models.WiFiNetwork, error) { if wifiBackend == nil { return nil, errWifiDisabled } // Get ordered networks from backend backendNetworks, err := wifiBackend.GetOrderedNetworks() if err != nil { return nil, fmt.Errorf("erreur lors de la récupération des réseaux: %v", err) } // Convert backend networks to models networks := make([]models.WiFiNetwork, 0, len(backendNetworks)) for _, backendNet := range backendNetworks { wifiNet := models.WiFiNetwork{ SSID: backendNet.SSID, Signal: signalToStrength(int(backendNet.SignalDBm)), Security: mapSecurityType(backendNet.SecurityType), BSSID: backendNet.BSSID, Channel: 0, // Not yet exposed by backends } networks = append(networks, wifiNet) } // Sort by signal strength (descending) sort.Slice(networks, func(i, j int) bool { return networks[i].Signal > networks[j].Signal }) return networks, nil } // ScanNetworks scans for available WiFi networks func ScanNetworks() ([]models.WiFiNetwork, error) { if wifiBackend == nil { return nil, errWifiDisabled } // Check if already scanning scanning, err := wifiBackend.IsScanning() if err == nil && scanning { time.Sleep(3 * time.Second) } else { // Trigger scan err := wifiBackend.ScanNetworks() if err != nil && !strings.Contains(err.Error(), "rejected") { return nil, fmt.Errorf("erreur lors du scan: %v", err) } time.Sleep(2 * time.Second) } // Get ordered networks from backend backendNetworks, err := wifiBackend.GetOrderedNetworks() if err != nil { return nil, fmt.Errorf("erreur lors de la récupération des réseaux: %v", err) } // Convert backend networks to models networks := make([]models.WiFiNetwork, 0, len(backendNetworks)) for _, backendNet := range backendNetworks { wifiNet := models.WiFiNetwork{ SSID: backendNet.SSID, Signal: signalToStrength(int(backendNet.SignalDBm)), Security: mapSecurityType(backendNet.SecurityType), BSSID: backendNet.BSSID, Channel: 0, // Not yet exposed by backends } networks = append(networks, wifiNet) } // Sort by signal strength (descending) sort.Slice(networks, func(i, j int) bool { return networks[i].Signal > networks[j].Signal }) // Broadcast to WebSocket clients if available if wifiBroadcaster != nil { wifiBroadcaster.BroadcastScanUpdate(networks) } return networks, nil } // Connect connects to a WiFi network func Connect(ssid, password string) error { if wifiBackend == nil { return errWifiDisabled } // Use backend to connect if err := wifiBackend.Connect(ssid, password); err != nil { return err } // Poll for connection for i := 0; i < 20; i++ { time.Sleep(500 * time.Millisecond) if IsConnected() { return nil } } return fmt.Errorf("timeout lors de la connexion") } // Disconnect disconnects from the current WiFi network func Disconnect() error { if wifiBackend == nil { return errWifiDisabled } return wifiBackend.Disconnect() } // IsConnected checks if WiFi is connected func IsConnected() bool { if wifiBackend == nil { return false } state, err := wifiBackend.GetConnectionState() if err != nil { return false } return state == backend.StateConnected } // GetConnectedSSID returns the SSID of the currently connected network func GetConnectedSSID() string { if wifiBackend == nil { return "" } return wifiBackend.GetConnectedSSID() } // GetConnectionState returns the current WiFi connection state func GetConnectionState() string { if wifiBackend == nil { return string(backend.StateDisconnected) } state, err := wifiBackend.GetConnectionState() if err != nil { return string(backend.StateDisconnected) } return string(state) } // StartEventMonitoring initializes signal monitoring and WebSocket broadcasting func StartEventMonitoring() error { if wifiBackend == nil { return nil } // Initialize broadcaster wifiBroadcaster = NewWifiBroadcaster() // Set up callbacks callbacks := backend.EventCallbacks{ OnStateChange: handleStateChange, OnScanComplete: handleScanComplete, } // Start backend monitoring return wifiBackend.StartEventMonitoring(callbacks) } // StopEventMonitoring stops signal monitoring func StopEventMonitoring() { if wifiBackend != nil { wifiBackend.StopEventMonitoring() } } // RegisterWebSocketClient registers a new WebSocket client for WiFi events func RegisterWebSocketClient(conn *websocket.Conn) { if wifiBroadcaster != nil { wifiBroadcaster.RegisterClient(conn) } } // UnregisterWebSocketClient removes a WebSocket client func UnregisterWebSocketClient(conn *websocket.Conn) { if wifiBroadcaster != nil { wifiBroadcaster.UnregisterClient(conn) } } // handleStateChange is called when WiFi connection state changes func handleStateChange(newState backend.ConnectionState, connectedSSID string) { if wifiBroadcaster != nil { wifiBroadcaster.BroadcastStateChange(string(newState), connectedSSID) } } // handleScanComplete is called when a WiFi scan completes func handleScanComplete() { // Get updated network list networks, err := GetCachedNetworks() if err == nil && wifiBroadcaster != nil { wifiBroadcaster.BroadcastScanUpdate(networks) } } // mapSecurityType maps backend security types to display format func mapSecurityType(securityType string) string { switch securityType { case "open": return "Open" case "wep": return "WEP" case "psk": return "WPA2" case "8021x": return "WPA2" default: return "WPA2" } } // signalToStrength converts signal level (dBm) to strength (1-5) func signalToStrength(level int) int { if level >= -30 { return 5 } else if level >= -50 { return 4 } else if level >= -60 { return 3 } else if level >= -70 { return 2 } else { return 1 } }