@@ -1129,7 +1129,7 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
11291129 // Handle real-time echo updates - update TxLogEntry as echoes are received
11301130 _pingService! .onEchoReceived = (txPing, repeater, isNew) {
11311131 debugLog ('[APP] ========== ECHO CALLBACK RECEIVED ==========' );
1132- debugLog ('[APP] Real-time echo: ${repeater .repeaterId } (SNR: ${repeater .snr }, isNew: $isNew )' );
1132+ debugLog ('[APP] Real-time echo: ${repeater .repeaterId } (SNR: ${repeater .snr ?? 'null' }, isNew: $isNew )' );
11331133 debugLog ('[APP] TxLogEntries count: ${_txLogEntries .length }' );
11341134
11351135 // Find the matching TxLogEntry and update its events
@@ -1216,8 +1216,8 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
12161216 if (lastTx.events.isNotEmpty) {
12171217 repeaters = lastTx.events.map ((e) => MarkerRepeaterInfo (
12181218 repeaterId: e.repeaterId,
1219- snr: e.snr,
1220- rssi: e.rssi,
1219+ snr: e.snr ?? 0.0 ,
1220+ rssi: e.rssi ?? 0 ,
12211221 )).toList ();
12221222 }
12231223 }
@@ -1404,6 +1404,10 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
14041404 _txTracker = TxTracker ();
14051405 _txTracker! .disableRssiFilter = _preferences.disableRssiFilter;
14061406
1407+ // Set CARpeater prefix for pass-through (replaces shouldIgnoreRepeater)
1408+ _txTracker! .carpeaterPrefix = _preferences.ignoreCarpeater ? _preferences.ignoreRepeaterId : null ;
1409+ debugLog ('[APP] TxTracker.carpeaterPrefix set to ${_txTracker !.carpeaterPrefix ?? 'null' }' );
1410+
14071411 // Log TX carpeater drops to error log (without navigating to error tab)
14081412 _txTracker! .onCarpeaterDrop = (String repeaterId, String reason) {
14091413 debugLog ('[APP] TX carpeater drop: repeater=$repeaterId , reason=$reason ' );
@@ -1412,37 +1416,16 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
14121416 };
14131417 debugLog ('[APP] TxTracker.onCarpeaterDrop callback SET' );
14141418
1415- // Function to check if repeater should be ignored (carpeater filter)
1416- _txTracker! .shouldIgnoreRepeater = (String repeaterId) {
1417- final prefs = _preferences;
1418- if (prefs.ignoreCarpeater && prefs.ignoreRepeaterId != null ) {
1419- final ignored = prefs.ignoreRepeaterId! .toUpperCase ();
1420- return repeaterId.toUpperCase () == ignored;
1421- }
1422- return false ;
1423- };
1424- debugLog ('[APP] TxTracker.shouldIgnoreRepeater callback SET' );
1425-
14261419 // Create RX logger (stored for use when enabling Passive Mode)
14271420 _rxLogger = RxLogger (
1428- // Function to check if repeater should be ignored (carpeater filter)
1429- shouldIgnoreRepeater: (String repeaterId) {
1430- // Check user preferences for ignored repeater ID
1431- final prefs = _preferences;
1432- if (prefs.ignoreCarpeater && prefs.ignoreRepeaterId != null ) {
1433- // Case-insensitive comparison (both uppercase)
1434- final ignored = prefs.ignoreRepeaterId! .toUpperCase ();
1435- final current = repeaterId.toUpperCase ();
1436- return current == ignored;
1437- }
1438- return false ;
1439- },
1421+ // CARpeater prefix for pass-through (replaces shouldIgnoreRepeater)
1422+ carpeaterPrefix: _preferences.ignoreCarpeater ? _preferences.ignoreRepeaterId : null ,
14401423 // Immediate observation callback - fires when packet is first validated
14411424 // Creates pin IMMEDIATELY for NEW repeaters (first time in current batch)
14421425 onObservation: (observation) {
14431426 try {
14441427 debugLog ('[APP] Immediate RX observation: repeater=${observation .repeaterId }, '
1445- 'snr=${observation .snr }, location=${observation .lat .toStringAsFixed (5 )},${observation .lon .toStringAsFixed (5 )}' );
1428+ 'snr=${observation .snr ?? 'null' }, location=${observation .lat .toStringAsFixed (5 )},${observation .lon .toStringAsFixed (5 )}' );
14461429
14471430 // Log current batch tracking state for debugging
14481431 debugLog ('[APP] Current batch tracking: ${_currentBatchRepeaters .length } repeaters: $_currentBatchRepeaters ' );
@@ -1457,8 +1440,8 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
14571440 longitude: observation.lon,
14581441 repeaterId: observation.repeaterId,
14591442 timestamp: DateTime .now (),
1460- snr: observation.snr,
1461- rssi: observation.rssi,
1443+ snr: observation.snr ?? 0.0 ,
1444+ rssi: observation.rssi ?? 0 ,
14621445 );
14631446 _rxPings.add (rxPing);
14641447 if (_rxPings.length > _maxMapPins) _rxPings.removeAt (0 );
@@ -1480,8 +1463,8 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
14801463 repeaters: [
14811464 MarkerRepeaterInfo (
14821465 repeaterId: observation.repeaterId,
1483- snr: observation.snr,
1484- rssi: observation.rssi,
1466+ snr: observation.snr ?? 0.0 ,
1467+ rssi: observation.rssi ?? 0 ,
14851468 ),
14861469 ],
14871470 );
@@ -1501,7 +1484,7 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
15011484 try {
15021485 debugLog ('[APP] ========== BATCH FLUSH CALLBACK ==========' );
15031486 debugLog ('[APP] Finalized RX entry (best SNR): repeater=${entry .repeaterId }, '
1504- 'snr=${entry .snr }, location=${entry .lat .toStringAsFixed (5 )},${entry .lon .toStringAsFixed (5 )}' );
1487+ 'snr=${entry .snr ?? 'null' }, location=${entry .lat .toStringAsFixed (5 )},${entry .lon .toStringAsFixed (5 )}' );
15051488
15061489 final repeaterKey = entry.repeaterId.toUpperCase ();
15071490
@@ -1518,20 +1501,22 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
15181501 if (lastPinIndex != - 1 ) {
15191502 // Update the pin's SNR to the best from this batch
15201503 final existingPin = _rxPings[lastPinIndex];
1521- if (entry.snr > existingPin.snr) {
1504+ // Only update if new SNR is non-null and better (null never replaces non-null)
1505+ final shouldUpdateSnr = entry.snr != null && entry.snr! > existingPin.snr;
1506+ if (shouldUpdateSnr) {
15221507 _rxPings[lastPinIndex] = RxPing (
15231508 latitude: existingPin.latitude, // KEEP batch start location
15241509 longitude: existingPin.longitude, // KEEP batch start location
15251510 repeaterId: entry.repeaterId,
15261511 timestamp: entry.timestamp,
1527- snr: entry.snr, // UPDATE to best SNR from batch
1528- rssi: entry.rssi,
1512+ snr: entry.snr ?? existingPin.snr, // UPDATE to best SNR from batch
1513+ rssi: entry.rssi ?? existingPin.rssi ,
15291514 );
15301515 debugLog ('[APP] Updated RX pin SNR for repeater=${entry .repeaterId }: '
1531- '${existingPin .snr .toStringAsFixed (2 )} -> ${entry .snr .toStringAsFixed (2 )}' );
1516+ '${existingPin .snr .toStringAsFixed (2 )} -> ${entry .snr ? .toStringAsFixed (2 ) ?? 'null' }' );
15321517 } else {
15331518 debugLog ('[APP] RX pin SNR unchanged for repeater=${entry .repeaterId }: '
1534- 'batch best ${entry .snr .toStringAsFixed (2 )} <= pin ${existingPin .snr .toStringAsFixed (2 )}' );
1519+ 'batch best ${entry .snr ? .toStringAsFixed (2 ) ?? 'null' } <= pin ${existingPin .snr .toStringAsFixed (2 )}' );
15351520 }
15361521 } else {
15371522 // Edge case: pin not found (should have been created in onObservation)
@@ -1540,8 +1525,8 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
15401525 longitude: entry.lon,
15411526 repeaterId: entry.repeaterId,
15421527 timestamp: entry.timestamp,
1543- snr: entry.snr,
1544- rssi: entry.rssi,
1528+ snr: entry.snr ?? 0.0 ,
1529+ rssi: entry.rssi ?? 0 ,
15451530 );
15461531 _rxPings.add (newRxPing);
15471532 if (_rxPings.length > _maxMapPins) _rxPings.removeAt (0 );
@@ -1571,13 +1556,15 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
15711556 _rxLogEntries.add (rxLogEntry);
15721557 if (_rxLogEntries.length > _maxLogEntries) _rxLogEntries.removeAt (0 );
15731558 debugLog ('[APP] Added RX log entry: repeater=${entry .repeaterId }, '
1574- 'snr=${entry .snr }, pathLen=${entry .pathLength }' );
1559+ 'snr=${entry .snr ?? 'null' }, pathLen=${entry .pathLength }' );
15751560
15761561 // Note: RX count is incremented in onObservation when pin is created (immediate feedback)
15771562
15781563 // Enqueue to API with formatted heard_repeats string
1579- // Format: "repeaterId(snr)" e.g. "4e(12.25)"
1580- final heardRepeats = '${entry .repeaterId }(${entry .snr .toStringAsFixed (2 )})' ;
1564+ // Format: "repeaterId(snr)" e.g. "4e(12.25)" or "4e(null)" for CARpeater pass-through
1565+ final heardRepeats = entry.snr != null
1566+ ? '${entry .repeaterId }(${entry .snr !.toStringAsFixed (2 )})'
1567+ : '${entry .repeaterId }(null)' ;
15811568 await _apiQueueService.enqueueRx (
15821569 latitude: entry.lat,
15831570 longitude: entry.lon,
@@ -2729,10 +2716,26 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
27292716 // Propagate RSSI filter setting to live trackers/validators
27302717 _syncRssiFilterSetting (preferences.disableRssiFilter);
27312718
2719+ // Propagate CARpeater prefix to live trackers
2720+ _syncCarpeaterPrefix ();
2721+
27322722 notifyListeners ();
27332723 _savePreferences ();
27342724 }
27352725
2726+ /// Propagate carpeaterPrefix to live TxTracker and RxLogger
2727+ void _syncCarpeaterPrefix () {
2728+ final prefix = _preferences.ignoreCarpeater ? _preferences.ignoreRepeaterId : null ;
2729+ if (_txTracker != null ) {
2730+ _txTracker! .carpeaterPrefix = prefix;
2731+ debugLog ('[APP] Synced TxTracker.carpeaterPrefix = ${prefix ?? 'null' }' );
2732+ }
2733+ if (_rxLogger != null ) {
2734+ _rxLogger! .carpeaterPrefix = prefix;
2735+ debugLog ('[APP] Synced RxLogger.carpeaterPrefix = ${prefix ?? 'null' }' );
2736+ }
2737+ }
2738+
27362739 /// Propagate disableRssiFilter to all active trackers and validators
27372740 void _syncRssiFilterSetting (bool disableRssiFilter) {
27382741 if (_txTracker != null ) {
0 commit comments