Bladeren bron

Auto-restart on IQ timeout and report last-sample age

master
Jan Svabenik 4 dagen geleden
bovenliggende
commit
956f06e612
4 gewijzigde bestanden met toevoegingen van 49 en 5 verwijderingen
  1. +36
    -0
      cmd/sdrd/main.go
  2. +4
    -3
      internal/sdr/source.go
  3. +7
    -1
      internal/sdrplay/sdrplay.go
  4. +2
    -1
      web/app.js

+ 36
- 0
cmd/sdrd/main.go Bestand weergeven

@@ -10,6 +10,7 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@@ -97,6 +98,36 @@ type sourceManager struct {
newSource func(cfg config.Config) (sdr.Source, error) newSource func(cfg config.Config) (sdr.Source, error)
} }


func (m *sourceManager) Restart(cfg config.Config) error {
m.mu.Lock()
defer m.mu.Unlock()
old := m.src
_ = old.Stop()
next, err := m.newSource(cfg)
if err != nil {
_ = old.Start()
m.src = old
return err
}
if err := next.Start(); err != nil {
_ = next.Stop()
_ = old.Start()
m.src = old
return err
}
m.src = next
return nil
}

func (m *sourceManager) Stats() sdr.SourceStats {
m.mu.RLock()
defer m.mu.RUnlock()
if sp, ok := m.src.(sdr.StatsProvider); ok {
return sp.Stats()
}
return sdr.SourceStats{}
}

func newSourceManager(src sdr.Source, newSource func(cfg config.Config) (sdr.Source, error)) *sourceManager { func newSourceManager(src sdr.Source, newSource func(cfg config.Config) (sdr.Source, error)) *sourceManager {
return &sourceManager{src: src, newSource: newSource} return &sourceManager{src: src, newSource: newSource}
} }
@@ -463,6 +494,11 @@ func runDSP(ctx context.Context, src sdr.Source, cfg config.Config, det *detecto
iq, err := src.ReadIQ(cfg.FFTSize) iq, err := src.ReadIQ(cfg.FFTSize)
if err != nil { if err != nil {
log.Printf("read IQ: %v", err) log.Printf("read IQ: %v", err)
if strings.Contains(err.Error(), "timeout") {
if err := src.Restart(cfg); err != nil {
log.Printf("restart failed: %v", err)
}
}
continue continue
} }
if !gotSamples { if !gotSamples {


+ 4
- 3
internal/sdr/source.go Bestand weergeven

@@ -13,9 +13,10 @@ type ConfigurableSource interface {
} }


type SourceStats struct { type SourceStats struct {
BufferSamples int `json:"buffer_samples"`
Dropped uint64 `json:"dropped"`
Resets uint64 `json:"resets"`
BufferSamples int `json:"buffer_samples"`
Dropped uint64 `json:"dropped"`
Resets uint64 `json:"resets"`
LastSampleAgoMs int64 `json:"last_sample_ago_ms"`
} }


type StatsProvider interface { type StatsProvider interface {


+ 7
- 1
internal/sdrplay/sdrplay.go Bestand weergeven

@@ -97,6 +97,7 @@ type Source struct {
bwKHz int bwKHz int
dropped uint64 dropped uint64
resets uint64 resets uint64
lastSample time.Time
cond *sync.Cond cond *sync.Cond
} }


@@ -284,7 +285,11 @@ func (s *Source) appendRing(samples []complex64) {
func (s *Source) Stats() sdr.SourceStats { func (s *Source) Stats() sdr.SourceStats {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
return sdr.SourceStats{BufferSamples: s.size, Dropped: s.dropped, Resets: s.resets}
ago := int64(-1)
if !s.lastSample.IsZero() {
ago = time.Since(s.lastSample).Milliseconds()
}
return sdr.SourceStats{BufferSamples: s.size, Dropped: s.dropped, Resets: s.resets, LastSampleAgoMs: ago}
} }


func (s *Source) Flush() { func (s *Source) Flush() {
@@ -383,6 +388,7 @@ func goStreamCallback(xi *C.short, xq *C.short, numSamples C.uint, reset C.uint,
iq[i] = complex(re, im) iq[i] = complex(re, im)
} }
src.mu.Lock() src.mu.Lock()
src.lastSample = time.Now()
src.appendRing(iq) src.appendRing(iq)
src.mu.Unlock() src.mu.Unlock()
} }


+ 2
- 1
web/app.js Bestand weergeven

@@ -353,7 +353,8 @@ function renderSpectrum() {


const binHz = sample_rate / n; const binHz = sample_rate / n;
const gpuState = gpuInfo.active ? 'GPU:ON' : (gpuInfo.available ? 'GPU:OFF' : 'GPU:N/A'); const gpuState = gpuInfo.active ? 'GPU:ON' : (gpuInfo.available ? 'GPU:OFF' : 'GPU:N/A');
metaEl.textContent = `Center ${(center_hz/1e6).toFixed(3)} MHz | Span ${(span/1e6).toFixed(3)} MHz | Res ${binHz.toFixed(1)} Hz/bin | Buf ${stats.buffer_samples} Drop ${stats.dropped} Reset ${stats.resets} | ${gpuState}`;
const lastAge = stats.last_sample_ago_ms >= 0 ? `${stats.last_sample_ago_ms}ms` : 'n/a';
metaEl.textContent = `Center ${(center_hz/1e6).toFixed(3)} MHz | Span ${(span/1e6).toFixed(3)} MHz | Res ${binHz.toFixed(1)} Hz/bin | Buf ${stats.buffer_samples} Drop ${stats.dropped} Reset ${stats.resets} Last ${lastAge} | ${gpuState}`;
} }


function renderWaterfall() { function renderWaterfall() {


Laden…
Annuleren
Opslaan