repeater/internal/wifi/broadcaster.go

140 lines
3.6 KiB
Go

package wifi
import (
"log"
"sync"
"github.com/gorilla/websocket"
"github.com/nemunaire/repeater/internal/models"
)
// WifiBroadcaster manages WebSocket clients and broadcasts WiFi events
type WifiBroadcaster struct {
clients map[*websocket.Conn]bool
clientsMu sync.RWMutex
// State deduplication
lastState string
lastSSID string
lastNetworks []models.WiFiNetwork
stateMu sync.RWMutex
}
// NewWifiBroadcaster creates a new WiFi broadcaster
func NewWifiBroadcaster() *WifiBroadcaster {
return &WifiBroadcaster{
clients: make(map[*websocket.Conn]bool),
}
}
// RegisterClient registers a new WebSocket client
func (wb *WifiBroadcaster) RegisterClient(conn *websocket.Conn) {
wb.clientsMu.Lock()
wb.clients[conn] = true
wb.clientsMu.Unlock()
// Send initial state to the new client
wb.sendInitialState(conn)
}
// UnregisterClient removes a WebSocket client
func (wb *WifiBroadcaster) UnregisterClient(conn *websocket.Conn) {
wb.clientsMu.Lock()
delete(wb.clients, conn)
wb.clientsMu.Unlock()
}
// sendInitialState sends the current WiFi state to a newly connected client
func (wb *WifiBroadcaster) sendInitialState(conn *websocket.Conn) {
wb.stateMu.RLock()
lastState := wb.lastState
lastSSID := wb.lastSSID
lastNetworks := make([]models.WiFiNetwork, len(wb.lastNetworks))
copy(lastNetworks, wb.lastNetworks)
wb.stateMu.RUnlock()
// Send last known state if available
if lastState != "" {
event := NewStateChangeEvent(lastState, lastSSID, "")
conn.WriteJSON(event)
}
// Send last known network list if available
if len(lastNetworks) > 0 {
event := NewScanUpdateEvent(lastNetworks)
conn.WriteJSON(event)
}
}
// BroadcastScanUpdate broadcasts a scan update event to all clients
func (wb *WifiBroadcaster) BroadcastScanUpdate(networks []models.WiFiNetwork) {
// Check for changes to avoid duplicate broadcasts
wb.stateMu.Lock()
if networksEqual(wb.lastNetworks, networks) {
wb.stateMu.Unlock()
return
}
wb.lastNetworks = make([]models.WiFiNetwork, len(networks))
copy(wb.lastNetworks, networks)
wb.stateMu.Unlock()
event := NewScanUpdateEvent(networks)
wb.broadcast(event)
}
// BroadcastStateChange broadcasts a state change event to all clients
func (wb *WifiBroadcaster) BroadcastStateChange(state, ssid string) {
// Check for changes to avoid duplicate broadcasts
wb.stateMu.Lock()
if wb.lastState == state && wb.lastSSID == ssid {
wb.stateMu.Unlock()
return
}
previousState := wb.lastState
wb.lastState = state
wb.lastSSID = ssid
wb.stateMu.Unlock()
event := NewStateChangeEvent(state, ssid, previousState)
wb.broadcast(event)
}
// BroadcastSignalUpdate broadcasts a signal update event to all clients
func (wb *WifiBroadcaster) BroadcastSignalUpdate(ssid string, signal, dbm int) {
event := NewSignalUpdateEvent(ssid, signal, dbm)
wb.broadcast(event)
}
// broadcast sends an event to all connected clients
func (wb *WifiBroadcaster) broadcast(event WifiEvent) {
// Get list of clients with read lock
wb.clientsMu.RLock()
clients := make([]*websocket.Conn, 0, len(wb.clients))
for client := range wb.clients {
clients = append(clients, client)
}
wb.clientsMu.RUnlock()
// Broadcast to all clients
for _, client := range clients {
err := client.WriteJSON(event)
if err != nil {
log.Printf("Erreur lors de l'envoi WebSocket WiFi: %v", err)
client.Close()
wb.UnregisterClient(client)
}
}
}
// networksEqual compares two network slices for equality
func networksEqual(a, b []models.WiFiNetwork) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i].SSID != b[i].SSID || a[i].Signal != b[i].Signal {
return false
}
}
return true
}