109 lines
3.6 KiB
Go
109 lines
3.6 KiB
Go
package radio
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
// Info descriptor.
|
|
type Info struct {
|
|
Name string `yaml:"name" json:"name"` // Name of the device
|
|
Device string `yaml:"device" json:"device"` // Device type
|
|
Manufacturer string `yaml:"manufacturer" json:"manufacturer"` // Device manufacturer
|
|
FirmwareDate time.Time `yaml:"firmware_date" json:"firmware_date,omitempty"` // Firmware date
|
|
FirmwareVersion string `yaml:"firmware_version" json:"firmware_version"` // Firmware version
|
|
Antenna string `yaml:"antenna" json:"antenna"` // Antenna type
|
|
Modulation string `yaml:"modulation" json:"modulation"` // Modulation (constant from protocol)
|
|
Position *Position `yaml:"position" json:"position"` // Position
|
|
Frequency float64 `yaml:"frequency" json:"frequency"` // Frequency (in MHz)
|
|
RXFrequency float64 `yaml:"rx_frequency" json:"rx_frequency,omitempty"` // Used with split VFOs
|
|
TXFrequency float64 `yaml:"tx_frequency" json:"tx_frequency,omitempty"` // Used with split VFOs
|
|
Bandwidth float64 `yaml:"bandwidth" json:"bandwidth"` // Bandwidth (in kHz)
|
|
Power float64 `yaml:"power" json:"power"` // Power (in dBm)
|
|
Gain float64 `yaml:"gain" json:"gain"` // Gain (in dBm)
|
|
LoRaSF uint8 `yaml:"lora_sf" json:"lora_sf,omitempty"` // LoRa spreading factor
|
|
LoRaCR uint8 `yaml:"lora_cr" json:"lora_cr,omitempty"` // LoRa coding rate
|
|
Extra map[string]any `yaml:"extra" json:"extra"` // Extra metadata
|
|
}
|
|
|
|
type Position struct {
|
|
Latitude float64 `json:"latitude"`
|
|
Longitude float64 `json:"longitude"`
|
|
Altitude float64 `json:"altitude,omitempty"`
|
|
}
|
|
|
|
const earthRadiusKm = 6371.0088 // WGS84 mean Earth radius
|
|
|
|
// RoundTo reduces the accuracy of the position by the provided radius.
|
|
func (pos *Position) RoundTo(km float64) *Position {
|
|
if km <= 0 {
|
|
return pos
|
|
}
|
|
|
|
// Convert km to angular distance (radians)
|
|
angular := km / earthRadiusKm
|
|
|
|
// Convert to degrees
|
|
degLatStep := angular * (180.0 / math.Pi)
|
|
|
|
// Longitude step depends on latitude
|
|
latRad := pos.Latitude * math.Pi / 180.0
|
|
cosLat := math.Cos(latRad)
|
|
|
|
var degLonStep float64
|
|
if cosLat > 1e-12 {
|
|
degLonStep = degLatStep / cosLat
|
|
} else {
|
|
// Near poles — longitude collapses
|
|
degLonStep = 360.0
|
|
}
|
|
|
|
roundedLat := math.Round(pos.Latitude/degLatStep) * degLatStep
|
|
roundedLon := math.Round(pos.Longitude/degLonStep) * degLonStep
|
|
|
|
// Normalize longitude to [-180, 180]
|
|
roundedLon = math.Mod(roundedLon+180.0, 360.0)
|
|
if roundedLon < 0 {
|
|
roundedLon += 360.0
|
|
}
|
|
roundedLon -= 180.0
|
|
|
|
return &Position{
|
|
Latitude: roundedLat,
|
|
Longitude: roundedLon,
|
|
Altitude: pos.Altitude,
|
|
}
|
|
}
|
|
|
|
// Device is the minimum implementation for a radio device.
|
|
type Device interface {
|
|
Close() error
|
|
Info() *Info
|
|
}
|
|
|
|
// DBmToW converts power in dBm to Watts.
|
|
//
|
|
// Formula:
|
|
//
|
|
// P(W) = 10^(dBm/10) / 1000
|
|
func DBmToW(dBm float64) float64 {
|
|
return math.Pow(10, dBm/10.0) / 1000.0
|
|
}
|
|
|
|
// WToDBm converts power in Watts to dBm.
|
|
//
|
|
// Behavior:
|
|
//
|
|
// watts > 0 → normal conversion
|
|
// watts <= 0 → returns -Inf (represents zero power)
|
|
//
|
|
// Formula:
|
|
//
|
|
// dBm = 10 * log10(P(W) * 1000)
|
|
func WToDBm(watts float64) float64 {
|
|
if watts <= 0 {
|
|
return math.Inf(-1)
|
|
}
|
|
return 10.0 * math.Log10(watts*1000.0)
|
|
}
|