mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Fix radio preset resync
This commit is contained in:
@@ -219,6 +219,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
bool _autoDiscoverySettingsSaved = false;
|
bool _autoDiscoverySettingsSaved = false;
|
||||||
bool _autoDiscoverySettingsDirty = false;
|
bool _autoDiscoverySettingsDirty = false;
|
||||||
String? _lastAutoDiscoverySignature;
|
String? _lastAutoDiscoverySignature;
|
||||||
|
String? _lastRadioSettingsSignature;
|
||||||
String? _publicInfoError;
|
String? _publicInfoError;
|
||||||
String? _radioSettingsError;
|
String? _radioSettingsError;
|
||||||
String? _autoDiscoverySettingsError;
|
String? _autoDiscoverySettingsError;
|
||||||
@@ -226,6 +227,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
int _selectedSpreadingFactor = 8;
|
int _selectedSpreadingFactor = 8;
|
||||||
int _selectedCodingRate = 8;
|
int _selectedCodingRate = 8;
|
||||||
_RadioPreset? _selectedRadioPreset;
|
_RadioPreset? _selectedRadioPreset;
|
||||||
|
bool _radioSettingsDirty = false;
|
||||||
|
|
||||||
final List<String> _bandwidthOptions = [
|
final List<String> _bandwidthOptions = [
|
||||||
'7.8 kHz',
|
'7.8 kHz',
|
||||||
@@ -280,29 +282,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
text: (deviceInfo.autoAddMaxHops ?? 0).toString(),
|
text: (deviceInfo.autoAddMaxHops ?? 0).toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (deviceInfo.radioBw != null &&
|
_syncRadioSettingsState(deviceInfo);
|
||||||
deviceInfo.radioBw! >= 0 &&
|
|
||||||
deviceInfo.radioBw! <= 9) {
|
|
||||||
_selectedBandwidth = _bandwidthFromValue(deviceInfo.radioBw!);
|
|
||||||
}
|
|
||||||
if (deviceInfo.radioSf != null &&
|
|
||||||
deviceInfo.radioSf! >= 7 &&
|
|
||||||
deviceInfo.radioSf! <= 12) {
|
|
||||||
_selectedSpreadingFactor = deviceInfo.radioSf!;
|
|
||||||
}
|
|
||||||
if (deviceInfo.radioCr != null &&
|
|
||||||
deviceInfo.radioCr! >= 5 &&
|
|
||||||
deviceInfo.radioCr! <= 8) {
|
|
||||||
_selectedCodingRate = deviceInfo.radioCr!;
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectedRadioPreset = _matchRadioPreset(
|
|
||||||
frequencyKhz: deviceInfo.radioFreq,
|
|
||||||
bandwidth: deviceInfo.radioBw,
|
|
||||||
spreadingFactor: deviceInfo.radioSf,
|
|
||||||
codingRate: deviceInfo.radioCr,
|
|
||||||
);
|
|
||||||
_showCustomRadioSettings = _selectedRadioPreset == null;
|
|
||||||
|
|
||||||
final telemetryModes = deviceInfo.telemetryModes;
|
final telemetryModes = deviceInfo.telemetryModes;
|
||||||
_baseTelemetryMode = telemetryModes != null ? telemetryModes & 0x03 : 0;
|
_baseTelemetryMode = telemetryModes != null ? telemetryModes & 0x03 : 0;
|
||||||
@@ -381,30 +361,140 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
return _bandwidthOptions.indexOf(bw);
|
return _bandwidthOptions.indexOf(bw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int? _normalizeBandwidthValue(int? bw) {
|
||||||
|
if (bw == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (bw >= 0 && bw <= 9) {
|
||||||
|
return bw;
|
||||||
|
}
|
||||||
|
switch (bw) {
|
||||||
|
case 7800:
|
||||||
|
return 0;
|
||||||
|
case 10400:
|
||||||
|
return 1;
|
||||||
|
case 15600:
|
||||||
|
return 2;
|
||||||
|
case 20800:
|
||||||
|
return 3;
|
||||||
|
case 31250:
|
||||||
|
return 4;
|
||||||
|
case 41700:
|
||||||
|
return 5;
|
||||||
|
case 62500:
|
||||||
|
return 6;
|
||||||
|
case 125000:
|
||||||
|
return 7;
|
||||||
|
case 250000:
|
||||||
|
return 8;
|
||||||
|
case 500000:
|
||||||
|
return 9;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _normalizeSpreadingFactor(int? spreadingFactor) {
|
||||||
|
if (spreadingFactor == null ||
|
||||||
|
spreadingFactor < 7 ||
|
||||||
|
spreadingFactor > 12) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return spreadingFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _normalizeCodingRate(int? codingRate) {
|
||||||
|
if (codingRate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (codingRate >= 5 && codingRate <= 8) {
|
||||||
|
return codingRate;
|
||||||
|
}
|
||||||
|
if (codingRate >= 1 && codingRate <= 4) {
|
||||||
|
return codingRate + 4;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
_RadioPreset? _matchRadioPreset({
|
_RadioPreset? _matchRadioPreset({
|
||||||
int? frequencyKhz,
|
int? frequencyKhz,
|
||||||
int? bandwidth,
|
int? bandwidth,
|
||||||
int? spreadingFactor,
|
int? spreadingFactor,
|
||||||
int? codingRate,
|
int? codingRate,
|
||||||
}) {
|
}) {
|
||||||
|
final normalizedBandwidth = _normalizeBandwidthValue(bandwidth);
|
||||||
|
final normalizedSpreadingFactor = _normalizeSpreadingFactor(
|
||||||
|
spreadingFactor,
|
||||||
|
);
|
||||||
|
final normalizedCodingRate = _normalizeCodingRate(codingRate);
|
||||||
|
|
||||||
if (frequencyKhz == null ||
|
if (frequencyKhz == null ||
|
||||||
bandwidth == null ||
|
normalizedBandwidth == null ||
|
||||||
spreadingFactor == null ||
|
normalizedSpreadingFactor == null ||
|
||||||
codingRate == null) {
|
normalizedCodingRate == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final preset in _radioPresets) {
|
for (final preset in _radioPresets) {
|
||||||
if (preset.frequencyKhz == frequencyKhz &&
|
if (preset.frequencyKhz == frequencyKhz &&
|
||||||
preset.bandwidth == bandwidth &&
|
preset.bandwidth == normalizedBandwidth &&
|
||||||
preset.spreadingFactor == spreadingFactor &&
|
preset.spreadingFactor == normalizedSpreadingFactor &&
|
||||||
preset.codingRate == codingRate) {
|
preset.codingRate == normalizedCodingRate) {
|
||||||
return preset;
|
return preset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _radioSettingsSignature(DeviceInfo deviceInfo) {
|
||||||
|
return [
|
||||||
|
deviceInfo.radioFreq,
|
||||||
|
_normalizeBandwidthValue(deviceInfo.radioBw),
|
||||||
|
_normalizeSpreadingFactor(deviceInfo.radioSf),
|
||||||
|
_normalizeCodingRate(deviceInfo.radioCr),
|
||||||
|
deviceInfo.txPower,
|
||||||
|
deviceInfo.clientRepeat,
|
||||||
|
deviceInfo.pathHashMode,
|
||||||
|
].join('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _syncRadioSettingsState(DeviceInfo deviceInfo) {
|
||||||
|
final normalizedBandwidth = _normalizeBandwidthValue(deviceInfo.radioBw);
|
||||||
|
final normalizedSpreadingFactor = _normalizeSpreadingFactor(
|
||||||
|
deviceInfo.radioSf,
|
||||||
|
);
|
||||||
|
final normalizedCodingRate = _normalizeCodingRate(deviceInfo.radioCr);
|
||||||
|
|
||||||
|
if (deviceInfo.radioFreq != null) {
|
||||||
|
_freqController.text = (deviceInfo.radioFreq! / 1000).toStringAsFixed(3);
|
||||||
|
}
|
||||||
|
if (normalizedBandwidth != null) {
|
||||||
|
_selectedBandwidth = _bandwidthFromValue(normalizedBandwidth);
|
||||||
|
}
|
||||||
|
if (normalizedSpreadingFactor != null) {
|
||||||
|
_selectedSpreadingFactor = normalizedSpreadingFactor;
|
||||||
|
}
|
||||||
|
if (normalizedCodingRate != null) {
|
||||||
|
_selectedCodingRate = normalizedCodingRate;
|
||||||
|
}
|
||||||
|
if (deviceInfo.txPower != null) {
|
||||||
|
_txPowerController.text = deviceInfo.txPower!.toString();
|
||||||
|
}
|
||||||
|
if (deviceInfo.clientRepeat != null) {
|
||||||
|
_repeatEnabled = deviceInfo.clientRepeat!;
|
||||||
|
}
|
||||||
|
_selectedPathHashMode = deviceInfo.pathHashMode;
|
||||||
|
_selectedRadioPreset = _matchRadioPreset(
|
||||||
|
frequencyKhz: deviceInfo.radioFreq,
|
||||||
|
bandwidth: deviceInfo.radioBw,
|
||||||
|
spreadingFactor: deviceInfo.radioSf,
|
||||||
|
codingRate: deviceInfo.radioCr,
|
||||||
|
);
|
||||||
|
_showCustomRadioSettings = _selectedRadioPreset == null;
|
||||||
|
_radioSettingsDirty = false;
|
||||||
|
_lastRadioSettingsSignature = _radioSettingsSignature(deviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
void _applyRadioPreset(_RadioPreset preset) {
|
void _applyRadioPreset(_RadioPreset preset) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedRadioPreset = preset;
|
_selectedRadioPreset = preset;
|
||||||
@@ -441,6 +531,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _markRadioSettingsDirty() {
|
void _markRadioSettingsDirty() {
|
||||||
|
_radioSettingsDirty = true;
|
||||||
if (_radioSettingsSaved || _radioSettingsError != null) {
|
if (_radioSettingsSaved || _radioSettingsError != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_radioSettingsSaved = false;
|
_radioSettingsSaved = false;
|
||||||
@@ -472,15 +563,30 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleConnectionProviderChanged() {
|
void _handleConnectionProviderChanged() {
|
||||||
if (!mounted || _autoDiscoverySettingsDirty) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
final deviceInfo = _connectionProvider.deviceInfo;
|
final deviceInfo = _connectionProvider.deviceInfo;
|
||||||
final nextSignature = _autoDiscoverySignature(deviceInfo);
|
final nextRadioSettingsSignature = _radioSettingsSignature(deviceInfo);
|
||||||
if (nextSignature == _lastAutoDiscoverySignature) return;
|
final nextAutoDiscoverySignature = _autoDiscoverySignature(deviceInfo);
|
||||||
|
final shouldSyncRadioSettings =
|
||||||
|
!_radioSettingsDirty &&
|
||||||
|
nextRadioSettingsSignature != _lastRadioSettingsSignature;
|
||||||
|
final shouldSyncAutoDiscovery =
|
||||||
|
!_autoDiscoverySettingsDirty &&
|
||||||
|
nextAutoDiscoverySignature != _lastAutoDiscoverySignature;
|
||||||
|
|
||||||
|
if (!shouldSyncRadioSettings && !shouldSyncAutoDiscovery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_lastAutoDiscoverySignature = nextSignature;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_syncAutoDiscoveryState(deviceInfo);
|
if (shouldSyncRadioSettings) {
|
||||||
|
_syncRadioSettingsState(deviceInfo);
|
||||||
|
}
|
||||||
|
if (shouldSyncAutoDiscovery) {
|
||||||
|
_lastAutoDiscoverySignature = nextAutoDiscoverySignature;
|
||||||
|
_syncAutoDiscoveryState(deviceInfo);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,6 +828,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_syncRadioSettingsState(connectionProvider.deviceInfo);
|
||||||
_isSavingRadioSettings = false;
|
_isSavingRadioSettings = false;
|
||||||
_radioSettingsSaved = true;
|
_radioSettingsSaved = true;
|
||||||
});
|
});
|
||||||
@@ -1754,6 +1861,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_selectedRadioPreset = null;
|
_selectedRadioPreset = null;
|
||||||
_showCustomRadioSettings = true;
|
_showCustomRadioSettings = true;
|
||||||
|
_radioSettingsDirty = true;
|
||||||
_radioSettingsSaved = false;
|
_radioSettingsSaved = false;
|
||||||
_radioSettingsError = null;
|
_radioSettingsError = null;
|
||||||
});
|
});
|
||||||
|
|||||||
123
test/screens/device_config_screen_test.dart
Normal file
123
test/screens/device_config_screen_test.dart
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:meshcore_sar_app/l10n/app_localizations.dart';
|
||||||
|
import 'package:meshcore_sar_app/models/device_info.dart' as device_info;
|
||||||
|
import 'package:meshcore_sar_app/providers/channels_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/connection_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/screens/device_config_screen.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class _FakeConnectionProvider extends ChangeNotifier
|
||||||
|
implements ConnectionProvider {
|
||||||
|
_FakeConnectionProvider(this._deviceInfo);
|
||||||
|
|
||||||
|
device_info.DeviceInfo _deviceInfo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
device_info.DeviceInfo get deviceInfo => _deviceInfo;
|
||||||
|
|
||||||
|
void updateDeviceInfo(device_info.DeviceInfo nextDeviceInfo) {
|
||||||
|
_deviceInfo = nextDeviceInfo;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, String>> getCustomVars() async => const {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> getBatteryAndStorage() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> getAutoaddConfig() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> getAllowedRepeatFreq() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pumpDeviceConfigScreen(
|
||||||
|
WidgetTester tester, {
|
||||||
|
required ConnectionProvider connectionProvider,
|
||||||
|
}) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider<ConnectionProvider>.value(
|
||||||
|
value: connectionProvider,
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<ChannelsProvider>(
|
||||||
|
create: (_) => ChannelsProvider()..initializePublicChannel(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<ContactsProvider>(
|
||||||
|
create: (_) => ContactsProvider(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: const DeviceConfigScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets(
|
||||||
|
'matches a preset when bandwidth arrives as raw Hz',
|
||||||
|
(tester) async {
|
||||||
|
final connectionProvider = _FakeConnectionProvider(
|
||||||
|
device_info.DeviceInfo(
|
||||||
|
connectionState: device_info.ConnectionState.connected,
|
||||||
|
radioFreq: 869618,
|
||||||
|
radioBw: 62500,
|
||||||
|
radioSf: 8,
|
||||||
|
radioCr: 8,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await _pumpDeviceConfigScreen(
|
||||||
|
tester,
|
||||||
|
connectionProvider: connectionProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byKey(const ValueKey('eu_uk_narrow')), findsOneWidget);
|
||||||
|
expect(find.text('EU/UK (Narrow)'), findsWidgets);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
're-syncs the preset dropdown when device info changes',
|
||||||
|
(tester) async {
|
||||||
|
final connectionProvider = _FakeConnectionProvider(
|
||||||
|
device_info.DeviceInfo(
|
||||||
|
connectionState: device_info.ConnectionState.connected,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await _pumpDeviceConfigScreen(
|
||||||
|
tester,
|
||||||
|
connectionProvider: connectionProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byKey(const ValueKey('custom')), findsOneWidget);
|
||||||
|
|
||||||
|
connectionProvider.updateDeviceInfo(
|
||||||
|
device_info.DeviceInfo(
|
||||||
|
connectionState: device_info.ConnectionState.connected,
|
||||||
|
radioFreq: 869618,
|
||||||
|
radioBw: 62500,
|
||||||
|
radioSf: 8,
|
||||||
|
radioCr: 8,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byKey(const ValueKey('eu_uk_narrow')), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user