Migrate to a better architectured project

This commit is contained in:
nemunaire 2025-10-28 19:23:27 +07:00
commit b1b9eaa028
21 changed files with 2712 additions and 1540 deletions

273
internal/wifi/wifi.go Normal file
View file

@ -0,0 +1,273 @@
package wifi
import (
"fmt"
"os/exec"
"sort"
"strings"
"time"
"github.com/godbus/dbus/v5"
"github.com/nemunaire/repeater/internal/models"
)
const (
WLAN_INTERFACE = "wlan0"
WPA_CONF = "/etc/wpa_supplicant/wpa_supplicant.conf"
// D-Bus constants for wpa_supplicant
WPA_SUPPLICANT_SERVICE = "fi.w1.wpa_supplicant1"
WPA_SUPPLICANT_PATH = "/fi/w1/wpa_supplicant1"
WPA_SUPPLICANT_IFACE = "fi.w1.wpa_supplicant1"
WPA_INTERFACE_IFACE = "fi.w1.wpa_supplicant1.Interface"
WPA_BSS_IFACE = "fi.w1.wpa_supplicant1.BSS"
WPA_NETWORK_IFACE = "fi.w1.wpa_supplicant1.Network"
)
var (
dbusConn *dbus.Conn
wpaSupplicant dbus.BusObject
)
// Initialize initializes the WiFi service with D-Bus connection
func Initialize() error {
var err error
dbusConn, err = dbus.SystemBus()
if err != nil {
return fmt.Errorf("failed to connect to D-Bus: %v", err)
}
wpaSupplicant = dbusConn.Object(WPA_SUPPLICANT_SERVICE, dbus.ObjectPath(WPA_SUPPLICANT_PATH))
return nil
}
// Close closes the D-Bus connection
func Close() {
if dbusConn != nil {
dbusConn.Close()
}
}
// ScanNetworks scans for available WiFi networks
func ScanNetworks() ([]models.WiFiNetwork, error) {
interfacePath, err := getWiFiInterfacePath()
if err != nil {
return nil, fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err)
}
// Trigger a scan
wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath)
call := wifiInterface.Call(WPA_INTERFACE_IFACE+".Scan", 0, map[string]dbus.Variant{"Type": dbus.MakeVariant("active")})
if call.Err != nil {
return nil, fmt.Errorf("erreur lors du scan: %v", call.Err)
}
// Wait for scan to complete
time.Sleep(2 * time.Second)
// Retrieve BSS list
bssePaths, err := wifiInterface.GetProperty(WPA_INTERFACE_IFACE + ".BSSs")
if err != nil {
return nil, fmt.Errorf("erreur lors de la récupération des BSS: %v", err)
}
var networks []models.WiFiNetwork
seenSSIDs := make(map[string]bool)
for _, bssPath := range bssePaths.Value().([]dbus.ObjectPath) {
bss := dbusConn.Object(WPA_SUPPLICANT_SERVICE, bssPath)
// Get BSS properties
var props map[string]dbus.Variant
err = bss.Call("org.freedesktop.DBus.Properties.GetAll", 0, WPA_BSS_IFACE).Store(&props)
if err != nil {
continue
}
network := models.WiFiNetwork{}
// Extract SSID
if ssidBytes, ok := props["SSID"].Value().([]byte); ok {
network.SSID = string(ssidBytes)
}
// Skip duplicates and empty SSIDs
if network.SSID == "" || seenSSIDs[network.SSID] {
continue
}
seenSSIDs[network.SSID] = true
// Extract BSSID
if bssidBytes, ok := props["BSSID"].Value().([]byte); ok {
network.BSSID = fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
bssidBytes[0], bssidBytes[1], bssidBytes[2], bssidBytes[3], bssidBytes[4], bssidBytes[5])
}
// Extract signal strength
if signal, ok := props["Signal"].Value().(int16); ok {
network.Signal = signalToStrength(int(signal))
}
// Extract frequency and calculate channel
if frequency, ok := props["Frequency"].Value().(uint16); ok {
network.Channel = frequencyToChannel(int(frequency))
}
// Determine security
if privacyVal, ok := props["Privacy"].Value().(bool); ok && privacyVal {
if wpaProps, ok := props["WPA"].Value().(map[string]dbus.Variant); ok && len(wpaProps) > 0 {
network.Security = "WPA"
} else if rsnProps, ok := props["RSN"].Value().(map[string]dbus.Variant); ok && len(rsnProps) > 0 {
network.Security = "WPA2"
} else {
network.Security = "WEP"
}
} else {
network.Security = "Open"
}
networks = append(networks, network)
}
// Sort by signal strength
sort.Slice(networks, func(i, j int) bool {
return networks[i].Signal > networks[j].Signal
})
return networks, nil
}
// Connect connects to a WiFi network using D-Bus
func Connect(ssid, password string) error {
interfacePath, err := getWiFiInterfacePath()
if err != nil {
return fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err)
}
wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath)
// Create a new network
networkConfig := map[string]dbus.Variant{
"ssid": dbus.MakeVariant(ssid),
}
if password != "" {
networkConfig["psk"] = dbus.MakeVariant(password)
}
var networkPath dbus.ObjectPath
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".AddNetwork", 0, networkConfig).Store(&networkPath)
if err != nil {
return fmt.Errorf("erreur lors de l'ajout du réseau: %v", err)
}
// Select the network
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".SelectNetwork", 0, networkPath).Err
if err != nil {
return fmt.Errorf("erreur lors de la sélection du réseau: %v", err)
}
// Wait 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 {
interfacePath, err := getWiFiInterfacePath()
if err != nil {
return fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err)
}
wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath)
// Disconnect
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Disconnect", 0).Err
if err != nil {
return fmt.Errorf("erreur lors de la déconnexion: %v", err)
}
// Remove all networks
var networks []dbus.ObjectPath
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Get", 0, WPA_INTERFACE_IFACE, "Networks").Store(&networks)
if err == nil {
for _, networkPath := range networks {
wifiInterface.Call(WPA_INTERFACE_IFACE+".RemoveNetwork", 0, networkPath)
}
}
return nil
}
// IsConnected checks if WiFi is connected using D-Bus
func IsConnected() bool {
interfacePath, err := getWiFiInterfacePath()
if err != nil {
return false
}
wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath)
var state string
err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Get", 0, WPA_INTERFACE_IFACE, "State").Store(&state)
if err != nil {
return false
}
return state == "completed"
}
// IsConnectedLegacy checks if WiFi is connected using iwconfig (fallback)
func IsConnectedLegacy() bool {
cmd := exec.Command("iwconfig", WLAN_INTERFACE)
output, err := cmd.Output()
if err != nil {
return false
}
return strings.Contains(string(output), "Access Point:")
}
// getWiFiInterfacePath retrieves the D-Bus path for the WiFi interface
func getWiFiInterfacePath() (dbus.ObjectPath, error) {
var interfacePath dbus.ObjectPath
err := wpaSupplicant.Call(WPA_SUPPLICANT_IFACE+".GetInterface", 0, WLAN_INTERFACE).Store(&interfacePath)
if err != nil {
return "", fmt.Errorf("erreur lors de la récupération des interfaces: %v", err)
}
return interfacePath, nil
}
// frequencyToChannel converts WiFi frequency to channel number
func frequencyToChannel(frequency int) int {
if frequency >= 2412 && frequency <= 2484 {
if frequency == 2484 {
return 14
}
return (frequency-2412)/5 + 1
} else if frequency >= 5170 && frequency <= 5825 {
return (frequency - 5000) / 5
}
return 0
}
// 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
}
}