From 4d0c45c11cbf59307abbedd05c63a341a8da1021 Mon Sep 17 00:00:00 2001 From: Janez T Date: Mon, 2 Mar 2026 09:02:20 +0100 Subject: [PATCH] Include client repeat info --- lib/models/device_info.dart | 10 +++++++ lib/providers/connection_provider.dart | 21 +++++++++++++++ lib/screens/device_config_screen.dart | 36 ++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/lib/models/device_info.dart b/lib/models/device_info.dart index 9d778a3..bbff67a 100644 --- a/lib/models/device_info.dart +++ b/lib/models/device_info.dart @@ -86,6 +86,10 @@ class DeviceInfo { final String? manufacturerModel; final String? semanticVersion; + // Repeat mode (firmware v9+) + final bool? clientRepeat; + final List<({int lower, int upper})>? allowedRepeatFreqRanges; + DeviceInfo({ this.deviceId, this.deviceName, @@ -119,6 +123,8 @@ class DeviceInfo { this.firmwareBuildDate, this.manufacturerModel, this.semanticVersion, + this.clientRepeat, + this.allowedRepeatFreqRanges, }); /// Check if device is connected @@ -238,6 +244,8 @@ class DeviceInfo { String? firmwareBuildDate, String? manufacturerModel, String? semanticVersion, + bool? clientRepeat, + List<({int lower, int upper})>? allowedRepeatFreqRanges, }) { return DeviceInfo( deviceId: deviceId ?? this.deviceId, @@ -272,6 +280,8 @@ class DeviceInfo { firmwareBuildDate: firmwareBuildDate ?? this.firmwareBuildDate, manufacturerModel: manufacturerModel ?? this.manufacturerModel, semanticVersion: semanticVersion ?? this.semanticVersion, + clientRepeat: clientRepeat ?? this.clientRepeat, + allowedRepeatFreqRanges: allowedRepeatFreqRanges ?? this.allowedRepeatFreqRanges, ); } diff --git a/lib/providers/connection_provider.dart b/lib/providers/connection_provider.dart index 9553016..317cde6 100644 --- a/lib/providers/connection_provider.dart +++ b/lib/providers/connection_provider.dart @@ -535,6 +535,7 @@ class ConnectionProvider with ChangeNotifier { firmwareBuildDate: deviceInfo['firmwareBuildDate'] as String?, manufacturerModel: deviceInfo['manufacturerModel'] as String?, semanticVersion: deviceInfo['semanticVersion'] as String?, + clientRepeat: deviceInfo['clientRepeat'] as bool?, ); notifyListeners(); debugPrint('✅ [Provider] Device info updated with DeviceInfo'); @@ -624,6 +625,12 @@ class ConnectionProvider with ChangeNotifier { }); }; + _bleService.onAllowedRepeatFreqReceived = (ranges) { + debugPrint('📥 [Provider] Received AllowedRepeatFreq: $ranges'); + _deviceInfo = _deviceInfo.copyWith(allowedRepeatFreqRanges: ranges); + notifyListeners(); + }; + _bleService.onTxActivity = () { _txActivity = true; notifyListeners(); @@ -1652,6 +1659,7 @@ class ConnectionProvider with ChangeNotifier { required int bandwidth, required int spreadingFactor, required int codingRate, + bool? repeat, }) async { if (!_bleService.isConnected) { _error = 'Not connected to device'; @@ -1665,6 +1673,7 @@ class ConnectionProvider with ChangeNotifier { bandwidth: bandwidth, spreadingFactor: spreadingFactor, codingRate: codingRate, + repeat: repeat, ); } catch (e) { _error = 'Failed to set radio params: $e'; @@ -1672,6 +1681,16 @@ class ConnectionProvider with ChangeNotifier { } } + /// Request the list of allowed repeat frequency ranges from the device (firmware v9+) + Future getAllowedRepeatFreq() async { + if (!_bleService.isConnected) return; + try { + await _bleService.getAllowedRepeatFreq(); + } catch (e) { + debugPrint('Failed to get allowed repeat freq: $e'); + } + } + /// Set transmit power Future setTxPower(int powerDbm) async { if (!_bleService.isConnected) { @@ -1725,6 +1744,8 @@ class ConnectionProvider with ChangeNotifier { try { // The device query command triggers a SelfInfo response await _bleService.refreshDeviceInfo(); + // Also request allowed repeat frequencies (firmware v9+, no-op on older firmware) + await _bleService.getAllowedRepeatFreq(); } catch (e) { _error = 'Failed to refresh device info: $e'; notifyListeners(); diff --git a/lib/screens/device_config_screen.dart b/lib/screens/device_config_screen.dart index 690bd5d..e8243c1 100644 --- a/lib/screens/device_config_screen.dart +++ b/lib/screens/device_config_screen.dart @@ -21,6 +21,7 @@ class _DeviceConfigScreenState extends State { late TextEditingController _txPowerController; bool _telemetryEnabled = false; + bool _repeatEnabled = false; String _selectedBandwidth = '62.5 kHz'; int _selectedSpreadingFactor = 8; int _selectedCodingRate = 8; @@ -85,6 +86,17 @@ class _DeviceConfigScreenState extends State { _telemetryEnabled = (deviceInfo.advLat != null && deviceInfo.advLat! != 0) || (deviceInfo.advLon != null && deviceInfo.advLon! != 0); + + // Initialize repeat mode from device info (firmware v9+) + _repeatEnabled = deviceInfo.clientRepeat ?? false; + + // Fetch allowed repeat frequencies on open if device supports repeat mode + if (deviceInfo.clientRepeat != null && + deviceInfo.allowedRepeatFreqRanges == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().getAllowedRepeatFreq(); + }); + } } @override @@ -263,6 +275,7 @@ class _DeviceConfigScreenState extends State { bandwidth: _bandwidthToValue(_selectedBandwidth), spreadingFactor: _selectedSpreadingFactor, codingRate: _selectedCodingRate, + repeat: deviceInfo.clientRepeat != null ? _repeatEnabled : null, ); // Save TX power @@ -702,6 +715,29 @@ class _DeviceConfigScreenState extends State { ), keyboardType: TextInputType.number, ), + + // Repeat Mode (firmware v9+) + if (deviceInfo.clientRepeat != null) ...[ + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Client Repeat Mode'), + subtitle: deviceInfo.allowedRepeatFreqRanges != null && + deviceInfo.allowedRepeatFreqRanges!.isNotEmpty + ? Text( + 'Allowed: ${deviceInfo.allowedRepeatFreqRanges!.map((r) => r.lower == r.upper ? '${(r.lower / 1000).toStringAsFixed(3)} MHz' : '${(r.lower / 1000).toStringAsFixed(3)}–${(r.upper / 1000).toStringAsFixed(3)} MHz').join(', ')}', + ) + : const Text( + 'Repeat packets on behalf of nearby nodes', + ), + value: _repeatEnabled, + onChanged: (value) { + setState(() { + _repeatEnabled = value; + }); + }, + ), + ], ], ), ),