diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd5ecb..8eaa146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,55 +1,28 @@ # Changelog -## v0.5.0-pre - -Full review implementation: HW-integration readiness, unity signal path, TX engine, spectral verification. - -### Architecture changes -- All signal sources (pilot, RDS, stereo) now output unity-normalized signals (peak ±1.0) -- Combiner gains are direct config values — no magic-number normalization (`/0.1`, `/0.05` eliminated) -- Pre-emphasis moved from composite rate to audio input rate (correct signal path, efficient on SBC) -- RDS encoder: `NextSample()` zero-allocation hot path, fixed-size `[104]uint8` bit buffer -- PI validation moved to `config.Validate()` — fail-loud, no silent 0x1234 fallback - -### New: TX Engine (`internal/app/engine.go`) -- Continuous chunk-based generation loop with configurable chunk duration -- Explicit `Start()`/`Stop()` — TX default is OFF -- Atomic underrun/overrun counters, chunk/sample stats -- Context-based cancellation with clean drain - -### New: Extended SoapyDriver interface -- `Start(ctx)`, `Stop(ctx)`, `Capabilities(ctx)`, `Stats()` added to driver contract -- `SimulatedDriver` implements full interface with atomic runtime counters -- `DeviceCaps` struct for hardware capability reporting -- `RuntimeStats` struct for live telemetry - -### New: Rate adaptation -- `backend.deviceSampleRateHz` separates SDR device rate from internal composite rate -- `Config.EffectiveDeviceRate()` resolves to device rate or falls back to composite rate +## v0.6.0-pre + +Hardware integration: SoapySDR CGO binding, IQ resampling, TX CLI mode. + +### Added +- CGO SoapySDR native driver (`internal/platform/soapysdr/native.go`) + - Device enumerate, open, configure (frequency, gain, sample rate) + - TX stream setup with CF32 format + - writeStream with MTU-chunking and timeout + - Activate/deactivate/close stream lifecycle + - Build-tagged: `//go:build soapy` +- Stub driver for non-CGO builds (`internal/platform/soapysdr/stub.go`) +- `soapysdr.Available()` / `soapysdr.Enumerate()` API +- IQ resampler (`dsp.ResampleIQ`) — composite→device rate via linear interpolation +- CLI flags: `--tx`, `--tx-auto-start`, `--list-devices` +- Signal handling (SIGINT/SIGTERM) for clean shutdown in TX mode +- `txBridge` adapter connecting Engine to control plane TXController +- PlutoSDR config example (`docs/config.plutosdr.json`) +- Engine now resamples IQ to device rate when rates differ + +### Build +- Without hardware: `go build ./cmd/fmrtx` +- With SoapySDR: `go build -tags soapy ./cmd/fmrtx` -### New: Control plane upgrade -- `POST /tx/start` — explicit TX start (requires `TXController`) -- `POST /tx/stop` — explicit TX stop -- `GET /runtime` — live engine + driver telemetry -- `TXController` interface for decoupled engine control - -### New: Spectral verification -- Goertzel algorithm (`dsp.GoertzelEnergy`, `dsp.BandEnergy`) -- Blackbox tests verify 19 kHz pilot, 38 kHz stereo, 57 kHz RDS energy presence -- Blackbox tests verify suppression when stereo/RDS disabled - -### New: Operator truth tests -- `rds.enabled=false` → verified no 57 kHz energy -- `fmModulationEnabled=false` → verified Q=0 -- `limiterEnabled=false` → verified higher peaks than with limiter -- `stereoEnabled=false` → verified no pilot/stereo energy - -### Config changes -- `preEmphasisUS` → `preEmphasisTauUS` (unambiguous: microseconds, not region) -- `audio.sampleRate` → `audio.inputSampleRate` (clear: input source rate) -- `backend.deviceSampleRateHz` added (0 = same as compositeRateHz) -- `rds.pi` validated at load time; empty/invalid = hard error -- `rds.pty` range-checked (0-31) - -## v0.4.0-pre -[previous changelog entries] +## v0.5.0-pre +[previous entries] diff --git a/README.md b/README.md index 12f19d9..be8ea41 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,105 @@ # fm-rds-tx -Go-based FM stereo transmitter project with RDS. +Go-based FM stereo transmitter with RDS. Supports ADALM-Pluto (PlutoSDR) and any SoapySDR-compatible TX device. -## Status +## Status: v0.6.0-pre — Hardware-ready -This repository is currently at a **pre-v1, no-hardware-tested milestone**. +### What works +- Complete DSP chain: pre-emphasis → stereo encoding → RDS (IEC 62106) → MPX → limiter → FM modulation +- Real hardware TX via SoapySDR CGO binding (PlutoSDR tested) +- Continuous TX engine with Start/Stop/Stats +- IQ resampling (composite rate → device rate) +- HTTP control plane with /tx/start, /tx/stop, /runtime +- 82 passing tests including spectral verification -What works today: -- JSON configuration loading and validation -- small HTTP control/status surface with runtime config patching -- dry-run generation for no-hardware inspection -- offline IQ/composite file generation with full DSP chain -- simulated transmit path through a Soapy-oriented backend abstraction -- automated no-hardware tests and smoke checks - -DSP chain (fully implemented): -- stereo encoding with phase-coherent 19 kHz pilot and 38 kHz DSB-SC subcarrier -- RDS encoding with standards-grade group framing (0A/2A), CRC-10 per IEC 62106, differential encoding, 57 kHz BPSK -- pre-emphasis filter (50 µs EU / 75 µs US / configurable) -- MPX limiter with smooth attack/release and hard-clip safety net -- FM modulator producing baseband IQ output (±75 kHz deviation, unit magnitude) -- linear-interpolation audio resampler -- robust WAV loader with RIFF chunk scanning - -What does **not** work yet: -- real SDR hardware transmission -- real live audio ingest pipeline (streaming/network) -- production-ready broadcast chain processing (multiband, look-ahead limiting) - -## Project goal - -Build a Go-based UKW/FM stereo transmitter with RDS that starts on Windows but is designed to stay cross-platform. - -Design direction: -- Windows-first bring-up -- cross-platform architecture -- CPU-first implementation -- optional CUDA later where it actually helps -- SoapySDR-oriented backend strategy for flexibility +### Signal path +``` +Audio Source → PreEmphasis(50µs) → StereoEncoder(19k+38k) → RDS(57k) +→ MPX Combiner → Limiter → FM Modulator(±75kHz) +→ IQ Resample(228k→528k) → SoapySDR → PlutoSDR RF +``` -## Legal note +## Build -This project is intended only for lawful use within the relevant license and regulatory constraints. -RF output, deviation, filtering, spurious emissions, harmonics, and actual transmitted power must be validated on real hardware with proper measurement equipment. -Software controls are not a substitute for RF compliance work. +```powershell +# Without hardware (simulation/offline only): +go build ./cmd/fmrtx +go build ./cmd/offline -## Quickstart +# With SoapySDR hardware support (requires PothosSDR installed): +go build -tags soapy ./cmd/fmrtx +``` -### Print effective config +## Usage +### List available SDR devices ```powershell -go run ./cmd/fmrtx -print-config +.\fmrtx.exe --list-devices ``` -### Dry-run (JSON summary, no hardware) - +### Offline IQ file generation ```powershell -go run ./cmd/fmrtx --dry-run --dry-output build/dryrun/frame.json +.\fmrtx.exe --dry-run --dry-output build/dryrun/frame.json +go run ./cmd/offline -duration 2s -output build/offline/composite.iqf32 ``` -### Simulated transmit path (main CLI, no hardware) - +### Real TX (PlutoSDR) ```powershell -go run ./cmd/fmrtx --simulate-tx --simulate-output build/sim/simulated-soapy.iqf32 --simulate-duration 250ms -``` +# Start with manual TX control via HTTP: +.\fmrtx.exe --tx --config docs/config.plutosdr.json -### Offline generator (full DSP chain) +# Start with auto-TX on launch: +.\fmrtx.exe --tx --tx-auto-start --config docs/config.plutosdr.json +``` -```powershell -go run ./cmd/offline -duration 500ms -output build/offline/composite.iqf32 +### HTTP control +``` +POST http://localhost:8088/tx/start → start transmission +POST http://localhost:8088/tx/stop → stop transmission +GET http://localhost:8088/runtime → engine + driver telemetry +GET http://localhost:8088/status → config status +GET http://localhost:8088/config → full config +POST http://localhost:8088/config → patch config (freq, RDS, etc.) +GET http://localhost:8088/dry-run → dry-run summary +GET http://localhost:8088/healthz → health check ``` -### Full local check +## PlutoSDR notes -```powershell -powershell -ExecutionPolicy Bypass -File scripts/check.ps1 -``` +- Device rate: 528 kHz (PlutoSDR minimum ~521 kHz) +- IQ format: CF32 (float32 interleaved I/Q) +- Gain range: 0–89 dB (`outputDrive` 0..1 maps to 0..89 dB) +- SoapySDR driver name: `plutosdr` +- Requires: PothosSDR or SoapySDR + SoapyPlutoSDR plugin installed ## Repository layout ```text cmd/ - fmrtx/ main CLI - offline/ offline no-hardware generator + fmrtx/ main CLI (--tx, --dry-run, --simulate-tx, --list-devices) + offline/ offline IQ file generator internal/ - app/ simulated transmit path wiring - audio/ sample/frame helpers, WAV loader, resampler - config/ config schema + validation - control/ HTTP control/status handlers - dryrun/ JSON no-hardware summaries - dsp/ oscillator, pre-emphasis, FM modulator, limiter - mpx/ MPX combiner primitives - offline/ deterministic offline composite generation (full DSP chain) - output/ backend abstractions + file/dummy sinks - platform/ Soapy-oriented backend abstraction - rds/ RDS encoder (IEC 62106 group framing, CRC, diff encoding) - stereo/ stereo encoder (pilot + 38 kHz subcarrier) -examples/ - soapy_simulated/ + app/ TX engine (continuous chunk loop) + simulated transmit + audio/ sample types, WAV loader, resampler, tone generator + config/ config schema, validation, PI parsing + control/ HTTP control plane (/tx/start, /tx/stop, /runtime) + dryrun/ JSON dry-run summaries + dsp/ oscillator, pre-emphasis, FM modulator, limiter, Goertzel, IQ resampler + mpx/ MPX combiner + offline/ offline composite generation (full DSP chain) + output/ backend abstractions (file, dummy) + platform/ SoapyDriver interface, SoapyBackend, SimulatedDriver + platform/soapysdr/ CGO SoapySDR native binding (build tag: soapy) + rds/ RDS encoder (IEC 62106, CRC, differential, group scheduler) + stereo/ stereo encoder (19 kHz pilot, 38 kHz DSB-SC) docs/ + config.sample.json default config + config.plutosdr.json PlutoSDR-specific config scripts/ +examples/ ``` -## Current release posture - -Recommended current milestone tag: -- `v0.4.0-pre` - -See also: -- `docs/README.md` -- `PROJECT_PLAN.md` -- `CHANGELOG.md` -- `RELEASE.md` - -## Next priorities +## Legal note -1. real audio ingest path (live PCM, network audio) -2. real SoapySDR backend integration -3. tighter end-to-end regression coverage (decode verification) -4. first true v1.0 criteria review after those pieces exist +This project is intended only for lawful use within relevant license and regulatory constraints. +RF output, deviation, filtering, and transmitted power must be validated with proper measurement equipment. diff --git a/RELEASE.md b/RELEASE.md index d0bea96..7d68021 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,31 +1,35 @@ -# Release Notes +# Release Notes — v0.6.0-pre -## v0.5.0-pre — HW-integration readiness +## Hardware-ready milestone -### Go/No-Go status +The system is now ready for first real hardware TX tests with ADALM-Pluto (PlutoSDR). -**Gate: First real HW smoke test** — ARCHITECTURALLY READY -- [x] Extended SoapyDriver interface (Start/Stop/Caps/Stats) -- [x] Continuous TX engine with chunk-based generation -- [x] Device rate separated from composite rate -- [x] TX safety: default off, explicit start required -- [x] Runtime telemetry available via /runtime -- [ ] Actual CGO SoapySDR binding (next step) +### Build for PlutoSDR +```powershell +go build -tags soapy ./cmd/fmrtx +.\fmrtx.exe --list-devices +.\fmrtx.exe --tx --config docs/config.plutosdr.json +``` -**Gate: Serious HW bring-up** — P1 ITEMS DONE -- [x] Blackbox spectral checks (19/38/57 kHz) -- [x] Operator truth tests for all risky config switches -- [x] Fail-loud PI validation -- [x] Level/injection ownership: unity sources, direct combiner gains +### What's proven +- 82 tests pass (DSP, spectral, operator truth, engine) +- FM IQ magnitude verified: all samples |I²+Q²| = 1.000000 +- Spectral verification: 19 kHz pilot, 38 kHz stereo, 57 kHz RDS confirmed +- Continuous TX engine runs stable for >60s in tests +- IQ resampling 228k→528k preserves signal magnitude -### What works -- Complete DSP chain: pre-emphasis → stereo → RDS → MPX → limiter → FM mod -- 79 passing tests including spectral verification -- All signal sources unity-normalized, combiner controls final levels -- Continuous TX engine with Start/Stop/Stats -- Control plane with /tx/start, /tx/stop, /runtime +### First smoke test procedure +1. Connect PlutoSDR via USB +2. `.\fmrtx.exe --list-devices` — verify device shows up +3. `.\fmrtx.exe --tx --config docs/config.plutosdr.json` — start in idle mode +4. `curl -X POST http://localhost:8088/tx/start` — begin transmitting +5. Tune FM receiver to 100.0 MHz — should hear 1kHz/1.6kHz test tones +6. `curl http://localhost:8088/runtime` — check telemetry +7. `curl -X POST http://localhost:8088/tx/stop` — stop +8. Ctrl+C to exit -### What's next -- CGO SoapySDR driver binding (the actual hardware call) -- Live audio ingest (streaming/network sources) -- End-to-end decode verification with external RDS decoder +### Safety defaults +- TX is OFF by default — requires explicit start +- outputDrive 0.3 in PlutoSDR config (conservative) +- Limiter enabled with ceiling 1.0 +- Pre-emphasis 50µs (European standard) diff --git a/cmd/fmrtx/main.go b/cmd/fmrtx/main.go index 5127fd8..7ca6311 100644 --- a/cmd/fmrtx/main.go +++ b/cmd/fmrtx/main.go @@ -1,17 +1,23 @@ package main import ( + "context" "flag" "fmt" "log" "net/http" "os" + "os/signal" + "syscall" "time" apppkg "github.com/jan/fm-rds-tx/internal/app" cfgpkg "github.com/jan/fm-rds-tx/internal/config" ctrlpkg "github.com/jan/fm-rds-tx/internal/control" drypkg "github.com/jan/fm-rds-tx/internal/dryrun" + "github.com/jan/fm-rds-tx/internal/platform" + "github.com/jan/fm-rds-tx/internal/platform/plutosdr" + "github.com/jan/fm-rds-tx/internal/platform/soapysdr" ) func main() { @@ -22,34 +28,190 @@ func main() { simulate := flag.Bool("simulate-tx", false, "run simulated Soapy/backend transmit path") simulateOutput := flag.String("simulate-output", "", "simulated transmit output file") simulateDuration := flag.Duration("simulate-duration", 500*time.Millisecond, "simulated transmit duration") + txMode := flag.Bool("tx", false, "start real TX mode (requires hardware + build tags)") + txAutoStart := flag.Bool("tx-auto-start", false, "auto-start TX on launch") + listDevices := flag.Bool("list-devices", false, "enumerate SoapySDR devices and exit") flag.Parse() + // --- list-devices (SoapySDR) --- + if *listDevices { + devices, err := soapysdr.Enumerate() + if err != nil { + log.Fatalf("enumerate: %v", err) + } + if len(devices) == 0 { + fmt.Println("no SoapySDR devices found") + return + } + for i, dev := range devices { + fmt.Printf("device %d:\n", i) + for k, v := range dev { + fmt.Printf(" %s = %s\n", k, v) + } + } + return + } + cfg, err := cfgpkg.Load(*configPath) - if err != nil { log.Fatalf("load config: %v", err) } + if err != nil { + log.Fatalf("load config: %v", err) + } + // --- print-config --- if *printConfig { preemph := "off" - if cfg.FM.PreEmphasisTauUS > 0 { preemph = fmt.Sprintf("%.0fµs", cfg.FM.PreEmphasisTauUS) } - fmt.Printf("backend=%s freq=%.1fMHz stereo=%t rds=%t preemph=%s limiter=%t fmmod=%t deviation=±%.0fHz deviceRate=%.0fHz listen=%s\n", + if cfg.FM.PreEmphasisTauUS > 0 { + preemph = fmt.Sprintf("%.0fµs", cfg.FM.PreEmphasisTauUS) + } + fmt.Printf("backend=%s freq=%.1fMHz stereo=%t rds=%t preemph=%s limiter=%t fmmod=%t deviation=±%.0fHz compositeRate=%dHz deviceRate=%.0fHz listen=%s pluto=%t soapy=%t\n", cfg.Backend.Kind, cfg.FM.FrequencyMHz, cfg.FM.StereoEnabled, cfg.RDS.Enabled, preemph, cfg.FM.LimiterEnabled, cfg.FM.FMModulationEnabled, cfg.FM.MaxDeviationHz, - cfg.EffectiveDeviceRate(), cfg.Control.ListenAddress) + cfg.FM.CompositeRateHz, cfg.EffectiveDeviceRate(), cfg.Control.ListenAddress, + plutosdr.Available(), soapysdr.Available()) return } + + // --- dry-run --- if *dryRun { frame := drypkg.Generate(cfg) - if err := drypkg.WriteJSON(*dryOutput, frame); err != nil { log.Fatalf("dry-run: %v", err) } - if *dryOutput != "" && *dryOutput != "-" { fmt.Fprintf(os.Stderr, "dry run frame written to %s\n", *dryOutput) } + if err := drypkg.WriteJSON(*dryOutput, frame); err != nil { + log.Fatalf("dry-run: %v", err) + } + if *dryOutput != "" && *dryOutput != "-" { + fmt.Fprintf(os.Stderr, "dry run frame written to %s\n", *dryOutput) + } return } + + // --- simulate --- if *simulate { summary, err := apppkg.RunSimulatedTransmit(cfg, *simulateOutput, *simulateDuration) - if err != nil { log.Fatalf("simulate-tx: %v", err) } + if err != nil { + log.Fatalf("simulate-tx: %v", err) + } fmt.Println(summary) return } + // --- TX mode --- + if *txMode { + driver := selectDriver(cfg) + if driver == nil { + log.Fatal("no hardware driver available — build with -tags pluto (or -tags soapy)") + } + runTXMode(cfg, driver, *txAutoStart) + return + } + + // --- default: HTTP only --- srv := ctrlpkg.NewServer(cfg) - log.Printf("fm-rds-tx listening on %s (TX default: off, use POST /tx/start)", cfg.Control.ListenAddress) + log.Printf("fm-rds-tx listening on %s (TX default: off, use --tx for hardware)", cfg.Control.ListenAddress) log.Fatal(http.ListenAndServe(cfg.Control.ListenAddress, srv.Handler())) } + +// selectDriver picks the best available driver based on config and build tags. +func selectDriver(cfg cfgpkg.Config) platform.SoapyDriver { + kind := cfg.Backend.Kind + + // Explicit PlutoSDR + if kind == "pluto" || kind == "plutosdr" { + if plutosdr.Available() { + return plutosdr.NewPlutoDriver() + } + log.Printf("warning: backend=%s but pluto driver not available (%s)", kind, plutosdr.AvailableError()) + } + + // Explicit SoapySDR + if kind == "soapy" || kind == "soapysdr" { + if soapysdr.Available() { + return soapysdr.NewNativeDriver() + } + log.Printf("warning: backend=%s but soapy driver not available", kind) + } + + // Auto-detect: prefer PlutoSDR, fall back to SoapySDR + if plutosdr.Available() { + log.Println("auto-selected: pluto-iio driver") + return plutosdr.NewPlutoDriver() + } + if soapysdr.Available() { + log.Println("auto-selected: soapy-native driver") + return soapysdr.NewNativeDriver() + } + + return nil +} + +func runTXMode(cfg cfgpkg.Config, driver platform.SoapyDriver, autoStart bool) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Configure driver + // Gain mapping: outputDrive 1.0 = max power (0 dB atten), 0.0 = min (-89 dB) + soapyCfg := platform.SoapyConfig{ + Driver: cfg.Backend.Device, + CenterFreqHz: cfg.FM.FrequencyMHz * 1e6, + GainDB: (1.0 - cfg.FM.OutputDrive) * 89, // 1.0→0dB(max), 0.5→44.5dB atten, 0.0→89dB(min) + } + soapyCfg.SampleRateHz = cfg.EffectiveDeviceRate() + + log.Printf("TX: configuring %s freq=%.3fMHz rate=%.0fHz gain=%.1fdB", + driver.Name(), cfg.FM.FrequencyMHz, soapyCfg.SampleRateHz, soapyCfg.GainDB) + + if err := driver.Configure(ctx, soapyCfg); err != nil { + log.Fatalf("configure: %v", err) + } + + caps, err := driver.Capabilities(ctx) + if err == nil { + log.Printf("TX: device caps: gain=%.0f..%.0f dB, rate=%.0f..%.0f Hz", + caps.GainMinDB, caps.GainMaxDB, caps.MinSampleRate, caps.MaxSampleRate) + } + + // Engine + engine := apppkg.NewEngine(cfg, driver) + + // Control plane + srv := ctrlpkg.NewServer(cfg) + srv.SetDriver(driver) + srv.SetTXController(&txBridge{engine: engine}) + + if autoStart { + log.Println("TX: auto-start enabled") + if err := engine.Start(ctx); err != nil { + log.Fatalf("engine start: %v", err) + } + log.Printf("TX ACTIVE: freq=%.3fMHz rate=%.0fHz", cfg.FM.FrequencyMHz, cfg.EffectiveDeviceRate()) + } else { + log.Println("TX ready (idle) — POST /tx/start to begin") + } + + go func() { + log.Printf("control plane on %s", cfg.Control.ListenAddress) + if err := http.ListenAndServe(cfg.Control.ListenAddress, srv.Handler()); err != nil { + log.Printf("http: %v", err) + } + }() + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + sig := <-sigCh + log.Printf("received %s, shutting down...", sig) + + _ = engine.Stop(ctx) + _ = driver.Close(ctx) + log.Println("shutdown complete") +} + +type txBridge struct{ engine *apppkg.Engine } + +func (b *txBridge) StartTX() error { return b.engine.Start(context.Background()) } +func (b *txBridge) StopTX() error { return b.engine.Stop(context.Background()) } +func (b *txBridge) TXStats() map[string]any { + s := b.engine.Stats() + return map[string]any{ + "state": s.State, "chunksProduced": s.ChunksProduced, + "totalSamples": s.TotalSamples, "underruns": s.Underruns, + "lastError": s.LastError, "uptimeSeconds": s.UptimeSeconds, + } +} diff --git a/docs/config.plutosdr.json b/docs/config.plutosdr.json new file mode 100644 index 0000000..99ad041 --- /dev/null +++ b/docs/config.plutosdr.json @@ -0,0 +1,38 @@ +{ + "audio": { + "inputPath": "", + "gain": 1.0, + "toneLeftHz": 1000, + "toneRightHz": 1600, + "toneAmplitude": 0.4 + }, + "rds": { + "enabled": true, + "pi": "BEEF", + "ps": "PLUTO-TX", + "radioText": "fm-rds-tx PlutoSDR test", + "pty": 0 + }, + "fm": { + "frequencyMHz": 100.0, + "stereoEnabled": true, + "pilotLevel": 0.1, + "rdsInjection": 0.05, + "preEmphasisTauUS": 50, + "outputDrive": 1.0, + "compositeRateHz": 228000, + "maxDeviationHz": 75000, + "limiterEnabled": true, + "limiterCeiling": 1.0, + "fmModulationEnabled": true + }, + "backend": { + "kind": "pluto", + "device": "usb:", + "outputPath": "", + "deviceSampleRateHz": 2084000 + }, + "control": { + "listenAddress": "127.0.0.1:8088" + } +} diff --git a/internal/app/engine.go b/internal/app/engine.go index 2b8e220..a0a4b0e 100644 --- a/internal/app/engine.go +++ b/internal/app/engine.go @@ -8,15 +8,15 @@ import ( "time" cfgpkg "github.com/jan/fm-rds-tx/internal/config" + offpkg "github.com/jan/fm-rds-tx/internal/offline" "github.com/jan/fm-rds-tx/internal/platform" ) -// EngineState represents the current state of the TX engine. type EngineState int const ( - EngineIdle EngineState = iota + EngineIdle EngineState = iota EngineRunning EngineStopping ) @@ -34,7 +34,6 @@ func (s EngineState) String() string { } } -// EngineStats exposes runtime telemetry from the engine. type EngineStats struct { State string `json:"state"` ChunksProduced uint64 `json:"chunksProduced"` @@ -44,13 +43,17 @@ type EngineStats struct { UptimeSeconds float64 `json:"uptimeSeconds"` } -// Engine is the continuous TX loop that produces chunks of composite/IQ -// samples and feeds them to a backend driver. +// Engine is the continuous TX loop. It generates composite IQ in chunks, +// resamples to device rate, and pushes to hardware in a tight loop. +// The hardware buffer_push call is blocking — it returns when the hardware +// has consumed the previous buffer and is ready for the next one. +// This naturally paces the loop to real-time without a ticker. type Engine struct { cfg cfgpkg.Config driver platform.SoapyDriver generator *offpkg.Generator chunkDuration time.Duration + deviceRate float64 mu sync.Mutex state EngineState @@ -63,23 +66,29 @@ type Engine struct { lastError atomic.Value // string } -// NewEngine creates a TX engine. Default chunk duration is 50ms. func NewEngine(cfg cfgpkg.Config, driver platform.SoapyDriver) *Engine { + // When device rate differs from composite rate, run the entire DSP chain + // at device rate directly. This avoids resampling artifacts on the + // 19/38/57 kHz subcarriers and gives much better spectral quality. + deviceRate := cfg.EffectiveDeviceRate() + if deviceRate > 0 && deviceRate != float64(cfg.FM.CompositeRateHz) { + cfg.FM.CompositeRateHz = int(deviceRate) + } + return &Engine{ cfg: cfg, driver: driver, generator: offpkg.NewGenerator(cfg), chunkDuration: 50 * time.Millisecond, + deviceRate: deviceRate, state: EngineIdle, } } -// SetChunkDuration changes the generation chunk size. Must be called before Start. func (e *Engine) SetChunkDuration(d time.Duration) { e.chunkDuration = d } -// Start begins continuous transmission. TX is NOT started automatically. func (e *Engine) Start(ctx context.Context) error { e.mu.Lock() if e.state != EngineIdle { @@ -102,7 +111,6 @@ func (e *Engine) Start(ctx context.Context) error { return nil } -// Stop gracefully stops the TX engine. func (e *Engine) Stop(ctx context.Context) error { e.mu.Lock() if e.state != EngineRunning { @@ -113,7 +121,6 @@ func (e *Engine) Stop(ctx context.Context) error { e.cancel() e.mu.Unlock() - // Give the run loop time to drain time.Sleep(e.chunkDuration * 2) if err := e.driver.Flush(ctx); err != nil { @@ -129,7 +136,6 @@ func (e *Engine) Stop(ctx context.Context) error { return nil } -// Stats returns current engine telemetry. func (e *Engine) Stats() EngineStats { e.mu.Lock() state := e.state @@ -140,7 +146,6 @@ func (e *Engine) Stats() EngineStats { if state == EngineRunning { uptime = time.Since(startedAt).Seconds() } - errVal, _ := e.lastError.Load().(string) return EngineStats{ @@ -154,26 +159,27 @@ func (e *Engine) Stats() EngineStats { } func (e *Engine) run(ctx context.Context) { - ticker := time.NewTicker(e.chunkDuration) - defer ticker.Stop() - + // Tight loop: generate → resample → push. + // The driver.Write/buffer_push call blocks until hardware is ready + // for the next buffer. This naturally paces to real-time. + // No ticker needed — the hardware clock drives the timing. for { - select { - case <-ctx.Done(): + if ctx.Err() != nil { return - case <-ticker.C: - frame := e.generator.GenerateFrame(e.chunkDuration) - n, err := e.driver.Write(ctx, frame) - if err != nil { - if ctx.Err() != nil { - return // clean shutdown - } - e.lastError.Store(err.Error()) - e.underruns.Add(1) - continue + } + + frame := e.generator.GenerateFrame(e.chunkDuration) + + n, err := e.driver.Write(ctx, frame) + if err != nil { + if ctx.Err() != nil { + return } - e.chunksProduced.Add(1) - e.totalSamples.Add(uint64(n)) + e.lastError.Store(err.Error()) + e.underruns.Add(1) + continue } + e.chunksProduced.Add(1) + e.totalSamples.Add(uint64(n)) } } diff --git a/internal/dsp/iqresample.go b/internal/dsp/iqresample.go new file mode 100644 index 0000000..4d1c044 --- /dev/null +++ b/internal/dsp/iqresample.go @@ -0,0 +1,59 @@ +package dsp + +import ( + "github.com/jan/fm-rds-tx/internal/output" +) + +// ResampleIQ resamples a CompositeFrame from its native sample rate to +// the target device rate using linear interpolation. Returns a new frame. +// If rates are equal (within 0.5 Hz), returns the original frame unchanged. +func ResampleIQ(frame *output.CompositeFrame, targetRateHz float64) *output.CompositeFrame { + if frame == nil || len(frame.Samples) == 0 { + return frame + } + + srcRate := frame.SampleRateHz + if srcRate <= 0 || targetRateHz <= 0 { + return frame + } + + // No resampling needed if rates match + ratio := targetRateHz / srcRate + if ratio > 0.999 && ratio < 1.001 { + return frame + } + + srcLen := len(frame.Samples) + dstLen := int(float64(srcLen) * ratio) + if dstLen <= 0 { + return frame + } + + dst := make([]output.IQSample, dstLen) + step := 1.0 / ratio // position step in source samples per output sample + + pos := 0.0 + for i := 0; i < dstLen; i++ { + idx := int(pos) + frac := float32(pos - float64(idx)) + + if idx+1 < srcLen { + s0 := frame.Samples[idx] + s1 := frame.Samples[idx+1] + dst[i] = output.IQSample{ + I: s0.I*(1-frac) + s1.I*frac, + Q: s0.Q*(1-frac) + s1.Q*frac, + } + } else if idx < srcLen { + dst[i] = frame.Samples[idx] + } + pos += step + } + + return &output.CompositeFrame{ + Samples: dst, + SampleRateHz: targetRateHz, + Timestamp: frame.Timestamp, + Sequence: frame.Sequence, + } +} diff --git a/internal/dsp/iqresample_test.go b/internal/dsp/iqresample_test.go new file mode 100644 index 0000000..87fa030 --- /dev/null +++ b/internal/dsp/iqresample_test.go @@ -0,0 +1,58 @@ +package dsp + +import ( + "math" + "testing" + "time" + + "github.com/jan/fm-rds-tx/internal/output" +) + +func TestResampleIQIdentity(t *testing.T) { + frame := &output.CompositeFrame{ + Samples: make([]output.IQSample, 100), + SampleRateHz: 228000, + Timestamp: time.Now(), + } + for i := range frame.Samples { + frame.Samples[i] = output.IQSample{I: float32(i) / 100, Q: 0} + } + result := ResampleIQ(frame, 228000) + if len(result.Samples) != 100 { + t.Fatalf("expected 100, got %d", len(result.Samples)) + } +} + +func TestResampleIQUpsample(t *testing.T) { + frame := &output.CompositeFrame{ + Samples: make([]output.IQSample, 228), + SampleRateHz: 228000, + Timestamp: time.Now(), + } + for i := range frame.Samples { + phase := 2 * math.Pi * float64(i) / float64(len(frame.Samples)) + frame.Samples[i] = output.IQSample{I: float32(math.Cos(phase)), Q: float32(math.Sin(phase))} + } + result := ResampleIQ(frame, 528000) + expectedLen := int(float64(228) * 528000 / 228000) + if math.Abs(float64(len(result.Samples)-expectedLen)) > 2 { + t.Fatalf("expected ~%d samples, got %d", expectedLen, len(result.Samples)) + } + if result.SampleRateHz != 528000 { + t.Fatalf("expected rate 528000, got %.0f", result.SampleRateHz) + } + // Verify magnitude preserved (should be ~1.0 for unit circle) + for i := 10; i < len(result.Samples)-10; i++ { + s := result.Samples[i] + mag := math.Sqrt(float64(s.I)*float64(s.I) + float64(s.Q)*float64(s.Q)) + if mag < 0.9 || mag > 1.1 { + t.Fatalf("sample %d: magnitude=%.4f", i, mag) + } + } +} + +func TestResampleIQNil(t *testing.T) { + if ResampleIQ(nil, 528000) != nil { + t.Fatal("expected nil") + } +} diff --git a/internal/platform/plutosdr/available_pluto.go b/internal/platform/plutosdr/available_pluto.go new file mode 100644 index 0000000..6ca6e11 --- /dev/null +++ b/internal/platform/plutosdr/available_pluto.go @@ -0,0 +1,11 @@ +//go:build pluto && windows + +package plutosdr + +func Available() bool { + return true +} + +func AvailableError() string { + return "" +} diff --git a/internal/platform/plutosdr/pluto_windows.go b/internal/platform/plutosdr/pluto_windows.go new file mode 100644 index 0000000..d8fe56a --- /dev/null +++ b/internal/platform/plutosdr/pluto_windows.go @@ -0,0 +1,523 @@ +//go:build pluto && windows + +// Package plutosdr provides a direct libiio-based TX driver for ADALM-Pluto. +// Pure Go on Windows — loads libiio.dll via syscall.LoadLibrary at runtime. +// No CGO, no C compiler required. +// +// Build: go build -tags pluto ./cmd/fmrtx +// Requires: libiio installed (https://github.com/analogdevicesinc/libiio/releases) +package plutosdr + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "syscall" + "time" + "unsafe" + + "github.com/jan/fm-rds-tx/internal/output" + "github.com/jan/fm-rds-tx/internal/platform" +) + +// iioLib holds function pointers loaded from libiio.dll +type iioLib struct { + dll *syscall.DLL + + // Context + pCreateCtxFromURI *syscall.Proc + pDestroyCtx *syscall.Proc + + // Device + pCtxFindDevice *syscall.Proc + pDeviceFindChannel *syscall.Proc + pChannelEnable *syscall.Proc + pChannelDisable *syscall.Proc + pChannelIsEnabled *syscall.Proc + + // Attributes + pChannelAttrWriteLongLong *syscall.Proc + pChannelAttrWriteBool *syscall.Proc + pDeviceAttrWriteLongLong *syscall.Proc + + // Buffer + pCreateBuffer *syscall.Proc + pDestroyBuffer *syscall.Proc + pBufferPush *syscall.Proc + pBufferStep *syscall.Proc + pBufferStart *syscall.Proc + pBufferEnd *syscall.Proc + pBufferFirst *syscall.Proc +} + +var dllSearchPaths = []string{ + "libiio", + "iio", + `C:\Program Files\libiio\libiio.dll`, + `C:\Program Files (x86)\libiio\libiio.dll`, +} + +func loadIIOLib() (*iioLib, error) { + var dll *syscall.DLL + var lastErr error + for _, path := range dllSearchPaths { + dll, lastErr = syscall.LoadDLL(path) + if dll != nil { + break + } + } + if dll == nil { + return nil, fmt.Errorf("cannot load libiio.dll: %v", lastErr) + } + + p := func(name string) *syscall.Proc { + proc, _ := dll.FindProc(name) + return proc + } + + return &iioLib{ + dll: dll, + pCreateCtxFromURI: p("iio_create_context_from_uri"), + pDestroyCtx: p("iio_context_destroy"), + pCtxFindDevice: p("iio_context_find_device"), + pDeviceFindChannel: p("iio_device_find_channel"), + pChannelEnable: p("iio_channel_enable"), + pChannelDisable: p("iio_channel_disable"), + pChannelIsEnabled: p("iio_channel_is_enabled"), + pChannelAttrWriteLongLong: p("iio_channel_attr_write_longlong"), + pChannelAttrWriteBool: p("iio_channel_attr_write_bool"), + pDeviceAttrWriteLongLong: p("iio_device_attr_write_longlong"), + pCreateBuffer: p("iio_device_create_buffer"), + pDestroyBuffer: p("iio_buffer_destroy"), + pBufferPush: p("iio_buffer_push"), + pBufferStep: p("iio_buffer_step"), + pBufferStart: p("iio_buffer_start"), + pBufferEnd: p("iio_buffer_end"), + pBufferFirst: p("iio_buffer_first"), + }, nil +} + +// --- Driver --- + +type PlutoDriver struct { + mu sync.Mutex + lib *iioLib + cfg platform.SoapyConfig + + ctx uintptr // iio_context* + txDev uintptr // iio_device* (cf-ad9361-dds-core-lpc) + phyDev uintptr // iio_device* (ad9361-phy) + chanI uintptr // iio_channel* TX I + chanQ uintptr // iio_channel* TX Q + buf uintptr // iio_buffer* + bufSize int // samples per buffer push + + started bool + configured bool + framesWritten atomic.Uint64 + samplesWritten atomic.Uint64 + underruns atomic.Uint64 + lastError string + lastErrorAt string + initError string +} + +func NewPlutoDriver() platform.SoapyDriver { + lib, err := loadIIOLib() + if err != nil { + return &PlutoDriver{initError: err.Error()} + } + return &PlutoDriver{lib: lib} +} + +func (d *PlutoDriver) Name() string { return "pluto-iio" } + +func (d *PlutoDriver) Configure(_ context.Context, cfg platform.SoapyConfig) error { + d.mu.Lock() + defer d.mu.Unlock() + + if d.lib == nil { + return fmt.Errorf("libiio not loaded: %s", d.initError) + } + + // Cleanup existing + d.cleanup() + d.cfg = cfg + + // Create IIO context via USB + uri := "usb:" + if cfg.Device != "" && cfg.Device != "plutosdr" { + uri = cfg.Device // allow "ip:192.168.2.1" or specific USB + } + ctx, err := d.createContext(uri) + if err != nil { + return err + } + d.ctx = ctx + + // Find TX streaming device + txDev := d.findDevice("cf-ad9361-dds-core-lpc") + if txDev == 0 { + return fmt.Errorf("pluto: TX device 'cf-ad9361-dds-core-lpc' not found") + } + d.txDev = txDev + + // Find PHY device for configuration + phyDev := d.findDevice("ad9361-phy") + if phyDev == 0 { + return fmt.Errorf("pluto: PHY device 'ad9361-phy' not found") + } + d.phyDev = phyDev + + // --- AD9361 PHY configuration --- + // The AD9364 (PlutoSDR) has TX on voltage3 (output), not voltage0 + // voltage0 = RX input, voltage3 = TX output on single-channel AD9364 + + // Find TX PHY output channel + phyChanTX := d.findChannel(phyDev, "voltage3", true) // output=true + if phyChanTX == 0 { + // Fallback for dual-channel AD9361: try voltage0 output + phyChanTX = d.findChannel(phyDev, "voltage0", true) + } + if phyChanTX == 0 { + return fmt.Errorf("pluto: PHY TX channel not found (tried voltage3, voltage0)") + } + + // Sample rate — AD9361 minimum is ~2.084 MHz. + // We set the hardware to this minimum and resample from composite rate. + rate := int64(cfg.SampleRateHz) + if rate < 2084000 { + rate = 2084000 // AD9361 minimum + } + // Update effective rate so the engine knows the real device rate + d.cfg.SampleRateHz = float64(rate) + + d.writeChanAttrLL(phyChanTX, "sampling_frequency", rate) + + // RF bandwidth — set to match our signal bandwidth (wider than composite) + bw := rate + if bw > 2000000 { + bw = 2000000 // 2 MHz BW is enough for FM broadcast + } + d.writeChanAttrLL(phyChanTX, "rf_bandwidth", bw) + + // TX LO frequency + phyChanLO := d.findChannel(phyDev, "altvoltage1", true) // TX LO + if phyChanLO != 0 { + freqHz := int64(cfg.CenterFreqHz) + if freqHz <= 0 { + freqHz = 100000000 // 100 MHz default + } + d.writeChanAttrLL(phyChanLO, "frequency", freqHz) + } + + // TX gain/attenuation + // PlutoSDR TX hardwaregain: 0 dB = max power, -89.75 dB = min + // Value is in dB (not millidB) as a negative number + // For max power: set to 0. For safety: set to -20 or so. + // cfg.GainDB from our config is 0..89 positive, we negate it and subtract from 0 + attenDB := int64(0) // default = max power + if cfg.GainDB > 0 { + // GainDB=89 means full attenuation, GainDB=0 means max power + attenDB = -int64(89 - cfg.GainDB) + if attenDB > 0 { + attenDB = 0 + } + if attenDB < -89 { + attenDB = -89 + } + } + d.writeChanAttrLL(phyChanTX, "hardwaregain", attenDB*1000) // millidB + + // --- TX streaming channels on cf-ad9361-dds-core-lpc --- + // voltage0 (I) and voltage1 (Q) are output channels + chanI := d.findChannel(txDev, "voltage0", true) + chanQ := d.findChannel(txDev, "voltage1", true) + if chanI == 0 || chanQ == 0 { + return fmt.Errorf("pluto: TX I/Q channels not found on streaming device") + } + d.enableChannel(chanI) + d.enableChannel(chanQ) + d.chanI = chanI + d.chanQ = chanQ + + // Create buffer — samples per push (per channel) + // At 2.084 MHz with 50ms chunks = 104200 samples. Buffer must fit this. + d.bufSize = int(rate) / 20 // 50ms worth + if d.bufSize < 4096 { + d.bufSize = 4096 + } + // libiio can handle large buffers; no artificial cap needed + buf := d.createBuffer(txDev, d.bufSize, false) + if buf == 0 { + return fmt.Errorf("pluto: failed to create TX buffer (size=%d)", d.bufSize) + } + d.buf = buf + + d.configured = true + return nil +} + +func (d *PlutoDriver) Capabilities(_ context.Context) (platform.DeviceCaps, error) { + return platform.DeviceCaps{ + MinSampleRate: 521e3, + MaxSampleRate: 61.44e6, + HasGain: true, + GainMinDB: -89, + GainMaxDB: 0, + Channels: []int{0}, + }, nil +} + +func (d *PlutoDriver) Start(_ context.Context) error { + d.mu.Lock() + defer d.mu.Unlock() + if !d.configured { + return fmt.Errorf("pluto: not configured") + } + if d.started { + return fmt.Errorf("pluto: already started") + } + d.started = true + return nil +} + +func (d *PlutoDriver) Write(_ context.Context, frame *output.CompositeFrame) (int, error) { + d.mu.Lock() + lib := d.lib + buf := d.buf + started := d.started + bufSize := d.bufSize + d.mu.Unlock() + + if !started || buf == 0 || lib == nil { + return 0, fmt.Errorf("pluto: not active") + } + if frame == nil || len(frame.Samples) == 0 { + return 0, nil + } + + written := 0 + total := len(frame.Samples) + + for written < total { + chunk := total - written + if chunk > bufSize { + chunk = bufSize + } + + // Get buffer pointers + start := d.bufferStart(buf) + end := d.bufferEnd(buf) + step := d.bufferStep(buf) + + if start == 0 || end == 0 || step == 0 { + return written, fmt.Errorf("pluto: invalid buffer pointers") + } + + // Fill buffer with interleaved I/Q as int16 (PlutoSDR native format) + // IQSample is {I float32, Q float32} normalized to [-1,+1] + // PlutoSDR expects int16 interleaved: I0 Q0 I1 Q1 ... + bufLen := (end - start) / uintptr(step) + if int(bufLen) < chunk { + chunk = int(bufLen) + } + + ptr := start + for i := 0; i < chunk; i++ { + s := frame.Samples[written+i] + // Scale float32 [-1,+1] to int16 [-32767,+32767] + iVal := int16(float32(s.I) * 32767) + qVal := int16(float32(s.Q) * 32767) + *(*int16)(unsafe.Pointer(ptr)) = iVal + *(*int16)(unsafe.Pointer(ptr + 2)) = qVal + ptr += uintptr(step) + } + + // Push buffer to hardware + pushed := d.bufferPush(buf) + if pushed < 0 { + d.mu.Lock() + d.lastError = fmt.Sprintf("buffer_push: %d", pushed) + d.lastErrorAt = time.Now().UTC().Format(time.RFC3339) + d.underruns.Add(1) + d.mu.Unlock() + return written, fmt.Errorf("pluto: buffer_push returned %d", pushed) + } + + written += chunk + } + + d.framesWritten.Add(1) + d.samplesWritten.Add(uint64(written)) + return written, nil +} + +func (d *PlutoDriver) Stop(_ context.Context) error { + d.mu.Lock() + defer d.mu.Unlock() + d.started = false + return nil +} + +func (d *PlutoDriver) Flush(_ context.Context) error { return nil } + +func (d *PlutoDriver) Close(_ context.Context) error { + d.mu.Lock() + defer d.mu.Unlock() + d.started = false + d.cleanup() + return nil +} + +func (d *PlutoDriver) Stats() platform.RuntimeStats { + d.mu.Lock() + defer d.mu.Unlock() + return platform.RuntimeStats{ + TXEnabled: d.started, + StreamActive: d.started && d.buf != 0, + FramesWritten: d.framesWritten.Load(), + SamplesWritten: d.samplesWritten.Load(), + Underruns: d.underruns.Load(), + LastError: d.lastError, + LastErrorAt: d.lastErrorAt, + EffectiveRate: d.cfg.SampleRateHz, + } +} + +// --- internal helpers --- + +func (d *PlutoDriver) cleanup() { + if d.buf != 0 && d.lib.pDestroyBuffer != nil { + d.lib.pDestroyBuffer.Call(d.buf) + d.buf = 0 + } + if d.chanI != 0 { + d.disableChannel(d.chanI) + d.chanI = 0 + } + if d.chanQ != 0 { + d.disableChannel(d.chanQ) + d.chanQ = 0 + } + if d.ctx != 0 && d.lib.pDestroyCtx != nil { + d.lib.pDestroyCtx.Call(d.ctx) + d.ctx = 0 + } + d.configured = false +} + +func (d *PlutoDriver) createContext(uri string) (uintptr, error) { + if d.lib.pCreateCtxFromURI == nil { + return 0, fmt.Errorf("iio_create_context_from_uri not found") + } + cURI, _ := syscall.BytePtrFromString(uri) + ret, _, _ := d.lib.pCreateCtxFromURI.Call(uintptr(unsafe.Pointer(cURI))) + if ret == 0 { + return 0, fmt.Errorf("pluto: failed to create IIO context (uri=%s)", uri) + } + return ret, nil +} + +func (d *PlutoDriver) findDevice(name string) uintptr { + if d.lib.pCtxFindDevice == nil || d.ctx == 0 { + return 0 + } + cName, _ := syscall.BytePtrFromString(name) + ret, _, _ := d.lib.pCtxFindDevice.Call(d.ctx, uintptr(unsafe.Pointer(cName))) + return ret +} + +func (d *PlutoDriver) findChannel(dev uintptr, name string, isOutput bool) uintptr { + if d.lib.pDeviceFindChannel == nil || dev == 0 { + return 0 + } + cName, _ := syscall.BytePtrFromString(name) + out := uintptr(0) + if isOutput { + out = 1 + } + ret, _, _ := d.lib.pDeviceFindChannel.Call(dev, uintptr(unsafe.Pointer(cName)), out) + return ret +} + +func (d *PlutoDriver) enableChannel(ch uintptr) { + if d.lib.pChannelEnable != nil && ch != 0 { + d.lib.pChannelEnable.Call(ch) + } +} + +func (d *PlutoDriver) disableChannel(ch uintptr) { + if d.lib.pChannelDisable != nil && ch != 0 { + d.lib.pChannelDisable.Call(ch) + } +} + +func (d *PlutoDriver) writeChanAttrLL(ch uintptr, attr string, val int64) { + if d.lib.pChannelAttrWriteLongLong == nil || ch == 0 { + return + } + cAttr, _ := syscall.BytePtrFromString(attr) + d.lib.pChannelAttrWriteLongLong.Call(ch, uintptr(unsafe.Pointer(cAttr)), uintptr(val)) +} + +func (d *PlutoDriver) writeDevAttrLL(dev uintptr, attr string, val int64) { + if d.lib.pDeviceAttrWriteLongLong == nil || dev == 0 { + return + } + cAttr, _ := syscall.BytePtrFromString(attr) + d.lib.pDeviceAttrWriteLongLong.Call(dev, uintptr(unsafe.Pointer(cAttr)), uintptr(val)) +} + +func (d *PlutoDriver) createBuffer(dev uintptr, sampleCount int, cyclic bool) uintptr { + if d.lib.pCreateBuffer == nil || dev == 0 { + return 0 + } + c := uintptr(0) + if cyclic { + c = 1 + } + ret, _, _ := d.lib.pCreateBuffer.Call(dev, uintptr(sampleCount), c) + return ret +} + +func (d *PlutoDriver) bufferPush(buf uintptr) int { + if d.lib.pBufferPush == nil || buf == 0 { + return -1 + } + ret, _, _ := d.lib.pBufferPush.Call(buf) + return int(int32(ret)) +} + +func (d *PlutoDriver) bufferStart(buf uintptr) uintptr { + if d.lib.pBufferStart == nil { + return 0 + } + ret, _, _ := d.lib.pBufferStart.Call(buf) + return ret +} + +func (d *PlutoDriver) bufferEnd(buf uintptr) uintptr { + if d.lib.pBufferEnd == nil { + return 0 + } + ret, _, _ := d.lib.pBufferEnd.Call(buf) + return ret +} + +func (d *PlutoDriver) bufferStep(buf uintptr) uintptr { + if d.lib.pBufferStep == nil { + return 0 + } + ret, _, _ := d.lib.pBufferStep.Call(buf) + return ret +} + +func (d *PlutoDriver) bufferFirst(buf uintptr, ch uintptr) uintptr { + if d.lib.pBufferFirst == nil { + return 0 + } + ret, _, _ := d.lib.pBufferFirst.Call(buf, ch) + return ret +} diff --git a/internal/platform/plutosdr/stub.go b/internal/platform/plutosdr/stub.go new file mode 100644 index 0000000..96c5db4 --- /dev/null +++ b/internal/platform/plutosdr/stub.go @@ -0,0 +1,21 @@ +//go:build !pluto || !windows + +package plutosdr + +import ( + "fmt" + + "github.com/jan/fm-rds-tx/internal/platform" +) + +func NewPlutoDriver() platform.SoapyDriver { + return nil +} + +func Available() bool { + return false +} + +func AvailableError() string { + return fmt.Sprintf("plutosdr: not compiled with -tags pluto or not on supported platform") +} diff --git a/internal/platform/soapysdr/available_soapy.go b/internal/platform/soapysdr/available_soapy.go new file mode 100644 index 0000000..13f0497 --- /dev/null +++ b/internal/platform/soapysdr/available_soapy.go @@ -0,0 +1,8 @@ +//go:build soapy + +package soapysdr + +// Available reports whether SoapySDR native support was compiled in. +func Available() bool { + return true +} diff --git a/internal/platform/soapysdr/lib_unix.go b/internal/platform/soapysdr/lib_unix.go new file mode 100644 index 0000000..3afe80b --- /dev/null +++ b/internal/platform/soapysdr/lib_unix.go @@ -0,0 +1,306 @@ +//go:build soapy && !windows + +package soapysdr + +import ( + "fmt" + "math" + "unsafe" +) + +/* +#include +#include +#include + +// Minimal dlopen wrapper — this is the ONLY cgo usage and it's +// just for dlopen/dlsym which are part of libc, not SoapySDR. +// No SoapySDR headers needed at compile time. + +static void* soapy_dlopen(const char* path) { + return dlopen(path, 2); // RTLD_NOW +} + +static void* soapy_dlsym(void* handle, const char* name) { + return dlsym(handle, name); +} + +static const char* soapy_dlerror() { + return dlerror(); +} + +// Function call trampolines — we call function pointers loaded via dlsym. +// These avoid the complexity of calling C function pointers from Go directly. + +typedef void* (*make_fn)(void*); +typedef int (*unmake_fn)(void*); +typedef int (*set_double_fn)(void*, int, size_t, double); +typedef int (*set_freq_fn)(void*, int, size_t, double, void*); +typedef void* (*setup_stream_fn)(void*, int, const char*, size_t*, size_t, void*); +typedef int (*close_stream_fn)(void*, void*); +typedef size_t (*mtu_fn)(void*, void*); +typedef int (*activate_fn)(void*, void*, int, long long, size_t); +typedef int (*deactivate_fn)(void*, void*, int, long long); +typedef int (*write_fn)(void*, void*, const void**, size_t, int*, long long, long); +typedef void* (*enumerate_fn)(void*, size_t*); +typedef void (*kwargs_clear_fn)(void*, size_t); +typedef void (*kwargs_set_fn)(void*, const char*, const char*); + +// --- KWArgs struct matching SoapySDRKwargs --- +typedef struct { + size_t size; + char** keys; + char** vals; +} GoKwargs; + +static void* call_make(void* fn, void* args) { + return ((make_fn)fn)(args); +} +static int call_unmake(void* fn, void* dev) { + return ((unmake_fn)fn)(dev); +} +static int call_set_sample_rate(void* fn, void* dev, int dir, size_t ch, double rate) { + return ((set_double_fn)fn)(dev, dir, ch, rate); +} +static int call_set_frequency(void* fn, void* dev, int dir, size_t ch, double freq, void* kw) { + return ((set_freq_fn)fn)(dev, dir, ch, freq, kw); +} +static int call_set_gain(void* fn, void* dev, int dir, size_t ch, double gain) { + return ((set_double_fn)fn)(dev, dir, ch, gain); +} +static void* call_setup_stream(void* fn, void* dev, int dir, const char* fmt, size_t* chs, size_t nch, void* kw) { + return ((setup_stream_fn)fn)(dev, dir, fmt, chs, nch, kw); +} +static int call_close_stream(void* fn, void* dev, void* stream) { + return ((close_stream_fn)fn)(dev, stream); +} +static size_t call_mtu(void* fn, void* dev, void* stream) { + return ((mtu_fn)fn)(dev, stream); +} +static int call_activate(void* fn, void* dev, void* stream) { + return ((activate_fn)fn)(dev, stream, 0, 0, 0); +} +static int call_deactivate(void* fn, void* dev, void* stream) { + return ((deactivate_fn)fn)(dev, stream, 0, 0); +} +static int call_write(void* fn, void* dev, void* stream, const void* buf, size_t n, int* flags, long timeout) { + const void* buffs[1]; + buffs[0] = buf; + *flags = 0; + return ((write_fn)fn)(dev, stream, buffs, n, flags, 0, timeout); +} +static void* call_enumerate(void* fn, void* kw, size_t* length) { + return ((enumerate_fn)fn)(kw, length); +} +static void call_kwargs_clear(void* fn, void* list, size_t length) { + ((kwargs_clear_fn)fn)(list, length); +} +static void call_kwargs_set(void* fn, void* kw, const char* key, const char* val) { + ((kwargs_set_fn)fn)(kw, key, val); +} +*/ +import "C" + +type soapyLib struct { + handle unsafe.Pointer + fnEnumerate unsafe.Pointer + fnKwargsListClear unsafe.Pointer + fnKwargsSet unsafe.Pointer + fnMake unsafe.Pointer + fnUnmake unsafe.Pointer + fnSetSampleRate unsafe.Pointer + fnSetFrequency unsafe.Pointer + fnSetGain unsafe.Pointer + fnGetGainRange unsafe.Pointer + fnSetupStream unsafe.Pointer + fnCloseStream unsafe.Pointer + fnGetStreamMTU unsafe.Pointer + fnActivateStream unsafe.Pointer + fnDeactivateStream unsafe.Pointer + fnWriteStream unsafe.Pointer +} + +var libNames = []string{ + "libSoapySDR.so.0.8", + "libSoapySDR.so", + "libSoapySDR.dylib", +} + +func loadSoapyLib() (*soapyLib, error) { + var handle unsafe.Pointer + for _, name := range libNames { + cName := C.CString(name) + handle = C.soapy_dlopen(cName) + C.free(unsafe.Pointer(cName)) + if handle != nil { + break + } + } + if handle == nil { + errMsg := C.GoString(C.soapy_dlerror()) + return nil, fmt.Errorf("cannot load SoapySDR: %s", errMsg) + } + + sym := func(name string) unsafe.Pointer { + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + return C.soapy_dlsym(handle, cName) + } + + return &soapyLib{ + handle: handle, + fnEnumerate: sym("SoapySDRDevice_enumerate"), + fnKwargsListClear: sym("SoapySDRKwargsList_clear"), + fnKwargsSet: sym("SoapySDRKwargs_set"), + fnMake: sym("SoapySDRDevice_make"), + fnUnmake: sym("SoapySDRDevice_unmake"), + fnSetSampleRate: sym("SoapySDRDevice_setSampleRate"), + fnSetFrequency: sym("SoapySDRDevice_setFrequency"), + fnSetGain: sym("SoapySDRDevice_setGain"), + fnGetGainRange: sym("SoapySDRDevice_getGainRange"), + fnSetupStream: sym("SoapySDRDevice_setupStream"), + fnCloseStream: sym("SoapySDRDevice_closeStream"), + fnGetStreamMTU: sym("SoapySDRDevice_getStreamMTU"), + fnActivateStream: sym("SoapySDRDevice_activateStream"), + fnDeactivateStream: sym("SoapySDRDevice_deactivateStream"), + fnWriteStream: sym("SoapySDRDevice_writeStream"), + }, nil +} + +// --- kwargs helper --- + +type kwargs = C.GoKwargs + +func (lib *soapyLib) kwargsSet(kw *kwargs, key, val string) { + if lib.fnKwargsSet == nil { return } + cK := C.CString(key); cV := C.CString(val) + defer C.free(unsafe.Pointer(cK)); defer C.free(unsafe.Pointer(cV)) + C.call_kwargs_set(lib.fnKwargsSet, unsafe.Pointer(kw), cK, cV) +} + +// --- Enumerate --- + +func (lib *soapyLib) enumerate() ([]map[string]string, error) { + if lib.fnEnumerate == nil { return nil, fmt.Errorf("enumerate not available") } + var kw kwargs + var length C.size_t + ret := C.call_enumerate(lib.fnEnumerate, unsafe.Pointer(&kw), &length) + if ret == nil || length == 0 { return nil, nil } + defer func() { + if lib.fnKwargsListClear != nil { + C.call_kwargs_clear(lib.fnKwargsListClear, ret, length) + } + }() + + devices := make([]map[string]string, int(length)) + kwSize := unsafe.Sizeof(kwargs{}) + base := uintptr(ret) + for i := 0; i < int(length); i++ { + kw := (*kwargs)(unsafe.Pointer(base + uintptr(i)*kwSize)) + m := make(map[string]string) + for j := 0; j < int(kw.size); j++ { + keyPtr := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(kw.keys)) + uintptr(j)*unsafe.Sizeof(uintptr(0)))) + valPtr := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(kw.vals)) + uintptr(j)*unsafe.Sizeof(uintptr(0)))) + if keyPtr != 0 && valPtr != 0 { + m[C.GoString((*C.char)(unsafe.Pointer(keyPtr)))] = C.GoString((*C.char)(unsafe.Pointer(valPtr))) + } + } + devices[i] = m + } + return devices, nil +} + +// --- Device --- + +func (lib *soapyLib) makeDevice(driver, device string, args map[string]string) (uintptr, error) { + if lib.fnMake == nil { return 0, fmt.Errorf("make not available") } + var kw kwargs + if driver != "" { lib.kwargsSet(&kw, "driver", driver) } + if device != "" { lib.kwargsSet(&kw, "device", device) } + for k, v := range args { lib.kwargsSet(&kw, k, v) } + ret := C.call_make(lib.fnMake, unsafe.Pointer(&kw)) + if ret == nil { return 0, fmt.Errorf("soapy: failed to open device") } + return uintptr(ret), nil +} + +func (lib *soapyLib) unmakeDevice(dev uintptr) { + if lib.fnUnmake != nil { C.call_unmake(lib.fnUnmake, unsafe.Pointer(dev)) } +} + +// --- Config --- + +func (lib *soapyLib) setSampleRate(dev uintptr, dir, ch int, rate float64) error { + if lib.fnSetSampleRate == nil { return fmt.Errorf("not available") } + rc := C.call_set_sample_rate(lib.fnSetSampleRate, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(rate)) + if rc != 0 { return fmt.Errorf("soapy: setSampleRate(%.0f) failed: %d", rate, rc) } + return nil +} + +func (lib *soapyLib) setFrequency(dev uintptr, dir, ch int, freq float64) error { + if lib.fnSetFrequency == nil { return fmt.Errorf("not available") } + var kw kwargs + rc := C.call_set_frequency(lib.fnSetFrequency, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(freq), unsafe.Pointer(&kw)) + if rc != 0 { return fmt.Errorf("soapy: setFrequency(%.0f) failed: %d", freq, rc) } + return nil +} + +func (lib *soapyLib) setGain(dev uintptr, dir, ch int, gain float64) error { + if lib.fnSetGain == nil { return nil } + C.call_set_gain(lib.fnSetGain, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(gain)) + return nil +} + +func (lib *soapyLib) getGainRange(dev uintptr, dir, ch int) (float64, float64) { + _ = math.Float64bits // keep import + // Fallback if function not available + return 0, 89 +} + +// --- Stream --- + +func (lib *soapyLib) setupStream(dev uintptr, dir int, format string, channels []uint) (uintptr, error) { + if lib.fnSetupStream == nil { return 0, fmt.Errorf("not available") } + cFmt := C.CString(format); defer C.free(unsafe.Pointer(cFmt)) + chs := make([]C.size_t, len(channels)) + for i, c := range channels { chs[i] = C.size_t(c) } + var chPtr *C.size_t + if len(chs) > 0 { chPtr = &chs[0] } + var kw kwargs + ret := C.call_setup_stream(lib.fnSetupStream, unsafe.Pointer(dev), C.int(dir), cFmt, chPtr, C.size_t(len(channels)), unsafe.Pointer(&kw)) + if ret == nil { return 0, fmt.Errorf("soapy: setupStream failed") } + return uintptr(ret), nil +} + +func (lib *soapyLib) closeStream(dev, stream uintptr) { + if lib.fnCloseStream != nil { + C.call_close_stream(lib.fnCloseStream, unsafe.Pointer(dev), unsafe.Pointer(stream)) + } +} + +func (lib *soapyLib) getStreamMTU(dev, stream uintptr) int { + if lib.fnGetStreamMTU == nil { return 4096 } + ret := C.call_mtu(lib.fnGetStreamMTU, unsafe.Pointer(dev), unsafe.Pointer(stream)) + if ret == 0 { return 4096 } + return int(ret) +} + +func (lib *soapyLib) activateStream(dev, stream uintptr) error { + if lib.fnActivateStream == nil { return fmt.Errorf("not available") } + rc := C.call_activate(lib.fnActivateStream, unsafe.Pointer(dev), unsafe.Pointer(stream)) + if rc != 0 { return fmt.Errorf("soapy: activateStream failed: %d", rc) } + return nil +} + +func (lib *soapyLib) deactivateStream(dev, stream uintptr) { + if lib.fnDeactivateStream != nil { + C.call_deactivate(lib.fnDeactivateStream, unsafe.Pointer(dev), unsafe.Pointer(stream)) + } +} + +func (lib *soapyLib) writeStream(dev, stream uintptr, buf unsafe.Pointer, numElems int) (int, error) { + if lib.fnWriteStream == nil { return 0, fmt.Errorf("not available") } + var flags C.int + rc := C.call_write(lib.fnWriteStream, unsafe.Pointer(dev), unsafe.Pointer(stream), buf, C.size_t(numElems), &flags, 100000) + if rc < 0 { return 0, fmt.Errorf("soapy: writeStream returned %d", rc) } + return int(rc), nil +} diff --git a/internal/platform/soapysdr/lib_windows.go b/internal/platform/soapysdr/lib_windows.go new file mode 100644 index 0000000..b69d888 --- /dev/null +++ b/internal/platform/soapysdr/lib_windows.go @@ -0,0 +1,293 @@ +//go:build soapy && windows + +package soapysdr + +import ( + "fmt" + "math" + "syscall" + "unsafe" +) + +type soapyLib struct { + dll *syscall.DLL + pEnumerate *syscall.Proc + pKwargsListClear *syscall.Proc + pMake *syscall.Proc + pUnmake *syscall.Proc + pSetSampleRate *syscall.Proc + pSetFrequency *syscall.Proc + pSetGain *syscall.Proc + pGetGainRange *syscall.Proc + pSetupStream *syscall.Proc + pCloseStream *syscall.Proc + pGetStreamMTU *syscall.Proc + pActivateStream *syscall.Proc + pDeactivateStream *syscall.Proc + pWriteStream *syscall.Proc + pKwargsSet *syscall.Proc +} + +var searchPaths = []string{ + "SoapySDR", + `C:\Program Files\PothosSDR\bin\SoapySDR.dll`, + `C:\PothosSDR\bin\SoapySDR.dll`, +} + +func loadSoapyLib() (*soapyLib, error) { + var dll *syscall.DLL + var lastErr error + for _, path := range searchPaths { + dll, lastErr = syscall.LoadDLL(path) + if dll != nil { + break + } + } + if dll == nil { + return nil, fmt.Errorf("cannot load SoapySDR.dll: %v (searched: %v)", lastErr, searchPaths) + } + + mustProc := func(name string) *syscall.Proc { + p, _ := dll.FindProc(name) + return p + } + + return &soapyLib{ + dll: dll, + pEnumerate: mustProc("SoapySDRDevice_enumerate"), + pKwargsListClear: mustProc("SoapySDRKwargsList_clear"), + pMake: mustProc("SoapySDRDevice_make"), + pUnmake: mustProc("SoapySDRDevice_unmake"), + pSetSampleRate: mustProc("SoapySDRDevice_setSampleRate"), + pSetFrequency: mustProc("SoapySDRDevice_setFrequency"), + pSetGain: mustProc("SoapySDRDevice_setGain"), + pGetGainRange: mustProc("SoapySDRDevice_getGainRange"), + pSetupStream: mustProc("SoapySDRDevice_setupStream"), + pCloseStream: mustProc("SoapySDRDevice_closeStream"), + pGetStreamMTU: mustProc("SoapySDRDevice_getStreamMTU"), + pActivateStream: mustProc("SoapySDRDevice_activateStream"), + pDeactivateStream: mustProc("SoapySDRDevice_deactivateStream"), + pWriteStream: mustProc("SoapySDRDevice_writeStream"), + pKwargsSet: mustProc("SoapySDRKwargs_set"), + }, nil +} + +// --- KWArgs helper (stack-allocated 64-byte struct matching SoapySDRKwargs) --- +// SoapySDRKwargs = { size_t size; char** keys; char** vals; } +// On 64-bit: 8 + 8 + 8 = 24 bytes + +type kwargs struct { + size uintptr + keys uintptr + vals uintptr +} + +func (lib *soapyLib) kwargsSet(kw *kwargs, key, val string) { + if lib.pKwargsSet == nil { + return + } + cKey, _ := syscall.BytePtrFromString(key) + cVal, _ := syscall.BytePtrFromString(val) + lib.pKwargsSet.Call(uintptr(unsafe.Pointer(kw)), uintptr(unsafe.Pointer(cKey)), uintptr(unsafe.Pointer(cVal))) +} + +// --- Enumerate --- + +func (lib *soapyLib) enumerate() ([]map[string]string, error) { + if lib.pEnumerate == nil { + return nil, fmt.Errorf("SoapySDRDevice_enumerate not found") + } + var kw kwargs + var length uintptr + ret, _, _ := lib.pEnumerate.Call(uintptr(unsafe.Pointer(&kw)), uintptr(unsafe.Pointer(&length))) + if ret == 0 || length == 0 { + return nil, nil + } + defer func() { + if lib.pKwargsListClear != nil { + lib.pKwargsListClear.Call(ret, length) + } + }() + + devices := make([]map[string]string, int(length)) + kwSize := unsafe.Sizeof(kwargs{}) + for i := 0; i < int(length); i++ { + kw := (*kwargs)(unsafe.Pointer(ret + uintptr(i)*kwSize)) + m := make(map[string]string) + for j := 0; j < int(kw.size); j++ { + keyPtr := *(*uintptr)(unsafe.Pointer(kw.keys + uintptr(j)*unsafe.Sizeof(uintptr(0)))) + valPtr := *(*uintptr)(unsafe.Pointer(kw.vals + uintptr(j)*unsafe.Sizeof(uintptr(0)))) + if keyPtr != 0 && valPtr != 0 { + m[goString(keyPtr)] = goString(valPtr) + } + } + devices[i] = m + } + return devices, nil +} + +// --- Device lifecycle --- + +func (lib *soapyLib) makeDevice(driver, device string, args map[string]string) (uintptr, error) { + if lib.pMake == nil { + return 0, fmt.Errorf("SoapySDRDevice_make not found") + } + var kw kwargs + if driver != "" { + lib.kwargsSet(&kw, "driver", driver) + } + if device != "" { + lib.kwargsSet(&kw, "device", device) + } + for k, v := range args { + lib.kwargsSet(&kw, k, v) + } + ret, _, _ := lib.pMake.Call(uintptr(unsafe.Pointer(&kw))) + if ret == 0 { + return 0, fmt.Errorf("soapy: failed to open device (driver=%q device=%q)", driver, device) + } + return ret, nil +} + +func (lib *soapyLib) unmakeDevice(dev uintptr) { + if lib.pUnmake != nil && dev != 0 { + lib.pUnmake.Call(dev) + } +} + +// --- Configuration --- + +func (lib *soapyLib) setSampleRate(dev uintptr, dir, ch int, rate float64) error { + if lib.pSetSampleRate == nil { + return fmt.Errorf("setSampleRate not available") + } + bits := math.Float64bits(rate) + rc, _, _ := lib.pSetSampleRate.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits)) + if int32(rc) != 0 { + return fmt.Errorf("soapy: setSampleRate(%.0f) failed: %d", rate, int32(rc)) + } + return nil +} + +func (lib *soapyLib) setFrequency(dev uintptr, dir, ch int, freq float64) error { + if lib.pSetFrequency == nil { + return fmt.Errorf("setFrequency not available") + } + bits := math.Float64bits(freq) + var kw kwargs + rc, _, _ := lib.pSetFrequency.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits), uintptr(unsafe.Pointer(&kw))) + if int32(rc) != 0 { + return fmt.Errorf("soapy: setFrequency(%.0f) failed: %d", freq, int32(rc)) + } + return nil +} + +func (lib *soapyLib) setGain(dev uintptr, dir, ch int, gain float64) error { + if lib.pSetGain == nil { + return nil + } + bits := math.Float64bits(gain) + lib.pSetGain.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits)) + return nil +} + +func (lib *soapyLib) getGainRange(dev uintptr, dir, ch int) (float64, float64) { + if lib.pGetGainRange == nil { + return 0, 89 + } + // SoapySDRRange is { double minimum; double maximum; double step; } = 24 bytes + var buf [3]float64 + lib.pGetGainRange.Call(uintptr(unsafe.Pointer(&buf)), dev, uintptr(dir), uintptr(ch)) + return buf[0], buf[1] +} + +// --- Stream --- + +func (lib *soapyLib) setupStream(dev uintptr, dir int, format string, channels []uint) (uintptr, error) { + if lib.pSetupStream == nil { + return 0, fmt.Errorf("setupStream not available") + } + cFmt, _ := syscall.BytePtrFromString(format) + var chPtr uintptr + if len(channels) > 0 { + chPtr = uintptr(unsafe.Pointer(&channels[0])) + } + var kw kwargs + ret, _, _ := lib.pSetupStream.Call(dev, uintptr(dir), uintptr(unsafe.Pointer(cFmt)), + chPtr, uintptr(len(channels)), uintptr(unsafe.Pointer(&kw))) + if ret == 0 { + return 0, fmt.Errorf("soapy: setupStream failed") + } + return ret, nil +} + +func (lib *soapyLib) closeStream(dev, stream uintptr) { + if lib.pCloseStream != nil { + lib.pCloseStream.Call(dev, stream) + } +} + +func (lib *soapyLib) getStreamMTU(dev, stream uintptr) int { + if lib.pGetStreamMTU == nil { + return 4096 + } + ret, _, _ := lib.pGetStreamMTU.Call(dev, stream) + if ret == 0 { + return 4096 + } + return int(ret) +} + +func (lib *soapyLib) activateStream(dev, stream uintptr) error { + if lib.pActivateStream == nil { + return fmt.Errorf("activateStream not available") + } + rc, _, _ := lib.pActivateStream.Call(dev, stream, 0, 0, 0) + if int32(rc) != 0 { + return fmt.Errorf("soapy: activateStream failed: %d", int32(rc)) + } + return nil +} + +func (lib *soapyLib) deactivateStream(dev, stream uintptr) { + if lib.pDeactivateStream != nil { + lib.pDeactivateStream.Call(dev, stream, 0, 0) + } +} + +func (lib *soapyLib) writeStream(dev, stream uintptr, buf unsafe.Pointer, numElems int) (int, error) { + if lib.pWriteStream == nil { + return 0, fmt.Errorf("writeStream not available") + } + buffs := [1]uintptr{uintptr(buf)} + var flags int32 + rc, _, _ := lib.pWriteStream.Call(dev, stream, + uintptr(unsafe.Pointer(&buffs[0])), + uintptr(numElems), + uintptr(unsafe.Pointer(&flags)), + 0, // timeNs + 100000, // timeoutUs = 100ms + ) + n := int32(rc) + if n < 0 { + return 0, fmt.Errorf("soapy: writeStream returned %d", n) + } + return int(n), nil +} + +// --- C string helper --- + +func goString(p uintptr) string { + if p == 0 { + return "" + } + buf := make([]byte, 0, 256) + for i := uintptr(0); ; i++ { + b := *(*byte)(unsafe.Pointer(p + i)) + if b == 0 { + break + } + buf = append(buf, b) + } + return string(buf) +} diff --git a/internal/platform/soapysdr/native.go b/internal/platform/soapysdr/native.go new file mode 100644 index 0000000..de60027 --- /dev/null +++ b/internal/platform/soapysdr/native.go @@ -0,0 +1,248 @@ +//go:build soapy + +// Package soapysdr provides a pure-Go SoapySDR driver that loads the +// SoapySDR shared library at runtime via dlopen/LoadLibrary. +// No CGO required. No C compiler required. +// +// Build with: go build -tags soapy +// Requires: SoapySDR shared library installed on the system. +// Windows: SoapySDR.dll (via PothosSDR) +// Linux: libSoapySDR.so (via package manager) +// macOS: libSoapySDR.dylib (via brew) +package soapysdr + +import ( + "context" + "fmt" + "math" + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/jan/fm-rds-tx/internal/output" + "github.com/jan/fm-rds-tx/internal/platform" +) + +// nativeDriver implements platform.SoapyDriver using runtime-loaded SoapySDR. +type nativeDriver struct { + mu sync.Mutex + lib *soapyLib + cfg platform.SoapyConfig + dev uintptr // SoapySDRDevice* + stream uintptr // SoapySDRStream* + mtu int + + started bool + configured bool + framesWritten atomic.Uint64 + samplesWritten atomic.Uint64 + underruns atomic.Uint64 + lastError string + lastErrorAt string +} + +// NewNativeDriver creates an uninitialized SoapySDR native driver. +func NewNativeDriver() platform.SoapyDriver { + lib, err := loadSoapyLib() + if err != nil { + // Return a driver that will fail on Configure with a clear message + return &nativeDriver{lastError: fmt.Sprintf("load SoapySDR library: %v", err)} + } + return &nativeDriver{lib: lib} +} + +// Enumerate lists available SoapySDR devices. +func Enumerate() ([]map[string]string, error) { + lib, err := loadSoapyLib() + if err != nil { + return nil, fmt.Errorf("load SoapySDR: %w", err) + } + return lib.enumerate() +} + +func (d *nativeDriver) Name() string { return "soapy-native" } + +func (d *nativeDriver) Configure(_ context.Context, cfg platform.SoapyConfig) error { + d.mu.Lock() + defer d.mu.Unlock() + + if d.lib == nil { + return fmt.Errorf("soapy: library not loaded: %s", d.lastError) + } + + // Close existing + if d.dev != 0 { + if d.stream != 0 { + d.lib.deactivateStream(d.dev, d.stream) + d.lib.closeStream(d.dev, d.stream) + d.stream = 0 + } + d.lib.unmakeDevice(d.dev) + d.dev = 0 + } + d.cfg = cfg + + // Open device + dev, err := d.lib.makeDevice(cfg.Driver, cfg.Device, cfg.DeviceArgs) + if err != nil { + return err + } + d.dev = dev + + // Sample rate + rate := cfg.SampleRateHz + if rate <= 0 { + rate = 528000 + } + if err := d.lib.setSampleRate(d.dev, dirTX, 0, rate); err != nil { + return err + } + + // Frequency + if cfg.CenterFreqHz > 0 { + if err := d.lib.setFrequency(d.dev, dirTX, 0, cfg.CenterFreqHz); err != nil { + return err + } + } + + // Gain + if cfg.GainDB != 0 { + _ = d.lib.setGain(d.dev, dirTX, 0, cfg.GainDB) + } + + // Setup TX stream (CF32) + stream, err := d.lib.setupStream(d.dev, dirTX, "CF32", []uint{0}) + if err != nil { + return err + } + d.stream = stream + + d.mtu = d.lib.getStreamMTU(d.dev, d.stream) + if d.mtu <= 0 { + d.mtu = 4096 + } + + d.configured = true + return nil +} + +func (d *nativeDriver) Capabilities(_ context.Context) (platform.DeviceCaps, error) { + d.mu.Lock() + defer d.mu.Unlock() + if d.dev == 0 || d.lib == nil { + return platform.DeviceCaps{}, fmt.Errorf("device not opened") + } + gMin, gMax := d.lib.getGainRange(d.dev, dirTX, 0) + return platform.DeviceCaps{ + MinSampleRate: 521e3, MaxSampleRate: 61.44e6, + HasGain: true, GainMinDB: gMin, GainMaxDB: gMax, + Channels: []int{0}, + }, nil +} + +func (d *nativeDriver) Start(_ context.Context) error { + d.mu.Lock() + defer d.mu.Unlock() + if !d.configured || d.dev == 0 || d.stream == 0 { + return fmt.Errorf("soapy: not configured") + } + if d.started { + return fmt.Errorf("soapy: already started") + } + if err := d.lib.activateStream(d.dev, d.stream); err != nil { + return err + } + d.started = true + return nil +} + +func (d *nativeDriver) Write(_ context.Context, frame *output.CompositeFrame) (int, error) { + d.mu.Lock() + lib, dev, stream, started, mtu := d.lib, d.dev, d.stream, d.started, d.mtu + d.mu.Unlock() + + if !started || dev == 0 || stream == 0 { + return 0, fmt.Errorf("soapy: stream not active") + } + if frame == nil || len(frame.Samples) == 0 { + return 0, nil + } + + total := len(frame.Samples) + written := 0 + for written < total { + chunk := total - written + if chunk > mtu { + chunk = mtu + } + // IQSample is {I float32, Q float32} — contiguous CF32 in memory + ptr := unsafe.Pointer(&frame.Samples[written]) + n, err := lib.writeStream(dev, stream, ptr, chunk) + if err != nil { + d.mu.Lock() + d.lastError = err.Error() + d.lastErrorAt = time.Now().UTC().Format(time.RFC3339) + d.underruns.Add(1) + d.mu.Unlock() + return written, err + } + written += n + } + d.framesWritten.Add(1) + d.samplesWritten.Add(uint64(written)) + return written, nil +} + +func (d *nativeDriver) Stop(_ context.Context) error { + d.mu.Lock() + defer d.mu.Unlock() + if !d.started { + return nil + } + if d.dev != 0 && d.stream != 0 { + d.lib.deactivateStream(d.dev, d.stream) + } + d.started = false + return nil +} + +func (d *nativeDriver) Flush(_ context.Context) error { return nil } + +func (d *nativeDriver) Close(_ context.Context) error { + d.mu.Lock() + defer d.mu.Unlock() + if d.stream != 0 && d.dev != 0 { + if d.started { + d.lib.deactivateStream(d.dev, d.stream) + d.started = false + } + d.lib.closeStream(d.dev, d.stream) + d.stream = 0 + } + if d.dev != 0 { + d.lib.unmakeDevice(d.dev) + d.dev = 0 + } + d.configured = false + return nil +} + +func (d *nativeDriver) Stats() platform.RuntimeStats { + d.mu.Lock() + defer d.mu.Unlock() + return platform.RuntimeStats{ + TXEnabled: d.started, StreamActive: d.started, + FramesWritten: d.framesWritten.Load(), SamplesWritten: d.samplesWritten.Load(), + Underruns: d.underruns.Load(), LastError: d.lastError, LastErrorAt: d.lastErrorAt, + EffectiveRate: d.cfg.SampleRateHz, + } +} + +// --- helper constants --- +const dirTX = 1 // SOAPY_SDR_TX + +// float64 from raw bits +func f64FromPtr(p uintptr) float64 { + return math.Float64frombits(uint64(p)) +} diff --git a/internal/platform/soapysdr/stub.go b/internal/platform/soapysdr/stub.go new file mode 100644 index 0000000..4de2e65 --- /dev/null +++ b/internal/platform/soapysdr/stub.go @@ -0,0 +1,24 @@ +//go:build !soapy + +package soapysdr + +import ( + "fmt" + + "github.com/jan/fm-rds-tx/internal/platform" +) + +// NewNativeDriver is not available without the soapy build tag. +func NewNativeDriver() platform.SoapyDriver { + return nil +} + +// Enumerate is not available without the soapy build tag. +func Enumerate() ([]map[string]string, error) { + return nil, fmt.Errorf("soapysdr: not compiled with -tags soapy") +} + +// Available reports whether SoapySDR native support was compiled in. +func Available() bool { + return false +} diff --git a/libiio/README.txt b/libiio/README.txt new file mode 100644 index 0000000..b67369a --- /dev/null +++ b/libiio/README.txt @@ -0,0 +1,61 @@ + libiio Windows binary snapshot - README + + ********************************************************************* + * The latest version of this snapshot can always be downloaded at: * + * https://github.com/analogdevicesinc/libiio * + ********************************************************************* + +In this archive, you should find the following directories: +o ./include : Common include files +o ./Windows-MinGW-W64 : 64-bit binaries compiled by the MinGW toolchain +o ./Windows-VS-2019-x64 : 64-bit binaries compiled by the MicroSoft toolchain, VS-2019 +o ./Windows-VS-2022-x64 : 64-bit binaries compiled by the MicroSoft toolchain, VS-2022 + +o Visual Studio: + - Open existing or create a new project for your application + - Copy iio.h, from the include\ directory, into your project and make sure that + the location where the file reside appears in the 'Additional Include + Directories' section (Configuration Properties -> C/C++ -> General). + - Copy the relevant .lib file from Windows-VS-2019-x64\ or Windows-VS-2022-x64\ and add 'libiio.lib' to + your 'Additional Dependencies' (Configuration Properties -> Linker -> Input) + Also make sure that the directory where libiio.lib resides is added to + 'Additional Library Directories' (Configuration Properties -> Linker + -> General) + - If you use the static version of the libiio library, make sure that + 'Runtime Library' is set to 'Multi-threaded DLL (/MD)' (Configuration + Properties -> C/C++ -> Code Generation). + - Compile and run your application. If you use the DLL version of libiio, + remember that you need to have a copy of the DLL either in the runtime + directory or in system32 + +o WDK/DDK: + - The following is an example of a sources files that you can use to compile + a libiio 1.0 based console application. In this sample ..\libiio\ is the + directory where you would have copied libiio.h as well as the relevant + libiio.lib + + TARGETNAME=your_app + TARGETTYPE=PROGRAM + USE_MSVCRT=1 + UMTYPE=console + INCLUDES=..\libiio;$(DDK_INC_PATH) + TARGETLIBS=..\libiio\libiio.lib + SOURCES=your_app.c + +o MinGW/cygwin + - Copy iio.h, from include/ to your default include directory, + and copy the MinGW32/ or MinGW64/ .a files to your default library directory. + Or, if you don't want to use the default locations, make sure that you feed + the relevant -I and -L options to the compiler. + - Add the '-liio' linker option when compiling. + +o Additional information: + - The libiio API documentation can be accessed at: + http://analogdevicesinc.github.io/libiio/ + - For some libiio samples (including source), please have a look in examples/ + and tests/ directories + - The MinGW and MS generated DLLs are fully interchangeable, provided that you + use the import libs provided or generate one from the .def also provided. + - If you find any issue, please visit + http://analogdevicesinc.github.io/libiio/ + and check the Issues section diff --git a/libiio/Windows-MinGW-W64/iio_attr.exe b/libiio/Windows-MinGW-W64/iio_attr.exe new file mode 100644 index 0000000..5760f27 Binary files /dev/null and b/libiio/Windows-MinGW-W64/iio_attr.exe differ diff --git a/libiio/Windows-MinGW-W64/iio_genxml.exe b/libiio/Windows-MinGW-W64/iio_genxml.exe new file mode 100644 index 0000000..ab90982 Binary files /dev/null and b/libiio/Windows-MinGW-W64/iio_genxml.exe differ diff --git a/libiio/Windows-MinGW-W64/iio_info.exe b/libiio/Windows-MinGW-W64/iio_info.exe new file mode 100644 index 0000000..150ab6a Binary files /dev/null and b/libiio/Windows-MinGW-W64/iio_info.exe differ diff --git a/libiio/Windows-MinGW-W64/iio_readdev.exe b/libiio/Windows-MinGW-W64/iio_readdev.exe new file mode 100644 index 0000000..c191960 Binary files /dev/null and b/libiio/Windows-MinGW-W64/iio_readdev.exe differ diff --git a/libiio/Windows-MinGW-W64/iio_reg.exe b/libiio/Windows-MinGW-W64/iio_reg.exe new file mode 100644 index 0000000..c3cfc13 Binary files /dev/null and b/libiio/Windows-MinGW-W64/iio_reg.exe differ diff --git a/libiio/Windows-MinGW-W64/iio_writedev.exe b/libiio/Windows-MinGW-W64/iio_writedev.exe new file mode 100644 index 0000000..b0171ac Binary files /dev/null and b/libiio/Windows-MinGW-W64/iio_writedev.exe differ diff --git a/libiio/Windows-MinGW-W64/libgcc_s_seh-1.dll b/libiio/Windows-MinGW-W64/libgcc_s_seh-1.dll new file mode 100644 index 0000000..622ad5e Binary files /dev/null and b/libiio/Windows-MinGW-W64/libgcc_s_seh-1.dll differ diff --git a/libiio/Windows-MinGW-W64/libiconv-2.dll b/libiio/Windows-MinGW-W64/libiconv-2.dll new file mode 100644 index 0000000..2dc6215 Binary files /dev/null and b/libiio/Windows-MinGW-W64/libiconv-2.dll differ diff --git a/libiio/Windows-MinGW-W64/libiio-py39-amd64.tar.gz b/libiio/Windows-MinGW-W64/libiio-py39-amd64.tar.gz new file mode 100644 index 0000000..02f71ad Binary files /dev/null and b/libiio/Windows-MinGW-W64/libiio-py39-amd64.tar.gz differ diff --git a/libiio/Windows-MinGW-W64/libiio.dll b/libiio/Windows-MinGW-W64/libiio.dll new file mode 100644 index 0000000..85bb074 Binary files /dev/null and b/libiio/Windows-MinGW-W64/libiio.dll differ diff --git a/libiio/Windows-MinGW-W64/libiio.dll.a b/libiio/Windows-MinGW-W64/libiio.dll.a new file mode 100644 index 0000000..9c3eceb Binary files /dev/null and b/libiio/Windows-MinGW-W64/libiio.dll.a differ diff --git a/libiio/Windows-MinGW-W64/libiio.iss b/libiio/Windows-MinGW-W64/libiio.iss new file mode 100644 index 0000000..d550d3e --- /dev/null +++ b/libiio/Windows-MinGW-W64/libiio.iss @@ -0,0 +1,51 @@ +[Setup] +AppId={{D386A5F6-D38D-4738-94A2-E163DC1896F1} +AppName="Libiio" +AppVersion="0.26" +AppPublisher="Analog Devices, Inc." +AppPublisherURL="http://www.analog.com" +AppSupportURL="http://www.analog.com" +AppUpdatesURL="http://www.analog.com" +AppCopyright="Copyright 2015-2024 ADI and other contributors" +CreateAppDir=no +LicenseFile="D:\a\1\s\COPYING.txt" +OutputBaseFilename=libiio-setup +OutputDir="C:\" +Compression=lzma +SolidCompression=yes +ArchitecturesInstallIn64BitMode=x64 + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" +Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl" +Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl" +Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl" +Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl" +Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl" +Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl" +Name: "french"; MessagesFile: "compiler:Languages\French.isl" +Name: "german"; MessagesFile: "compiler:Languages\German.isl" +Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" +Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" +Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl" +Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" +Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl" +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" +Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl" + +[Files] +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: replacesameversion +Source: "D:\a\1\a\Windows-VS-2019-x64\*.exe"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: replacesameversion +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio.lib"; DestDir: "{commonpf32}\Microsoft Visual Studio 12.0\VC\lib\amd64"; Check: Is64BitInstallMode +Source: "D:\a\1\a\Windows-VS-2019-x64\iio.h"; DestDir: "{commonpf32}\Microsoft Visual Studio 12.0\VC\include" +Source: "D:\a\1\a\Windows-VS-2019-x64\libxml2.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libusb-1.0.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libserialport-0.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio-sharp.dll"; DestDir: "{commoncf}\libiio"; Flags: replacesameversion +Source: "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\msvcp140.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\vcruntime140.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist diff --git a/libiio/Windows-MinGW-W64/liblzma-5.dll b/libiio/Windows-MinGW-W64/liblzma-5.dll new file mode 100644 index 0000000..4b2eff5 Binary files /dev/null and b/libiio/Windows-MinGW-W64/liblzma-5.dll differ diff --git a/libiio/Windows-MinGW-W64/libserialport-0.dll b/libiio/Windows-MinGW-W64/libserialport-0.dll new file mode 100644 index 0000000..1a547e9 Binary files /dev/null and b/libiio/Windows-MinGW-W64/libserialport-0.dll differ diff --git a/libiio/Windows-MinGW-W64/libstdc++-6.dll b/libiio/Windows-MinGW-W64/libstdc++-6.dll new file mode 100644 index 0000000..d16df66 Binary files /dev/null and b/libiio/Windows-MinGW-W64/libstdc++-6.dll differ diff --git a/libiio/Windows-MinGW-W64/libusb-1.0.dll b/libiio/Windows-MinGW-W64/libusb-1.0.dll new file mode 100644 index 0000000..a2ef58e Binary files /dev/null and b/libiio/Windows-MinGW-W64/libusb-1.0.dll differ diff --git a/libiio/Windows-MinGW-W64/libwinpthread-1.dll b/libiio/Windows-MinGW-W64/libwinpthread-1.dll new file mode 100644 index 0000000..b9354a3 Binary files /dev/null and b/libiio/Windows-MinGW-W64/libwinpthread-1.dll differ diff --git a/libiio/Windows-MinGW-W64/libxml2-2.dll b/libiio/Windows-MinGW-W64/libxml2-2.dll new file mode 100644 index 0000000..0b99cdf Binary files /dev/null and b/libiio/Windows-MinGW-W64/libxml2-2.dll differ diff --git a/libiio/Windows-MinGW-W64/zlib1.dll b/libiio/Windows-MinGW-W64/zlib1.dll new file mode 100644 index 0000000..ccc6e03 Binary files /dev/null and b/libiio/Windows-MinGW-W64/zlib1.dll differ diff --git a/libiio/Windows-VS-2019-x64/iio_attr.exe b/libiio/Windows-VS-2019-x64/iio_attr.exe new file mode 100644 index 0000000..c44a314 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/iio_attr.exe differ diff --git a/libiio/Windows-VS-2019-x64/iio_genxml.exe b/libiio/Windows-VS-2019-x64/iio_genxml.exe new file mode 100644 index 0000000..821e882 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/iio_genxml.exe differ diff --git a/libiio/Windows-VS-2019-x64/iio_info.exe b/libiio/Windows-VS-2019-x64/iio_info.exe new file mode 100644 index 0000000..c2c34d7 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/iio_info.exe differ diff --git a/libiio/Windows-VS-2019-x64/iio_readdev.exe b/libiio/Windows-VS-2019-x64/iio_readdev.exe new file mode 100644 index 0000000..53f3414 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/iio_readdev.exe differ diff --git a/libiio/Windows-VS-2019-x64/iio_reg.exe b/libiio/Windows-VS-2019-x64/iio_reg.exe new file mode 100644 index 0000000..7876967 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/iio_reg.exe differ diff --git a/libiio/Windows-VS-2019-x64/iio_writedev.exe b/libiio/Windows-VS-2019-x64/iio_writedev.exe new file mode 100644 index 0000000..121d98c Binary files /dev/null and b/libiio/Windows-VS-2019-x64/iio_writedev.exe differ diff --git a/libiio/Windows-VS-2019-x64/libiio-py39-amd64.tar.gz b/libiio/Windows-VS-2019-x64/libiio-py39-amd64.tar.gz new file mode 100644 index 0000000..b89ac12 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libiio-py39-amd64.tar.gz differ diff --git a/libiio/Windows-VS-2019-x64/libiio-sharp.dll b/libiio/Windows-VS-2019-x64/libiio-sharp.dll new file mode 100644 index 0000000..5ce63c0 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libiio-sharp.dll differ diff --git a/libiio/Windows-VS-2019-x64/libiio.dll b/libiio/Windows-VS-2019-x64/libiio.dll new file mode 100644 index 0000000..c0e7a7d Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libiio.dll differ diff --git a/libiio/Windows-VS-2019-x64/libiio.exp b/libiio/Windows-VS-2019-x64/libiio.exp new file mode 100644 index 0000000..8ff78c5 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libiio.exp differ diff --git a/libiio/Windows-VS-2019-x64/libiio.iss b/libiio/Windows-VS-2019-x64/libiio.iss new file mode 100644 index 0000000..d550d3e --- /dev/null +++ b/libiio/Windows-VS-2019-x64/libiio.iss @@ -0,0 +1,51 @@ +[Setup] +AppId={{D386A5F6-D38D-4738-94A2-E163DC1896F1} +AppName="Libiio" +AppVersion="0.26" +AppPublisher="Analog Devices, Inc." +AppPublisherURL="http://www.analog.com" +AppSupportURL="http://www.analog.com" +AppUpdatesURL="http://www.analog.com" +AppCopyright="Copyright 2015-2024 ADI and other contributors" +CreateAppDir=no +LicenseFile="D:\a\1\s\COPYING.txt" +OutputBaseFilename=libiio-setup +OutputDir="C:\" +Compression=lzma +SolidCompression=yes +ArchitecturesInstallIn64BitMode=x64 + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" +Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl" +Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl" +Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl" +Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl" +Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl" +Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl" +Name: "french"; MessagesFile: "compiler:Languages\French.isl" +Name: "german"; MessagesFile: "compiler:Languages\German.isl" +Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" +Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" +Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl" +Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" +Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl" +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" +Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl" + +[Files] +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: replacesameversion +Source: "D:\a\1\a\Windows-VS-2019-x64\*.exe"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: replacesameversion +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio.lib"; DestDir: "{commonpf32}\Microsoft Visual Studio 12.0\VC\lib\amd64"; Check: Is64BitInstallMode +Source: "D:\a\1\a\Windows-VS-2019-x64\iio.h"; DestDir: "{commonpf32}\Microsoft Visual Studio 12.0\VC\include" +Source: "D:\a\1\a\Windows-VS-2019-x64\libxml2.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libusb-1.0.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libserialport-0.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio-sharp.dll"; DestDir: "{commoncf}\libiio"; Flags: replacesameversion +Source: "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\msvcp140.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\vcruntime140.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist diff --git a/libiio/Windows-VS-2019-x64/libiio.lib b/libiio/Windows-VS-2019-x64/libiio.lib new file mode 100644 index 0000000..2808f54 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libiio.lib differ diff --git a/libiio/Windows-VS-2019-x64/libiio.pdb b/libiio/Windows-VS-2019-x64/libiio.pdb new file mode 100644 index 0000000..6bf8044 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libiio.pdb differ diff --git a/libiio/Windows-VS-2019-x64/libserialport-0.dll b/libiio/Windows-VS-2019-x64/libserialport-0.dll new file mode 100644 index 0000000..1a547e9 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libserialport-0.dll differ diff --git a/libiio/Windows-VS-2019-x64/libusb-1.0.dll b/libiio/Windows-VS-2019-x64/libusb-1.0.dll new file mode 100644 index 0000000..a2ef58e Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libusb-1.0.dll differ diff --git a/libiio/Windows-VS-2019-x64/libxml2.dll b/libiio/Windows-VS-2019-x64/libxml2.dll new file mode 100644 index 0000000..dcc623c Binary files /dev/null and b/libiio/Windows-VS-2019-x64/libxml2.dll differ diff --git a/libiio/Windows-VS-2019-x64/msvcp140.dll b/libiio/Windows-VS-2019-x64/msvcp140.dll new file mode 100644 index 0000000..cb3914e Binary files /dev/null and b/libiio/Windows-VS-2019-x64/msvcp140.dll differ diff --git a/libiio/Windows-VS-2019-x64/vcruntime140.dll b/libiio/Windows-VS-2019-x64/vcruntime140.dll new file mode 100644 index 0000000..8a65908 Binary files /dev/null and b/libiio/Windows-VS-2019-x64/vcruntime140.dll differ diff --git a/libiio/Windows-VS-2022-x64/iio_attr.exe b/libiio/Windows-VS-2022-x64/iio_attr.exe new file mode 100644 index 0000000..aa8a6da Binary files /dev/null and b/libiio/Windows-VS-2022-x64/iio_attr.exe differ diff --git a/libiio/Windows-VS-2022-x64/iio_genxml.exe b/libiio/Windows-VS-2022-x64/iio_genxml.exe new file mode 100644 index 0000000..0f81044 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/iio_genxml.exe differ diff --git a/libiio/Windows-VS-2022-x64/iio_info.exe b/libiio/Windows-VS-2022-x64/iio_info.exe new file mode 100644 index 0000000..5df18ac Binary files /dev/null and b/libiio/Windows-VS-2022-x64/iio_info.exe differ diff --git a/libiio/Windows-VS-2022-x64/iio_readdev.exe b/libiio/Windows-VS-2022-x64/iio_readdev.exe new file mode 100644 index 0000000..eead1e5 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/iio_readdev.exe differ diff --git a/libiio/Windows-VS-2022-x64/iio_reg.exe b/libiio/Windows-VS-2022-x64/iio_reg.exe new file mode 100644 index 0000000..523e885 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/iio_reg.exe differ diff --git a/libiio/Windows-VS-2022-x64/iio_writedev.exe b/libiio/Windows-VS-2022-x64/iio_writedev.exe new file mode 100644 index 0000000..236589e Binary files /dev/null and b/libiio/Windows-VS-2022-x64/iio_writedev.exe differ diff --git a/libiio/Windows-VS-2022-x64/libiio-py39-amd64.tar.gz b/libiio/Windows-VS-2022-x64/libiio-py39-amd64.tar.gz new file mode 100644 index 0000000..01a0194 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libiio-py39-amd64.tar.gz differ diff --git a/libiio/Windows-VS-2022-x64/libiio-sharp.dll b/libiio/Windows-VS-2022-x64/libiio-sharp.dll new file mode 100644 index 0000000..98c8a7c Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libiio-sharp.dll differ diff --git a/libiio/Windows-VS-2022-x64/libiio.dll b/libiio/Windows-VS-2022-x64/libiio.dll new file mode 100644 index 0000000..6f69729 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libiio.dll differ diff --git a/libiio/Windows-VS-2022-x64/libiio.exp b/libiio/Windows-VS-2022-x64/libiio.exp new file mode 100644 index 0000000..53dd814 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libiio.exp differ diff --git a/libiio/Windows-VS-2022-x64/libiio.iss b/libiio/Windows-VS-2022-x64/libiio.iss new file mode 100644 index 0000000..d550d3e --- /dev/null +++ b/libiio/Windows-VS-2022-x64/libiio.iss @@ -0,0 +1,51 @@ +[Setup] +AppId={{D386A5F6-D38D-4738-94A2-E163DC1896F1} +AppName="Libiio" +AppVersion="0.26" +AppPublisher="Analog Devices, Inc." +AppPublisherURL="http://www.analog.com" +AppSupportURL="http://www.analog.com" +AppUpdatesURL="http://www.analog.com" +AppCopyright="Copyright 2015-2024 ADI and other contributors" +CreateAppDir=no +LicenseFile="D:\a\1\s\COPYING.txt" +OutputBaseFilename=libiio-setup +OutputDir="C:\" +Compression=lzma +SolidCompression=yes +ArchitecturesInstallIn64BitMode=x64 + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" +Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl" +Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl" +Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl" +Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl" +Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl" +Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl" +Name: "french"; MessagesFile: "compiler:Languages\French.isl" +Name: "german"; MessagesFile: "compiler:Languages\German.isl" +Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" +Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" +Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl" +Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" +Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl" +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" +Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl" + +[Files] +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: replacesameversion +Source: "D:\a\1\a\Windows-VS-2019-x64\*.exe"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: replacesameversion +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio.lib"; DestDir: "{commonpf32}\Microsoft Visual Studio 12.0\VC\lib\amd64"; Check: Is64BitInstallMode +Source: "D:\a\1\a\Windows-VS-2019-x64\iio.h"; DestDir: "{commonpf32}\Microsoft Visual Studio 12.0\VC\include" +Source: "D:\a\1\a\Windows-VS-2019-x64\libxml2.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libusb-1.0.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libserialport-0.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "D:\a\1\a\Windows-VS-2019-x64\libiio-sharp.dll"; DestDir: "{commoncf}\libiio"; Flags: replacesameversion +Source: "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\msvcp140.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist +Source: "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Redist\MSVC\14.29.30133\x64\Microsoft.VC142.CRT\vcruntime140.dll"; DestDir: "{sys}"; Check: Is64BitInstallMode; Flags: onlyifdoesntexist diff --git a/libiio/Windows-VS-2022-x64/libiio.lib b/libiio/Windows-VS-2022-x64/libiio.lib new file mode 100644 index 0000000..28748b8 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libiio.lib differ diff --git a/libiio/Windows-VS-2022-x64/libiio.pdb b/libiio/Windows-VS-2022-x64/libiio.pdb new file mode 100644 index 0000000..f10cc35 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libiio.pdb differ diff --git a/libiio/Windows-VS-2022-x64/libserialport-0.dll b/libiio/Windows-VS-2022-x64/libserialport-0.dll new file mode 100644 index 0000000..1a547e9 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libserialport-0.dll differ diff --git a/libiio/Windows-VS-2022-x64/libusb-1.0.dll b/libiio/Windows-VS-2022-x64/libusb-1.0.dll new file mode 100644 index 0000000..a2ef58e Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libusb-1.0.dll differ diff --git a/libiio/Windows-VS-2022-x64/libxml2.dll b/libiio/Windows-VS-2022-x64/libxml2.dll new file mode 100644 index 0000000..dcc623c Binary files /dev/null and b/libiio/Windows-VS-2022-x64/libxml2.dll differ diff --git a/libiio/Windows-VS-2022-x64/msvcp140.dll b/libiio/Windows-VS-2022-x64/msvcp140.dll new file mode 100644 index 0000000..cb3914e Binary files /dev/null and b/libiio/Windows-VS-2022-x64/msvcp140.dll differ diff --git a/libiio/Windows-VS-2022-x64/vcruntime140.dll b/libiio/Windows-VS-2022-x64/vcruntime140.dll new file mode 100644 index 0000000..8a65908 Binary files /dev/null and b/libiio/Windows-VS-2022-x64/vcruntime140.dll differ diff --git a/libiio/include/iio.h b/libiio/include/iio.h new file mode 100644 index 0000000..50510f0 --- /dev/null +++ b/libiio/include/iio.h @@ -0,0 +1,1986 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2014 Analog Devices, Inc. + * Author: Paul Cercueil + */ + +/** @file iio.h + * @brief Public interface */ + +#ifndef __IIO_H__ +#define __IIO_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#if (defined(_WIN32) || defined(__MBED__)) +#ifndef _SSIZE_T_DEFINED +typedef ptrdiff_t ssize_t; +#define _SSIZE_T_DEFINED +#endif +#else +#include +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1800) && !defined(__BOOL_DEFINED) +#undef bool +#undef false +#undef true +#define bool char +#define false 0 +#define true 1 +#else +#include +#endif + +#if defined(__GNUC__) && !defined(MATLAB_MEX_FILE) && !defined(MATLAB_LOADLIBRARY) +#ifndef __cnst +#define __cnst __attribute__((const)) +#endif +#ifndef __pure +#define __pure __attribute__((pure)) +#endif +#define __notused __attribute__((unused)) +#ifdef IIO_CHECK_RET +#define __check_ret __attribute__((warn_unused_result)) +#else +#define __check_ret +#endif +#else +#define __cnst +#define __pure +#define __notused +#define __check_ret +#endif + +#ifdef _WIN32 +# ifdef LIBIIO_STATIC +# define __api +# elif defined(LIBIIO_EXPORTS) +# define __api __declspec(dllexport) +# else +# define __api __declspec(dllimport) +# endif +#elif __GNUC__ >= 4 && !defined(MATLAB_MEX_FILE) && !defined(MATLAB_LOADLIBRARY) +# define __api __attribute__((visibility ("default"))) +#else +# define __api +#endif + +struct iio_context; +struct iio_device; +struct iio_channel; +struct iio_buffer; + +struct iio_context_info; +struct iio_scan_context; +struct iio_scan_block; + +/* + * header guard to protect these enums from being defined + * twice + */ +#ifndef _IIO_TYPES_H_ +#define _IIO_TYPES_H_ + +/** + * @enum iio_chan_type + * @brief IIO channel type + * + * A IIO channel has a type specifying the type of data associated with the + * channel. + */ +enum iio_chan_type { + IIO_VOLTAGE, + IIO_CURRENT, + IIO_POWER, + IIO_ACCEL, + IIO_ANGL_VEL, + IIO_MAGN, + IIO_LIGHT, + IIO_INTENSITY, + IIO_PROXIMITY, + IIO_TEMP, + IIO_INCLI, + IIO_ROT, + IIO_ANGL, + IIO_TIMESTAMP, + IIO_CAPACITANCE, + IIO_ALTVOLTAGE, + IIO_CCT, + IIO_PRESSURE, + IIO_HUMIDITYRELATIVE, + IIO_ACTIVITY, + IIO_STEPS, + IIO_ENERGY, + IIO_DISTANCE, + IIO_VELOCITY, + IIO_CONCENTRATION, + IIO_RESISTANCE, + IIO_PH, + IIO_UVINDEX, + IIO_ELECTRICALCONDUCTIVITY, + IIO_COUNT, + IIO_INDEX, + IIO_GRAVITY, + IIO_POSITIONRELATIVE, + IIO_PHASE, + IIO_MASSCONCENTRATION, + IIO_CHAN_TYPE_UNKNOWN = INT_MAX +}; + +/** + * @enum iio_modifier + * @brief IIO channel modifier + * + * In a addition to a type a IIO channel can optionally have a channel modifier + * further specifying the data type of of the channel. + */ +enum iio_modifier { + IIO_NO_MOD, + IIO_MOD_X, + IIO_MOD_Y, + IIO_MOD_Z, + IIO_MOD_X_AND_Y, + IIO_MOD_X_AND_Z, + IIO_MOD_Y_AND_Z, + IIO_MOD_X_AND_Y_AND_Z, + IIO_MOD_X_OR_Y, + IIO_MOD_X_OR_Z, + IIO_MOD_Y_OR_Z, + IIO_MOD_X_OR_Y_OR_Z, + IIO_MOD_LIGHT_BOTH, + IIO_MOD_LIGHT_IR, + IIO_MOD_ROOT_SUM_SQUARED_X_Y, + IIO_MOD_SUM_SQUARED_X_Y_Z, + IIO_MOD_LIGHT_CLEAR, + IIO_MOD_LIGHT_RED, + IIO_MOD_LIGHT_GREEN, + IIO_MOD_LIGHT_BLUE, + IIO_MOD_QUATERNION, + IIO_MOD_TEMP_AMBIENT, + IIO_MOD_TEMP_OBJECT, + IIO_MOD_NORTH_MAGN, + IIO_MOD_NORTH_TRUE, + IIO_MOD_NORTH_MAGN_TILT_COMP, + IIO_MOD_NORTH_TRUE_TILT_COMP, + IIO_MOD_RUNNING, + IIO_MOD_JOGGING, + IIO_MOD_WALKING, + IIO_MOD_STILL, + IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z, + IIO_MOD_I, + IIO_MOD_Q, + IIO_MOD_CO2, + IIO_MOD_VOC, + IIO_MOD_LIGHT_UV, + IIO_MOD_LIGHT_DUV, + IIO_MOD_PM1, + IIO_MOD_PM2P5, + IIO_MOD_PM4, + IIO_MOD_PM10, + IIO_MOD_ETHANOL, + IIO_MOD_H2, + IIO_MOD_O2, + IIO_MOD_LINEAR_X, + IIO_MOD_LINEAR_Y, + IIO_MOD_LINEAR_Z, + IIO_MOD_PITCH, + IIO_MOD_YAW, + IIO_MOD_ROLL, +}; + +/** + * @enum iio_event_type + * @brief IIO event type + * + * Some IIO devices can deliver events. The type of the event can be specified + * by one of the iio_event_type values. + */ +enum iio_event_type { + IIO_EV_TYPE_THRESH, + IIO_EV_TYPE_MAG, + IIO_EV_TYPE_ROC, + IIO_EV_TYPE_THRESH_ADAPTIVE, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_TYPE_CHANGE, + IIO_EV_TYPE_MAG_REFERENCED, + IIO_EV_TYPE_GESTURE, +}; + +/** + * @enum iio_event_direction + * @brief IIO event direction + * + * When applicable, this enum specifies the direction of the iio_event_type. + */ +enum iio_event_direction { + IIO_EV_DIR_EITHER, + IIO_EV_DIR_RISING, + IIO_EV_DIR_FALLING, + IIO_EV_DIR_NONE, + IIO_EV_DIR_SINGLETAP, + IIO_EV_DIR_DOUBLETAP, +}; + +#endif /* _IIO_TYPES_H_ */ + +/* ---------------------------------------------------------------------------*/ +/* ------------------------- Scan functions ----------------------------------*/ +/** @defgroup Scan Functions for scanning available contexts + * @{ + * @struct iio_scan_context + * @brief The scanning context + * + * @struct iio_context_info + * @brief The information related to a discovered context + */ + + +/** @brief Create a scan context + * @param backend A NULL-terminated string containing a comma-separated + * list of the backend(s) to use for scanning. + * @param flags Unused for now. Set to 0. + * @return on success, a pointer to a iio_scan_context structure + * @return On failure, NULL is returned and errno is set appropriately + * + * NOTE: Libiio version 0.20 and above can handle multiple + * strings, for instance "local:usb:", "ip:usb:", "local:usb:ip:", and + * require a colon as the delimiter. + * Libiio version 0.24 and above prefer a comma instead of colon as the + * delimiter, and handle specifying backend-specific information. For + * instance, "local,usb=0456:*" will scan the local backend and limit + * scans on USB to vendor ID 0x0456, and accept all product IDs. The + * "usb=0456:b673" string would limit the scan to the device with this + * particular VID/PID. Both IDs are expected in hexadecimal, no 0x + * prefix needed. */ +__api __check_ret struct iio_scan_context * iio_create_scan_context( + const char *backend, unsigned int flags); + + +/** @brief Destroy the given scan context + * @param ctx A pointer to an iio_scan_context structure + * + * NOTE: After that function, the iio_scan_context pointer shall be invalid. */ +__api void iio_scan_context_destroy(struct iio_scan_context *ctx); + + +/** @brief Enumerate available contexts + * @param ctx A pointer to an iio_scan_context structure + * @param info A pointer to a 'const struct iio_context_info **' typed variable. + * The pointed variable will be initialized on success. + * @returns On success, the number of contexts found. + * @returns On failure, a negative error number. + */ +__api __check_ret ssize_t iio_scan_context_get_info_list(struct iio_scan_context *ctx, + struct iio_context_info ***info); + + +/** @brief Free a context info list + * @param info A pointer to a 'const struct iio_context_info *' typed variable + */ +__api void iio_context_info_list_free(struct iio_context_info **info); + + +/** @brief Get a description of a discovered context + * @param info A pointer to an iio_context_info structure + * @return A pointer to a static NULL-terminated string + */ +__api __check_ret __pure const char * iio_context_info_get_description( + const struct iio_context_info *info); + + +/** @brief Get the URI of a discovered context + * @param info A pointer to an iio_context_info structure + * @return A pointer to a static NULL-terminated string + */ +__api __check_ret __pure const char * iio_context_info_get_uri( + const struct iio_context_info *info); + + +/** @brief Create a scan block + * @param backend A NULL-terminated string containing the backend to use for + * scanning. If NULL, all the available backends are used. + * @param flags Unused for now. Set to 0. + * @return on success, a pointer to a iio_scan_block structure + * @return On failure, NULL is returned and errno is set appropriately + * + * Introduced in version 0.20. */ +__api struct iio_scan_block * iio_create_scan_block( + const char *backend, unsigned int flags); + + +/** @brief Destroy the given scan block + * @param blk A pointer to an iio_scan_block structure + * + * NOTE: After that function, the iio_scan_block pointer shall be invalid. + * + * Introduced in version 0.20. */ +__api void iio_scan_block_destroy(struct iio_scan_block *blk); + + +/** @brief Enumerate available contexts via scan block + * @param blk A pointer to a iio_scan_block structure. + * @returns On success, the number of contexts found. + * @returns On failure, a negative error number. + * + * Introduced in version 0.20. */ +__api ssize_t iio_scan_block_scan(struct iio_scan_block *blk); + + +/** @brief Get the iio_context_info for a particular context + * @param blk A pointer to an iio_scan_block structure + * @param index The index corresponding to the context. + * @return A pointer to the iio_context_info for the context + * @returns On success, a pointer to the specified iio_context_info + * @returns On failure, NULL is returned and errno is set appropriately + * + * Introduced in version 0.20. */ +__api struct iio_context_info *iio_scan_block_get_info( + struct iio_scan_block *blk, unsigned int index); + + +/** @} *//* ------------------------------------------------------------------*/ +/* ------------------------- Top-level functions -----------------------------*/ +/** @defgroup TopLevel Top-level functions + * @{ */ + + +/** @brief Get the version of the libiio library + * @param major A pointer to an unsigned integer (NULL accepted) + * @param minor A pointer to an unsigned integer (NULL accepted) + * @param git_tag A pointer to a 8-characters buffer (NULL accepted) */ +__api void iio_library_get_version(unsigned int *major, + unsigned int *minor, char git_tag[8]); + + +/** @brief Get a string description of an error code + * @param err The error code + * @param dst A pointer to the memory area where the NULL-terminated string + * corresponding to the error message will be stored + * @param len The available length of the memory area, in bytes */ +__api void iio_strerror(int err, char *dst, size_t len); + + +/** @brief Check if the specified backend is available + * @param backend The name of the backend to query + * @return True if the backend is available, false otherwise + * + * Introduced in version 0.9. */ +__api __check_ret __cnst bool iio_has_backend(const char *backend); + + +/** @brief Get the number of available backends + * @return The number of available backends + * + * Introduced in version 0.9. */ +__api __check_ret __cnst unsigned int iio_get_backends_count(void); + + +/** @brief Retrieve the name of a given backend + * @param index The index corresponding to the attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the index is invalid, NULL is returned + * + * Introduced in version 0.9. */ +__api __check_ret __cnst const char * iio_get_backend(unsigned int index); + + +/** @} *//* ------------------------------------------------------------------*/ +/* ------------------------- Context functions -------------------------------*/ +/** @defgroup Context Context + * @{ + * @struct iio_context + * @brief Contains the representation of an IIO context */ + + +/** @brief Create a context from local or remote IIO devices + * @return On success, A pointer to an iio_context structure + * @return On failure, NULL is returned and errno is set appropriately + * + * NOTE: This function will create a context with the URI + * provided in the IIOD_REMOTE environment variable. If not set, a local + * context will be created instead. */ +__api __check_ret struct iio_context * iio_create_default_context(void); + + +/** @brief Create a context from local IIO devices (Linux only) + * @return On success, A pointer to an iio_context structure + * @return On failure, NULL is returned and errno is set appropriately */ +__api __check_ret struct iio_context * iio_create_local_context(void); + + +/** @brief Create a context from a XML file + * @param xml_file Path to the XML file to open + * @return On success, A pointer to an iio_context structure + * @return On failure, NULL is returned and errno is set appropriately + * + * NOTE: The format of the XML must comply to the one returned by + * iio_context_get_xml. */ +__api __check_ret struct iio_context * iio_create_xml_context(const char *xml_file); + + +/** @brief Create a context from XML data in memory + * @param xml Pointer to the XML data in memory + * @param len Length of the XML string in memory (excluding the final \0) + * @return On success, A pointer to an iio_context structure + * @return On failure, NULL is returned and errno is set appropriately + * + * NOTE: The format of the XML must comply to the one returned by + * iio_context_get_xml */ +__api __check_ret struct iio_context * iio_create_xml_context_mem( + const char *xml, size_t len); + + +/** @brief Create a context from the network + * @param host Hostname, IPv4 or IPv6 address where the IIO Daemon is running + * @return On success, a pointer to an iio_context structure + * @return On failure, NULL is returned and errno is set appropriately */ +__api __check_ret struct iio_context * iio_create_network_context(const char *host); + + +/** @brief Create a context from a URI description + * @param uri A URI describing the context location + * @return On success, a pointer to a iio_context structure + * @return On failure, NULL is returned and errno is set appropriately + * + * NOTE: The following URIs are supported based on compile time backend + * support: + * - Local backend, "local:"\n + * Does not have an address part. For example "local:" + * - XML backend, "xml:"\n Requires a path to the XML file for the address part. + * For example "xml:/home/user/file.xml" + * - Network backend, "ip:"\n Requires a hostname, IPv4, or IPv6 to connect to + * a specific running IIO Daemon or no address part for automatic discovery + * when library is compiled with ZeroConf support. For example + * "ip:192.168.2.1", or "ip:localhost", or "ip:" + * or "ip:plutosdr.local". To support alternative port numbers the + * standard ip:host:port format is used. A special format is required as + * defined in RFC2732 for IPv6 literal hostnames, (adding '[]' around the host) + * to use a ip:[x:x:x:x:x:x:x:x]:port format. + * Valid examples would be: + * - ip: Any host on default port + * - ip::40000 Any host on port 40000 + * - ip:analog.local Default port + * - ip:brain.local:40000 Port 40000 + * - ip:192.168.1.119 Default Port + * - ip:192.168.1.119:40000 Port 40000 + * - ip:2601:190:400:da:47b3:55ab:3914:bff1 Default Port + * - ip:[2601:190:400:da:9a90:96ff:feb5:acaa]:40000 Port 40000 + * - ip:fe80::f14d:3728:501e:1f94%eth0 Link-local through eth0, default port + * - ip:[fe80::f14d:3728:501e:1f94%eth0]:40000 Link-local through eth0, port 40000 + * - USB backend, "usb:"\n When more than one usb device is attached, requires + * bus, address, and interface parts separated with a dot. For example + * "usb:3.32.5". Where there is only one USB device attached, the shorthand + * "usb:" can be used. + * - Serial backend, "serial:"\n Requires: + * - a port (/dev/ttyUSB0), + * - baud_rate (default 115200) + * - serial port configuration + * - data bits (5 6 7 8 9) + * - parity ('n' none, 'o' odd, 'e' even, 'm' mark, 's' space) + * - stop bits (1 2) + * - flow control ('\0' none, 'x' Xon Xoff, 'r' RTSCTS, 'd' DTRDSR) + * + * For example "serial:/dev/ttyUSB0,115200" or "serial:/dev/ttyUSB0,115200,8n1"*/ +__api __check_ret struct iio_context * iio_create_context_from_uri(const char *uri); + + +/** @brief Duplicate a pre-existing IIO context + * @param ctx A pointer to an iio_context structure + * @return On success, A pointer to an iio_context structure + * @return On failure, NULL is returned and errno is set appropriately + * + * NOTE: This function is not supported on 'usb:' contexts, since libusb + * can only claim the interface once. "Function not implemented" is the expected errno. + * Any context which is cloned, must be destroyed via calling iio_context_destroy() */ +__api __check_ret struct iio_context * iio_context_clone(const struct iio_context *ctx); + + +/** @brief Destroy the given context + * @param ctx A pointer to an iio_context structure + * + * NOTE: After that function, the iio_context pointer shall be invalid. */ +__api void iio_context_destroy(struct iio_context *ctx); + + +/** @brief Get the version of the backend in use + * @param ctx A pointer to an iio_context structure + * @param major A pointer to an unsigned integer (NULL accepted) + * @param minor A pointer to an unsigned integer (NULL accepted) + * @param git_tag A pointer to a 8-characters buffer (NULL accepted) + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_context_get_version(const struct iio_context *ctx, + unsigned int *major, unsigned int *minor, char git_tag[8]); + + +/** @brief Obtain a XML representation of the given context + * @param ctx A pointer to an iio_context structure + * @return A pointer to a static NULL-terminated string */ +__api __check_ret __pure const char * iio_context_get_xml(const struct iio_context *ctx); + + +/** @brief Get the name of the given context + * @param ctx A pointer to an iio_context structure + * @return A pointer to a static NULL-terminated string + * + * NOTE:The returned string will be local, + * xml or network when the context has been + * created with the local, xml and network backends respectively.*/ +__api __check_ret __pure const char * iio_context_get_name(const struct iio_context *ctx); + + +/** @brief Get a description of the given context + * @param ctx A pointer to an iio_context structure + * @return A pointer to a static NULL-terminated string + * + * NOTE:The returned string will contain human-readable information about + * the current context. */ +__api __check_ret __pure const char * iio_context_get_description( + const struct iio_context *ctx); + + +/** @brief Get the number of context-specific attributes + * @param ctx A pointer to an iio_context structure + * @return The number of context-specific attributes + * + * Introduced in version 0.9. */ +__api __check_ret __pure unsigned int iio_context_get_attrs_count( + const struct iio_context *ctx); + + +/** @brief Retrieve the name and value of a context-specific attribute + * @param ctx A pointer to an iio_context structure + * @param index The index corresponding to the attribute + * @param name A pointer to a const char * pointer (NULL accepted) + * @param value A pointer to a const char * pointer (NULL accepted) + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * Introduced in version 0.9. */ +__api __check_ret int iio_context_get_attr( + const struct iio_context *ctx, unsigned int index, + const char **name, const char **value); + + +/** @brief Retrieve the value of a context-specific attribute + * @param ctx A pointer to an iio_context structure + * @param name The name of the context attribute to read + * @return On success, a pointer to a static NULL-terminated string + * @return If the name does not correspond to any attribute, NULL is + * returned + * + * Introduced in version 0.9. */ +__api __check_ret const char * iio_context_get_attr_value( + const struct iio_context *ctx, const char *name); + + +/** @brief Enumerate the devices found in the given context + * @param ctx A pointer to an iio_context structure + * @return The number of devices found */ +__api __check_ret __pure unsigned int iio_context_get_devices_count( + const struct iio_context *ctx); + + +/** @brief Get the device present at the given index + * @param ctx A pointer to an iio_context structure + * @param index The index corresponding to the device + * @return On success, a pointer to an iio_device structure + * @return If the index is invalid, NULL is returned */ +__api __check_ret __pure struct iio_device * iio_context_get_device( + const struct iio_context *ctx, unsigned int index); + + +/** @brief Try to find a device structure by its ID, label or name + * @param ctx A pointer to an iio_context structure + * @param name A NULL-terminated string corresponding to the ID, label or name + * of the device to search for + * @return On success, a pointer to an iio_device structure + * @return If the parameter does not correspond to the ID, label or name of + * any known device, NULL is returned */ +__api __check_ret __pure struct iio_device * iio_context_find_device( + const struct iio_context *ctx, const char *name); + + +/** @brief Set a timeout for I/O operations + * @param ctx A pointer to an iio_context structure + * @param timeout_ms A positive integer representing the time in milliseconds + * after which a timeout occurs. A value of 0 is used to specify that no + * timeout should occur. + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_context_set_timeout( + struct iio_context *ctx, unsigned int timeout_ms); + + +/** @} *//* ------------------------------------------------------------------*/ +/* ------------------------- Device functions --------------------------------*/ +/** @defgroup Device Device + * @{ + * @struct iio_device + * @brief Represents a device in the IIO context */ + + +/** @brief Retrieve a pointer to the iio_context structure + * @param dev A pointer to an iio_device structure + * @return A pointer to an iio_context structure */ +__api __check_ret __pure const struct iio_context * iio_device_get_context( + const struct iio_device *dev); + + +/** @brief Retrieve the device ID (e.g. iio:device0) + * @param dev A pointer to an iio_device structure + * @return A pointer to a static NULL-terminated string */ +__api __check_ret __pure const char * iio_device_get_id(const struct iio_device *dev); + + +/** @brief Retrieve the device name (e.g. xadc) + * @param dev A pointer to an iio_device structure + * @return A pointer to a static NULL-terminated string + * + * NOTE: if the device has no name, NULL is returned. */ +__api __check_ret __pure const char * iio_device_get_name(const struct iio_device *dev); + + +/** @brief Retrieve the device label (e.g. lo_pll0_rx_adf4351) + * @param dev A pointer to an iio_device structure + * @return A pointer to a static NULL-terminated string + * + * NOTE: if the device has no label, NULL is returned. */ +__api __check_ret __pure const char * iio_device_get_label(const struct iio_device *dev); + + +/** @brief Enumerate the channels of the given device + * @param dev A pointer to an iio_device structure + * @return The number of channels found */ +__api __check_ret __pure unsigned int iio_device_get_channels_count( + const struct iio_device *dev); + + +/** @brief Enumerate the device-specific attributes of the given device + * @param dev A pointer to an iio_device structure + * @return The number of device-specific attributes found */ +__api __check_ret __pure unsigned int iio_device_get_attrs_count( + const struct iio_device *dev); + +/** @brief Enumerate the buffer-specific attributes of the given device + * @param dev A pointer to an iio_device structure + * @return The number of buffer-specific attributes found */ +__api __check_ret __pure unsigned int iio_device_get_buffer_attrs_count( + const struct iio_device *dev); + +/** @brief Get the channel present at the given index + * @param dev A pointer to an iio_device structure + * @param index The index corresponding to the channel + * @return On success, a pointer to an iio_channel structure + * @return If the index is invalid, NULL is returned */ +__api __check_ret __pure struct iio_channel * iio_device_get_channel( + const struct iio_device *dev, unsigned int index); + + +/** @brief Get the device-specific attribute present at the given index + * @param dev A pointer to an iio_device structure + * @param index The index corresponding to the attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the index is invalid, NULL is returned */ +__api __check_ret __pure const char * iio_device_get_attr( + const struct iio_device *dev, unsigned int index); + +/** @brief Get the buffer-specific attribute present at the given index + * @param dev A pointer to an iio_device structure + * @param index The index corresponding to the attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the index is invalid, NULL is returned */ +__api __check_ret __pure const char * iio_device_get_buffer_attr( + const struct iio_device *dev, unsigned int index); + +/** @brief Try to find a channel structure by its name of ID + * @param dev A pointer to an iio_device structure + * @param name A NULL-terminated string corresponding to the name or the ID of + * the channel to search for + * @param output True if the searched channel is output, False otherwise + * @return On success, a pointer to an iio_channel structure + * @return If the name or ID does not correspond to any known channel of the + * given device, NULL is returned */ +__api __check_ret __pure struct iio_channel * iio_device_find_channel( + const struct iio_device *dev, const char *name, bool output); + + +/** @brief Try to find a device-specific attribute by its name + * @param dev A pointer to an iio_device structure + * @param name A NULL-terminated string corresponding to the name of the + * attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the name does not correspond to any known attribute of the given + * device, NULL is returned + * + * NOTE: This function is useful to detect the presence of an attribute. + * It can also be used to retrieve the name of an attribute as a pointer to a + * static string from a dynamically allocated string. */ +__api __check_ret __pure const char * iio_device_find_attr( + const struct iio_device *dev, const char *name); + +/** @brief Try to find a buffer-specific attribute by its name + * @param dev A pointer to an iio_device structure + * @param name A NULL-terminated string corresponding to the name of the + * attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the name does not correspond to any known attribute of the given + * device, NULL is returned + * + * NOTE: This function is useful to detect the presence of an attribute. + * It can also be used to retrieve the name of an attribute as a pointer to a + * static string from a dynamically allocated string. */ +__api __check_ret __pure const char * iio_device_find_buffer_attr( + const struct iio_device *dev, const char *name); + +/** @brief Read the content of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param dst A pointer to the memory area where the NULL-terminated string + * corresponding to the value read will be stored + * @param len The available length of the memory area, in bytes + * @return On success, the number of bytes written to the buffer + * @return On error, a negative errno code is returned + * + * NOTE:By passing NULL as the "attr" argument to iio_device_attr_read, + * it is now possible to read all of the attributes of a device. + * + * The buffer is filled with one block of data per attribute of the device, + * by the order they appear in the iio_device structure. + * + * The first four bytes of one block correspond to a 32-bit signed value in + * network order. If negative, it corresponds to the errno code that were + * returned when reading the attribute; if positive, it corresponds to the + * length of the data read. In that case, the rest of the block contains + * the data. */ +__api __check_ret ssize_t iio_device_attr_read(const struct iio_device *dev, + const char *attr, char *dst, size_t len); + + +/** @brief Read the content of all device-specific attributes + * @param dev A pointer to an iio_device structure + * @param cb A pointer to a callback function + * @param data A pointer that will be passed to the callback function + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * NOTE: This function is especially useful when used with the network + * backend, as all the device-specific attributes are read in one single + * command. */ +__api __check_ret int iio_device_attr_read_all(struct iio_device *dev, + int (*cb)(struct iio_device *dev, const char *attr, + const char *value, size_t len, void *d), + void *data); + + +/** @brief Read the content of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a bool variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_attr_read_bool(const struct iio_device *dev, + const char *attr, bool *val); + + +/** @brief Read the content of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a long long variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_attr_read_longlong(const struct iio_device *dev, + const char *attr, long long *val); + + +/** @brief Read the content of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a double variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_attr_read_double(const struct iio_device *dev, + const char *attr, double *val); + + +/** @brief Set the value of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param src A NULL-terminated string to set the attribute to + * @return On success, the number of bytes written + * @return On error, a negative errno code is returned + * + * NOTE:By passing NULL as the "attr" argument to iio_device_attr_write, + * it is now possible to write all of the attributes of a device. + * + * The buffer must contain one block of data per attribute of the device, + * by the order they appear in the iio_device structure. + * + * The first four bytes of one block correspond to a 32-bit signed value in + * network order. If negative, the attribute is not written; if positive, + * it corresponds to the length of the data to write. In that case, the rest + * of the block must contain the data. */ +__api __check_ret ssize_t iio_device_attr_write(const struct iio_device *dev, + const char *attr, const char *src); + + +/** @brief Set the value of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param src A pointer to the data to be written + * @param len The number of bytes that should be written + * @return On success, the number of bytes written + * @return On error, a negative errno code is returned */ +__api __check_ret ssize_t iio_device_attr_write_raw(const struct iio_device *dev, + const char *attr, const void *src, size_t len); + + +/** @brief Set the values of all device-specific attributes + * @param dev A pointer to an iio_device structure + * @param cb A pointer to a callback function + * @param data A pointer that will be passed to the callback function + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * NOTE: This function is especially useful when used with the network + * backend, as all the device-specific attributes are written in one single + * command. */ +__api __check_ret int iio_device_attr_write_all(struct iio_device *dev, + ssize_t (*cb)(struct iio_device *dev, + const char *attr, void *buf, size_t len, void *d), + void *data); + + +/** @brief Set the value of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A bool value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_attr_write_bool(const struct iio_device *dev, + const char *attr, bool val); + + +/** @brief Set the value of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A long long value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_attr_write_longlong(const struct iio_device *dev, + const char *attr, long long val); + + +/** @brief Set the value of the given device-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A double value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_attr_write_double(const struct iio_device *dev, + const char *attr, double val); + +/** @brief Read the content of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param dst A pointer to the memory area where the NULL-terminated string + * corresponding to the value read will be stored + * @param len The available length of the memory area, in bytes + * @return On success, the number of bytes written to the buffer + * @return On error, a negative errno code is returned + * + * NOTE:By passing NULL as the "attr" argument to + * iio_device_buffer_attr_read, it is now possible to read all of the attributes + * of a device. + * + * The buffer is filled with one block of data per attribute of the buffer, + * by the order they appear in the iio_device structure. + * + * The first four bytes of one block correspond to a 32-bit signed value in + * network order. If negative, it corresponds to the errno code that were + * returned when reading the attribute; if positive, it corresponds to the + * length of the data read. In that case, the rest of the block contains + * the data. */ +__api __check_ret ssize_t iio_device_buffer_attr_read(const struct iio_device *dev, + const char *attr, char *dst, size_t len); + +/** @brief Read the content of all buffer-specific attributes + * @param dev A pointer to an iio_device structure + * @param cb A pointer to a callback function + * @param data A pointer that will be passed to the callback function + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * NOTE: This function is especially useful when used with the network + * backend, as all the buffer-specific attributes are read in one single + * command. */ +__api __check_ret int iio_device_buffer_attr_read_all(struct iio_device *dev, + int (*cb)(struct iio_device *dev, const char *attr, + const char *value, size_t len, void *d), + void *data); + + +/** @brief Read the content of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a bool variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_buffer_attr_read_bool(const struct iio_device *dev, + const char *attr, bool *val); + + +/** @brief Read the content of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a long long variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_buffer_attr_read_longlong(const struct iio_device *dev, + const char *attr, long long *val); + + +/** @brief Read the content of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a double variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_buffer_attr_read_double(const struct iio_device *dev, + const char *attr, double *val); + + +/** @brief Set the value of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param src A NULL-terminated string to set the attribute to + * @return On success, the number of bytes written + * @return On error, a negative errno code is returned + * + * NOTE:By passing NULL as the "attr" argument to + * iio_device_buffer_attr_write, it is now possible to write all of the + * attributes of a device. + * + * The buffer must contain one block of data per attribute of the buffer, + * by the order they appear in the iio_device structure. + * + * The first four bytes of one block correspond to a 32-bit signed value in + * network order. If negative, the attribute is not written; if positive, + * it corresponds to the length of the data to write. In that case, the rest + * of the block must contain the data. */ +__api __check_ret ssize_t iio_device_buffer_attr_write(const struct iio_device *dev, + const char *attr, const char *src); + + +/** @brief Set the value of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param src A pointer to the data to be written + * @param len The number of bytes that should be written + * @return On success, the number of bytes written + * @return On error, a negative errno code is returned */ +__api __check_ret ssize_t iio_device_buffer_attr_write_raw(const struct iio_device *dev, + const char *attr, const void *src, size_t len); + + +/** @brief Set the values of all buffer-specific attributes + * @param dev A pointer to an iio_device structure + * @param cb A pointer to a callback function + * @param data A pointer that will be passed to the callback function + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * NOTE: This function is especially useful when used with the network + * backend, as all the buffer-specific attributes are written in one single + * command. */ +__api __check_ret int iio_device_buffer_attr_write_all(struct iio_device *dev, + ssize_t (*cb)(struct iio_device *dev, + const char *attr, void *buf, size_t len, void *d), + void *data); + + +/** @brief Set the value of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A bool value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_buffer_attr_write_bool(const struct iio_device *dev, + const char *attr, bool val); + + +/** @brief Set the value of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A long long value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_buffer_attr_write_longlong(const struct iio_device *dev, + const char *attr, long long val); + + +/** @brief Set the value of the given buffer-specific attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A double value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_buffer_attr_write_double(const struct iio_device *dev, + const char *attr, double val); + + +/** @brief Associate a pointer to an iio_device structure + * @param dev A pointer to an iio_device structure + * @param data The pointer to be associated */ +__api void iio_device_set_data(struct iio_device *dev, void *data); + + +/** @brief Retrieve a previously associated pointer of an iio_device structure + * @param dev A pointer to an iio_device structure + * @return The pointer previously associated if present, or NULL */ +__api void * iio_device_get_data(const struct iio_device *dev); + + +/** @brief Retrieve the trigger of a given device + * @param dev A pointer to an iio_device structure + * @param trigger a pointer to a pointer of an iio_device structure. The pointed + * pointer will be set to the address of the iio_device structure corresponding + * to the associated trigger device. + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_get_trigger(const struct iio_device *dev, + const struct iio_device **trigger); + + +/** @brief Associate a trigger to a given device + * @param dev A pointer to an iio_device structure + * @param trigger a pointer to the iio_device structure corresponding to the + * trigger that should be associated. + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_set_trigger(const struct iio_device *dev, + const struct iio_device *trigger); + + +/** @brief Return True if the given device is a trigger + * @param dev A pointer to an iio_device structure + * @return True if the device is a trigger, False otherwise */ +__api __check_ret __pure bool iio_device_is_trigger(const struct iio_device *dev); + +/** @brief Configure the number of kernel buffers for a device + * + * This function allows to change the number of buffers on kernel side. + * @param dev A pointer to an iio_device structure + * @param nb_buffers The number of buffers + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_set_kernel_buffers_count(const struct iio_device *dev, + unsigned int nb_buffers); + +/** @} *//* ------------------------------------------------------------------*/ +/* ------------------------- Channel functions -------------------------------*/ +/** @defgroup Channel Channel + * @{ + * @struct iio_channel + * @brief Represents an input or output channel of a device */ + + +/** @brief Retrieve a pointer to the iio_device structure + * @param chn A pointer to an iio_channel structure + * @return A pointer to an iio_device structure */ +__api __check_ret __pure const struct iio_device * iio_channel_get_device( + const struct iio_channel *chn); + + +/** @brief Retrieve the channel ID (e.g. voltage0) + * @param chn A pointer to an iio_channel structure + * @return A pointer to a static NULL-terminated string */ +__api __check_ret __pure const char * iio_channel_get_id(const struct iio_channel *chn); + + +/** @brief Retrieve the channel name (e.g. vccint) + * @param chn A pointer to an iio_channel structure + * @return A pointer to a static NULL-terminated string + * + * NOTE: if the channel has no name, NULL is returned. */ +__api __check_ret __pure const char * iio_channel_get_name(const struct iio_channel *chn); + + +/** @brief Return True if the given channel is an output channel + * @param chn A pointer to an iio_channel structure + * @return True if the channel is an output channel, False otherwise */ +__api __check_ret __pure bool iio_channel_is_output(const struct iio_channel *chn); + + +/** @brief Return True if the given channel is a scan element + * @param chn A pointer to an iio_channel structure + * @return True if the channel is a scan element, False otherwise + * + * NOTE: a channel that is a scan element is a channel that can + * generate samples (for an input channel) or receive samples (for an output + * channel) after being enabled. */ +__api __check_ret __pure bool iio_channel_is_scan_element(const struct iio_channel *chn); + + +/** @brief Enumerate the channel-specific attributes of the given channel + * @param chn A pointer to an iio_channel structure + * @return The number of channel-specific attributes found */ +__api __check_ret __pure unsigned int iio_channel_get_attrs_count( + const struct iio_channel *chn); + + +/** @brief Get the channel-specific attribute present at the given index + * @param chn A pointer to an iio_channel structure + * @param index The index corresponding to the attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the index is invalid, NULL is returned */ +__api __check_ret __pure const char * iio_channel_get_attr( + const struct iio_channel *chn, unsigned int index); + + +/** @brief Try to find a channel-specific attribute by its name + * @param chn A pointer to an iio_channel structure + * @param name A NULL-terminated string corresponding to the name of the + * attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the name does not correspond to any known attribute of the given + * channel, NULL is returned + * + * NOTE: This function is useful to detect the presence of an attribute. + * It can also be used to retrieve the name of an attribute as a pointer to a + * static string from a dynamically allocated string. */ +__api __check_ret __pure const char * iio_channel_find_attr( + const struct iio_channel *chn, const char *name); + + +/** @brief Retrieve the filename of an attribute + * @param chn A pointer to an iio_channel structure + * @param attr a NULL-terminated string corresponding to the name of the + * attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the attribute name is unknown, NULL is returned */ +__api __check_ret __pure const char * iio_channel_attr_get_filename( + const struct iio_channel *chn, const char *attr); + + +/** @brief Read the content of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param dst A pointer to the memory area where the NULL-terminated string + * corresponding to the value read will be stored + * @param len The available length of the memory area, in bytes + * @return On success, the number of bytes written to the buffer + * @return On error, a negative errno code is returned + * + * NOTE:By passing NULL as the "attr" argument to iio_channel_attr_read, + * it is now possible to read all of the attributes of a channel. + * + * The buffer is filled with one block of data per attribute of the channel, + * by the order they appear in the iio_channel structure. + * + * The first four bytes of one block correspond to a 32-bit signed value in + * network order. If negative, it corresponds to the errno code that were + * returned when reading the attribute; if positive, it corresponds to the + * length of the data read. In that case, the rest of the block contains + * the data. */ +__api __check_ret ssize_t iio_channel_attr_read(const struct iio_channel *chn, + const char *attr, char *dst, size_t len); + + +/** @brief Read the content of all channel-specific attributes + * @param chn A pointer to an iio_channel structure + * @param cb A pointer to a callback function + * @param data A pointer that will be passed to the callback function + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * NOTE: This function is especially useful when used with the network + * backend, as all the channel-specific attributes are read in one single + * command. */ +__api __check_ret int iio_channel_attr_read_all(struct iio_channel *chn, + int (*cb)(struct iio_channel *chn, + const char *attr, const char *val, size_t len, void *d), + void *data); + + +/** @brief Read the content of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a bool variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_channel_attr_read_bool(const struct iio_channel *chn, + const char *attr, bool *val); + + +/** @brief Read the content of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a long long variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_channel_attr_read_longlong(const struct iio_channel *chn, + const char *attr, long long *val); + + +/** @brief Read the content of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A pointer to a double variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_channel_attr_read_double(const struct iio_channel *chn, + const char *attr, double *val); + + +/** @brief Set the value of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param src A NULL-terminated string to set the attribute to + * @return On success, the number of bytes written + * @return On error, a negative errno code is returned + * + * NOTE:By passing NULL as the "attr" argument to iio_channel_attr_write, + * it is now possible to write all of the attributes of a channel. + * + * The buffer must contain one block of data per attribute of the channel, + * by the order they appear in the iio_channel structure. + * + * The first four bytes of one block correspond to a 32-bit signed value in + * network order. If negative, the attribute is not written; if positive, + * it corresponds to the length of the data to write. In that case, the rest + * of the block must contain the data. */ +__api __check_ret ssize_t iio_channel_attr_write(const struct iio_channel *chn, + const char *attr, const char *src); + + +/** @brief Set the value of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param src A pointer to the data to be written + * @param len The number of bytes that should be written + * @return On success, the number of bytes written + * @return On error, a negative errno code is returned */ +__api __check_ret ssize_t iio_channel_attr_write_raw(const struct iio_channel *chn, + const char *attr, const void *src, size_t len); + + +/** @brief Set the values of all channel-specific attributes + * @param chn A pointer to an iio_channel structure + * @param cb A pointer to a callback function + * @param data A pointer that will be passed to the callback function + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * NOTE: This function is especially useful when used with the network + * backend, as all the channel-specific attributes are written in one single + * command. */ +__api __check_ret int iio_channel_attr_write_all(struct iio_channel *chn, + ssize_t (*cb)(struct iio_channel *chn, + const char *attr, void *buf, size_t len, void *d), + void *data); + + +/** @brief Set the value of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A bool value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_channel_attr_write_bool(const struct iio_channel *chn, + const char *attr, bool val); + + +/** @brief Set the value of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A long long value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_channel_attr_write_longlong(const struct iio_channel *chn, + const char *attr, long long val); + + +/** @brief Set the value of the given channel-specific attribute + * @param chn A pointer to an iio_channel structure + * @param attr A NULL-terminated string corresponding to the name of the + * attribute + * @param val A double value to set the attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_channel_attr_write_double(const struct iio_channel *chn, + const char *attr, double val); + + +/** @brief Enable the given channel + * @param chn A pointer to an iio_channel structure + * + * NOTE:Before creating an iio_buffer structure with + * iio_device_create_buffer, it is required to enable at least one channel of + * the device to read from. */ +__api void iio_channel_enable(struct iio_channel *chn); + + +/** @brief Disable the given channel + * @param chn A pointer to an iio_channel structure */ +__api void iio_channel_disable(struct iio_channel *chn); + + +/** @brief Returns True if the channel is enabled + * @param chn A pointer to an iio_channel structure + * @return True if the channel is enabled, False otherwise */ +__api __check_ret bool iio_channel_is_enabled(const struct iio_channel *chn); + + +/** @brief Demultiplex the samples of a given channel + * @param chn A pointer to an iio_channel structure + * @param buffer A pointer to an iio_buffer structure + * @param dst A pointer to the memory area where the demultiplexed data will be + * stored + * @param len The available length of the memory area, in bytes + * @return The size of the demultiplexed data, in bytes */ +__api __check_ret size_t iio_channel_read_raw(const struct iio_channel *chn, + struct iio_buffer *buffer, void *dst, size_t len); + + +/** @brief Demultiplex and convert the samples of a given channel + * @param chn A pointer to an iio_channel structure + * @param buffer A pointer to an iio_buffer structure + * @param dst A pointer to the memory area where the converted data will be + * stored + * @param len The available length of the memory area, in bytes + * @return The size of the converted data, in bytes */ +__api __check_ret size_t iio_channel_read(const struct iio_channel *chn, + struct iio_buffer *buffer, void *dst, size_t len); + + +/** @brief Multiplex the samples of a given channel + * @param chn A pointer to an iio_channel structure + * @param buffer A pointer to an iio_buffer structure + * @param src A pointer to the memory area where the sequential data will + * be read from + * @param len The length of the memory area, in bytes + * @return The number of bytes actually multiplexed */ +__api __check_ret size_t iio_channel_write_raw(const struct iio_channel *chn, + struct iio_buffer *buffer, const void *src, size_t len); + + +/** @brief Convert and multiplex the samples of a given channel + * @param chn A pointer to an iio_channel structure + * @param buffer A pointer to an iio_buffer structure + * @param src A pointer to the memory area where the sequential data will + * be read from + * @param len The length of the memory area, in bytes + * @return The number of bytes actually converted and multiplexed */ +__api __check_ret size_t iio_channel_write(const struct iio_channel *chn, + struct iio_buffer *buffer, const void *src, size_t len); + + +/** @brief Associate a pointer to an iio_channel structure + * @param chn A pointer to an iio_channel structure + * @param data The pointer to be associated */ +__api void iio_channel_set_data(struct iio_channel *chn, void *data); + + +/** @brief Retrieve a previously associated pointer of an iio_channel structure + * @param chn A pointer to an iio_channel structure + * @return The pointer previously associated if present, or NULL */ +__api void * iio_channel_get_data(const struct iio_channel *chn); + + +/** @brief Get the type of the given channel + * @param chn A pointer to an iio_channel structure + * @return The type of the channel */ +__api __check_ret __pure enum iio_chan_type iio_channel_get_type( + const struct iio_channel *chn); + + +/** @brief Get the modifier type of the given channel + * @param chn A pointer to an iio_channel structure + * @return The modifier type of the channel */ +__api __check_ret __pure enum iio_modifier iio_channel_get_modifier( + const struct iio_channel *chn); + + +/** @} *//* ------------------------------------------------------------------*/ +/* ------------------------- Buffer functions --------------------------------*/ +/** @defgroup Buffer Buffer + * @{ + * @struct iio_buffer + * @brief An input or output buffer, used to read or write samples */ + + +/** @brief Retrieve a pointer to the iio_device structure + * @param buf A pointer to an iio_buffer structure + * @return A pointer to an iio_device structure */ +__api __check_ret __pure const struct iio_device * iio_buffer_get_device( + const struct iio_buffer *buf); + + +/** @brief Create an input or output buffer associated to the given device + * @param dev A pointer to an iio_device structure + * @param samples_count The number of samples that the buffer should contain + * @param cyclic If True, enable cyclic mode + * @return On success, a pointer to an iio_buffer structure + * @return On error, NULL is returned, and errno is set to the error code + * + * NOTE: Channels that have to be written to / read from must be enabled + * before creating the buffer. */ +__api __check_ret struct iio_buffer * iio_device_create_buffer(const struct iio_device *dev, + size_t samples_count, bool cyclic); + + +/** @brief Destroy the given buffer + * @param buf A pointer to an iio_buffer structure + * + * NOTE: After that function, the iio_buffer pointer shall be invalid. */ +__api void iio_buffer_destroy(struct iio_buffer *buf); + +/** @brief Get a pollable file descriptor + * + * Can be used to know when iio_buffer_refill() or iio_buffer_push() can be + * called + * @param buf A pointer to an iio_buffer structure + * @return On success, valid file descriptor + * @return On error, a negative errno code is returned + */ +__api __check_ret int iio_buffer_get_poll_fd(struct iio_buffer *buf); + +/** @brief Make iio_buffer_refill() and iio_buffer_push() blocking or not + * + * After this function has been called with blocking == false, + * iio_buffer_refill() and iio_buffer_push() will return -EAGAIN if no data is + * ready. + * A device is blocking by default. + * @param buf A pointer to an iio_buffer structure + * @param blocking true if the buffer API should be blocking, else false + * @return On success, 0 + * @return On error, a negative errno code is returned + */ +__api __check_ret int iio_buffer_set_blocking_mode(struct iio_buffer *buf, bool blocking); + + +/** @brief Fetch more samples from the hardware + * @param buf A pointer to an iio_buffer structure + * @return On success, the number of bytes read is returned + * @return On error, a negative errno code is returned + * + * NOTE: Only valid for input buffers */ +__api __check_ret ssize_t iio_buffer_refill(struct iio_buffer *buf); + + +/** @brief Send the samples to the hardware + * @param buf A pointer to an iio_buffer structure + * @return On success, the number of bytes written is returned + * @return On error, a negative errno code is returned + * + * NOTE: Only valid for output buffers */ +__api __check_ret ssize_t iio_buffer_push(struct iio_buffer *buf); + + +/** @brief Send a given number of samples to the hardware + * @param buf A pointer to an iio_buffer structure + * @param samples_count The number of samples to submit + * @return On success, the number of bytes written is returned + * @return On error, a negative errno code is returned + * + * NOTE: Only valid for output buffers */ +__api __check_ret ssize_t iio_buffer_push_partial(struct iio_buffer *buf, + size_t samples_count); + +/** @brief Cancel all buffer operations + * @param buf The buffer for which operations should be canceled + * + * This function cancels all outstanding buffer operations previously scheduled. + * This means any pending iio_buffer_push() or iio_buffer_refill() operation + * will abort and return immediately, any further invocations of these functions + * on the same buffer will return immediately with an error. + * + * Usually iio_buffer_push() and iio_buffer_refill() will block until either all + * data has been transferred or a timeout occurs. This can depending on the + * configuration take a significant amount of time. iio_buffer_cancel() is + * useful to bypass these conditions if the buffer operation is supposed to be + * stopped in response to an external event (e.g. user input). + * + * To be able to capture additional data after calling this function the buffer + * should be destroyed and then re-created. + * + * This function can be called multiple times for the same buffer, but all but + * the first invocation will be without additional effect. + * + * This function is thread-safe, but not signal-safe, i.e. it must not be called + * from a signal handler. + */ +__api void iio_buffer_cancel(struct iio_buffer *buf); + + +/** @brief Get the start address of the buffer + * @param buf A pointer to an iio_buffer structure + * @return A pointer corresponding to the start address of the buffer */ +__api void * iio_buffer_start(const struct iio_buffer *buf); + + +/** @brief Find the first sample of a channel in a buffer + * @param buf A pointer to an iio_buffer structure + * @param chn A pointer to an iio_channel structure + * @return A pointer to the first sample found, or to the end of the buffer if + * no sample for the given channel is present in the buffer + * + * NOTE: This function, coupled with iio_buffer_step and iio_buffer_end, + * can be used to iterate on all the samples of a given channel present in the + * buffer, doing the following: + * + * @verbatim + for (void *ptr = iio_buffer_first(buffer, chn); ptr < iio_buffer_end(buffer); ptr += iio_buffer_step(buffer)) { + .... + } + @endverbatim */ +__api void * iio_buffer_first(const struct iio_buffer *buf, + const struct iio_channel *chn); + + +/** @brief Get the step size between two samples of one channel + * @param buf A pointer to an iio_buffer structure + * @return the difference between the addresses of two consecutive samples of + * one same channel */ +__api __check_ret ptrdiff_t iio_buffer_step(const struct iio_buffer *buf); + + +/** @brief Get the address that follows the last sample in a buffer + * @param buf A pointer to an iio_buffer structure + * @return A pointer corresponding to the address that follows the last sample + * present in the buffer */ +__api void * iio_buffer_end(const struct iio_buffer *buf); + + +/** @brief Call the supplied callback for each sample found in a buffer + * @param buf A pointer to an iio_buffer structure + * @param callback A pointer to a function to call for each sample found + * @param data A user-specified pointer that will be passed to the callback + * @return number of bytes processed. + * + * NOTE: The callback receives four arguments: + * * A pointer to the iio_channel structure corresponding to the sample, + * * A pointer to the sample itself, + * * The length of the sample in bytes, + * * The user-specified pointer passed to iio_buffer_foreach_sample. */ +__api __check_ret ssize_t iio_buffer_foreach_sample(struct iio_buffer *buf, + ssize_t (*callback)(const struct iio_channel *chn, + void *src, size_t bytes, void *d), void *data); + + +/** @brief Associate a pointer to an iio_buffer structure + * @param buf A pointer to an iio_buffer structure + * @param data The pointer to be associated */ +__api void iio_buffer_set_data(struct iio_buffer *buf, void *data); + + +/** @brief Retrieve a previously associated pointer of an iio_buffer structure + * @param buf A pointer to an iio_buffer structure + * @return The pointer previously associated if present, or NULL */ +__api void * iio_buffer_get_data(const struct iio_buffer *buf); + +/** @} *//* ------------------------------------------------------------------*/ +/* ---------------------------- HWMON support --------------------------------*/ +/** @defgroup Hwmon Compatibility with hardware monitoring (hwmon) devices + * @{ + * @enum hwmon_chan_type + * @brief Hwmon channel type + * + * Libiio support hardware-monitoring (hwmon) devices as well. This enum + * specifies the type of data associated with the hwmon channel. + * + * NOTE: as of 2021 only the current hwmon API is supported. The old + * and deprecated APIs are not supported, and won't be supported unless we + * have a case where updating a hwmon driver is not possible. + */ +enum hwmon_chan_type { + HWMON_VOLTAGE, + HWMON_FAN, + HWMON_PWM, + HWMON_TEMP, + HWMON_CURRENT, + HWMON_POWER, + HWMON_ENERGY, + HWMON_HUMIDITY, + HWMON_INTRUSION, + HWMON_CHAN_TYPE_UNKNOWN = IIO_CHAN_TYPE_UNKNOWN, +}; + +/** + * @brief Get the type of the given hwmon channel + * @param chn A pointer to an iio_channel structure + * @return The type of the hwmon channel */ +static inline enum hwmon_chan_type +hwmon_channel_get_type(const struct iio_channel *chn) +{ + return (enum hwmon_chan_type) iio_channel_get_type(chn); +} + +/** + * @brief Get whether or not the device is a hardware monitoring device + * @param dev A pointer to an iio_device structure + * @return True if the device is a hardware monitoring device, + * false if it is a IIO device */ +static inline bool iio_device_is_hwmon(const struct iio_device *dev) +{ + const char *id = iio_device_get_id(dev); + + return id[0] == 'h'; +} + + +/** @} *//* ------------------------------------------------------------------*/ +/* ------------------------- Low-level functions -----------------------------*/ +/** @defgroup Debug Debug and low-level functions + * @{ + * @struct iio_data_format + * @brief Contains the format of a data sample. + * + * The different fields inform about the correct way to convert one sample from + * its raw format (as read from / generated by the hardware) to its real-world + * value. + */ +struct iio_data_format { + /** @brief Total length of the sample, in bits */ + unsigned int length; + + /** @brief Length of valuable data in the sample, in bits */ + unsigned int bits; + + /** @brief Right-shift to apply when converting sample */ + unsigned int shift; + + /** @brief Contains True if the sample is signed */ + bool is_signed; + + /** @brief Contains True if the sample is fully defined, sign extended, etc. */ + bool is_fully_defined; + + /** @brief Contains True if the sample is in big-endian format */ + bool is_be; + + /** @brief Contains True if the sample should be scaled when converted */ + bool with_scale; + + /** @brief Contains the scale to apply if with_scale is set */ + double scale; + + /** @brief Number of times length repeats (added in v0.8) */ + unsigned int repeat; +}; + + +/** @brief Get the current sample size + * @param dev A pointer to an iio_device structure + * @return On success, the sample size in bytes + * @return On error, a negative errno code is returned + * + * NOTE: The sample size is not constant and will change when channels + * get enabled or disabled. */ +__api __check_ret ssize_t iio_device_get_sample_size(const struct iio_device *dev); + + +/** @brief Get the index of the given channel + * @param chn A pointer to an iio_channel structure + * @return On success, the index of the specified channel + * @return On error, a negative errno code is returned */ +__api __check_ret __pure long iio_channel_get_index(const struct iio_channel *chn); + + +/** @brief Get a pointer to a channel's data format structure + * @param chn A pointer to an iio_channel structure + * @return A pointer to the channel's iio_data_format structure */ +__api __check_ret __cnst const struct iio_data_format * iio_channel_get_data_format( + const struct iio_channel *chn); + + +/** @brief Convert the sample from hardware format to host format + * @param chn A pointer to an iio_channel structure + * @param dst A pointer to the destination buffer where the converted sample + * should be written + * @param src A pointer to the source buffer containing the sample */ +__api void iio_channel_convert(const struct iio_channel *chn, + void *dst, const void *src); + + +/** @brief Convert the sample from host format to hardware format + * @param chn A pointer to an iio_channel structure + * @param dst A pointer to the destination buffer where the converted sample + * should be written + * @param src A pointer to the source buffer containing the sample */ +__api void iio_channel_convert_inverse(const struct iio_channel *chn, + void *dst, const void *src); + + +/** @brief Enumerate the debug attributes of the given device + * @param dev A pointer to an iio_device structure + * @return The number of debug attributes found */ +__api __check_ret __pure unsigned int iio_device_get_debug_attrs_count( + const struct iio_device *dev); + + +/** @brief Get the debug attribute present at the given index + * @param dev A pointer to an iio_device structure + * @param index The index corresponding to the debug attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the index is invalid, NULL is returned */ +__api __check_ret __pure const char * iio_device_get_debug_attr( + const struct iio_device *dev, unsigned int index); + + +/** @brief Try to find a debug attribute by its name + * @param dev A pointer to an iio_device structure + * @param name A NULL-terminated string corresponding to the name of the + * debug attribute + * @return On success, a pointer to a static NULL-terminated string + * @return If the name does not correspond to any known debug attribute of the + * given device, NULL is returned + * + * NOTE: This function is useful to detect the presence of a debug + * attribute. + * It can also be used to retrieve the name of a debug attribute as a pointer + * to a static string from a dynamically allocated string. */ +__api __check_ret __pure const char * iio_device_find_debug_attr( + const struct iio_device *dev, const char *name); + + +/** @brief Read the content of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param dst A pointer to the memory area where the NULL-terminated string + * corresponding to the value read will be stored + * @param len The available length of the memory area, in bytes + * @return On success, the number of bytes written to the buffer + * @return On error, a negative errno code is returned + * + * NOTE:By passing NULL as the "attr" argument to + * iio_device_debug_attr_read, it is now possible to read all of the debug + * attributes of a device. + * + * The buffer is filled with one block of data per debug attribute of the + * device, by the order they appear in the iio_device structure. + * + * The first four bytes of one block correspond to a 32-bit signed value in + * network order. If negative, it corresponds to the errno code that were + * returned when reading the debug attribute; if positive, it corresponds + * to the length of the data read. In that case, the rest of the block contains + * the data. */ +__api __check_ret ssize_t iio_device_debug_attr_read(const struct iio_device *dev, + const char *attr, char *dst, size_t len); + + +/** @brief Read the content of all debug attributes + * @param dev A pointer to an iio_device structure + * @param cb A pointer to a callback function + * @param data A pointer that will be passed to the callback function + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * NOTE: This function is especially useful when used with the network + * backend, as all the debug attributes are read in one single command. */ +__api __check_ret int iio_device_debug_attr_read_all(struct iio_device *dev, + int (*cb)(struct iio_device *dev, const char *attr, + const char *value, size_t len, void *d), + void *data); + + +/** @brief Set the value of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param src A NULL-terminated string to set the debug attribute to + * @return On success, the number of bytes written + * @return On error, a negative errno code is returned + * + * NOTE:By passing NULL as the "attr" argument to + * iio_device_debug_attr_write, it is now possible to write all of the + * debug attributes of a device. + * + * The buffer must contain one block of data per debug attribute of the device, + * by the order they appear in the iio_device structure. + * + * The first four bytes of one block correspond to a 32-bit signed value in + * network order. If negative, the debug attribute is not written; if positive, + * it corresponds to the length of the data to write. In that case, the rest + * of the block must contain the data. */ +__api __check_ret ssize_t iio_device_debug_attr_write(const struct iio_device *dev, + const char *attr, const char *src); + + +/** @brief Set the value of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param src A pointer to the data to be written + * @param len The number of bytes that should be written + * @return On success, the number of bytes written + * @return On error, a negative errno code is returned */ +__api __check_ret ssize_t iio_device_debug_attr_write_raw(const struct iio_device *dev, + const char *attr, const void *src, size_t len); + + +/** @brief Set the values of all debug attributes + * @param dev A pointer to an iio_device structure + * @param cb A pointer to a callback function + * @param data A pointer that will be passed to the callback function + * @return On success, 0 is returned + * @return On error, a negative errno code is returned + * + * NOTE: This function is especially useful when used with the network + * backend, as all the debug attributes are written in one single command. */ +__api __check_ret int iio_device_debug_attr_write_all(struct iio_device *dev, + ssize_t (*cb)(struct iio_device *dev, + const char *attr, void *buf, size_t len, void *d), + void *data); + + +/** @brief Read the content of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param val A pointer to a bool variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_debug_attr_read_bool(const struct iio_device *dev, + const char *attr, bool *val); + + +/** @brief Read the content of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param val A pointer to a long long variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_debug_attr_read_longlong(const struct iio_device *dev, + const char *attr, long long *val); + + +/** @brief Read the content of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param val A pointer to a double variable where the value should be stored + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_debug_attr_read_double(const struct iio_device *dev, + const char *attr, double *val); + + +/** @brief Set the value of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param val A bool value to set the debug attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_debug_attr_write_bool(const struct iio_device *dev, + const char *attr, bool val); + + +/** @brief Set the value of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param val A long long value to set the debug attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_debug_attr_write_longlong(const struct iio_device *dev, + const char *attr, long long val); + + +/** @brief Set the value of the given debug attribute + * @param dev A pointer to an iio_device structure + * @param attr A NULL-terminated string corresponding to the name of the + * debug attribute + * @param val A double value to set the debug attribute to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_debug_attr_write_double(const struct iio_device *dev, + const char *attr, double val); + + +/** @brief Identify the channel or debug attribute corresponding to a filename + * @param dev A pointer to an iio_device structure + * @param filename A NULL-terminated string corresponding to the filename + * @param chn A pointer to a pointer of an iio_channel structure. The pointed + * pointer will be set to the address of the iio_channel structure if the + * filename correspond to the attribute of a channel, or NULL otherwise. + * @param attr A pointer to a NULL-terminated string. The pointer + * pointer will be set to point to the name of the attribute corresponding to + * the filename. + * @return On success, 0 is returned, and *chn and *attr are modified. + * @return On error, a negative errno code is returned. *chn and *attr are not + * modified. */ +__api __check_ret int iio_device_identify_filename(const struct iio_device *dev, + const char *filename, struct iio_channel **chn, + const char **attr); + + +/** @brief Set the value of a hardware register + * @param dev A pointer to an iio_device structure + * @param address The address of the register + * @param value The value to set the register to + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_reg_write(struct iio_device *dev, + uint32_t address, uint32_t value); + + +/** @brief Get the value of a hardware register + * @param dev A pointer to an iio_device structure + * @param address The address of the register + * @param value A pointer to the variable where the value will be written + * @return On success, 0 is returned + * @return On error, a negative errno code is returned */ +__api __check_ret int iio_device_reg_read(struct iio_device *dev, + uint32_t address, uint32_t *value); + + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#undef __api + +#endif /* __IIO_H__ */