Verified against FlexLib 4.2.20.41343 (2026-08-02). Every API member referenced on this page was checked against the 4.2.20 source: the member exists and is declared on the type used here. Prose describing behavior and semantics has not been re-read against the source, and the examples have not been compiled.
This document provides an overview of FlexLib's architecture, design patterns, and internal workings.
- System Overview
- Core Components
- Design Patterns
- Threading Model
- Network Protocol
- Data Flow
- Best Practices
FlexLib is a client library that communicates with FlexRadio software-defined radios over TCP/IP networks. It provides a high-level C# API for radio control, audio streaming, and telemetry monitoring.
┌─────────────────────────────────────────────────┐
│ Your Application │
│ (Console, WPF, WinForms, Service) │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ FlexLib API │
│ ┌─────────┬──────────┬────────────┬─────────┐ │
│ │ API │ Radio │ Slice │ Meter │ │
│ │ Class │ Class │ Class │ Class │ │
│ └─────────┴──────────┴────────────┴─────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ Discovery & Communication Layer │ │
│ └─────────────────────────────────────────┘ │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Network Layer │
│ ┌──────────┬────────────┬──────────────────┐ │
│ │ UDP │ TCP │ VITA-49 │ │
│ │Discovery │ Commands │ Streaming │ │
│ └──────────┴────────────┴──────────────────┘ │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ FlexRadio Device │
│ (FLEX-6300, 6400, 6600, etc.) │
└─────────────────────────────────────────────────┘
- Application Layer: Your code using FlexLib
- API Layer: High-level classes (Radio, Slice, Meter, etc.)
- Communication Layer: Network protocol handling
- Transport Layer: UDP/TCP/VITA-49 protocols
- Hardware Layer: FlexRadio SDR device
Purpose: Central initialization and radio management.
Responsibilities:
- Initialize discovery system
- Maintain list of discovered radios
- Manage radio lifecycle
- Provide static access to radios
Key Members:
public static void Init()
public static List<Radio> RadioList
public static void CloseSession()
public static event RadioAddedEventHandler RadioAdded
public static event RadioRemovedEventHandler RadioRemovedUsage Pattern:
API.Init(); // Start discovery
var radios = API.RadioList; // Get discovered radios
API.CloseSession(); // CleanupPurpose: Represents a single FlexRadio device.
Responsibilities:
- Manage connection to radio
- Handle command/response protocol
- Maintain radio state
- Manage child objects (Slices, Panadapters, etc.)
- Process status updates
- Handle audio/data streams
Key Collections:
public List<Slice> SliceList
public List<Panadapter> PanadapterList
public List<Memory> MemoryList
public List<TNF> TNFList
public List<Amplifier> AmplifierListThese are List<T>, not ObservableCollection<T>. Do not bind to them
expecting collection-change notifications; use the SliceAdded /
SliceRemoved style events instead.
There is no Radio.MeterList and no Radio.WaterfallList. Meters are
looked up by name (see the Meter Class section below); waterfalls are
reached through their panadapter.
State Properties:
public bool Connected // Connection status
public string ConnectedState // e.g. "Available", "In Use"
public string Nickname // User-defined name
public string Model // Radio model
public string Serial // Serial number
public IPAddress IP // Network address
public ulong Version // Firmware version (not a string)
public string Status // Current statusDesign: Implements INotifyPropertyChanged for real-time updates.
Purpose: Represents a receiver/transmitter channel.
Responsibilities:
- Frequency control
- Mode selection
- Filter configuration
- Audio routing
- PTT control
- Antenna selection
Key Properties:
public double Freq // Frequency in MHz
public string DemodMode // Operating mode; valid values in ModeList
public int FilterLow // Low-pass filter Hz
public int FilterHigh // High-pass filter Hz
public bool Active // Slice active state
public bool IsTransmitSlice // Whether this slice is the TX slice
public string RXAnt // RX antenna
public string TXAnt // TX antennaThe mode property is DemodMode, not Mode. There is no Slice.Transmit
for keying: PTT is Radio.Mox. IsTransmitSlice reports which slice
transmits, it does not key the radio.
Lifecycle:
- Created by
Radio.RequestSlice() - Configured via property setters
- Monitored via
PropertyChangedevents - Removed by
slice.Close(), not by a method onRadio
Purpose: Discover FlexRadio devices on the network.
Mechanism:
- Opens UDP socket on port 4992
- Sends discovery packets periodically
- Listens for radio announcements
- Parses discovery information
- Notifies API class of new radios
Protocol:
Radio → UDP Broadcast → Port 4992
Format: Key=Value pairs
Example: "model=FLEX-6600 serial=1234-5678-9012-3456 ..."
Discovery Packet Fields:
discovery_protocol_versionmodelserialversionnicknameipportstatus
Purpose: Receive audio from radio for playback or processing.
Characteristics:
- Mono audio stream
- 24kHz, 48kHz, or 96kHz sample rate
- 32-bit float samples
- VITA-49 packet protocol
Usage:
radio.DAXRXAudioStreamAdded += (audioStream) =>
{
// DataReadyEventHandler signature: (RXAudioStream stream, float[] rx_data)
audioStream.DataReady += (stream, rx_data) => ProcessAudio(rx_data);
};
radio.RequestDAXRXAudioStream(1); // DAX channel 1Purpose: Send audio to radio for transmission.
Usage: Provide audio samples for transmission.
Purpose: IQ data streaming for digital modes.
Characteristics:
- Interleaved I/Q samples
- High sample rate (up to 192kHz)
- Used for SDR applications
Purpose: Real-time telemetry monitoring.
Types of Meters:
Meter names are exact strings the radio reports. The ones the library matches on include:
- Power:
FWDPWR,REFPWR,SWR,PAEFF - Audio and drive:
LEVEL,MIC,MICPEAK,COMPPEAK,HWALC - System:
PATEMP,+13.8A
A lookup with a name the radio does not report returns null rather
than throwing, so a typo fails silently.
Usage Pattern:
Radio exposes no MeterAdded event and no Value property on
Meter. Look the meter up by name, then subscribe to DataReady:
Meter fwd = radio.FindMeterByName("FWDPWR");
if (fwd != null)
{
// DataReadyEventHandler signature: (Meter meter, float data)
fwd.DataReady += (m, value) =>
{
Console.WriteLine($"{m.Name}: {value} ({m.Units})");
};
}MeterAdded does exist on Slice, Amplifier, and Tuner, each carrying
its own owner type. On Slice the signature
(Slice slc, Meter m), for meters that appear as those objects are
created:
slice.MeterAdded += (slc, m) =>
{
m.DataReady += (meter, value) => Console.WriteLine($"{meter.Name}: {value}");
};Usage: Extensive use of C# events for state changes.
Implementation:
// INotifyPropertyChanged for object property changes
public event PropertyChangedEventHandler PropertyChanged;
// Custom events for object lifecycle
public event SliceAddedEventHandler SliceAdded;
public event SliceRemovedEventHandler SliceRemoved;
// Data events for streaming
public event DataReadyEventHandler DataReady;Benefits:
- Decoupled architecture
- Real-time updates
- Event-driven programming model
- Natural fit for UI applications
Usage: Radio command/response protocol.
Implementation:
// Internal command structure
class RadioCommand
{
public string Command { get; set; }
public int SequenceNumber { get; set; }
public TaskCompletionSource<string> Response { get; set; }
}
// Example usage internally:
SendCommand("slice tune " + index + " " + freq);Protocol Format:
Client → Radio: "C<seq>|<command> <args>"
Radio → Client: "R<seq>|<result>"
Example:
Client: "C1|slice create"
Radio: "R1|0|slice 0"
Usage: Creating streaming objects and connections.
Examples:
// Request methods on Radio class (streams arrive via Added events)
public void RequestDAXRXAudioStream(int channel); // → DAXRXAudioStreamAdded
public void RequestDAXIQStream(int channel); // → DAXIQStreamAdded
public void RequestRXRemoteAudioStream(); // → RXRemoteAudioStreamAdded
public TXRemoteAudioStream CreateOpusStream(); // returns directlyBenefits:
- Centralized object creation
- Proper initialization
- Resource management
Integration: FlexLib supports MVVM via ObservableObject base class.
Provided by: UiWpfFramework
Usage:
// Radio, Slice, and other classes inherit from ObservableObject
public class Slice : ObservableObject
{
private double _freq;
public double Freq
{
get { return _freq; }
set
{
if (_freq != value)
{
_freq = value;
RaisePropertyChanged("Freq");
}
}
}
}Benefits for WPF/UI:
- Data binding support
- Automatic UI updates
- Reduced boilerplate code
- UI Thread: Your application's main thread
- Discovery Thread: UDP discovery listener
- Command Thread: TCP command socket reader
- VITA Thread(s): Audio/IQ packet processors
- Meter Thread: Meter data processing
Event Callbacks: FlexLib events fire on background threads, not the UI thread.
Important: For WPF/UI updates, use Dispatcher.Invoke():
radio.PropertyChanged += (sender, e) =>
{
// This runs on a background thread!
Application.Current.Dispatcher.Invoke(() =>
{
// Now safe to update UI
statusLabel.Content = radio.Status;
});
};FlexLib uses:
lockstatements for critical sectionsConcurrentDictionaryfor thread-safe collectionsImmutableListfor read-only collectionsObservableCollectionwith proper locking
Port: 4992
Type: UDP Broadcast
Direction: Radio → Client
Packet Format:
discovery_protocol_version=3.0.0.2
model=FLEX-6600
serial=1234-5678-9012-3456
version=3.2.39
nickname=K5DTO Station
ip=192.168.1.100
port=4992
status=Available
...
Timing: Radios broadcast every 5-10 seconds.
Port: 4992
Type: TCP
Format: ASCII text, newline-terminated
Command Format:
C<sequence>|<command> <args>
Response Format:
R<sequence>|<result_code>|<data>
Status Updates (unsolicited):
S<hex_handle>|<object_type> <properties>
Examples:
# Create a slice
Client: C1|slice create
Radio: R1|0|slice 0
# Tune slice
Client: C2|slice tune 0 14.200
Radio: R2|0
# Status update
Radio: S12345678|slice 0 freq=14.200000 mode=USB
Result Codes:
0: Success- Non-zero: Error (with error message)
Purpose: High-performance audio/IQ streaming.
Characteristics:
- UDP packets
- Binary format
- Header + payload structure
- Sequence numbers for packet loss detection
- Timestamps for synchronization
Packet Structure:
┌────────────────┐
│ VITA Header │ 28 bytes
├────────────────┤
│ Audio/IQ Data │ Variable
└────────────────┘
Stream Types:
- Audio: 32-bit float PCM
- IQ: Interleaved I/Q samples
- DAX: Multiple channels possible
1. Application calls API.Init()
↓
2. Discovery starts, sends UDP broadcasts
↓
3. Radio responds with discovery packet
↓
4. Radio object created, added to RadioList
↓
5. RadioAdded event fired
↓
6. Application calls radio.Connect()
↓
7. TCP connection established
↓
8. Initial status updates received
↓
9. Connected property set to true
↓
10. Application can now control radio
1. Application calls radio.RequestSlice()
↓
2. Command sent: "C<seq>|slice create"
↓
3. Radio responds: "R<seq>|0|slice 0"
↓
4. Radio sends status: "S...|slice 0 freq=14.200 ..."
↓
5. Slice object created
↓
6. SliceAdded event fired
↓
7. Slice appears in radio.SliceList
1. Application sets: slice.Freq = 14.200
↓
2. Property setter checks if value changed
↓
3. Command sent: "C<seq>|slice tune 0 14.200"
↓
4. Radio responds: "R<seq>|0"
↓
5. Radio applies change
↓
6. Radio sends status: "S...|slice 0 freq=14.200000"
↓
7. Status parser updates local property
↓
8. PropertyChanged event fired
↓
9. UI/application updates
✅ Do:
// Initialize once at startup
API.ProgramName = "MyApp";
API.Init();
// Subscribe to events before Init()
API.RadioAdded += OnRadioAdded;
API.Init();❌ Don't:
// Don't call Init() multiple times
API.Init();
API.Init(); // BAD!
// Don't forget to set ProgramName
API.Init(); // Missing ProgramName✅ Do:
// Check availability before connecting
if (radio.ConnectedState == "Available" && !radio.Connected)
{
radio.Connect();
}
// Wait for connection with timeout
for (int i = 0; i < 50 && !radio.Connected; i++)
await Task.Delay(100);❌ Don't:
// Don't assume immediate connection
radio.Connect();
var slice = radio.SliceList.First(); // May fail!
// Don't connect without checking
radio.Connect(); // Maybe already connected or unavailable✅ Do:
// Use PropertyChanged for updates
slice.PropertyChanged += (s, e) =>
{
if (e.PropertyName == "Freq")
Console.WriteLine($"Frequency: {slice.Freq}");
};
// Batch related changes
slice.Freq = 14.200;
slice.DemodMode = "USB";
slice.FilterLow = 200;
slice.FilterHigh = 2800;❌ Don't:
// Don't poll properties
while (true)
{
Console.WriteLine(slice.Freq); // Inefficient!
Thread.Sleep(100);
}✅ Do:
// Use Dispatcher for UI updates
radio.PropertyChanged += (s, e) =>
{
Dispatcher.Invoke(() =>
{
statusLabel.Content = radio.Status;
});
};
// Use async/await properly
await Task.Run(() => LongRunningOperation());❌ Don't:
// Don't update UI from events directly
radio.PropertyChanged += (s, e) =>
{
statusLabel.Content = radio.Status; // Cross-thread!
};✅ Do:
try
{
API.Init();
// Your code...
}
finally
{
// Always cleanup
foreach (var radio in API.RadioList)
radio.Disconnect();
API.CloseSession();
}❌ Don't:
// Don't forget cleanup
API.Init();
// ... code ...
return; // Leaked resources!✅ Do:
try
{
radio.Connect();
}
catch (Exception ex)
{
Console.WriteLine($"Connection failed: {ex.Message}");
// Handle gracefully
}
// Check results
if (radio.Connected)
{
// Proceed
}
else
{
// Handle failure
}❌ Don't:
// Don't ignore errors
radio.Connect(); // May throw or fail silently
// Don't swallow exceptions
try { radio.Connect(); }
catch { } // Lost error information!- Discovery takes 2-5 seconds typically
- Wait adequate time before assuming no radios
- Don't call
API.Init()repeatedly
- TCP commands: ~10-50ms round trip
- Status updates: Asynchronous, varies
- Don't send commands too rapidly
- VITA-49 uses UDP: Some packet loss expected
- Buffer audio to handle jitter
- Use appropriate sample rates for your needs
- Each radio: ~10-50 MB depending on configuration
- Audio streams: Additional buffers
- Monitor memory if managing many radios
FlexLib can enable debug logging:
Create file:
%APPDATA%\FlexRadio Systems\log_discovery.txt
%APPDATA%\FlexRadio Systems\log_disconnect.txt
Use Wireshark to monitor:
- UDP port 4992 (discovery)
- TCP port 4992 (commands)
- UDP streaming ports (audio/IQ)
- No radios discovered: Check network, firewall
- Connection fails: Radio may be in use
- Commands ignored: Check radio connection
- No audio: Verify DAX configuration
- UI freezes: Use Dispatcher for events
FlexLib provides a powerful, event-driven API for FlexRadio control:
- Clean architecture: Layered design with clear separation
- Flexible: Supports console, GUI, and service applications
- Real-time: Event-driven for responsive applications
- Comprehensive: Full radio control and streaming
- Well-tested: Used in SmartSDR and many applications
Understanding this architecture will help you build robust, efficient FlexRadio applications!