Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

121 wiersze
3.6KB

  1. package control
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "sync"
  6. "github.com/jan/fm-rds-tx/internal/config"
  7. drypkg "github.com/jan/fm-rds-tx/internal/dryrun"
  8. )
  9. type Server struct {
  10. mu sync.RWMutex
  11. cfg config.Config
  12. }
  13. type ConfigPatch struct {
  14. FrequencyMHz *float64 `json:"frequencyMHz,omitempty"`
  15. OutputDrive *float64 `json:"outputDrive,omitempty"`
  16. ToneLeftHz *float64 `json:"toneLeftHz,omitempty"`
  17. ToneRightHz *float64 `json:"toneRightHz,omitempty"`
  18. ToneAmplitude *float64 `json:"toneAmplitude,omitempty"`
  19. PS *string `json:"ps,omitempty"`
  20. RadioText *string `json:"radioText,omitempty"`
  21. }
  22. func NewServer(cfg config.Config) *Server { return &Server{cfg: cfg} }
  23. func (s *Server) Handler() http.Handler {
  24. mux := http.NewServeMux()
  25. mux.HandleFunc("/healthz", s.handleHealth)
  26. mux.HandleFunc("/status", s.handleStatus)
  27. mux.HandleFunc("/dry-run", s.handleDryRun)
  28. mux.HandleFunc("/config", s.handleConfig)
  29. return mux
  30. }
  31. func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) {
  32. w.Header().Set("Content-Type", "application/json")
  33. _ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
  34. }
  35. func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) {
  36. s.mu.RLock()
  37. cfg := s.cfg
  38. s.mu.RUnlock()
  39. w.Header().Set("Content-Type", "application/json")
  40. _ = json.NewEncoder(w).Encode(map[string]any{
  41. "service": "fm-rds-tx",
  42. "backend": cfg.Backend.Kind,
  43. "frequencyMHz": cfg.FM.FrequencyMHz,
  44. "stereoEnabled": cfg.FM.StereoEnabled,
  45. "rdsEnabled": cfg.RDS.Enabled,
  46. "toneLeftHz": cfg.Audio.ToneLeftHz,
  47. "toneRightHz": cfg.Audio.ToneRightHz,
  48. })
  49. }
  50. func (s *Server) handleDryRun(w http.ResponseWriter, _ *http.Request) {
  51. s.mu.RLock()
  52. cfg := s.cfg
  53. s.mu.RUnlock()
  54. w.Header().Set("Content-Type", "application/json")
  55. _ = json.NewEncoder(w).Encode(drypkg.Generate(cfg))
  56. }
  57. func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
  58. switch r.Method {
  59. case http.MethodGet:
  60. s.mu.RLock()
  61. cfg := s.cfg
  62. s.mu.RUnlock()
  63. w.Header().Set("Content-Type", "application/json")
  64. _ = json.NewEncoder(w).Encode(cfg)
  65. case http.MethodPost:
  66. var patch ConfigPatch
  67. if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
  68. http.Error(w, err.Error(), http.StatusBadRequest)
  69. return
  70. }
  71. s.mu.Lock()
  72. next := s.cfg
  73. if patch.FrequencyMHz != nil {
  74. next.FM.FrequencyMHz = *patch.FrequencyMHz
  75. }
  76. if patch.OutputDrive != nil {
  77. next.FM.OutputDrive = *patch.OutputDrive
  78. }
  79. if patch.ToneLeftHz != nil {
  80. next.Audio.ToneLeftHz = *patch.ToneLeftHz
  81. }
  82. if patch.ToneRightHz != nil {
  83. next.Audio.ToneRightHz = *patch.ToneRightHz
  84. }
  85. if patch.ToneAmplitude != nil {
  86. next.Audio.ToneAmplitude = *patch.ToneAmplitude
  87. }
  88. if patch.PS != nil {
  89. next.RDS.PS = *patch.PS
  90. }
  91. if patch.RadioText != nil {
  92. next.RDS.RadioText = *patch.RadioText
  93. }
  94. if err := next.Validate(); err != nil {
  95. s.mu.Unlock()
  96. http.Error(w, err.Error(), http.StatusBadRequest)
  97. return
  98. }
  99. s.cfg = next
  100. s.mu.Unlock()
  101. w.Header().Set("Content-Type", "application/json")
  102. _ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
  103. default:
  104. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  105. }
  106. }