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>
133 lines
No EOL
2.9 KiB
Go
133 lines
No EOL
2.9 KiB
Go
// Package main implements a simple VRS JSON parser test utility.
|
|
//
|
|
// This utility connects to a VRS JSON source and displays the parsed
|
|
// aircraft data for testing and debugging purposes.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"skyview/internal/vrs"
|
|
)
|
|
|
|
func main() {
|
|
host := flag.String("host", "svovel", "VRS host to connect to")
|
|
port := flag.Int("port", 33005, "VRS port to connect to")
|
|
flag.Parse()
|
|
|
|
fmt.Printf("Connecting to VRS JSON source at %s:%d...\n", *host, *port)
|
|
|
|
// Connect to VRS source
|
|
addr := fmt.Sprintf("%s:%d", *host, *port)
|
|
conn, err := net.DialTimeout("tcp", addr, 30*time.Second)
|
|
if err != nil {
|
|
log.Fatalf("Failed to connect to %s: %v", addr, err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
fmt.Printf("Connected to %s\n", addr)
|
|
|
|
// Create VRS parser
|
|
parser := vrs.NewParser(conn, "test")
|
|
|
|
// Set up channels for messages and errors
|
|
msgChan := make(chan *vrs.VRSMessage, 100)
|
|
errChan := make(chan error, 10)
|
|
|
|
// Set up signal handling
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
// Start parsing in background
|
|
go parser.ParseStream(msgChan, errChan)
|
|
|
|
// Statistics tracking
|
|
messageCount := 0
|
|
aircraftCount := 0
|
|
startTime := time.Now()
|
|
|
|
fmt.Println("Receiving VRS JSON data... (Press Ctrl+C to stop)")
|
|
fmt.Println("----------------------------------------")
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
fmt.Println("\nShutting down...")
|
|
return
|
|
|
|
case <-sigChan:
|
|
fmt.Println("\nReceived interrupt signal")
|
|
cancel()
|
|
|
|
case err := <-errChan:
|
|
log.Printf("Parser error: %v", err)
|
|
cancel()
|
|
|
|
case msg := <-msgChan:
|
|
if msg == nil {
|
|
cancel()
|
|
continue
|
|
}
|
|
|
|
messageCount++
|
|
aircraftCount += len(msg.AcList)
|
|
|
|
fmt.Printf("Message %d: %d aircraft\n", messageCount, len(msg.AcList))
|
|
|
|
// Display first few aircraft for debugging
|
|
for i, ac := range msg.AcList {
|
|
if i >= 3 { // Only show first 3 aircraft per message
|
|
fmt.Printf(" ... and %d more aircraft\n", len(msg.AcList)-i)
|
|
break
|
|
}
|
|
|
|
icao, _ := ac.GetICAO24()
|
|
fmt.Printf(" ICAO: %06X", icao)
|
|
|
|
if ac.Call != "" {
|
|
fmt.Printf(" Call: %-8s", ac.Call)
|
|
}
|
|
|
|
if ac.HasPosition() {
|
|
fmt.Printf(" Pos: %7.3f°, %8.3f°", ac.Lat, ac.Long)
|
|
}
|
|
|
|
if ac.HasAltitude() {
|
|
fmt.Printf(" Alt: %5d ft", ac.GetAltitude())
|
|
}
|
|
|
|
if ac.Spd > 0 {
|
|
fmt.Printf(" Spd: %3.0f kt", ac.Spd)
|
|
}
|
|
|
|
if ac.Trak > 0 {
|
|
fmt.Printf(" Hdg: %3.0f°", ac.Trak)
|
|
}
|
|
|
|
if ac.Gnd {
|
|
fmt.Printf(" [GND]")
|
|
}
|
|
|
|
fmt.Printf(" Src: %s\n", ac.GetPositionSource())
|
|
}
|
|
|
|
// Show statistics every 10 messages
|
|
if messageCount%10 == 0 {
|
|
elapsed := time.Since(startTime)
|
|
fmt.Printf("Stats: %d messages, %d total aircraft, %.1fs elapsed\n",
|
|
messageCount, aircraftCount, elapsed.Seconds())
|
|
}
|
|
|
|
fmt.Println("----------------------------------------")
|
|
}
|
|
}
|
|
} |