Clean up codebase and fix server host binding for IPv6 support
Cleanup: - Remove unused aircraft-icon.svg (replaced by type-specific icons) - Remove test files: beast-dump-with-heli.bin, beast.test, main, old.json, ux.png - Remove duplicate config.json.example (kept config.example.json) - Remove empty internal/coverage/ directory - Move CLAUDE.md to project root - Update assets.go documentation to reflect current icon structure - Format all Go code with gofmt Server Host Binding Fix: - Fix critical bug where server host configuration was ignored - Add host parameter to Server struct and NewWebServer constructor - Rename NewServer to NewWebServer for better clarity - Fix IPv6 address formatting in server binding (wrap in brackets) - Update startup message to show correct bind address format - Support localhost-only, IPv4, IPv6, and interface-specific binding This resolves the "too many colons in address" error for IPv6 hosts like ::1 and enables proper localhost-only deployment as configured. Closes #15 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
67d0e0612a
commit
0d60592b9f
16 changed files with 266 additions and 289 deletions
40
CLAUDE.md
40
CLAUDE.md
|
|
@ -1 +1,39 @@
|
||||||
- This project uses forgejo for source control and the fj client is available.
|
# SkyView Project Guidelines
|
||||||
|
|
||||||
|
## Documentation Requirements
|
||||||
|
- We should always have an up to date document describing our architecture and features
|
||||||
|
- Include links to any external resources we've used
|
||||||
|
- We should also always have an up to date README describing the project
|
||||||
|
- Shell scripts should be validated with shellcheck
|
||||||
|
- Always make sure the code is well documented with explanations for why and how a particular solution is selected
|
||||||
|
|
||||||
|
## Development Principles
|
||||||
|
- An overarching principle with all code is KISS, Keep It Simple Stupid
|
||||||
|
- We do not want to create code that is more complicated than necessary
|
||||||
|
- When changing code, always make sure to update any relevant tests
|
||||||
|
- Use proper error handling - aviation applications need reliability
|
||||||
|
|
||||||
|
## SkyView-Specific Guidelines
|
||||||
|
|
||||||
|
### Architecture & Design
|
||||||
|
- Multi-source ADS-B data fusion is the core feature - prioritize signal strength-based conflict resolution
|
||||||
|
- Embedded resources (SQLite ICAO database, static assets) over external dependencies
|
||||||
|
- Low-latency performance is critical - optimize for fast WebSocket updates
|
||||||
|
- Support concurrent aircraft tracking (100+ aircraft should work smoothly)
|
||||||
|
|
||||||
|
### Code Organization
|
||||||
|
- Keep Go packages focused: beast parsing, modes decoding, merger, server, clients
|
||||||
|
- Frontend should be modular: separate managers for aircraft, map, UI, websockets
|
||||||
|
- Database operations should be fast (use indexes, avoid N+1 queries)
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
- Beast binary parsing must handle high message rates (1000+ msg/sec per source)
|
||||||
|
- WebSocket broadcasting should not block on slow clients
|
||||||
|
- Memory usage should be bounded (configurable history limits)
|
||||||
|
- CPU usage should remain low during normal operation
|
||||||
|
|
||||||
|
### Documentation Maintenance
|
||||||
|
- Always update docs/ARCHITECTURE.md when changing system design
|
||||||
|
- README.md should stay current with features and usage
|
||||||
|
- External resources (ICAO docs, ADS-B standards) should be linked in documentation
|
||||||
|
- Country database updates should be straightforward (replace SQLite file)
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
// - index.html: Main web interface with aircraft tracking map
|
// - index.html: Main web interface with aircraft tracking map
|
||||||
// - css/style.css: Styling for the web interface
|
// - css/style.css: Styling for the web interface
|
||||||
// - js/app.js: JavaScript client for WebSocket communication and map rendering
|
// - js/app.js: JavaScript client for WebSocket communication and map rendering
|
||||||
// - aircraft-icon.svg: SVG icon for aircraft markers
|
// - icons/*.svg: Type-specific SVG icons for aircraft markers
|
||||||
// - favicon.ico: Browser icon
|
// - favicon.ico: Browser icon
|
||||||
//
|
//
|
||||||
// The embedded filesystem is used by the HTTP server to serve static content
|
// The embedded filesystem is used by the HTTP server to serve static content
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#00a8ff" stroke="#ffffff" stroke-width="1">
|
|
||||||
<path d="M12 2l-2 16 2-2 2 2-2-16z"/>
|
|
||||||
<path d="M4 10l8-2-1 2-7 0z"/>
|
|
||||||
<path d="M20 10l-8-2 1 2 7 0z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 224 B |
Binary file not shown.
|
|
@ -5,14 +5,16 @@
|
||||||
// in human-readable format on the console.
|
// in human-readable format on the console.
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// beast-dump -tcp host:port # Read from TCP socket
|
//
|
||||||
// beast-dump -file path/to/file # Read from file
|
// beast-dump -tcp host:port # Read from TCP socket
|
||||||
// beast-dump -verbose # Show detailed message parsing
|
// beast-dump -file path/to/file # Read from file
|
||||||
|
// beast-dump -verbose # Show detailed message parsing
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
// beast-dump -tcp svovel:30005 # Connect to dump1090 Beast stream
|
//
|
||||||
// beast-dump -file beast.test # Parse Beast data from file
|
// beast-dump -tcp svovel:30005 # Connect to dump1090 Beast stream
|
||||||
// beast-dump -tcp localhost:30005 -verbose # Verbose TCP parsing
|
// beast-dump -file beast.test # Parse Beast data from file
|
||||||
|
// beast-dump -tcp localhost:30005 -verbose # Verbose TCP parsing
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -42,11 +44,11 @@ type BeastDumper struct {
|
||||||
parser *beast.Parser
|
parser *beast.Parser
|
||||||
decoder *modes.Decoder
|
decoder *modes.Decoder
|
||||||
stats struct {
|
stats struct {
|
||||||
totalMessages int64
|
totalMessages int64
|
||||||
validMessages int64
|
validMessages int64
|
||||||
aircraftSeen map[uint32]bool
|
aircraftSeen map[uint32]bool
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
lastMessageTime time.Time
|
lastMessageTime time.Time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,11 +104,11 @@ func NewBeastDumper(config *Config) *BeastDumper {
|
||||||
config: config,
|
config: config,
|
||||||
decoder: modes.NewDecoder(0.0, 0.0), // beast-dump doesn't have reference position, use default
|
decoder: modes.NewDecoder(0.0, 0.0), // beast-dump doesn't have reference position, use default
|
||||||
stats: struct {
|
stats: struct {
|
||||||
totalMessages int64
|
totalMessages int64
|
||||||
validMessages int64
|
validMessages int64
|
||||||
aircraftSeen map[uint32]bool
|
aircraftSeen map[uint32]bool
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
lastMessageTime time.Time
|
lastMessageTime time.Time
|
||||||
}{
|
}{
|
||||||
aircraftSeen: make(map[uint32]bool),
|
aircraftSeen: make(map[uint32]bool),
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"server": {
|
|
||||||
"address": ":8080",
|
|
||||||
"port": 8080
|
|
||||||
},
|
|
||||||
"dump1090": {
|
|
||||||
"host": "192.168.1.100",
|
|
||||||
"data_port": 30003
|
|
||||||
},
|
|
||||||
"origin": {
|
|
||||||
"latitude": 37.7749,
|
|
||||||
"longitude": -122.4194,
|
|
||||||
"name": "San Francisco"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
# SkyView Project Guidelines
|
|
||||||
|
|
||||||
## Documentation Requirements
|
|
||||||
- We should always have an up to date document describing our architecture and features
|
|
||||||
- Include links to any external resources we've used
|
|
||||||
- We should also always have an up to date README describing the project
|
|
||||||
- Shell scripts should be validated with shellcheck
|
|
||||||
- Always make sure the code is well documented with explanations for why and how a particular solution is selected
|
|
||||||
|
|
||||||
## Development Principles
|
|
||||||
- An overarching principle with all code is KISS, Keep It Simple Stupid
|
|
||||||
- We do not want to create code that is more complicated than necessary
|
|
||||||
- When changing code, always make sure to update any relevant tests
|
|
||||||
- Use proper error handling - aviation applications need reliability
|
|
||||||
|
|
||||||
## SkyView-Specific Guidelines
|
|
||||||
|
|
||||||
### Architecture & Design
|
|
||||||
- Multi-source ADS-B data fusion is the core feature - prioritize signal strength-based conflict resolution
|
|
||||||
- Embedded resources (SQLite ICAO database, static assets) over external dependencies
|
|
||||||
- Low-latency performance is critical - optimize for fast WebSocket updates
|
|
||||||
- Support concurrent aircraft tracking (100+ aircraft should work smoothly)
|
|
||||||
|
|
||||||
### Code Organization
|
|
||||||
- Keep Go packages focused: beast parsing, modes decoding, merger, server, clients
|
|
||||||
- Frontend should be modular: separate managers for aircraft, map, UI, websockets
|
|
||||||
- Database operations should be fast (use indexes, avoid N+1 queries)
|
|
||||||
|
|
||||||
### Performance Considerations
|
|
||||||
- Beast binary parsing must handle high message rates (1000+ msg/sec per source)
|
|
||||||
- WebSocket broadcasting should not block on slow clients
|
|
||||||
- Memory usage should be bounded (configurable history limits)
|
|
||||||
- CPU usage should remain low during normal operation
|
|
||||||
|
|
||||||
### Documentation Maintenance
|
|
||||||
- Always update docs/ARCHITECTURE.md when changing system design
|
|
||||||
- README.md should stay current with features and usage
|
|
||||||
- External resources (ICAO docs, ADS-B standards) should be linked in documentation
|
|
||||||
- Country database updates should be straightforward (replace SQLite file)
|
|
||||||
|
|
@ -88,12 +88,12 @@ func NewParser(r io.Reader, sourceID string) *Parser {
|
||||||
// ReadMessage reads and parses a single Beast message from the stream.
|
// ReadMessage reads and parses a single Beast message from the stream.
|
||||||
//
|
//
|
||||||
// The parsing process:
|
// The parsing process:
|
||||||
// 1. Search for the escape character (0x1A) that marks message start
|
// 1. Search for the escape character (0x1A) that marks message start
|
||||||
// 2. Read and validate the message type byte
|
// 2. Read and validate the message type byte
|
||||||
// 3. Read the 48-bit timestamp (big-endian, padded to 64-bit)
|
// 3. Read the 48-bit timestamp (big-endian, padded to 64-bit)
|
||||||
// 4. Read the signal level byte
|
// 4. Read the signal level byte
|
||||||
// 5. Read the message payload (length depends on message type)
|
// 5. Read the message payload (length depends on message type)
|
||||||
// 6. Process escape sequences in the payload data
|
// 6. Process escape sequences in the payload data
|
||||||
//
|
//
|
||||||
// The parser can recover from protocol errors by continuing to search for
|
// The parser can recover from protocol errors by continuing to search for
|
||||||
// the next valid message boundary. Status messages are currently skipped
|
// the next valid message boundary. Status messages are currently skipped
|
||||||
|
|
|
||||||
|
|
@ -39,15 +39,15 @@ import (
|
||||||
// continuously processes incoming messages until stopped or the source
|
// continuously processes incoming messages until stopped or the source
|
||||||
// becomes unavailable.
|
// becomes unavailable.
|
||||||
type BeastClient struct {
|
type BeastClient struct {
|
||||||
source *merger.Source // Source configuration and status
|
source *merger.Source // Source configuration and status
|
||||||
merger *merger.Merger // Data merger for multi-source fusion
|
merger *merger.Merger // Data merger for multi-source fusion
|
||||||
decoder *modes.Decoder // Mode S/ADS-B message decoder
|
decoder *modes.Decoder // Mode S/ADS-B message decoder
|
||||||
conn net.Conn // TCP connection to Beast source
|
conn net.Conn // TCP connection to Beast source
|
||||||
parser *beast.Parser // Beast format message parser
|
parser *beast.Parser // Beast format message parser
|
||||||
msgChan chan *beast.Message // Buffered channel for parsed messages
|
msgChan chan *beast.Message // Buffered channel for parsed messages
|
||||||
errChan chan error // Error reporting channel
|
errChan chan error // Error reporting channel
|
||||||
stopChan chan struct{} // Shutdown signal channel
|
stopChan chan struct{} // Shutdown signal channel
|
||||||
wg sync.WaitGroup // Wait group for goroutine coordination
|
wg sync.WaitGroup // Wait group for goroutine coordination
|
||||||
|
|
||||||
// Reconnection parameters
|
// Reconnection parameters
|
||||||
reconnectDelay time.Duration // Initial reconnect delay
|
reconnectDelay time.Duration // Initial reconnect delay
|
||||||
|
|
@ -102,9 +102,9 @@ func (c *BeastClient) Start(ctx context.Context) {
|
||||||
// Stop gracefully shuts down the client and all associated goroutines.
|
// Stop gracefully shuts down the client and all associated goroutines.
|
||||||
//
|
//
|
||||||
// The shutdown process:
|
// The shutdown process:
|
||||||
// 1. Signals all goroutines to stop via stopChan
|
// 1. Signals all goroutines to stop via stopChan
|
||||||
// 2. Closes the TCP connection if active
|
// 2. Closes the TCP connection if active
|
||||||
// 3. Waits for all goroutines to complete
|
// 3. Waits for all goroutines to complete
|
||||||
//
|
//
|
||||||
// This method blocks until the shutdown is complete.
|
// This method blocks until the shutdown is complete.
|
||||||
func (c *BeastClient) Stop() {
|
func (c *BeastClient) Stop() {
|
||||||
|
|
@ -118,11 +118,11 @@ func (c *BeastClient) Stop() {
|
||||||
// run implements the main client connection and reconnection loop.
|
// run implements the main client connection and reconnection loop.
|
||||||
//
|
//
|
||||||
// This method handles the complete client lifecycle:
|
// This method handles the complete client lifecycle:
|
||||||
// 1. Connection establishment with timeout
|
// 1. Connection establishment with timeout
|
||||||
// 2. Exponential backoff on connection failures
|
// 2. Exponential backoff on connection failures
|
||||||
// 3. Message parsing and processing goroutine management
|
// 3. Message parsing and processing goroutine management
|
||||||
// 4. Connection monitoring and failure detection
|
// 4. Connection monitoring and failure detection
|
||||||
// 5. Automatic reconnection on disconnection
|
// 5. Automatic reconnection on disconnection
|
||||||
//
|
//
|
||||||
// The exponential backoff starts at reconnectDelay (5s) and doubles on each
|
// The exponential backoff starts at reconnectDelay (5s) and doubles on each
|
||||||
// failure up to maxReconnect (60s), then resets on successful connection.
|
// failure up to maxReconnect (60s), then resets on successful connection.
|
||||||
|
|
@ -210,10 +210,10 @@ func (c *BeastClient) readMessages() {
|
||||||
// processMessages runs in a dedicated goroutine to decode and merge aircraft data.
|
// processMessages runs in a dedicated goroutine to decode and merge aircraft data.
|
||||||
//
|
//
|
||||||
// For each received Beast message, this method:
|
// For each received Beast message, this method:
|
||||||
// 1. Decodes the Mode S/ADS-B message payload
|
// 1. Decodes the Mode S/ADS-B message payload
|
||||||
// 2. Extracts aircraft information (position, altitude, speed, etc.)
|
// 2. Extracts aircraft information (position, altitude, speed, etc.)
|
||||||
// 3. Updates the data merger with new aircraft state
|
// 3. Updates the data merger with new aircraft state
|
||||||
// 4. Updates source statistics (message count)
|
// 4. Updates source statistics (message count)
|
||||||
//
|
//
|
||||||
// Invalid or unparseable messages are silently discarded to maintain
|
// Invalid or unparseable messages are silently discarded to maintain
|
||||||
// system stability. The merger handles data fusion from multiple sources
|
// system stability. The merger handles data fusion from multiple sources
|
||||||
|
|
@ -262,9 +262,9 @@ func (c *BeastClient) processMessages() {
|
||||||
// All clients share the same data merger, enabling automatic data fusion
|
// All clients share the same data merger, enabling automatic data fusion
|
||||||
// and conflict resolution across multiple receivers.
|
// and conflict resolution across multiple receivers.
|
||||||
type MultiSourceClient struct {
|
type MultiSourceClient struct {
|
||||||
clients []*BeastClient // Managed Beast clients
|
clients []*BeastClient // Managed Beast clients
|
||||||
merger *merger.Merger // Shared data merger for all sources
|
merger *merger.Merger // Shared data merger for all sources
|
||||||
mu sync.RWMutex // Protects clients slice
|
mu sync.RWMutex // Protects clients slice
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMultiSourceClient creates a client manager for multiple Beast format sources.
|
// NewMultiSourceClient creates a client manager for multiple Beast format sources.
|
||||||
|
|
@ -292,9 +292,9 @@ func NewMultiSourceClient(merger *merger.Merger) *MultiSourceClient {
|
||||||
// AddSource registers and configures a new Beast format data source.
|
// AddSource registers and configures a new Beast format data source.
|
||||||
//
|
//
|
||||||
// This method:
|
// This method:
|
||||||
// 1. Registers the source with the data merger
|
// 1. Registers the source with the data merger
|
||||||
// 2. Creates a new BeastClient for the source
|
// 2. Creates a new BeastClient for the source
|
||||||
// 3. Adds the client to the managed clients list
|
// 3. Adds the client to the managed clients list
|
||||||
//
|
//
|
||||||
// The source is not automatically started; call Start() to begin connections.
|
// The source is not automatically started; call Start() to begin connections.
|
||||||
// Sources can be added before or after starting the multi-source client.
|
// Sources can be added before or after starting the multi-source client.
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ type Merger struct {
|
||||||
sources map[string]*Source // Source ID -> source information
|
sources map[string]*Source // Source ID -> source information
|
||||||
icaoDB *icao.Database // ICAO country lookup database
|
icaoDB *icao.Database // ICAO country lookup database
|
||||||
mu sync.RWMutex // Protects all maps and slices
|
mu sync.RWMutex // Protects all maps and slices
|
||||||
historyLimit int // Maximum history points to retain
|
historyLimit int // Maximum history points to retain
|
||||||
staleTimeout time.Duration // Time before aircraft considered stale (15 seconds)
|
staleTimeout time.Duration // Time before aircraft considered stale (15 seconds)
|
||||||
updateMetrics map[uint32]*updateMetric // ICAO24 -> update rate calculation data
|
updateMetrics map[uint32]*updateMetric // ICAO24 -> update rate calculation data
|
||||||
}
|
}
|
||||||
|
|
@ -291,13 +291,13 @@ func (m *Merger) AddSource(source *Source) {
|
||||||
// UpdateAircraft merges new aircraft data from a source using intelligent fusion strategies.
|
// UpdateAircraft merges new aircraft data from a source using intelligent fusion strategies.
|
||||||
//
|
//
|
||||||
// This is the core method of the merger, handling:
|
// This is the core method of the merger, handling:
|
||||||
// 1. Aircraft state creation for new aircraft
|
// 1. Aircraft state creation for new aircraft
|
||||||
// 2. Source data tracking and statistics
|
// 2. Source data tracking and statistics
|
||||||
// 3. Multi-source data fusion with conflict resolution
|
// 3. Multi-source data fusion with conflict resolution
|
||||||
// 4. Historical data updates with retention limits
|
// 4. Historical data updates with retention limits
|
||||||
// 5. Distance and bearing calculations
|
// 5. Distance and bearing calculations
|
||||||
// 6. Update rate metrics
|
// 6. Update rate metrics
|
||||||
// 7. Source status maintenance
|
// 7. Source status maintenance
|
||||||
//
|
//
|
||||||
// Data fusion strategies:
|
// Data fusion strategies:
|
||||||
// - Position: Use source with strongest signal
|
// - Position: Use source with strongest signal
|
||||||
|
|
@ -585,10 +585,10 @@ func (m *Merger) updateHistories(state *AircraftState, aircraft *modes.Aircraft,
|
||||||
// updateUpdateRate calculates and maintains the message update rate for an aircraft.
|
// updateUpdateRate calculates and maintains the message update rate for an aircraft.
|
||||||
//
|
//
|
||||||
// The calculation:
|
// The calculation:
|
||||||
// 1. Records the timestamp of each update
|
// 1. Records the timestamp of each update
|
||||||
// 2. Maintains a sliding 30-second window of updates
|
// 2. Maintains a sliding 30-second window of updates
|
||||||
// 3. Calculates updates per second over this window
|
// 3. Calculates updates per second over this window
|
||||||
// 4. Updates the aircraft's UpdateRate field
|
// 4. Updates the aircraft's UpdateRate field
|
||||||
//
|
//
|
||||||
// This provides real-time feedback on data quality and can help identify
|
// This provides real-time feedback on data quality and can help identify
|
||||||
// aircraft that are updating frequently (close, good signal) vs infrequently
|
// aircraft that are updating frequently (close, good signal) vs infrequently
|
||||||
|
|
@ -644,10 +644,10 @@ func (m *Merger) getBestSignalSource(state *AircraftState) string {
|
||||||
// GetAircraft returns a snapshot of all current aircraft states.
|
// GetAircraft returns a snapshot of all current aircraft states.
|
||||||
//
|
//
|
||||||
// This method:
|
// This method:
|
||||||
// 1. Filters out stale aircraft (older than staleTimeout)
|
// 1. Filters out stale aircraft (older than staleTimeout)
|
||||||
// 2. Calculates current age for each aircraft
|
// 2. Calculates current age for each aircraft
|
||||||
// 3. Determines closest receiver distance and bearing
|
// 3. Determines closest receiver distance and bearing
|
||||||
// 4. Returns copies to prevent external modification
|
// 4. Returns copies to prevent external modification
|
||||||
//
|
//
|
||||||
// The returned map uses ICAO24 addresses as keys and can be safely
|
// The returned map uses ICAO24 addresses as keys and can be safely
|
||||||
// used by multiple goroutines without affecting the internal state.
|
// used by multiple goroutines without affecting the internal state.
|
||||||
|
|
|
||||||
|
|
@ -107,36 +107,36 @@ const (
|
||||||
// depending on the messages received and aircraft capabilities.
|
// depending on the messages received and aircraft capabilities.
|
||||||
type Aircraft struct {
|
type Aircraft struct {
|
||||||
// Core Identification
|
// Core Identification
|
||||||
ICAO24 uint32 // 24-bit ICAO aircraft address (unique identifier)
|
ICAO24 uint32 // 24-bit ICAO aircraft address (unique identifier)
|
||||||
Callsign string // 8-character flight callsign (from identification messages)
|
Callsign string // 8-character flight callsign (from identification messages)
|
||||||
|
|
||||||
// Position and Navigation
|
// Position and Navigation
|
||||||
Latitude float64 // Position latitude in decimal degrees
|
Latitude float64 // Position latitude in decimal degrees
|
||||||
Longitude float64 // Position longitude in decimal degrees
|
Longitude float64 // Position longitude in decimal degrees
|
||||||
Altitude int // Altitude in feet (barometric or geometric)
|
Altitude int // Altitude in feet (barometric or geometric)
|
||||||
BaroAltitude int // Barometric altitude in feet (QNH corrected)
|
BaroAltitude int // Barometric altitude in feet (QNH corrected)
|
||||||
GeomAltitude int // Geometric altitude in feet (GNSS height)
|
GeomAltitude int // Geometric altitude in feet (GNSS height)
|
||||||
|
|
||||||
// Motion and Dynamics
|
// Motion and Dynamics
|
||||||
VerticalRate int // Vertical rate in feet per minute (climb/descent)
|
VerticalRate int // Vertical rate in feet per minute (climb/descent)
|
||||||
GroundSpeed int // Ground speed in knots (integer)
|
GroundSpeed int // Ground speed in knots (integer)
|
||||||
Track int // Track angle in degrees (0-359, integer)
|
Track int // Track angle in degrees (0-359, integer)
|
||||||
Heading int // Aircraft heading in degrees (magnetic, integer)
|
Heading int // Aircraft heading in degrees (magnetic, integer)
|
||||||
|
|
||||||
// Aircraft Information
|
// Aircraft Information
|
||||||
Category string // Aircraft category (size, type, performance)
|
Category string // Aircraft category (size, type, performance)
|
||||||
Squawk string // 4-digit transponder squawk code (octal)
|
Squawk string // 4-digit transponder squawk code (octal)
|
||||||
|
|
||||||
// Status and Alerts
|
// Status and Alerts
|
||||||
Emergency string // Emergency/priority status description
|
Emergency string // Emergency/priority status description
|
||||||
OnGround bool // Aircraft is on ground (surface movement)
|
OnGround bool // Aircraft is on ground (surface movement)
|
||||||
Alert bool // Alert flag (ATC attention required)
|
Alert bool // Alert flag (ATC attention required)
|
||||||
SPI bool // Special Position Identification (pilot activated)
|
SPI bool // Special Position Identification (pilot activated)
|
||||||
|
|
||||||
// Data Quality Indicators
|
// Data Quality Indicators
|
||||||
NACp uint8 // Navigation Accuracy Category - Position (0-11)
|
NACp uint8 // Navigation Accuracy Category - Position (0-11)
|
||||||
NACv uint8 // Navigation Accuracy Category - Velocity (0-4)
|
NACv uint8 // Navigation Accuracy Category - Velocity (0-4)
|
||||||
SIL uint8 // Surveillance Integrity Level (0-3)
|
SIL uint8 // Surveillance Integrity Level (0-3)
|
||||||
|
|
||||||
// Autopilot/Flight Management
|
// Autopilot/Flight Management
|
||||||
SelectedAltitude int // MCP/FCU selected altitude in feet
|
SelectedAltitude int // MCP/FCU selected altitude in feet
|
||||||
|
|
@ -199,10 +199,10 @@ func NewDecoder(refLat, refLon float64) *Decoder {
|
||||||
// Decode processes a Mode S message and extracts all available aircraft information.
|
// Decode processes a Mode S message and extracts all available aircraft information.
|
||||||
//
|
//
|
||||||
// This is the main entry point for message decoding. The method:
|
// This is the main entry point for message decoding. The method:
|
||||||
// 1. Validates message length and extracts the Downlink Format (DF)
|
// 1. Validates message length and extracts the Downlink Format (DF)
|
||||||
// 2. Extracts the ICAO24 aircraft address
|
// 2. Extracts the ICAO24 aircraft address
|
||||||
// 3. Routes to appropriate decoder based on message type
|
// 3. Routes to appropriate decoder based on message type
|
||||||
// 4. Returns populated Aircraft struct with available data
|
// 4. Returns populated Aircraft struct with available data
|
||||||
//
|
//
|
||||||
// Different message types provide different information:
|
// Different message types provide different information:
|
||||||
// - DF4/20: Altitude only
|
// - DF4/20: Altitude only
|
||||||
|
|
@ -369,10 +369,10 @@ func (d *Decoder) decodeIdentification(data []byte, aircraft *Aircraft) {
|
||||||
// - Even/odd flag for CPR decoding
|
// - Even/odd flag for CPR decoding
|
||||||
//
|
//
|
||||||
// CPR (Compact Position Reporting) Process:
|
// CPR (Compact Position Reporting) Process:
|
||||||
// 1. Extract the even/odd flag and CPR lat/lon values
|
// 1. Extract the even/odd flag and CPR lat/lon values
|
||||||
// 2. Normalize CPR values to 0-1 range (divide by 2^17)
|
// 2. Normalize CPR values to 0-1 range (divide by 2^17)
|
||||||
// 3. Store values for this aircraft's ICAO address
|
// 3. Store values for this aircraft's ICAO address
|
||||||
// 4. Attempt position decoding if both even and odd messages are available
|
// 4. Attempt position decoding if both even and odd messages are available
|
||||||
//
|
//
|
||||||
// The actual position calculation requires both even and odd messages to
|
// The actual position calculation requires both even and odd messages to
|
||||||
// resolve the ambiguity inherent in the compressed encoding format.
|
// resolve the ambiguity inherent in the compressed encoding format.
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -35,8 +36,8 @@ import (
|
||||||
// This is used as the center point for the web map interface and for
|
// This is used as the center point for the web map interface and for
|
||||||
// distance calculations in coverage analysis.
|
// distance calculations in coverage analysis.
|
||||||
type OriginConfig struct {
|
type OriginConfig struct {
|
||||||
Latitude float64 `json:"latitude"` // Reference latitude in decimal degrees
|
Latitude float64 `json:"latitude"` // Reference latitude in decimal degrees
|
||||||
Longitude float64 `json:"longitude"` // Reference longitude in decimal degrees
|
Longitude float64 `json:"longitude"` // Reference longitude in decimal degrees
|
||||||
Name string `json:"name,omitempty"` // Descriptive name for the origin point
|
Name string `json:"name,omitempty"` // Descriptive name for the origin point
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,11 +52,12 @@ type OriginConfig struct {
|
||||||
// - Concurrent broadcast system for WebSocket clients
|
// - Concurrent broadcast system for WebSocket clients
|
||||||
// - CORS support for cross-origin web applications
|
// - CORS support for cross-origin web applications
|
||||||
type Server struct {
|
type Server struct {
|
||||||
port int // TCP port for HTTP server
|
host string // Bind address for HTTP server
|
||||||
merger *merger.Merger // Data source for aircraft information
|
port int // TCP port for HTTP server
|
||||||
staticFiles embed.FS // Embedded static web assets
|
merger *merger.Merger // Data source for aircraft information
|
||||||
server *http.Server // HTTP server instance
|
staticFiles embed.FS // Embedded static web assets
|
||||||
origin OriginConfig // Geographic reference point
|
server *http.Server // HTTP server instance
|
||||||
|
origin OriginConfig // Geographic reference point
|
||||||
|
|
||||||
// WebSocket management
|
// WebSocket management
|
||||||
wsClients map[*websocket.Conn]bool // Active WebSocket client connections
|
wsClients map[*websocket.Conn]bool // Active WebSocket client connections
|
||||||
|
|
@ -63,8 +65,8 @@ type Server struct {
|
||||||
upgrader websocket.Upgrader // HTTP to WebSocket protocol upgrader
|
upgrader websocket.Upgrader // HTTP to WebSocket protocol upgrader
|
||||||
|
|
||||||
// Broadcast channels for real-time updates
|
// Broadcast channels for real-time updates
|
||||||
broadcastChan chan []byte // Channel for broadcasting updates to all clients
|
broadcastChan chan []byte // Channel for broadcasting updates to all clients
|
||||||
stopChan chan struct{} // Shutdown signal channel
|
stopChan chan struct{} // Shutdown signal channel
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebSocketMessage represents the standard message format for WebSocket communication.
|
// WebSocketMessage represents the standard message format for WebSocket communication.
|
||||||
|
|
@ -85,7 +87,7 @@ type AircraftUpdate struct {
|
||||||
Stats map[string]interface{} `json:"stats"` // System statistics and metrics
|
Stats map[string]interface{} `json:"stats"` // System statistics and metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new HTTP server instance for serving the SkyView web interface.
|
// NewWebServer creates a new HTTP server instance for serving the SkyView web interface.
|
||||||
//
|
//
|
||||||
// The server is configured with:
|
// The server is configured with:
|
||||||
// - WebSocket upgrader allowing all origins (suitable for development)
|
// - WebSocket upgrader allowing all origins (suitable for development)
|
||||||
|
|
@ -93,14 +95,16 @@ type AircraftUpdate struct {
|
||||||
// - Read/Write buffers optimized for aircraft data messages
|
// - Read/Write buffers optimized for aircraft data messages
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
|
// - host: Bind address (empty for all interfaces, "localhost" for local only)
|
||||||
// - port: TCP port number for the HTTP server
|
// - port: TCP port number for the HTTP server
|
||||||
// - merger: Data merger instance providing aircraft information
|
// - merger: Data merger instance providing aircraft information
|
||||||
// - staticFiles: Embedded filesystem containing web assets
|
// - staticFiles: Embedded filesystem containing web assets
|
||||||
// - origin: Geographic reference point for the map interface
|
// - origin: Geographic reference point for the map interface
|
||||||
//
|
//
|
||||||
// Returns a configured but not yet started server instance.
|
// Returns a configured but not yet started server instance.
|
||||||
func NewServer(port int, merger *merger.Merger, staticFiles embed.FS, origin OriginConfig) *Server {
|
func NewWebServer(host string, port int, merger *merger.Merger, staticFiles embed.FS, origin OriginConfig) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
|
host: host,
|
||||||
port: port,
|
port: port,
|
||||||
merger: merger,
|
merger: merger,
|
||||||
staticFiles: staticFiles,
|
staticFiles: staticFiles,
|
||||||
|
|
@ -121,9 +125,9 @@ func NewServer(port int, merger *merger.Merger, staticFiles embed.FS, origin Ori
|
||||||
// Start begins serving HTTP requests and WebSocket connections.
|
// Start begins serving HTTP requests and WebSocket connections.
|
||||||
//
|
//
|
||||||
// This method starts several background routines:
|
// This method starts several background routines:
|
||||||
// 1. Broadcast routine - handles WebSocket message distribution
|
// 1. Broadcast routine - handles WebSocket message distribution
|
||||||
// 2. Periodic update routine - sends regular updates to WebSocket clients
|
// 2. Periodic update routine - sends regular updates to WebSocket clients
|
||||||
// 3. HTTP server - serves API endpoints and static files
|
// 3. HTTP server - serves API endpoints and static files
|
||||||
//
|
//
|
||||||
// The method blocks until the server encounters an error or is shut down.
|
// The method blocks until the server encounters an error or is shut down.
|
||||||
// Use Stop() for graceful shutdown.
|
// Use Stop() for graceful shutdown.
|
||||||
|
|
@ -139,8 +143,15 @@ func (s *Server) Start() error {
|
||||||
// Setup routes
|
// Setup routes
|
||||||
router := s.setupRoutes()
|
router := s.setupRoutes()
|
||||||
|
|
||||||
|
// Format address correctly for IPv6
|
||||||
|
addr := fmt.Sprintf("%s:%d", s.host, s.port)
|
||||||
|
if strings.Contains(s.host, ":") {
|
||||||
|
// IPv6 address needs brackets
|
||||||
|
addr = fmt.Sprintf("[%s]:%d", s.host, s.port)
|
||||||
|
}
|
||||||
|
|
||||||
s.server = &http.Server{
|
s.server = &http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", s.port),
|
Addr: addr,
|
||||||
Handler: router,
|
Handler: router,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,9 +161,9 @@ func (s *Server) Start() error {
|
||||||
// Stop gracefully shuts down the server and all background routines.
|
// Stop gracefully shuts down the server and all background routines.
|
||||||
//
|
//
|
||||||
// This method:
|
// This method:
|
||||||
// 1. Signals all background routines to stop via stopChan
|
// 1. Signals all background routines to stop via stopChan
|
||||||
// 2. Shuts down the HTTP server with a 5-second timeout
|
// 2. Shuts down the HTTP server with a 5-second timeout
|
||||||
// 3. Closes WebSocket connections
|
// 3. Closes WebSocket connections
|
||||||
//
|
//
|
||||||
// The shutdown is designed to be safe and allow in-flight requests to complete.
|
// The shutdown is designed to be safe and allow in-flight requests to complete.
|
||||||
func (s *Server) Stop() {
|
func (s *Server) Stop() {
|
||||||
|
|
@ -382,10 +393,10 @@ func (s *Server) handleGetCoverage(w http.ResponseWriter, r *http.Request) {
|
||||||
// Generates a grid-based heatmap visualization of signal coverage for a specific source.
|
// Generates a grid-based heatmap visualization of signal coverage for a specific source.
|
||||||
//
|
//
|
||||||
// The heatmap is computed by:
|
// The heatmap is computed by:
|
||||||
// 1. Finding geographic bounds of all aircraft positions for the source
|
// 1. Finding geographic bounds of all aircraft positions for the source
|
||||||
// 2. Creating a 100x100 grid covering the bounds
|
// 2. Creating a 100x100 grid covering the bounds
|
||||||
// 3. Accumulating signal strength values in each grid cell
|
// 3. Accumulating signal strength values in each grid cell
|
||||||
// 4. Returning the grid data with boundary coordinates
|
// 4. Returning the grid data with boundary coordinates
|
||||||
//
|
//
|
||||||
// This provides a density-based visualization of where the source receives
|
// This provides a density-based visualization of where the source receives
|
||||||
// the strongest signals, useful for coverage analysis and antenna optimization.
|
// the strongest signals, useful for coverage analysis and antenna optimization.
|
||||||
|
|
@ -456,11 +467,11 @@ func (s *Server) handleGetHeatmap(w http.ResponseWriter, r *http.Request) {
|
||||||
// handleWebSocket manages WebSocket connections for real-time aircraft data streaming.
|
// handleWebSocket manages WebSocket connections for real-time aircraft data streaming.
|
||||||
//
|
//
|
||||||
// This handler:
|
// This handler:
|
||||||
// 1. Upgrades the HTTP connection to WebSocket protocol
|
// 1. Upgrades the HTTP connection to WebSocket protocol
|
||||||
// 2. Registers the client for broadcast updates
|
// 2. Registers the client for broadcast updates
|
||||||
// 3. Sends initial data snapshot to the client
|
// 3. Sends initial data snapshot to the client
|
||||||
// 4. Handles client messages (currently just ping/pong for keepalive)
|
// 4. Handles client messages (currently just ping/pong for keepalive)
|
||||||
// 5. Cleans up the connection when the client disconnects
|
// 5. Cleans up the connection when the client disconnects
|
||||||
//
|
//
|
||||||
// WebSocket clients receive periodic updates with current aircraft positions,
|
// WebSocket clients receive periodic updates with current aircraft positions,
|
||||||
// source status, and system statistics. The connection is kept alive until
|
// source status, and system statistics. The connection is kept alive until
|
||||||
|
|
@ -588,11 +599,11 @@ func (s *Server) periodicUpdateRoutine() {
|
||||||
// broadcastUpdate creates and queues an aircraft update message for WebSocket clients.
|
// broadcastUpdate creates and queues an aircraft update message for WebSocket clients.
|
||||||
//
|
//
|
||||||
// This function:
|
// This function:
|
||||||
// 1. Collects current aircraft data from the merger
|
// 1. Collects current aircraft data from the merger
|
||||||
// 2. Filters aircraft to only include "useful" ones (with position or callsign)
|
// 2. Filters aircraft to only include "useful" ones (with position or callsign)
|
||||||
// 3. Formats the data as a WebSocketMessage with type "aircraft_update"
|
// 3. Formats the data as a WebSocketMessage with type "aircraft_update"
|
||||||
// 4. Converts ICAO addresses to hex strings for JSON compatibility
|
// 4. Converts ICAO addresses to hex strings for JSON compatibility
|
||||||
// 5. Queues the message for broadcast (non-blocking)
|
// 5. Queues the message for broadcast (non-blocking)
|
||||||
//
|
//
|
||||||
// If the broadcast channel is full, the update is dropped to prevent blocking.
|
// If the broadcast channel is full, the update is dropped to prevent blocking.
|
||||||
// This ensures the system continues operating even if WebSocket clients
|
// This ensures the system continues operating even if WebSocket clients
|
||||||
|
|
@ -769,11 +780,11 @@ func (s *Server) handleDebugAircraft(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
"timestamp": time.Now().Unix(),
|
"timestamp": time.Now().Unix(),
|
||||||
"all_aircraft": allAircraftMap,
|
"all_aircraft": allAircraftMap,
|
||||||
"filtered_aircraft": filteredAircraftMap,
|
"filtered_aircraft": filteredAircraftMap,
|
||||||
"all_count": len(allAircraftMap),
|
"all_count": len(allAircraftMap),
|
||||||
"filtered_count": len(filteredAircraftMap),
|
"filtered_count": len(filteredAircraftMap),
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
|
||||||
BIN
main
BIN
main
Binary file not shown.
15
old.json
15
old.json
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"server": {
|
|
||||||
"address": ":8080",
|
|
||||||
"port": 8080
|
|
||||||
},
|
|
||||||
"dump1090": {
|
|
||||||
"host": "svovel",
|
|
||||||
"data_port": 30003
|
|
||||||
},
|
|
||||||
"origin": {
|
|
||||||
"latitude": 59.908127,
|
|
||||||
"longitude": 10.801460,
|
|
||||||
"name": "Etterstadsletta flyplass"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
ux.png
BIN
ux.png
Binary file not shown.
|
Before Width: | Height: | Size: 102 KiB |
Loading…
Add table
Add a link
Reference in a new issue