Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

136 líneas
3.8KB

  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. PreEmphasisUS *float64 `json:"preEmphasisUS,omitempty"`
  22. LimiterEnabled *bool `json:"limiterEnabled,omitempty"`
  23. LimiterCeiling *float64 `json:"limiterCeiling,omitempty"`
  24. }
  25. func NewServer(cfg config.Config) *Server { return &Server{cfg: cfg} }
  26. func (s *Server) Handler() http.Handler {
  27. mux := http.NewServeMux()
  28. mux.HandleFunc("/healthz", s.handleHealth)
  29. mux.HandleFunc("/status", s.handleStatus)
  30. mux.HandleFunc("/dry-run", s.handleDryRun)
  31. mux.HandleFunc("/config", s.handleConfig)
  32. return mux
  33. }
  34. func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) {
  35. w.Header().Set("Content-Type", "application/json")
  36. _ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
  37. }
  38. func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) {
  39. s.mu.RLock()
  40. cfg := s.cfg
  41. s.mu.RUnlock()
  42. w.Header().Set("Content-Type", "application/json")
  43. _ = json.NewEncoder(w).Encode(map[string]any{
  44. "service": "fm-rds-tx",
  45. "backend": cfg.Backend.Kind,
  46. "frequencyMHz": cfg.FM.FrequencyMHz,
  47. "stereoEnabled": cfg.FM.StereoEnabled,
  48. "rdsEnabled": cfg.RDS.Enabled,
  49. "toneLeftHz": cfg.Audio.ToneLeftHz,
  50. "toneRightHz": cfg.Audio.ToneRightHz,
  51. "preEmphasisUS": cfg.FM.PreEmphasisUS,
  52. "limiterEnabled": cfg.FM.LimiterEnabled,
  53. "fmModulationEnabled": cfg.FM.FMModulationEnabled,
  54. })
  55. }
  56. func (s *Server) handleDryRun(w http.ResponseWriter, _ *http.Request) {
  57. s.mu.RLock()
  58. cfg := s.cfg
  59. s.mu.RUnlock()
  60. w.Header().Set("Content-Type", "application/json")
  61. _ = json.NewEncoder(w).Encode(drypkg.Generate(cfg))
  62. }
  63. func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
  64. switch r.Method {
  65. case http.MethodGet:
  66. s.mu.RLock()
  67. cfg := s.cfg
  68. s.mu.RUnlock()
  69. w.Header().Set("Content-Type", "application/json")
  70. _ = json.NewEncoder(w).Encode(cfg)
  71. case http.MethodPost:
  72. var patch ConfigPatch
  73. if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
  74. http.Error(w, err.Error(), http.StatusBadRequest)
  75. return
  76. }
  77. s.mu.Lock()
  78. next := s.cfg
  79. if patch.FrequencyMHz != nil {
  80. next.FM.FrequencyMHz = *patch.FrequencyMHz
  81. }
  82. if patch.OutputDrive != nil {
  83. next.FM.OutputDrive = *patch.OutputDrive
  84. }
  85. if patch.ToneLeftHz != nil {
  86. next.Audio.ToneLeftHz = *patch.ToneLeftHz
  87. }
  88. if patch.ToneRightHz != nil {
  89. next.Audio.ToneRightHz = *patch.ToneRightHz
  90. }
  91. if patch.ToneAmplitude != nil {
  92. next.Audio.ToneAmplitude = *patch.ToneAmplitude
  93. }
  94. if patch.PS != nil {
  95. next.RDS.PS = *patch.PS
  96. }
  97. if patch.RadioText != nil {
  98. next.RDS.RadioText = *patch.RadioText
  99. }
  100. if patch.PreEmphasisUS != nil {
  101. next.FM.PreEmphasisUS = *patch.PreEmphasisUS
  102. }
  103. if patch.LimiterEnabled != nil {
  104. next.FM.LimiterEnabled = *patch.LimiterEnabled
  105. }
  106. if patch.LimiterCeiling != nil {
  107. next.FM.LimiterCeiling = *patch.LimiterCeiling
  108. }
  109. if err := next.Validate(); err != nil {
  110. s.mu.Unlock()
  111. http.Error(w, err.Error(), http.StatusBadRequest)
  112. return
  113. }
  114. s.cfg = next
  115. s.mu.Unlock()
  116. w.Header().Set("Content-Type", "application/json")
  117. _ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
  118. default:
  119. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  120. }
  121. }