Skip to content
Merged
102 changes: 102 additions & 0 deletions lib/helpers/remote_radio_commands.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import '../models/radio_preset.dart';

// Radio presets on remote nodes (repeater, room server, sensor) through the
// stock admin CLI (#733). Kept out of the screen so it can be tested.

/// Default `tempradio` duration (owner decision on #662).
const int kTempRadioDefaultMinutes = 1440;

/// Longest `tempradio` duration that works. Firmware schedules the revert at
/// `futureMillis(2000 + timeout_mins * 60 * 1000)` in a 32-bit int
/// (simple_repeater/MyMesh.cpp), which overflows past this.
const int kTempRadioMaxMinutes = 35791;

String _bandwidthKHz(int bandwidthHz) => '${bandwidthHz / 1000}';

/// Stock CLI: persist radio params (applied after a reboot).
String setRadioCommand(
String frequencyText,
int bandwidthHz,
int spreadingFactor,
int codingRate,
) =>
'set radio $frequencyText,${_bandwidthKHz(bandwidthHz)},'
'$spreadingFactor,$codingRate';

/// Stock CLI: apply radio params now, reverting after [minutes]. Nothing is
/// saved on the node. Coding rate is the 4/x denominator (4/5 is 5), which
/// is what firmware range-checks (5-8).
String tempRadioCommand(
String frequencyText,
int bandwidthHz,
int spreadingFactor,
int codingRate,
int minutes,
) =>
'tempradio $frequencyText,${_bandwidthKHz(bandwidthHz)},'
'$spreadingFactor,$codingRate,$minutes';

/// A valid `tempradio` duration in minutes (1 to [kTempRadioMaxMinutes]),
/// or null.
int? parseTempRadioMinutes(String text) {
final minutes = int.tryParse(text.trim());
if (minutes == null || minutes < 1 || minutes > kTempRadioMaxMinutes) {
return null;
}
return minutes;
}

/// `tempradio` retunes the node 2 s after it replies, so anything sent after
/// it in the same save would go out on the old frequency. Keep the original
/// order otherwise, with retuning commands moved to the end.
List<T> withRetuneLast<T>(List<T> commands, bool Function(T) retunes) => [
...commands.where((c) => !retunes(c)),
...commands.where(retunes),
];

bool isRetuneCommand(String command) => command.startsWith('tempradio ');

/// Stock CLI: path hash mode, zero-based (2 bytes = mode 1).
String setPathHashModeCommand(int mode) => 'set path.hash.mode $mode';

/// Whether a path hash that came from a preset goes out in this save (#735).
/// Firmware has no temporary path hash, so with a temporary radio change it
/// would outlive the revert; it stays pending for a normal save instead. A
/// path hash the user set by hand is always sent.
bool sendsPresetPathHash({
required bool temporaryRadio,
required bool pathHashFromPreset,
}) => !(temporaryRadio && pathHashFromPreset);

/// The preset whose radio values match the form's, or null for custom values.
/// The form holds frequency as text (the repeater reports e.g. "908.205017"),
/// so frequency matches to the nearest kHz. TX power is not compared: the
/// remote screen sets it separately. A preset with a path hash only matches
/// a node on that mode (#747): MeshCore's "Canada" and "USA" differ only there.
String? matchRemotePresetId(
List<RadioPreset> presets, {
required String frequencyText,
required int? bandwidthHz,
required int? spreadingFactor,
required int? codingRate,
required int pathHashMode,
}) {
final mhz = double.tryParse(frequencyText.trim());
if (mhz == null ||
bandwidthHz == null ||
spreadingFactor == null ||
codingRate == null) {
return null;
}
final khz = (mhz * 1000).round();
for (final p in presets) {
if (p.frequencyKHz == khz &&
p.bandwidth.hz == bandwidthHz &&
p.spreadingFactor.value == spreadingFactor &&
p.codingRate.value == codingRate &&
presetPathHashMatches(p, pathHashMode + 1)) {
return p.id;
}
}
return null;
}
13 changes: 13 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@
}
},
"contacts_manageRepeater": "Manage Repeater",
"contacts_manageSensor": "Manage Sensor",
"contacts_manageRoom": "Manage Room Server",
"contacts_roomLogin": "Room Server Login",
"contacts_openChat": "Open Chat",
Expand Down Expand Up @@ -1459,6 +1460,18 @@
"repeater_bandwidth": "Bandwidth",
"repeater_spreadingFactor": "Spreading Factor",
"repeater_codingRate": "Coding Rate",
"repeater_tempRadio": "Apply temporarily",
"repeater_tempRadioSubtitle": "Uses tempradio: the node switches now and goes back to its saved radio settings after the duration. Nothing is saved on the node.",
"repeater_tempRadioMinutes": "Duration (minutes)",
"repeater_tempRadioPathHashNote": "This preset's path hash size isn't applied with a temporary change: the node can't revert it on its own. It stays pending for a normal save.",
"repeater_tempRadioMinutesInvalid": "1 to {max} minutes",
"@repeater_tempRadioMinutesInvalid": {
"placeholders": {
"max": {
"type": "int"
}
}
},
"repeater_locationSettings": "Location Settings",
"repeater_latitude": "Latitude",
"repeater_latitudeHelper": "Decimal degrees (e.g., 37.7749)",
Expand Down
36 changes: 36 additions & 0 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2182,6 +2182,12 @@ abstract class AppLocalizations {
/// **'Manage Repeater'**
String get contacts_manageRepeater;

/// No description provided for @contacts_manageSensor.
///
/// In en, this message translates to:
/// **'Manage Sensor'**
String get contacts_manageSensor;

/// No description provided for @contacts_manageRoom.
///
/// In en, this message translates to:
Expand Down Expand Up @@ -4688,6 +4694,36 @@ abstract class AppLocalizations {
/// **'Coding Rate'**
String get repeater_codingRate;

/// No description provided for @repeater_tempRadio.
///
/// In en, this message translates to:
/// **'Apply temporarily'**
String get repeater_tempRadio;

/// No description provided for @repeater_tempRadioSubtitle.
///
/// In en, this message translates to:
/// **'Uses tempradio: the node switches now and goes back to its saved radio settings after the duration. Nothing is saved on the node.'**
String get repeater_tempRadioSubtitle;

/// No description provided for @repeater_tempRadioMinutes.
///
/// In en, this message translates to:
/// **'Duration (minutes)'**
String get repeater_tempRadioMinutes;

/// No description provided for @repeater_tempRadioPathHashNote.
///
/// In en, this message translates to:
/// **'This preset\'s path hash size isn\'t applied with a temporary change: the node can\'t revert it on its own. It stays pending for a normal save.'**
String get repeater_tempRadioPathHashNote;

/// No description provided for @repeater_tempRadioMinutesInvalid.
///
/// In en, this message translates to:
/// **'1 to {max} minutes'**
String repeater_tempRadioMinutesInvalid(int max);

/// No description provided for @repeater_locationSettings.
///
/// In en, this message translates to:
Expand Down
22 changes: 22 additions & 0 deletions lib/l10n/app_localizations_bg.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get contacts_manageRepeater => 'Управление на Повтарящ се Елемент';

@override
String get contacts_manageSensor => 'Manage Sensor';

@override
String get contacts_manageRoom => 'Управление на сървър за стая';

Expand Down Expand Up @@ -2654,6 +2657,25 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get repeater_codingRate => 'Такса за кодиране';

@override
String get repeater_tempRadio => 'Apply temporarily';

@override
String get repeater_tempRadioSubtitle =>
'Uses tempradio: the node switches now and goes back to its saved radio settings after the duration. Nothing is saved on the node.';

@override
String get repeater_tempRadioMinutes => 'Duration (minutes)';

@override
String get repeater_tempRadioPathHashNote =>
'This preset\'s path hash size isn\'t applied with a temporary change: the node can\'t revert it on its own. It stays pending for a normal save.';

@override
String repeater_tempRadioMinutesInvalid(int max) {
return '1 to $max minutes';
}

@override
String get repeater_locationSettings => 'Настройки на местоположението';

Expand Down
22 changes: 22 additions & 0 deletions lib/l10n/app_localizations_de.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contacts_manageRepeater => 'Repeater verwalten';

@override
String get contacts_manageSensor => 'Manage Sensor';

@override
String get contacts_manageRoom => 'Raum-Server verwalten';

Expand Down Expand Up @@ -2649,6 +2652,25 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get repeater_codingRate => 'Kodierungsrate';

@override
String get repeater_tempRadio => 'Apply temporarily';

@override
String get repeater_tempRadioSubtitle =>
'Uses tempradio: the node switches now and goes back to its saved radio settings after the duration. Nothing is saved on the node.';

@override
String get repeater_tempRadioMinutes => 'Duration (minutes)';

@override
String get repeater_tempRadioPathHashNote =>
'This preset\'s path hash size isn\'t applied with a temporary change: the node can\'t revert it on its own. It stays pending for a normal save.';

@override
String repeater_tempRadioMinutesInvalid(int max) {
return '1 to $max minutes';
}

@override
String get repeater_locationSettings => 'Standort Einstellungen';

Expand Down
22 changes: 22 additions & 0 deletions lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get contacts_manageRepeater => 'Manage Repeater';

@override
String get contacts_manageSensor => 'Manage Sensor';

@override
String get contacts_manageRoom => 'Manage Room Server';

Expand Down Expand Up @@ -2605,6 +2608,25 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get repeater_codingRate => 'Coding Rate';

@override
String get repeater_tempRadio => 'Apply temporarily';

@override
String get repeater_tempRadioSubtitle =>
'Uses tempradio: the node switches now and goes back to its saved radio settings after the duration. Nothing is saved on the node.';

@override
String get repeater_tempRadioMinutes => 'Duration (minutes)';

@override
String get repeater_tempRadioPathHashNote =>
'This preset\'s path hash size isn\'t applied with a temporary change: the node can\'t revert it on its own. It stays pending for a normal save.';

@override
String repeater_tempRadioMinutesInvalid(int max) {
return '1 to $max minutes';
}

@override
String get repeater_locationSettings => 'Location Settings';

Expand Down
22 changes: 22 additions & 0 deletions lib/l10n/app_localizations_es.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get contacts_manageRepeater => 'Gestionar Repetidor';

@override
String get contacts_manageSensor => 'Manage Sensor';

@override
String get contacts_manageRoom => 'Gestionar Servidor de Habitación';

Expand Down Expand Up @@ -2646,6 +2649,25 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get repeater_codingRate => 'Tasa de Programación';

@override
String get repeater_tempRadio => 'Apply temporarily';

@override
String get repeater_tempRadioSubtitle =>
'Uses tempradio: the node switches now and goes back to its saved radio settings after the duration. Nothing is saved on the node.';

@override
String get repeater_tempRadioMinutes => 'Duration (minutes)';

@override
String get repeater_tempRadioPathHashNote =>
'This preset\'s path hash size isn\'t applied with a temporary change: the node can\'t revert it on its own. It stays pending for a normal save.';

@override
String repeater_tempRadioMinutesInvalid(int max) {
return '1 to $max minutes';
}

@override
String get repeater_locationSettings => 'Configuración de Ubicación';

Expand Down
22 changes: 22 additions & 0 deletions lib/l10n/app_localizations_fr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get contacts_manageRepeater => 'Gérer le répéteur';

@override
String get contacts_manageSensor => 'Manage Sensor';

@override
String get contacts_manageRoom => 'Gérer le Room Server';

Expand Down Expand Up @@ -2658,6 +2661,25 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_codingRate => 'Taux de codage (CR)';

@override
String get repeater_tempRadio => 'Apply temporarily';

@override
String get repeater_tempRadioSubtitle =>
'Uses tempradio: the node switches now and goes back to its saved radio settings after the duration. Nothing is saved on the node.';

@override
String get repeater_tempRadioMinutes => 'Duration (minutes)';

@override
String get repeater_tempRadioPathHashNote =>
'This preset\'s path hash size isn\'t applied with a temporary change: the node can\'t revert it on its own. It stays pending for a normal save.';

@override
String repeater_tempRadioMinutesInvalid(int max) {
return '1 to $max minutes';
}

@override
String get repeater_locationSettings => 'Paramètres de localisation';

Expand Down
22 changes: 22 additions & 0 deletions lib/l10n/app_localizations_hu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,9 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get contacts_manageRepeater => 'Ellenőriző eszköz kezelése';

@override
String get contacts_manageSensor => 'Manage Sensor';

@override
String get contacts_manageRoom => 'A szobai szerver kezelése';

Expand Down Expand Up @@ -2662,6 +2665,25 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get repeater_codingRate => 'Kódolási sebesség';

@override
String get repeater_tempRadio => 'Apply temporarily';

@override
String get repeater_tempRadioSubtitle =>
'Uses tempradio: the node switches now and goes back to its saved radio settings after the duration. Nothing is saved on the node.';

@override
String get repeater_tempRadioMinutes => 'Duration (minutes)';

@override
String get repeater_tempRadioPathHashNote =>
'This preset\'s path hash size isn\'t applied with a temporary change: the node can\'t revert it on its own. It stays pending for a normal save.';

@override
String repeater_tempRadioMinutesInvalid(int max) {
return '1 to $max minutes';
}

@override
String get repeater_locationSettings => 'Helyszínbeállítások';

Expand Down
Loading
Loading