Add VRS JSON format support for readsb integration

Added comprehensive support for VRS (Virtual Radar Server) JSON format
as a simpler alternative to Beast binary protocol, enabling integration
with readsb --net-vrs-port output.

## Key Features:
- **VRS JSON Parser**: Stream parsing of newline-delimited JSON aircraft data
- **VRS Client**: TCP client with automatic reconnection and error recovery
- **Mixed Format Support**: Use Beast and VRS sources simultaneously
- **Enhanced Aircraft Data**: Added VRS-specific fields (registration, type, operator)
- **Position Source Tracking**: Identifies ADS-B, MLAT, TIS-B, and satellite positions

## Implementation:
- `internal/vrs/parser.go`: VRS JSON message parsing and validation
- `internal/client/vrs.go`: VRS TCP client implementation
- Enhanced `MultiSourceClient` to support both Beast and VRS formats
- Extended `Aircraft` struct with validity flags and additional metadata
- Updated configuration to include `format` field ("beast" or "vrs")

## Testing:
- Successfully tested against svovel:33005 VRS JSON stream
- Verified aircraft data parsing and position tracking
- Confirmed mixed-format operation with existing Beast clients

## Documentation:
- Updated README.md with VRS format configuration examples
- Enhanced ARCHITECTURE.md with VRS parser documentation
- Added data format comparison and configuration guide

This enables simpler integration with modern readsb installations while
maintaining full backward compatibility with existing Beast deployments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2025-08-31 11:02:27 +02:00
commit 073acb7304
10 changed files with 903 additions and 40 deletions

View file

@ -250,10 +250,10 @@ func (c *BeastClient) processMessages() {
}
}
// MultiSourceClient manages multiple Beast TCP clients for multi-receiver setups.
// MultiSourceClient manages multiple Beast and VRS clients for multi-receiver setups.
//
// This client coordinator:
// - Manages connections to multiple Beast format sources simultaneously
// - Manages connections to multiple Beast and VRS format sources simultaneously
// - Provides unified control for starting and stopping all clients
// - Runs periodic cleanup tasks for stale aircraft data
// - Aggregates statistics from all managed clients
@ -262,21 +262,23 @@ func (c *BeastClient) processMessages() {
// All clients share the same data merger, enabling automatic data fusion
// and conflict resolution across multiple receivers.
type MultiSourceClient struct {
clients []*BeastClient // Managed Beast clients
merger *merger.Merger // Shared data merger for all sources
mu sync.RWMutex // Protects clients slice
beastClients []*BeastClient // Managed Beast clients
vrsClients []*VRSClient // Managed VRS JSON clients
merger *merger.Merger // Shared data merger for all sources
mu sync.RWMutex // Protects client slices
}
// NewMultiSourceClient creates a client manager for multiple Beast format sources.
// NewMultiSourceClient creates a client manager for multiple Beast and VRS format sources.
//
// The multi-source client enables connecting to multiple dump1090 instances
// or other Beast format sources simultaneously. All sources feed into the
// same data merger, which handles automatic data fusion and conflict resolution.
// The multi-source client enables connecting to multiple dump1090/readsb instances
// using either Beast binary or VRS JSON formats simultaneously. All sources feed into
// the same data merger, which handles automatic data fusion and conflict resolution.
//
// This is essential for:
// - Improved coverage from multiple receivers
// - Redundancy in case of individual receiver failures
// - Data quality improvement through signal strength comparison
// - Support for different receiver output formats
//
// Parameters:
// - merger: Shared data merger instance for all sources
@ -284,18 +286,23 @@ type MultiSourceClient struct {
// Returns a configured multi-source client ready for source addition.
func NewMultiSourceClient(merger *merger.Merger) *MultiSourceClient {
return &MultiSourceClient{
clients: make([]*BeastClient, 0),
merger: merger,
beastClients: make([]*BeastClient, 0),
vrsClients: make([]*VRSClient, 0),
merger: merger,
}
}
// AddSource registers and configures a new Beast format data source.
// AddSource registers and configures a new data source (Beast or VRS format).
//
// This method:
// 1. Registers the source with the data merger
// 2. Creates a new BeastClient for the source
// 2. Creates appropriate client based on source format
// 3. Adds the client to the managed clients list
//
// The source format is determined by the source.Format field:
// - "beast": Creates a BeastClient for Beast binary protocol (default)
// - "vrs": Creates a VRSClient for VRS JSON protocol
//
// The source is not automatically started; call Start() to begin connections.
// Sources can be added before or after starting the multi-source client.
//
@ -305,18 +312,31 @@ func (m *MultiSourceClient) AddSource(source *merger.Source) {
m.mu.Lock()
defer m.mu.Unlock()
// Default to Beast format if not specified
if source.Format == "" {
source.Format = "beast"
}
// Register source with merger
m.merger.AddSource(source)
// Create and start client
client := NewBeastClient(source, m.merger)
m.clients = append(m.clients, client)
// Create appropriate client based on format
switch source.Format {
case "vrs":
client := NewVRSClient(source, m.merger)
m.vrsClients = append(m.vrsClients, client)
case "beast":
fallthrough
default:
client := NewBeastClient(source, m.merger)
m.beastClients = append(m.beastClients, client)
}
}
// Start begins connections to all configured Beast sources.
// Start begins connections to all configured sources (Beast and VRS).
//
// This method:
// - Starts all managed BeastClient instances in parallel
// - Starts all managed BeastClient and VRSClient instances in parallel
// - Begins the periodic cleanup routine for stale aircraft data
// - Uses the provided context for cancellation control
//
@ -330,7 +350,13 @@ func (m *MultiSourceClient) Start(ctx context.Context) {
m.mu.RLock()
defer m.mu.RUnlock()
for _, client := range m.clients {
// Start Beast clients
for _, client := range m.beastClients {
client.Start(ctx)
}
// Start VRS clients
for _, client := range m.vrsClients {
client.Start(ctx)
}
@ -338,7 +364,7 @@ func (m *MultiSourceClient) Start(ctx context.Context) {
go m.cleanupRoutine(ctx)
}
// Stop gracefully shuts down all managed Beast clients.
// Stop gracefully shuts down all managed clients (Beast and VRS).
//
// This method stops all clients in parallel and waits for their
// goroutines to complete. The shutdown is coordinated to ensure
@ -347,7 +373,13 @@ func (m *MultiSourceClient) Stop() {
m.mu.RLock()
defer m.mu.RUnlock()
for _, client := range m.clients {
// Stop Beast clients
for _, client := range m.beastClients {
client.Stop()
}
// Stop VRS clients
for _, client := range m.vrsClients {
client.Stop()
}
}
@ -382,8 +414,8 @@ func (m *MultiSourceClient) cleanupRoutine(ctx context.Context) {
//
// The statistics include:
// - All merger statistics (aircraft count, message rates, etc.)
// - Number of active client connections
// - Total number of configured clients
// - Number of active client connections (Beast and VRS)
// - Total number of configured clients by type
// - Per-source connection status and message counts
//
// This information is useful for monitoring system health, diagnosing
@ -397,15 +429,26 @@ func (m *MultiSourceClient) GetStatistics() map[string]interface{} {
stats := m.merger.GetStatistics()
// Add client-specific stats
activeClients := 0
for _, client := range m.clients {
activeBeastClients := 0
for _, client := range m.beastClients {
if client.source.Active {
activeClients++
activeBeastClients++
}
}
stats["active_clients"] = activeClients
stats["total_clients"] = len(m.clients)
activeVRSClients := 0
for _, client := range m.vrsClients {
if client.source.Active {
activeVRSClients++
}
}
stats["active_beast_clients"] = activeBeastClients
stats["active_vrs_clients"] = activeVRSClients
stats["active_clients"] = activeBeastClients + activeVRSClients
stats["total_beast_clients"] = len(m.beastClients)
stats["total_vrs_clients"] = len(m.vrsClients)
stats["total_clients"] = len(m.beastClients) + len(m.vrsClients)
return stats
}