Add companion radio stats, adaptive backoff, path hash width, and UI improvements

- Companion radio stats: poll and display noise floor, RSSI, SNR, airtime
  with dedicated ValueNotifier and ref-counted polling
- Adaptive RF-aware TX backoff based on radio conditions instead of fixed 5s
- Variable-width path hash support (1-3 bytes per hop)
- Air activity dot indicator in app bar with tap to open stats screen
- Jump to oldest unread setting for chat screens
- 1s send cooldown on DM and channel messages
- Link style: theme-aware orange, added EmailLinkifier
- New languages: Hungarian, Japanese, Korean
- Remove dead DeviceScreen and BatteryIndicatorChip
- Remove wakelock_plus dependency
- TX power fields now read as signed int8
This commit is contained in:
zjs81
2026-03-23 19:26:05 -07:00
parent e7e2bb91b8
commit 834850fb51
41 changed files with 17987 additions and 362 deletions
+10 -9
View File
@@ -186,6 +186,7 @@ class MeshCoreConnector extends ChangeNotifier {
DateTime _lastChannelMsgRxTime = DateTime.fromMillisecondsSinceEpoch(0);
static const int _radioQuietMs = 3000;
static const int _radioQuietMaxWaitMs = 3000;
/// When companion radio stats are unavailable, keep the legacy fixed backoff.
static const int _contactMsgBackoffFallbackMs = 5000;
static const int _contactMsgBackoffMinMs = 500;
@@ -349,6 +350,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (sw == null || !sw.isRunning) return false;
return sw.elapsed < const Duration(seconds: 2);
}
int? get currentFreqHz => _currentFreqHz;
int? get currentBwHz => _currentBwHz;
int? get currentSf => _currentSf;
@@ -818,18 +820,19 @@ class MeshCoreConnector extends ChangeNotifier {
// Quieter (more negative) → lower score; noisier → higher.
const noiseQuietDbm = -118.0;
const noiseNoisyDbm = -88.0;
final noiseT =
((nf - noiseQuietDbm) / (noiseNoisyDbm - noiseQuietDbm)).clamp(0.0, 1.0);
final noiseT = ((nf - noiseQuietDbm) / (noiseNoisyDbm - noiseQuietDbm))
.clamp(0.0, 1.0);
final snr = stats.lastSnrDb;
const snrGood = 12.0;
const snrBad = -2.0;
final snrT =
(1.0 - ((snr - snrBad) / (snrGood - snrBad))).clamp(0.0, 1.0);
final snrT = (1.0 - ((snr - snrBad) / (snrGood - snrBad))).clamp(0.0, 1.0);
final airBusy = _recentAirtimeBusyFraction();
final severity =
(math.max(noiseT, snrT) * 0.82 + airBusy * 0.18).clamp(0.0, 1.0);
final severity = (math.max(noiseT, snrT) * 0.82 + airBusy * 0.18).clamp(
0.0,
1.0,
);
return (_contactMsgBackoffMinMs +
severity * (_contactMsgBackoffMaxMs - _contactMsgBackoffMinMs))
@@ -856,9 +859,7 @@ class MeshCoreConnector extends ChangeNotifier {
return bumpAt.isAfter(lastInboundRxTime) ? bumpAt : lastInboundRxTime;
}
Future<void> _waitForRadioQuiet({
required DateTime lastInboundRxTime,
}) async {
Future<void> _waitForRadioQuiet({required DateTime lastInboundRxTime}) async {
// Wait for backoff after inbound traffic / RF airtime (avoid collision with
// mesh propagation). Elapsed time uses the dot's airtime bump when newer.
final backoffTargetMs = _contactMessageBackoffTargetMs();