package frontend import ( "context" "embed" "io/fs" "log" "net/http" "time" "alox.tool/api" "alox.tool/eventbus" "github.com/gorilla/websocket" ) //go:embed www var staticFiles embed.FS var upgrader = websocket.Upgrader{} type FServer struct { bus eventbus.EventBus mux *http.ServeMux } func New(bus eventbus.EventBus) *FServer { fsrv := &FServer{ bus: bus, mux: http.NewServeMux(), } fsrv.routes() return fsrv } func (fsrv *FServer) routes() { // Statische Dateien aus dem Embed-FS // "www" Präfix entfernen, damit index.html unter / verfügbar ist root, _ := fs.Sub(staticFiles, "www") fsrv.mux.Handle("/", http.FileServer(http.FS(root))) // WebSocket Endpunkt fsrv.mux.HandleFunc("/ws", fsrv.handleWS) } func (fsrv *FServer) handleWS(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Printf("Upgrade error: %v", err) return } defer conn.Close() // Kanäle für die Hardware-Events abonnieren rxChan := fsrv.bus.Subscribe(api.TopicUARTRx) txChan := fsrv.bus.Subscribe(api.TopicUARTTx) // Context nutzen, um Goroutinen zu stoppen, wenn die Verbindung abreißt ctx, cancel := context.WithCancel(r.Context()) defer cancel() // WRITER: Send Events to Browser go func() { for { select { case <-ctx.Done(): return case f := <-rxChan: if err := conn.WriteJSON(map[string]any{"type": "rx", "frame": f}); err != nil { return } case f := <-txChan: if err := conn.WriteJSON(map[string]any{"type": "tx", "frame": f}); err != nil { return } } } }() // READER: Commands from Browser for { var cmd api.FrontendCmd if err := conn.ReadJSON(&cmd); err != nil { log.Printf("WS Read Error: %v", err) break } fsrv.bus.Publish(api.TopicFrontendCmd, cmd) log.Printf("Browser Action: %s auf ID 0x%02X", cmd.Action, cmd.ID) } } func (fsrv *FServer) Start(addr string) error { server := &http.Server{ Addr: addr, Handler: fsrv.mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } log.Printf("Frontend Server gestartet auf %s", addr) return server.ListenAndServe() }