package frontend import ( "context" "embed" "encoding/json" "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() { // Static files from the Embed-FS // remove "www" prefix, so index.html is reachable over / root, _ := fs.Sub(staticFiles, "www") fsrv.mux.Handle("/", http.FileServer(http.FS(root))) fsrv.mux.HandleFunc("/ws", fsrv.handleWS) } func (fs *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() // 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 fs.HandleAppEvents(ctx, conn) // READER: Commands from Browser // This Function is Blocking fs.GetFrontendEvents(ctx, conn) } func (fs *FServer) Start(addr string) error { server := &http.Server{ Addr: addr, Handler: fs.mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } ctx, cancle := context.WithCancel(context.Background()) defer cancle() go fs.HandleFrontendEvents(ctx) log.Printf("Frontend Server gestartet auf %s", addr) return server.ListenAndServe() } func (fs *FServer) HandleAppEvents(ctx context.Context, conn *websocket.Conn) error { // Kanäle für die Hardware-Events abonnieren rxChan := fs.bus.Subscribe(api.TopicUARTRx) txChan := fs.bus.Subscribe(api.TopicUARTTx) UartActions := fs.bus.Subscribe(api.TopicUartAction) for { select { case <-ctx.Done(): return nil case f := <-rxChan: if err := conn.WriteJSON(map[string]any{"type": "rx", "frame": f}); err != nil { return nil } case f := <-txChan: if err := conn.WriteJSON(map[string]any{"type": "tx", "frame": f}); err != nil { return nil } case msgT := <-UartActions: switch msg := msgT.(type) { case api.ActionUartConnected: // TODO: nicht hier die daten nachhaltig speichern damit sie ans frontend gesendet werden können // TODO: das muss irgendwo central passieren nicht für jeden client if msg.Error != nil { } continue case api.ActionUartDisconnected: continue } } } } func (fs *FServer) GetFrontendEvents(ctx context.Context, conn *websocket.Conn) error { for { select { case <-ctx.Done(): return nil default: var cmd api.WsMessage if err := conn.ReadJSON(&cmd); err != nil { log.Printf("WS Read Error: %v", err) return err } val, ok := api.MessageReceiveRegistry[cmd.Cmd] if !ok { log.Printf("No Message Type mapped to %v", cmd.Cmd) continue } valM := val() err := json.Unmarshal(cmd.Payload, valM) if err != nil { log.Printf("Could not Unmarshal payload %v", cmd.Payload) } fs.bus.Publish(api.TopicFrontendCmd, valM) log.Printf("Browser Action: %s auf with %v", cmd.Cmd, cmd.Payload) } } } func (fs *FServer) HandleFrontendEvents(ctx context.Context) error { fChan := fs.bus.Subscribe(api.TopicFrontendCmd) for { select { case <-ctx.Done(): return nil case msg := <-fChan: switch msgT := msg.(type) { case api.WsUartSendMessage: log.Printf("Sending Uart Data % X", msgT.Data) fs.bus.Publish(api.TopicUartAction, api.ActionUartSendMessage{ MsgId: msgT.MsgId, Data: msgT.Data, }) continue case api.WsUartConnect: log.Printf("Connect with %s : %d", msgT.SelectedAdapter, msgT.Baudrate) fs.bus.Publish(api.TopicUartAction, api.ActionUartConnect{ Adapter: msgT.SelectedAdapter, Baudrate: msgT.Baudrate, }) continue case api.WsUartDisconnect: log.Printf("Disconnect from Uart Adapter") fs.bus.Publish(api.TopicUartAction, api.ActionUartDisconnect{}) continue } } } }