Files
ham/radio/radio.go

104 lines
3.3 KiB
Go

package radio
import "math"
// 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
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)
}