diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 7ae9333..f6fd648 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -282,6 +282,7 @@ class MeshCoreConnector extends ChangeNotifier { String? _firmwareVersion; String? _deviceModel; int? _offbandCaps; + bool? _femLnaEnabled; int _pathHashByteWidth = 1; CompanionRadioStats? _latestRadioStats; Stopwatch? _airtimeBumpStopwatch; @@ -589,6 +590,48 @@ class MeshCoreConnector extends ChangeNotifier { /// True when the radio's block store hit `MAX_BLOCKED_KEYS` (32) and rejected /// an ADD (ok=0). Local block still applies; those keys just aren't portable. bool get blockOffloadStoreFull => _blockOffloadStoreFull; + + /// Whether this specific radio can control its external FEM LNA (#304). + /// + /// Gated on the capability BIT alone — firmware derives it from a runtime FEM + /// probe, so it is a per-unit answer: two Heltec V4s can legitimately disagree + /// depending on the fitted chip, and `rak3401` reports false by design (its + /// SKY66122 gates LNA and PA off one line, so a toggle would kill TX). + /// Never shortcut this to a model or version check. + bool get supportsOffbandFemLna => firmwareSupportsOffbandFemLna(_offbandCaps); + + /// Current FEM LNA state as last reported by the radio, or null if unknown + /// (pre-v16 firmware). Always reflects hardware truth, never a local guess. + bool? get femLnaEnabled => _femLnaEnabled; + + /// Ask the radio to enable or bypass its FEM LNA. No-op unless the capability + /// bit is set, so a non-capable radio never sees `0xC3` traffic. State is + /// updated from the reply, not optimistically. + Future setFemLna(bool enabled) async { + if (!isConnected || !supportsOffbandFemLna) return; + await sendFrame(buildOffbandFemLnaSetFrame(enabled)); + } + + /// Fallback read; device-info (offset 83) is the primary source on connect. + Future requestFemLnaState() async { + if (!isConnected || !supportsOffbandFemLna) return; + await sendFrame(buildOffbandFemLnaGetFrame()); + } + + /// `[0xC3][sub][value]` — the value is the post-apply hardware state, so it is + /// adopted verbatim rather than assuming a SET took effect (#304). + void _handleOffbandFemLnaReply(Uint8List frame) { + final reply = parseOffbandFemLnaReply(frame); + if (reply == null) return; + if (_femLnaEnabled == reply.enabled) return; + _femLnaEnabled = reply.enabled; + appLogger.info( + 'FEM LNA now ${reply.enabled ? 'enabled' : 'bypassed'}', + tag: 'Connector', + ); + notifyListeners(); + } + Map? get currentCustomVars => _currentCustomVars; int? get batteryMillivolts => _batteryMillivolts; int? get storageUsedKb => _storageUsedKb; @@ -4323,6 +4366,9 @@ class MeshCoreConnector extends ChangeNotifier { case cmdOffbandBlock: _handleOffbandBlockFrame(frame); break; + case cmdOffbandFemLna: + _handleOffbandFemLnaReply(frame); + break; case respCodeSelfInfo: debugPrint('Got SELF_INFO'); _handleSelfInfo(frame); @@ -4627,6 +4673,14 @@ class MeshCoreConnector extends ChangeNotifier { static int? parseOffbandCaps(Uint8List frame) => frame.length >= 83 ? frame[82] : null; + /// FEM LNA state byte, appended immediately after the caps byte in device-info + /// v16+ (firmware #298). Appended **unconditionally** — including on + /// non-capable boards, where it reads 0 — so the byte's presence indicates + /// firmware version, not capability. The capability BIT is what decides + /// whether to render the control. Null on v15 and older (shorter frame). + static bool? parseFemLnaState(Uint8List frame) => + frame.length >= 84 ? frame[83] != femLnaBypass : null; + void _handleDeviceInfo(Uint8List frame) { if (frame.length < 4) return; if (_shouldGateInitialChannelSync) { @@ -4674,6 +4728,9 @@ class MeshCoreConnector extends ChangeNotifier { // Offband config capability v14+ (byte 82). Extracted + bounds-checked in a // testable helper; offset verified against firmware (see parseOffbandCaps). _offbandCaps = parseOffbandCaps(frame); + // FEM LNA state rides one byte past the caps byte on v16+ (#304). Primary + // read on connect — a 0xC3 GET is only the fallback. + _femLnaEnabled = parseFemLnaState(frame); // Caps just landed; (re)evaluate GPS polling in case a `gps=1` custom-var // frame arrived before this device-info reply set support. (#144) _reconcileGpsPolling(); diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index e5f8821..f486e79 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -287,9 +287,17 @@ Uint8List buildOffbandFemLnaSetFrame(bool enabled) => Uint8List.fromList([ Uint8List buildOffbandFemLnaGetFrame() => Uint8List.fromList([cmdOffbandFemLna, offbandFemLnaGet]); -/// Reply to a `0xC3` request: `[0xC3][sub][value]`. A malformed request draws -/// the generic `[respCodeErr][errCodeIllegalArg]` instead, which is NOT -/// 0xC3-prefixed and so never reaches this parser. +/// Reply to a `0xC3` request: `[0xC3][sub][value]`. +/// +/// The value is the **post-apply hardware state, not an echo of the request** +/// (firmware #298 as-built): if the FEM ever refused a write, this reports the +/// truth rather than confirming a change that didn't take. Always render from +/// this value; never assume the written value stuck. +/// +/// Error replies are never 0xC3-prefixed, so they don't reach this parser: +/// malformed → `[respCodeErr][errCodeIllegalArg]`; a request to a non-capable +/// board → `[respCodeErr][errCodeUnsupportedCmd]` (unreachable when gated on +/// the capability bit, but firmware answers it defensively). class OffbandFemLnaReply { const OffbandFemLnaReply(this.subType, this.value); final int subType; @@ -316,6 +324,12 @@ const int offbandBlockClear = 0x04; /// app must recognise the 2-byte error frame and not wait for a 0xC2 echo. const int errCodeIllegalArg = 6; +/// `ERR_CODE_UNSUPPORTED_CMD` — returned for an Offband command the connected +/// board can't service (e.g. a `0xC3` FEM LNA request to a board without FEM +/// control). Unreachable when the capability bit is respected; firmware answers +/// it defensively against a stale or mis-gated client. (#304) +const int errCodeUnsupportedCmd = 1; + Uint8List buildOffbandBlockAddFrame(Uint8List pubKey) => Uint8List.fromList([cmdOffbandBlock, offbandBlockAdd, ...pubKey]); Uint8List buildOffbandBlockRemoveFrame(Uint8List pubKey) => diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 88673f8..f66e71d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -253,6 +253,8 @@ "settings_txPowerInvalid": "Invalid TX power (0-22 dBm)", "settings_clientRepeat": "Off-Grid Repeat", "settings_clientRepeatSubtitle": "Allow this device to repeat mesh packets for others", + "settings_femLna": "Receive amplifier (FEM LNA)", + "settings_femLnaSubtitle": "Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.", "settings_clientRepeatFreqWarning": "Off-grid repeat requires 433, 869, or 918 MHz frequency", "settings_error": "Error: {message}", "@settings_error": { diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 5ddefeb..c0a16b8 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1276,6 +1276,18 @@ abstract class AppLocalizations { /// **'Allow this device to repeat mesh packets for others'** String get settings_clientRepeatSubtitle; + /// No description provided for @settings_femLna. + /// + /// In en, this message translates to: + /// **'Receive amplifier (FEM LNA)'** + String get settings_femLna; + + /// No description provided for @settings_femLnaSubtitle. + /// + /// In en, this message translates to: + /// **'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'** + String get settings_femLnaSubtitle; + /// No description provided for @settings_clientRepeatFreqWarning. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index f1f49ef..263706c 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -642,6 +642,13 @@ class AppLocalizationsBg extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Позволете на това устройство да предава пакети към мрежата за други устройства.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'За повторение извън мрежата са необходими честоти от 433, 869 или 918 MHz.'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index eac244b..c90f7d3 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -637,6 +637,13 @@ class AppLocalizationsDe extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Ermöglichen Sie diesem Gerät, Mesh-Pakete für andere zu wiederholen.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Die Kommunikation ohne Stromversorgung erfordert Frequenzen von 433, 869 oder 918 MHz.'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 3f25139..a274d5b 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -627,6 +627,13 @@ class AppLocalizationsEn extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Allow this device to repeat mesh packets for others'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Off-grid repeat requires 433, 869, or 918 MHz frequency'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index aee1983..127c588 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -638,6 +638,13 @@ class AppLocalizationsEs extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Permita que este dispositivo repita los paquetes de red para otros usuarios.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Para la comunicación fuera de la red, se requiere una frecuencia de 433, 869 o 918 MHz.'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 04cd4e9..560c8fc 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -642,6 +642,13 @@ class AppLocalizationsFr extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Permettez à cet appareil de répéter les paquets de données pour les autres.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Pour les transmissions hors réseau, il est nécessaire d\'utiliser les fréquences de 433, 869 ou 918 MHz.'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 48f7f97..3205556 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -642,6 +642,13 @@ class AppLocalizationsHu extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Engedje, hogy ez a eszköz mások számára is ismételje a hálózati csomagokat.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'A hálózat nélküli kommunikációhoz 433, 869 vagy 918 MHz frekvenciát igényel.'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 875eedc..d1b43bf 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -640,6 +640,13 @@ class AppLocalizationsIt extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Permetti a questo dispositivo di ripetere i pacchetti di rete per gli altri.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Per la comunicazione fuori rete, è necessario utilizzare frequenze di 433, 869 o 918 MHz.'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 4546578..73c62b1 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -613,6 +613,13 @@ class AppLocalizationsJa extends AppLocalizations { String get settings_clientRepeatSubtitle => 'このデバイスが、他のデバイスに対してメッシュパケットを繰り返し送信できるようにする。'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'オフグリッドでの再送には、433MHz、869MHz、または918MHzの周波数が必要です。'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index d45ac1b..4d50ab1 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -613,6 +613,13 @@ class AppLocalizationsKo extends AppLocalizations { String get settings_clientRepeatSubtitle => '이 장치가 다른 사람들을 위해 메시 패킷을 반복하도록 허용합니다.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => '오프그리드(무전력) 시스템 재연결에는 433MHz, 869MHz, 또는 918MHz 주파수가 필요합니다.'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 5d857ce..7f2d73f 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -634,6 +634,13 @@ class AppLocalizationsNl extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Laat dit apparaat de berichten van andere apparaten doorsturen.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Om een signaal buiten het netwerk te versturen, zijn frequenties van 433, 869 of 918 MHz vereist.'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 72d7782..10f7852 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -643,6 +643,13 @@ class AppLocalizationsPl extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Pozwól temu urządzeniu powtarzać pakiety danych dla innych urządzeń.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Powtórka poza siecią wymaga częstotliwości 433, 869 lub 918 MHz.'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index f7f33a4..b929742 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -640,6 +640,13 @@ class AppLocalizationsPt extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Permita que este dispositivo repita pacotes de rede para outros dispositivos.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'A repetição fora da rede requer frequências de 433, 869 ou 918 MHz.'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index c218e2c..e299f98 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -640,6 +640,13 @@ class AppLocalizationsRu extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Позвольте этому устройству повторять пакеты данных для других устройств.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Для работы в режиме \"без подключения к сети\" требуется частота 433, 869 или 918 МГц.'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 14e8a14..291d587 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -634,6 +634,13 @@ class AppLocalizationsSk extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Umožnite, aby toto zariadenie opakovávalo siete pre ostatných.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Použitie off-grid systému vyžaduje frekvencie 433, 869 alebo 918 MHz.'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 518d551..88cbfbd 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -631,6 +631,13 @@ class AppLocalizationsSl extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Omogočite temu naprave, da ponavlja paketne sporočila za druge.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Za ponovni prenos na brezžični način so potrebne frekvence 433, 869 ali 918 MHz.'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index c6f9fed..0262af4 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -629,6 +629,13 @@ class AppLocalizationsSv extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Låt enheten repetera nätpaket för andra användare.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'För att kunna kommunicera utanför elnätet krävs frekvenserna 433, 869 eller 918 MHz.'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index f0dab52..68c9be4 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -637,6 +637,13 @@ class AppLocalizationsUk extends AppLocalizations { String get settings_clientRepeatSubtitle => 'Дозвольте цьому пристрою повторювати пакети даних для інших пристроїв.'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => 'Повтор без підключення до мережі вимагає частоти 433, 869 або 918 МГц.'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index d39404c..09b81e5 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -604,6 +604,13 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settings_clientRepeatSubtitle => '允许此设备重复发送网状数据包给其他设备'; + @override + String get settings_femLna => 'Receive amplifier (FEM LNA)'; + + @override + String get settings_femLnaSubtitle => + 'Boosts receive sensitivity. Leave on unless you are troubleshooting a strong nearby signal.'; + @override String get settings_clientRepeatFreqWarning => '离网重复通信需要使用 433、869 或 918 兆赫兹的频率。'; diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 819687f..16b9e16 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -2192,6 +2192,23 @@ class _RadioSettingsFormState extends State<_RadioSettingsForm> { contentPadding: EdgeInsets.zero, ), ], + // Only this radio's own FEM probe decides whether this appears — never + // model or version (#304). Deliberately not mirrored into local state: + // firmware returns post-apply hardware truth, so the switch renders + // what the radio reports rather than what we asked for. + if (widget.connector.supportsOffbandFemLna) ...[ + const SizedBox(height: 16), + ListenableBuilder( + listenable: widget.connector, + builder: (context, _) => SwitchListTile( + title: Text(l10n.settings_femLna), + subtitle: Text(l10n.settings_femLnaSubtitle), + value: widget.connector.femLnaEnabled ?? true, + onChanged: (value) => widget.connector.setFemLna(value), + contentPadding: EdgeInsets.zero, + ), + ), + ], const SizedBox(height: 16), SizedBox( width: double.infinity, diff --git a/test/connector/offband_fem_lna_test.dart b/test/connector/offband_fem_lna_test.dart index 2768dd8..df56d6a 100644 --- a/test/connector/offband_fem_lna_test.dart +++ b/test/connector/offband_fem_lna_test.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; +import 'package:meshcore_open/connector/meshcore_connector.dart'; import 'package:meshcore_open/connector/meshcore_protocol.dart'; void main() { @@ -28,6 +29,55 @@ void main() { }); }); + group('FEM LNA device-info state byte, offset 83 (#304)', () { + Uint8List deviceInfo({required int length, int femByte = 0}) { + final frame = Uint8List(length); + frame[0] = respCodeDeviceInfo; + if (length >= 84) frame[83] = femByte; + return frame; + } + + test('reads the byte immediately after caps on v16+', () { + expect( + MeshCoreConnector.parseFemLnaState(deviceInfo(length: 84, femByte: 1)), + isTrue, + ); + expect( + MeshCoreConnector.parseFemLnaState(deviceInfo(length: 84, femByte: 0)), + isFalse, + ); + }); + + test('null on pre-v16 firmware that stops at the caps byte', () { + expect( + MeshCoreConnector.parseFemLnaState(deviceInfo(length: 83)), + isNull, + ); + expect(MeshCoreConnector.parseFemLnaState(Uint8List(0)), isNull); + }); + + test('does not disturb the caps byte at offset 82', () { + final frame = deviceInfo(length: 84, femByte: 1); + frame[82] = offbandCapBlock | offbandCapFemLna; + expect(MeshCoreConnector.parseOffbandCaps(frame), equals(0x06)); + expect(MeshCoreConnector.parseFemLnaState(frame), isTrue); + }); + + test('byte is present on non-capable boards and reads as bypassed', () { + // Firmware appends it unconditionally, so presence indicates version, + // not capability — the cap bit is what gates the UI. + final frame = deviceInfo(length: 84, femByte: 0); + frame[82] = 0x00; + expect(MeshCoreConnector.parseFemLnaState(frame), isFalse); + expect( + firmwareSupportsOffbandFemLna( + MeshCoreConnector.parseOffbandCaps(frame), + ), + isFalse, + ); + }); + }); + group('FEM LNA frames (#304)', () { test('SET carries the enable value', () { expect( diff --git a/untranslated.json b/untranslated.json index 56be62e..31dc9b1 100644 --- a/untranslated.json +++ b/untranslated.json @@ -15,6 +15,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -73,6 +75,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -131,6 +135,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -189,6 +195,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -247,6 +255,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -305,6 +315,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -363,6 +375,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -421,6 +435,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -479,6 +495,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -537,6 +555,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -595,6 +615,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -653,6 +675,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -711,6 +735,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -769,6 +795,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -827,6 +855,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -885,6 +915,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h", @@ -943,6 +975,8 @@ "settings_infoFirmware", "settings_infoModel", "settings_publicKeyCopied", + "settings_femLna", + "settings_femLnaSubtitle", "appSettings_clock", "appSettings_clockSystem", "appSettings_clock12h",