mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
247 lines
6.7 KiB
Go
247 lines
6.7 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package interfaces
|
|
|
|
import "math"
|
|
|
|
// PHY tables transcribed from RFnexus/modem73 (tnc_ui.hh, phy/robust_modem.hh,
|
|
// phy/mfsk_modem.hh, CONTROL_PORT.md). payload_size is the PHY capacity including
|
|
// the 2-byte length prefix that modem73 reports via get_config.
|
|
|
|
const (
|
|
modem73RobustModeCount = 12
|
|
modem73SampleRate = 48000
|
|
modem73RobustSYM = 960
|
|
)
|
|
|
|
var modem73RobustBPSExt = []int{
|
|
1150, 585, 296, 296, 149,
|
|
732, 378, 194, 197, 99,
|
|
780, 510,
|
|
}
|
|
|
|
var modem73RobustNRows = []int{
|
|
173, 345, 685, 685, 1369,
|
|
89, 177, 349, 345, 689,
|
|
257, 129,
|
|
}
|
|
|
|
var modem73RobustTimeoutBPSExt = []int{
|
|
295, 100, 75, 75, 38,
|
|
185, 95, 50, 50, 25,
|
|
200, 130,
|
|
}
|
|
|
|
var modem73OFDMPayloadShort = [8][5]int{
|
|
{128, 171, 192, 213, 64},
|
|
{128, 171, 192, 213, 64},
|
|
{512, 684, 768, 852, 256},
|
|
{256, 342, 384, 426, 128},
|
|
{1024, 1368, 1536, 1704, 512},
|
|
{1024, 1368, 1536, 1704, 512},
|
|
{2048, 2736, 3072, 3408, 1024},
|
|
{2048, 2736, 3072, 3408, 1024},
|
|
}
|
|
|
|
var modem73OFDMPayloadNormal = [8][5]int{
|
|
{256, 342, 384, 426, 128},
|
|
{512, 684, 768, 852, 256},
|
|
{1024, 1368, 1536, 1704, 512},
|
|
{1024, 1368, 1536, 1704, 512},
|
|
{2048, 2736, 3072, 3408, 1024},
|
|
{2048, 2736, 3072, 3408, 1024},
|
|
{4096, 5472, 6144, 6816, 2048},
|
|
{4096, 5472, 6144, 6816, 2048},
|
|
}
|
|
|
|
var modem73OFDMPayloadLong = [8][5]int{
|
|
{512, 684, 768, 852, 256},
|
|
{1024, 1368, 1536, 1704, 512},
|
|
{2048, 2736, 3072, 3408, 1024},
|
|
{2048, 2736, 3072, 3408, 1024},
|
|
{4096, 5472, 6144, 6816, 2048},
|
|
{4096, 5472, 6144, 6816, 2048},
|
|
{0, 0, 0, 0, 0},
|
|
{0, 0, 0, 0, 0},
|
|
}
|
|
|
|
var modem73OFDMBitrateShort = [8][5]int{
|
|
{700, 900, 1000, 1100, 300},
|
|
{1100, 1400, 1600, 1800, 500},
|
|
{2100, 2900, 3200, 3600, 1100},
|
|
{2100, 2900, 3200, 3600, 1000},
|
|
{4300, 5700, 6400, 7100, 2200},
|
|
{5400, 7300, 8200, 9100, 2700},
|
|
{7500, 10000, 11200, 12500, 3700},
|
|
{8600, 11400, 12800, 14200, 4300},
|
|
}
|
|
|
|
var modem73OFDMBitrateNormal = [8][5]int{
|
|
{800, 1100, 1200, 1300, 400},
|
|
{1600, 2100, 2400, 2600, 800},
|
|
{2400, 3200, 3600, 4000, 1200},
|
|
{3200, 4200, 4700, 5200, 1600},
|
|
{4800, 6400, 7200, 8000, 2400},
|
|
{6300, 8400, 9500, 10500, 3200},
|
|
{8300, 11000, 12400, 13800, 4100},
|
|
{9600, 12800, 14400, 16000, 4800},
|
|
}
|
|
|
|
var modem73OFDMBitrateLong = [8][5]int{
|
|
{856, 1144, 1285, 1425, 428},
|
|
{1713, 2288, 2569, 2850, 856},
|
|
{2551, 3408, 3826, 4245, 1275},
|
|
{3425, 4576, 5138, 5700, 1713},
|
|
{5101, 6815, 7652, 8489, 2551},
|
|
{6851, 9152, 10276, 11400, 3425},
|
|
{0, 0, 0, 0, 0},
|
|
{0, 0, 0, 0, 0},
|
|
}
|
|
|
|
var modem73OFDMDurationShortMs = [8]int{1500, 1000, 1900, 1000, 1900, 1500, 2200, 1900}
|
|
var modem73OFDMDurationNormalMs = [8]int{2600, 2600, 3400, 2600, 3400, 2600, 4000, 3400}
|
|
var modem73OFDMDurationLongMs = [8]int{4800, 4800, 6400, 4800, 6400, 4800, 0, 0}
|
|
|
|
var modem73OFDMRepPayloadShort = [8]int{128, 256, 512, 512, 1024, 1024, 2048, 2048}
|
|
var modem73OFDMRepPayloadNormal = [8]int{256, 512, 1024, 1024, 2048, 0, 0, 0}
|
|
var modem73OFDMRepDurationShortMs = [8]int{2600, 1500, 3400, 1500, 3400, 2600, 4000, 3400}
|
|
var modem73OFDMRepDurationNormalMs = [8]int{4800, 4800, 6400, 4800, 6400, 0, 0, 0}
|
|
|
|
var modem73MFSKPayload = []int{32, 64, 128, 96}
|
|
var modem73MFSKDurationS = 8.0
|
|
|
|
// Modem73PhyResult is the computed PHY view for a TNC configuration.
|
|
type Modem73PhyResult struct {
|
|
PayloadSize int
|
|
BitrateBPS int
|
|
AirtimeS float64
|
|
MTUBytes int
|
|
}
|
|
|
|
// Modem73RobustAirtime returns frame duration from nrows and sample clock.
|
|
func Modem73RobustAirtime(mode int) float64 {
|
|
if mode < 0 || mode >= len(modem73RobustNRows) {
|
|
return 0
|
|
}
|
|
syms := 5 + modem73RobustNRows[mode]
|
|
return float64(syms) * float64(modem73RobustSYM) / float64(modem73SampleRate)
|
|
}
|
|
|
|
// Modem73RobustPayload returns PHY payload_size for a robust mode.
|
|
func Modem73RobustPayload(mode int) int {
|
|
if mode < 0 || mode >= modem73RobustModeCount {
|
|
return 0
|
|
}
|
|
if (mode >= 5 && mode < 10) || mode == 11 {
|
|
return 172
|
|
}
|
|
return 512
|
|
}
|
|
|
|
// Modem73ComputePhy derives payload bitrate and airtime for a modem config.
|
|
func Modem73ComputePhy(modemType, robustMode, mfskMode, frameSize int, modulation, codeRate string) Modem73PhyResult {
|
|
switch modemType {
|
|
case modem73TypeRobust:
|
|
ps := Modem73RobustPayload(robustMode)
|
|
air := Modem73RobustAirtime(robustMode)
|
|
bps := 0
|
|
if robustMode >= 0 && robustMode < len(modem73RobustBPSExt) {
|
|
bps = modem73RobustBPSExt[robustMode]
|
|
} else if air > 0 {
|
|
bps = int(float64(ps*8) / air)
|
|
}
|
|
return Modem73PhyResult{PayloadSize: ps, BitrateBPS: bps, AirtimeS: air, MTUBytes: ps - 2}
|
|
case modem73TypeMFSK:
|
|
if mfskMode < 0 || mfskMode >= len(modem73MFSKPayload) {
|
|
mfskMode = 0
|
|
}
|
|
ps := modem73MFSKPayload[mfskMode] + 4
|
|
air := modem73MFSKDurationS
|
|
bps := int(float64(modem73MFSKPayload[mfskMode]*8) / air)
|
|
return Modem73PhyResult{PayloadSize: ps, BitrateBPS: bps, AirtimeS: air, MTUBytes: modem73MFSKPayload[mfskMode]}
|
|
default:
|
|
return modem73OFDMPhy(modulation, codeRate, frameSize)
|
|
}
|
|
}
|
|
|
|
func modem73OFDMPhy(modulation, codeRate string, frameSize int) Modem73PhyResult {
|
|
mod := modem73IndexOf(modem73OFDMModulations, modulation)
|
|
rate := modem73IndexOf(modem73OFDMCodeRates, codeRate)
|
|
if mod < 0 {
|
|
mod = 1
|
|
}
|
|
if rate < 0 {
|
|
rate = 0
|
|
}
|
|
if frameSize < 0 {
|
|
frameSize = 1
|
|
}
|
|
if frameSize > 2 {
|
|
frameSize = 2
|
|
}
|
|
|
|
if rate >= 5 {
|
|
var pl, du int
|
|
if frameSize == 0 {
|
|
pl = modem73OFDMRepPayloadShort[mod]
|
|
du = modem73OFDMRepDurationShortMs[mod]
|
|
} else if frameSize == 1 {
|
|
pl = modem73OFDMRepPayloadNormal[mod]
|
|
du = modem73OFDMRepDurationNormalMs[mod]
|
|
}
|
|
air := float64(du) / 1000
|
|
bps := 0
|
|
if du > 0 {
|
|
bps = int(float64(pl) * 8000 / float64(du))
|
|
}
|
|
mtu := 0
|
|
if pl > 0 {
|
|
mtu = pl - 2
|
|
}
|
|
return Modem73PhyResult{PayloadSize: pl, BitrateBPS: bps, AirtimeS: air, MTUBytes: mtu}
|
|
}
|
|
|
|
var pl, bps, du int
|
|
switch frameSize {
|
|
case 0:
|
|
pl = modem73OFDMPayloadShort[mod][rate]
|
|
bps = modem73OFDMBitrateShort[mod][rate]
|
|
du = modem73OFDMDurationShortMs[mod]
|
|
case 2:
|
|
pl = modem73OFDMPayloadLong[mod][rate]
|
|
bps = modem73OFDMBitrateLong[mod][rate]
|
|
du = modem73OFDMDurationLongMs[mod]
|
|
default:
|
|
pl = modem73OFDMPayloadNormal[mod][rate]
|
|
bps = modem73OFDMBitrateNormal[mod][rate]
|
|
du = modem73OFDMDurationNormalMs[mod]
|
|
}
|
|
air := float64(du) / 1000
|
|
mtu := 0
|
|
if pl > 0 {
|
|
mtu = pl - 2
|
|
}
|
|
return Modem73PhyResult{PayloadSize: pl, BitrateBPS: bps, AirtimeS: air, MTUBytes: mtu}
|
|
}
|
|
|
|
// Modem73BPSKBER returns approximate AWGN BPSK BER for SNR in dB.
|
|
func Modem73BPSKBER(snrDB float64) float64 {
|
|
if snrDB < -20 {
|
|
return 0.5
|
|
}
|
|
snrLin := math.Pow(10, snrDB/10)
|
|
return 0.5 * math.Erfc(math.Sqrt(snrLin))
|
|
}
|
|
|
|
// Modem73FrameErrorRate maps bit BER to approximate frame error rate.
|
|
func Modem73FrameErrorRate(ber float64, payloadBytes int) float64 {
|
|
if ber <= 0 {
|
|
return 0
|
|
}
|
|
if ber >= 0.5 {
|
|
return 1
|
|
}
|
|
n := float64(payloadBytes * 8)
|
|
return 1 - math.Pow(1-ber, n)
|
|
}
|