diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 836d351..a5759cd 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -13,6 +13,7 @@ import '../models/channel_message.dart'; import '../models/companion_radio_stats.dart'; import '../models/contact.dart'; import '../models/message.dart'; +import '../models/offband_gps_status.dart'; import '../models/path_selection.dart'; import '../models/translation_support.dart'; import '../helpers/reaction_helper.dart'; @@ -2549,6 +2550,7 @@ class MeshCoreConnector extends ChangeNotifier { _firmwareVerCode = null; _firmwareVersion = null; _deviceModel = null; + _latestOffbandGpsStatus = null; _batteryMillivolts = null; _repeaterBatterySnapshots.clear(); _batteryRequested = false; @@ -2637,8 +2639,11 @@ class MeshCoreConnector extends ChangeNotifier { _batteryPollTimer = null; } - /// Start polling the radio's GPS-backed self-info every minute. - /// No-op if already running. Triggered when the radio reports `gps=1`. + /// Poll the radio's GPS position every minute via the lightweight Offband + /// `0xC1` query — NOT `CMD_APP_START`, which re-inits the whole device + /// (re-walks channels, re-drains messages) and floods slow BLE links. (#140) + /// No-op if already running. Triggered when the radio reports `gps=1`; the + /// reply updates self-lat/lon via `_handleOffbandGps`. (#135) void _startGpsLocationPolling() { if (_gpsLocationPollTimer != null) return; _gpsLocationPollTimer = Timer.periodic(_gpsLocationPollInterval, (timer) { @@ -2647,7 +2652,7 @@ class MeshCoreConnector extends ChangeNotifier { _gpsLocationPollTimer = null; return; } - unawaited(sendFrame(buildAppStartFrame())); + unawaited(requestOffbandGps()); }); } @@ -3805,6 +3810,61 @@ class MeshCoreConnector extends ChangeNotifier { await getChannels(force: true); } + // --- Offband GPS query (CMD_OFFBAND_GPS / RESP_CODE_OFFBAND_GPS = 0xC1) (#135) + OffbandGpsStatus? _latestOffbandGpsStatus; + Completer? _offbandGpsCompleter; + + /// Latest GPS state from the most recent [requestOffbandGps], or null until + /// one completes (cleared on disconnect). + OffbandGpsStatus? get offbandGpsStatus => _latestOffbandGpsStatus; + + /// Sends the 1-byte `0xC1` request and resolves with the parsed reply, or null + /// on timeout / disconnect. Companion-available (no observer gate). (#135) + Future requestOffbandGps({ + Duration timeout = const Duration(seconds: 5), + }) async { + if (!isConnected) return null; + final existing = _offbandGpsCompleter; + if (existing != null && !existing.isCompleted) { + // A query is already in flight — share it rather than orphaning it. + return existing.future.timeout(timeout, onTimeout: () => null); + } + final completer = Completer(); + _offbandGpsCompleter = completer; + await sendFrame(buildOffbandGpsRequestFrame()); + try { + return await completer.future.timeout(timeout); + } on TimeoutException { + return null; + } finally { + if (identical(_offbandGpsCompleter, completer)) { + _offbandGpsCompleter = null; + } + } + } + + void _handleOffbandGps(Uint8List frame) { + if (frame.length < 2) return; + final text = utf8.decode(frame.sublist(1), allowMalformed: true); + final status = OffbandGpsStatus.parse(text); + // Claim the in-flight request's completer up front so a late reply can't + // complete a newer request's future (stale-data race). (#135) + final completer = _offbandGpsCompleter; + _offbandGpsCompleter = null; + _latestOffbandGpsStatus = status; + // A live fix is the device's real position — keep self-lat/lon (and the + // map) fresh from it, replacing what the old APP_START GPS poll did. (#140) + if (status.hasLiveFix) { + _selfLatitude = status.latitude; + _selfLongitude = status.longitude; + } + _appDebugLogService?.info('Offband GPS: $text', tag: 'GPS'); + if (completer != null && !completer.isCompleted) { + completer.complete(status); + } + notifyListeners(); + } + void _handleFrame(List data) { if (data.isEmpty) return; _lastRxTime = DateTime.now(); @@ -3823,6 +3883,9 @@ class MeshCoreConnector extends ChangeNotifier { case respCodeDeviceInfo: _handleDeviceInfo(frame); break; + case respCodeOffbandGps: + _handleOffbandGps(frame); + break; case respCodeSelfInfo: debugPrint('Got SELF_INFO'); _handleSelfInfo(frame); diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index 8b4144d..d30f878 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -255,6 +255,14 @@ const int respCodeCustomVars = 21; const int respCodeAutoAddConfig = 25; const int respCodeStats = 24; +/// Offband fork-only extension space (0xC0+) — never collides with upstream, +/// never submitted upstream. Request and reply share the code. (#135) +const int cmdOffbandGps = 0xC1; +const int respCodeOffbandGps = 0xC1; + +/// Request frame for [cmdOffbandGps] — a bare 1-byte command, no payload. (#135) +Uint8List buildOffbandGpsRequestFrame() => Uint8List.fromList([cmdOffbandGps]); + const int statsTypeCore = 0; const int statsTypeRadio = 1; const int statsTypePackets = 2; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9c7407e..936e0b4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -167,6 +167,16 @@ "settings_locationGPSEnable": "GPS Enable", "settings_locationGPSEnableSubtitle": "Enables GPS to automatically update location.", "settings_locationIntervalSec": "Interval for GPS (Seconds)", + "settings_gpsStatusTitle": "GPS status", + "settings_gpsStatusRefresh": "Query GPS", + "settings_gpsStatusLiveFix": "Live GPS fix", + "settings_gpsStatusNoFix": "No fix — using stored/last position", + "settings_gpsStatusCoords": "Coordinates", + "settings_gpsStatusSats": "Satellites", + "settings_gpsStatusAltitude": "Altitude", + "settings_gpsStatusTime": "Fix time", + "settings_gpsStatusTapRefresh": "Tap Query GPS to read the current GPS state", + "settings_gpsStatusNoResponse": "No response — device may need the GPS-query firmware build", "settings_locationIntervalInvalid": "Interval must be at least 60 seconds, and less than 86400 seconds.", "settings_latitude": "Latitude", "settings_longitude": "Longitude", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e111cbe..020ed4c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -802,6 +802,66 @@ abstract class AppLocalizations { /// **'Interval for GPS (Seconds)'** String get settings_locationIntervalSec; + /// No description provided for @settings_gpsStatusTitle. + /// + /// In en, this message translates to: + /// **'GPS status'** + String get settings_gpsStatusTitle; + + /// No description provided for @settings_gpsStatusRefresh. + /// + /// In en, this message translates to: + /// **'Query GPS'** + String get settings_gpsStatusRefresh; + + /// No description provided for @settings_gpsStatusLiveFix. + /// + /// In en, this message translates to: + /// **'Live GPS fix'** + String get settings_gpsStatusLiveFix; + + /// No description provided for @settings_gpsStatusNoFix. + /// + /// In en, this message translates to: + /// **'No fix — using stored/last position'** + String get settings_gpsStatusNoFix; + + /// No description provided for @settings_gpsStatusCoords. + /// + /// In en, this message translates to: + /// **'Coordinates'** + String get settings_gpsStatusCoords; + + /// No description provided for @settings_gpsStatusSats. + /// + /// In en, this message translates to: + /// **'Satellites'** + String get settings_gpsStatusSats; + + /// No description provided for @settings_gpsStatusAltitude. + /// + /// In en, this message translates to: + /// **'Altitude'** + String get settings_gpsStatusAltitude; + + /// No description provided for @settings_gpsStatusTime. + /// + /// In en, this message translates to: + /// **'Fix time'** + String get settings_gpsStatusTime; + + /// No description provided for @settings_gpsStatusTapRefresh. + /// + /// In en, this message translates to: + /// **'Tap Query GPS to read the current GPS state'** + String get settings_gpsStatusTapRefresh; + + /// No description provided for @settings_gpsStatusNoResponse. + /// + /// In en, this message translates to: + /// **'No response — device may need the GPS-query firmware build'** + String get settings_gpsStatusNoResponse; + /// No description provided for @settings_locationIntervalInvalid. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index e833c8b..e7dcab1 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -378,6 +378,38 @@ class AppLocalizationsBg extends AppLocalizations { @override String get settings_locationIntervalSec => 'Интервал за GPS (Секунди)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Интервалът трябва да бъде поне 60 секунди и по-малко от 86400 секунди.'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index c26821e..14c495e 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -380,6 +380,38 @@ class AppLocalizationsDe extends AppLocalizations { @override String get settings_locationIntervalSec => 'Intervall für GPS (Sekunden)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Das Intervall muss mindestens 60 Sekunden und weniger als 86400 Sekunden betragen.'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f2677d9..31d4642 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -374,6 +374,38 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settings_locationIntervalSec => 'Interval for GPS (Seconds)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Interval must be at least 60 seconds, and less than 86400 seconds.'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index e51b457..8141fce 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -378,6 +378,38 @@ class AppLocalizationsEs extends AppLocalizations { @override String get settings_locationIntervalSec => 'Intervalo para GPS (Segundos)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'El intervalo debe ser de al menos 60 segundos y menor que 86400 segundos.'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 399f6be..5d25d1d 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -381,6 +381,38 @@ class AppLocalizationsFr extends AppLocalizations { String get settings_locationIntervalSec => 'Intervalle de mise-à-jour du GPS (Secondes)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'L\'intervalle doit être compris entre 60 et 86400 secondes.'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index ed3e66d..f1160bd 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -380,6 +380,38 @@ class AppLocalizationsHu extends AppLocalizations { String get settings_locationIntervalSec => 'GPS-számolási intervallum (másodpercek)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Az intervallum legalább 60 másodpercnek, de legfeljebb 86400 másodpercnak kell lennie.'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 38d8b40..3597901 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -380,6 +380,38 @@ class AppLocalizationsIt extends AppLocalizations { @override String get settings_locationIntervalSec => 'Intervallo GPS (Secondi)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'L\'intervallo deve essere di almeno 60 secondi e inferiore a 86400 secondi.'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 9cdaa2f..aaf858d 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -363,6 +363,38 @@ class AppLocalizationsJa extends AppLocalizations { @override String get settings_locationIntervalSec => 'GPS データの取得間隔(秒)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => '間隔は少なくとも60秒で、86400秒未満でなければなりません。'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 44c35eb..d38ca2e 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -363,6 +363,38 @@ class AppLocalizationsKo extends AppLocalizations { @override String get settings_locationIntervalSec => 'GPS 간격 (초)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => '간격은 최소 60초 이상, 86400초 미만이어야 합니다.'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 9f1af28..21a6425 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -377,6 +377,38 @@ class AppLocalizationsNl extends AppLocalizations { @override String get settings_locationIntervalSec => 'Interval voor GPS (Seconden)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'De intervallen moeten minstens 60 seconden zijn en minder dan 86400 seconden.'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index f0e81fa..bba3222 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -383,6 +383,38 @@ class AppLocalizationsPl extends AppLocalizations { @override String get settings_locationIntervalSec => 'Interwał dla GPS (Sekundy)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Interwał musi wynosić co najmniej 60 sekund i mniej niż 86400 sekund.'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 3671f97..9cf43c5 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -380,6 +380,38 @@ class AppLocalizationsPt extends AppLocalizations { @override String get settings_locationIntervalSec => 'Intervalo para GPS (Segundos)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'O intervalo deve ser de pelo menos 60 segundos e inferior a 86400 segundos.'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index acb55d6..e2ee954 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -379,6 +379,38 @@ class AppLocalizationsRu extends AppLocalizations { String get settings_locationIntervalSec => 'Интервал для позиционирования GPS (секунды)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Интервал должен составлять не менее 60 секунд и не более 86400 секунд.'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 307fc79..2d924b4 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -378,6 +378,38 @@ class AppLocalizationsSk extends AppLocalizations { @override String get settings_locationIntervalSec => 'Interval pre GPS (Sekundy)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Interval musí byť aspoň 60 sekúnd a menej ako 86400 sekúnd.'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 55f9ff5..4119320 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -376,6 +376,38 @@ class AppLocalizationsSl extends AppLocalizations { @override String get settings_locationIntervalSec => 'Interval za GPS (Sekunde)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Intervallo mora biti vsaj 60 sekund in manj kot 86400 sekund.'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 3dac7ef..975167c 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -375,6 +375,38 @@ class AppLocalizationsSv extends AppLocalizations { @override String get settings_locationIntervalSec => 'Interval för GPS (Sekunder)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Intervalet måste vara minst 60 sekunder och mindre än 86400 sekunder.'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 1bbc53d..3f11c3b 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -377,6 +377,38 @@ class AppLocalizationsUk extends AppLocalizations { @override String get settings_locationIntervalSec => 'Інтервал для GPS (Секунди)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => 'Інтервал має бути не менше 60 секунд і менше 86400 секунд.'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 542e440..3a9b338 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -360,6 +360,38 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settings_locationIntervalSec => 'GPS 间隔(秒)'; + @override + String get settings_gpsStatusTitle => 'GPS status'; + + @override + String get settings_gpsStatusRefresh => 'Query GPS'; + + @override + String get settings_gpsStatusLiveFix => 'Live GPS fix'; + + @override + String get settings_gpsStatusNoFix => 'No fix — using stored/last position'; + + @override + String get settings_gpsStatusCoords => 'Coordinates'; + + @override + String get settings_gpsStatusSats => 'Satellites'; + + @override + String get settings_gpsStatusAltitude => 'Altitude'; + + @override + String get settings_gpsStatusTime => 'Fix time'; + + @override + String get settings_gpsStatusTapRefresh => + 'Tap Query GPS to read the current GPS state'; + + @override + String get settings_gpsStatusNoResponse => + 'No response — device may need the GPS-query firmware build'; + @override String get settings_locationIntervalInvalid => '间隔时间必须至少为 60 秒,但不超过 86400 秒。'; diff --git a/lib/models/offband_gps_status.dart b/lib/models/offband_gps_status.dart new file mode 100644 index 0000000..b21886d --- /dev/null +++ b/lib/models/offband_gps_status.dart @@ -0,0 +1,64 @@ +/// Parsed payload of a `RESP_CODE_OFFBAND_GPS` (0xC1) reply — the on-demand GPS +/// state from an Offband-fork companion radio. (#135) +/// +/// Wire format is one ASCII string of space-separated `key=value` tokens, e.g. +/// `enabled=1 detected=1 active=1 fix=1 lat=39421530 lon=-84449000 alt_cm=23390 sats=12 time=1782531085`. +/// `lat`/`lon` are 1e-6 degrees (same units as the SELF_INFO frame). +class OffbandGpsStatus { + final bool enabled; + final bool detected; + final bool active; + final bool fix; + final double? latitude; + final double? longitude; + final int? altitudeCm; + final int? satellites; + final DateTime? timestampUtc; + + const OffbandGpsStatus({ + required this.enabled, + required this.detected, + required this.active, + required this.fix, + this.latitude, + this.longitude, + this.altitudeCm, + this.satellites, + this.timestampUtc, + }); + + /// True when the radio reports a live satellite fix with usable coordinates — + /// i.e. position is GPS-driven, not a stored/manual location. (#135) + bool get hasLiveFix => fix && latitude != null && longitude != null; + + /// Parses the ASCII `key=value` reply string. Unknown/missing keys are + /// tolerated (null); `lat`/`lon` are scaled from 1e-6 degrees. + static OffbandGpsStatus parse(String raw) { + final fields = {}; + for (final token in raw.trim().split(RegExp(r'\s+'))) { + final eq = token.indexOf('='); + if (eq > 0) fields[token.substring(0, eq)] = token.substring(eq + 1); + } + int? asInt(String key) { + final v = fields[key]; + return v == null ? null : int.tryParse(v); + } + + final lat = asInt('lat'); + final lon = asInt('lon'); + final time = asInt('time'); + return OffbandGpsStatus( + enabled: fields['enabled'] == '1', + detected: fields['detected'] == '1', + active: fields['active'] == '1', + fix: fields['fix'] == '1', + latitude: lat == null ? null : lat / 1e6, + longitude: lon == null ? null : lon / 1e6, + altitudeCm: asInt('alt_cm'), + satellites: asInt('sats'), + timestampUtc: (time == null || time <= 0) + ? null + : DateTime.fromMillisecondsSinceEpoch(time * 1000, isUtc: true), + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 5443936..fff0339 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -8,6 +8,7 @@ import 'package:package_info_plus/package_info_plus.dart'; import '../connector/meshcore_connector.dart'; import '../connector/meshcore_protocol.dart'; import '../l10n/l10n.dart'; +import '../models/offband_gps_status.dart'; import '../models/radio_settings.dart'; import '../services/app_debug_log_service.dart'; import '../connector/observer_config_client.dart'; @@ -680,6 +681,124 @@ class _SettingsScreenState extends State { ); } + Widget _buildGpsQuerySection( + BuildContext context, + OffbandGpsStatus? status, + bool querying, + bool queriedOnce, + VoidCallback onRefresh, + ) { + final l10n = context.l10n; + final theme = Theme.of(context); + + Widget row(String label, String value) => Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label), + Text(value, style: const TextStyle(fontWeight: FontWeight.w500)), + ], + ), + ); + + final children = [ + const SizedBox(height: 8), + const Divider(), + Row( + children: [ + Expanded( + child: Text( + l10n.settings_gpsStatusTitle, + style: theme.textTheme.titleSmall, + ), + ), + if (querying) + const Padding( + padding: EdgeInsets.all(8), + child: SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ) + else + IconButton( + icon: const Icon(Icons.refresh), + tooltip: l10n.settings_gpsStatusRefresh, + onPressed: onRefresh, + ), + ], + ), + ]; + + if (status != null) { + final live = status.hasLiveFix; + final color = live ? Colors.green : Colors.orange; + children.add( + Row( + children: [ + Icon( + live ? Icons.gps_fixed : Icons.gps_off, + size: 18, + color: color, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + live + ? l10n.settings_gpsStatusLiveFix + : l10n.settings_gpsStatusNoFix, + style: TextStyle(color: color, fontWeight: FontWeight.w600), + ), + ), + ], + ), + ); + children.add(const SizedBox(height: 8)); + final coords = (status.latitude != null && status.longitude != null) + ? '${status.latitude!.toStringAsFixed(6)}, ' + '${status.longitude!.toStringAsFixed(6)}' + : '—'; + children.add(row(l10n.settings_gpsStatusCoords, coords)); + children.add( + row(l10n.settings_gpsStatusSats, status.satellites?.toString() ?? '—'), + ); + children.add( + row( + l10n.settings_gpsStatusAltitude, + status.altitudeCm != null + ? '${(status.altitudeCm! / 100).toStringAsFixed(1)} m' + : '—', + ), + ); + final t = status.timestampUtc?.toLocal(); + final ml = MaterialLocalizations.of(context); + final timeStr = t == null + ? '—' + : '${ml.formatCompactDate(t)} ' + '${ml.formatTimeOfDay(TimeOfDay.fromDateTime(t))}'; + children.add(row(l10n.settings_gpsStatusTime, timeStr)); + } else { + children.add( + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Text( + queriedOnce + ? l10n.settings_gpsStatusNoResponse + : l10n.settings_gpsStatusTapRefresh, + style: theme.textTheme.bodySmall, + ), + ), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ); + } + void _editLocation(BuildContext context, MeshCoreConnector connector) { final l10n = context.l10n; final latController = TextEditingController(); @@ -698,6 +817,10 @@ class _SettingsScreenState extends State { int.tryParse(customVars["gps_interval"] ?? "") ?? 900; intervalController.text = currentInterval.toString(); + OffbandGpsStatus? gpsStatus = connector.offbandGpsStatus; + bool queryingGps = false; + bool queriedOnce = false; + showDialog( context: context, builder: (dialogContext) => StatefulBuilder( @@ -763,6 +886,22 @@ class _SettingsScreenState extends State { } }, ), + _buildGpsQuerySection( + context, + gpsStatus, + queryingGps, + queriedOnce, + () async { + setDialogState(() => queryingGps = true); + final s = await connector.requestOffbandGps(); + if (!dialogContext.mounted) return; + setDialogState(() { + queryingGps = false; + queriedOnce = true; + gpsStatus = s; + }); + }, + ), ], ], ), diff --git a/test/offband_gps_status_test.dart b/test/offband_gps_status_test.dart new file mode 100644 index 0000000..d3d2a11 --- /dev/null +++ b/test/offband_gps_status_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:meshcore_open/models/offband_gps_status.dart'; + +void main() { + group('OffbandGpsStatus.parse', () { + test('parses a full reply with a live fix', () { + final s = OffbandGpsStatus.parse( + 'enabled=1 detected=1 active=1 fix=1 lat=39421530 lon=-84449000 ' + 'alt_cm=23390 sats=12 time=1782531085', + ); + expect(s.enabled, isTrue); + expect(s.detected, isTrue); + expect(s.active, isTrue); + expect(s.fix, isTrue); + expect(s.latitude, closeTo(39.42153, 1e-9)); + expect(s.longitude, closeTo(-84.449, 1e-9)); + expect(s.altitudeCm, 23390); + expect(s.satellites, 12); + expect( + s.timestampUtc, + DateTime.fromMillisecondsSinceEpoch(1782531085 * 1000, isUtc: true), + ); + expect(s.hasLiveFix, isTrue); + }); + + test('no fix → flags false, hasLiveFix false even with stale coords', () { + final s = OffbandGpsStatus.parse( + 'enabled=1 detected=1 active=0 fix=0 lat=39421530 lon=-84449000 ' + 'sats=0 time=0', + ); + expect(s.fix, isFalse); + expect(s.active, isFalse); + expect(s.hasLiveFix, isFalse); + expect(s.timestampUtc, isNull); // time=0 → null + }); + + test('missing optional keys are tolerated', () { + final s = OffbandGpsStatus.parse('enabled=0 detected=0 active=0 fix=0'); + expect(s.enabled, isFalse); + expect(s.latitude, isNull); + expect(s.longitude, isNull); + expect(s.altitudeCm, isNull); + expect(s.satellites, isNull); + expect(s.timestampUtc, isNull); + expect(s.hasLiveFix, isFalse); + }); + + test('extra whitespace and unknown keys are ignored', () { + final s = OffbandGpsStatus.parse( + ' enabled=1 fix=1 foo=bar lat=1000000 lon=2000000 ', + ); + expect(s.enabled, isTrue); + expect(s.fix, isTrue); + expect(s.latitude, closeTo(1.0, 1e-9)); + expect(s.longitude, closeTo(2.0, 1e-9)); + expect(s.hasLiveFix, isTrue); + }); + }); +} diff --git a/untranslated.json b/untranslated.json index 010fae4..068a04a 100644 --- a/untranslated.json +++ b/untranslated.json @@ -2,6 +2,16 @@ "bg": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -16,6 +26,16 @@ "de": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -30,6 +50,16 @@ "es": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -44,6 +74,16 @@ "fr": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -58,6 +98,16 @@ "hu": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -72,6 +122,16 @@ "it": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -86,6 +146,16 @@ "ja": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -100,6 +170,16 @@ "ko": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -114,6 +194,16 @@ "nl": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -128,6 +218,16 @@ "pl": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -142,6 +242,16 @@ "pt": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -156,6 +266,16 @@ "ru": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -170,6 +290,16 @@ "sk": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -184,6 +314,16 @@ "sl": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -198,6 +338,16 @@ "sv": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -212,6 +362,16 @@ "uk": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock", @@ -226,6 +386,16 @@ "zh": [ "settings_messageSettings", "settings_messageSettingsSubtitle", + "settings_gpsStatusTitle", + "settings_gpsStatusRefresh", + "settings_gpsStatusLiveFix", + "settings_gpsStatusNoFix", + "settings_gpsStatusCoords", + "settings_gpsStatusSats", + "settings_gpsStatusAltitude", + "settings_gpsStatusTime", + "settings_gpsStatusTapRefresh", + "settings_gpsStatusNoResponse", "settings_infoFirmware", "settings_infoModel", "appSettings_clock",