Include client repeat info

This commit is contained in:
Janez T
2026-03-02 09:02:20 +01:00
parent aa2a194b0b
commit 4d0c45c11c
3 changed files with 67 additions and 0 deletions

View File

@@ -86,6 +86,10 @@ class DeviceInfo {
final String? manufacturerModel; final String? manufacturerModel;
final String? semanticVersion; final String? semanticVersion;
// Repeat mode (firmware v9+)
final bool? clientRepeat;
final List<({int lower, int upper})>? allowedRepeatFreqRanges;
DeviceInfo({ DeviceInfo({
this.deviceId, this.deviceId,
this.deviceName, this.deviceName,
@@ -119,6 +123,8 @@ class DeviceInfo {
this.firmwareBuildDate, this.firmwareBuildDate,
this.manufacturerModel, this.manufacturerModel,
this.semanticVersion, this.semanticVersion,
this.clientRepeat,
this.allowedRepeatFreqRanges,
}); });
/// Check if device is connected /// Check if device is connected
@@ -238,6 +244,8 @@ class DeviceInfo {
String? firmwareBuildDate, String? firmwareBuildDate,
String? manufacturerModel, String? manufacturerModel,
String? semanticVersion, String? semanticVersion,
bool? clientRepeat,
List<({int lower, int upper})>? allowedRepeatFreqRanges,
}) { }) {
return DeviceInfo( return DeviceInfo(
deviceId: deviceId ?? this.deviceId, deviceId: deviceId ?? this.deviceId,
@@ -272,6 +280,8 @@ class DeviceInfo {
firmwareBuildDate: firmwareBuildDate ?? this.firmwareBuildDate, firmwareBuildDate: firmwareBuildDate ?? this.firmwareBuildDate,
manufacturerModel: manufacturerModel ?? this.manufacturerModel, manufacturerModel: manufacturerModel ?? this.manufacturerModel,
semanticVersion: semanticVersion ?? this.semanticVersion, semanticVersion: semanticVersion ?? this.semanticVersion,
clientRepeat: clientRepeat ?? this.clientRepeat,
allowedRepeatFreqRanges: allowedRepeatFreqRanges ?? this.allowedRepeatFreqRanges,
); );
} }

View File

@@ -535,6 +535,7 @@ class ConnectionProvider with ChangeNotifier {
firmwareBuildDate: deviceInfo['firmwareBuildDate'] as String?, firmwareBuildDate: deviceInfo['firmwareBuildDate'] as String?,
manufacturerModel: deviceInfo['manufacturerModel'] as String?, manufacturerModel: deviceInfo['manufacturerModel'] as String?,
semanticVersion: deviceInfo['semanticVersion'] as String?, semanticVersion: deviceInfo['semanticVersion'] as String?,
clientRepeat: deviceInfo['clientRepeat'] as bool?,
); );
notifyListeners(); notifyListeners();
debugPrint('✅ [Provider] Device info updated with DeviceInfo'); 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 = () { _bleService.onTxActivity = () {
_txActivity = true; _txActivity = true;
notifyListeners(); notifyListeners();
@@ -1652,6 +1659,7 @@ class ConnectionProvider with ChangeNotifier {
required int bandwidth, required int bandwidth,
required int spreadingFactor, required int spreadingFactor,
required int codingRate, required int codingRate,
bool? repeat,
}) async { }) async {
if (!_bleService.isConnected) { if (!_bleService.isConnected) {
_error = 'Not connected to device'; _error = 'Not connected to device';
@@ -1665,6 +1673,7 @@ class ConnectionProvider with ChangeNotifier {
bandwidth: bandwidth, bandwidth: bandwidth,
spreadingFactor: spreadingFactor, spreadingFactor: spreadingFactor,
codingRate: codingRate, codingRate: codingRate,
repeat: repeat,
); );
} catch (e) { } catch (e) {
_error = 'Failed to set radio params: $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<void> getAllowedRepeatFreq() async {
if (!_bleService.isConnected) return;
try {
await _bleService.getAllowedRepeatFreq();
} catch (e) {
debugPrint('Failed to get allowed repeat freq: $e');
}
}
/// Set transmit power /// Set transmit power
Future<void> setTxPower(int powerDbm) async { Future<void> setTxPower(int powerDbm) async {
if (!_bleService.isConnected) { if (!_bleService.isConnected) {
@@ -1725,6 +1744,8 @@ class ConnectionProvider with ChangeNotifier {
try { try {
// The device query command triggers a SelfInfo response // The device query command triggers a SelfInfo response
await _bleService.refreshDeviceInfo(); await _bleService.refreshDeviceInfo();
// Also request allowed repeat frequencies (firmware v9+, no-op on older firmware)
await _bleService.getAllowedRepeatFreq();
} catch (e) { } catch (e) {
_error = 'Failed to refresh device info: $e'; _error = 'Failed to refresh device info: $e';
notifyListeners(); notifyListeners();

View File

@@ -21,6 +21,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
late TextEditingController _txPowerController; late TextEditingController _txPowerController;
bool _telemetryEnabled = false; bool _telemetryEnabled = false;
bool _repeatEnabled = false;
String _selectedBandwidth = '62.5 kHz'; String _selectedBandwidth = '62.5 kHz';
int _selectedSpreadingFactor = 8; int _selectedSpreadingFactor = 8;
int _selectedCodingRate = 8; int _selectedCodingRate = 8;
@@ -85,6 +86,17 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
_telemetryEnabled = _telemetryEnabled =
(deviceInfo.advLat != null && deviceInfo.advLat! != 0) || (deviceInfo.advLat != null && deviceInfo.advLat! != 0) ||
(deviceInfo.advLon != null && deviceInfo.advLon! != 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<ConnectionProvider>().getAllowedRepeatFreq();
});
}
} }
@override @override
@@ -263,6 +275,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
bandwidth: _bandwidthToValue(_selectedBandwidth), bandwidth: _bandwidthToValue(_selectedBandwidth),
spreadingFactor: _selectedSpreadingFactor, spreadingFactor: _selectedSpreadingFactor,
codingRate: _selectedCodingRate, codingRate: _selectedCodingRate,
repeat: deviceInfo.clientRepeat != null ? _repeatEnabled : null,
); );
// Save TX power // Save TX power
@@ -702,6 +715,29 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
), ),
keyboardType: TextInputType.number, 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;
});
},
),
],
], ],
), ),
), ),