package api import ( "encoding/json" "log" "net/http" "strconv" "git.rouggy.com/rouggy/ShackMaster/internal/config" "github.com/gorilla/websocket" ) type Server struct { deviceManager *DeviceManager hub *Hub config *config.Config upgrader websocket.Upgrader } func NewServer(dm *DeviceManager, hub *Hub, cfg *config.Config) *Server { return &Server{ deviceManager: dm, hub: hub, config: cfg, upgrader: websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true // Allow all origins for now }, }, } } func (s *Server) SetupRoutes() *http.ServeMux { mux := http.NewServeMux() // WebSocket endpoint mux.HandleFunc("/ws", s.handleWebSocket) // REST API endpoints mux.HandleFunc("/api/status", s.handleGetStatus) mux.HandleFunc("/api/config", s.handleGetConfig) // WebSwitch endpoints mux.HandleFunc("/api/webswitch/relay/on", s.handleWebSwitchRelayOn) mux.HandleFunc("/api/webswitch/relay/off", s.handleWebSwitchRelayOff) mux.HandleFunc("/api/webswitch/all/on", s.handleWebSwitchAllOn) mux.HandleFunc("/api/webswitch/all/off", s.handleWebSwitchAllOff) // Rotator endpoints mux.HandleFunc("/api/rotator/heading", s.handleRotatorHeading) mux.HandleFunc("/api/rotator/cw", s.handleRotatorCW) mux.HandleFunc("/api/rotator/ccw", s.handleRotatorCCW) mux.HandleFunc("/api/rotator/stop", s.handleRotatorStop) // Ultrabeam endpoints mux.HandleFunc("/api/ultrabeam/frequency", s.handleUltrabeamFrequency) mux.HandleFunc("/api/ultrabeam/retract", s.handleUltrabeamRetract) mux.HandleFunc("/api/ultrabeam/autotrack", s.handleUltrabeamAutoTrack) // Tuner endpoints mux.HandleFunc("/api/tuner/operate", s.handleTunerOperate) mux.HandleFunc("/api/tuner/bypass", s.handleTunerBypass) mux.HandleFunc("/api/tuner/autotune", s.handleTunerAutoTune) // Antenna Genius endpoints mux.HandleFunc("/api/antenna/select", s.handleAntennaSelect) mux.HandleFunc("/api/antenna/reboot", s.handleAntennaReboot) // Power Genius endpoints mux.HandleFunc("/api/power/fanmode", s.handlePowerFanMode) mux.HandleFunc("/api/power/operate", s.handlePowerOperate) // Note: Static files are now served from embedded FS in main.go return mux } func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) { conn, err := s.upgrader.Upgrade(w, r, nil) if err != nil { log.Printf("WebSocket upgrade error: %v", err) return } ServeWs(s.hub, conn) } func (s *Server) handleGetStatus(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } status := s.deviceManager.GetStatus() s.sendJSON(w, status) } func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Only send public config info (not API keys) configInfo := map[string]interface{}{ "callsign": s.config.Location.Callsign, "location": map[string]float64{ "latitude": s.config.Location.Latitude, "longitude": s.config.Location.Longitude, }, } s.sendJSON(w, configInfo) } // WebSwitch handlers func (s *Server) handleWebSwitchRelayOn(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } relay, err := strconv.Atoi(r.URL.Query().Get("relay")) if err != nil || relay < 1 || relay > 5 { http.Error(w, "Invalid relay number", http.StatusBadRequest) return } if err := s.deviceManager.WebSwitch().TurnOn(relay); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleWebSwitchRelayOff(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } relay, err := strconv.Atoi(r.URL.Query().Get("relay")) if err != nil || relay < 1 || relay > 5 { http.Error(w, "Invalid relay number", http.StatusBadRequest) return } if err := s.deviceManager.WebSwitch().TurnOff(relay); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleWebSwitchAllOn(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.WebSwitch().AllOn(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleWebSwitchAllOff(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.WebSwitch().AllOff(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Rotator handlers func (s *Server) handleRotatorHeading(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Heading int `json:"heading"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.RotatorGenius().SetHeading(req.Heading); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleRotatorCW(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.RotatorGenius().RotateCW(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleRotatorCCW(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.RotatorGenius().RotateCCW(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleRotatorStop(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.RotatorGenius().Stop(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Tuner handlers func (s *Server) handleTunerOperate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Value int `json:"value"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.TunerGenius().SetOperate(req.Value); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleTunerBypass(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Value int `json:"value"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.TunerGenius().SetBypass(req.Value); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleTunerAutoTune(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.TunerGenius().AutoTune(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Antenna Genius handlers func (s *Server) handleAntennaSelect(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Port int `json:"port"` Antenna int `json:"antenna"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.AntennaGenius().SetAntenna(req.Port, req.Antenna); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleAntennaReboot(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.AntennaGenius().Reboot(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Power Genius handlers func (s *Server) handlePowerFanMode(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Mode string `json:"mode"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.PowerGenius().SetFanMode(req.Mode); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handlePowerOperate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Value int `json:"value"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.PowerGenius().SetOperate(req.Value); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Ultrabeam handlers func (s *Server) handleUltrabeamFrequency(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Frequency int `json:"frequency"` // KHz Direction int `json:"direction"` // 0=normal, 1=180°, 2=bi-dir } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.Ultrabeam().SetFrequency(req.Frequency, req.Direction); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleUltrabeamRetract(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.Ultrabeam().Retract(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleUltrabeamAutoTrack(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Enabled bool `json:"enabled"` Threshold int `json:"threshold"` // kHz } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } s.deviceManager.SetAutoTrack(req.Enabled, req.Threshold*1000) // Convert kHz to Hz s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) sendJSON(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(data) }