From 65688b7b99f5ed6c24e8deaf00bca41def4b381b Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Mon, 12 Jan 2026 16:18:42 +0100 Subject: [PATCH] Fixed BLE Scan activation after Disconect --- firmware/libs/ble_mgmt/src/ble_mgmt.c | 40 +++++- software/app/lib/l10n/app_de.arb | 4 +- software/app/lib/l10n/app_localizations.dart | 4 +- .../app/lib/l10n/app_localizations_de.dart | 4 +- software/app/lib/main.dart | 9 +- software/app/lib/models/device_model.dart | 1 + .../app/lib/providers/device_provider.dart | 127 +++++++++--------- .../ui/screens/device_selection_screen.dart | 13 +- .../app/lib/ui/widgets/rename_dialog.dart | 1 + software/app/test/widget_test.dart | 30 ----- 10 files changed, 119 insertions(+), 114 deletions(-) delete mode 100644 software/app/test/widget_test.dart diff --git a/firmware/libs/ble_mgmt/src/ble_mgmt.c b/firmware/libs/ble_mgmt/src/ble_mgmt.c index 1e24347..2eace3b 100644 --- a/firmware/libs/ble_mgmt/src/ble_mgmt.c +++ b/firmware/libs/ble_mgmt/src/ble_mgmt.c @@ -46,6 +46,7 @@ LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL); /* --- Global Variables --- */ static uint8_t device_role = 0; // Store device type for provisioning static uint8_t adv_enabled = 0; // Track advertising state +static struct k_work_delayable adv_restart_work; /* --- GATT Callbacks --- */ @@ -218,10 +219,24 @@ static const struct bt_data ad[] = { BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)), }; +static void adv_restart_work_handler(struct k_work *work) +{ + if (adv_enabled == 0) + { + int err = ble_mgmt_adv_start(); + if (err) { + LOG_ERR("Fehler beim verzögerten Neustarten des Advertisings (err %d)", err); + } else { + LOG_INF("Advertising nach Verzögerung erfolgreich neu gestartet"); + } + } +} + int ble_mgmt_init(uint8_t device_type) { device_role = device_type; - + k_work_init_delayable(&adv_restart_work, adv_restart_work_handler); + int err = bt_enable(NULL); if (err) return err; @@ -266,4 +281,25 @@ int ble_mgmt_adv_stop(void) adv_enabled = 0; } return err; -} \ No newline at end of file +} + +static void connected(struct bt_conn *conn, uint8_t err) +{ + if (err) { + LOG_ERR("Verbindung fehlgeschlagen (err %u)", err); + } else { + LOG_INF("Host verbunden"); + adv_enabled = 0; + } +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + LOG_INF("Verbindung getrennt (Grund %u)", reason); + k_work_reschedule(&adv_restart_work, K_MSEC(100)); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, +}; \ No newline at end of file diff --git a/software/app/lib/l10n/app_de.arb b/software/app/lib/l10n/app_de.arb index cbd4b8b..642ba13 100644 --- a/software/app/lib/l10n/app_de.arb +++ b/software/app/lib/l10n/app_de.arb @@ -3,9 +3,9 @@ "appTitle": "Lasertag Mission Control", "selectLeader": "Leader auswählen", "searching": "Suche nach Knoten...", - "listTypeLeader": "Gameleadergeräte", + "listTypeLeader": "Game LEader", "typeWeapon": "Waffe", "typeVest": "Weste", "typeBeacon": "Beacon", - "listTypeEquipment": "Ausrüstungsgeräte" + "listTypeEquipment": "Sonstiges" } diff --git a/software/app/lib/l10n/app_localizations.dart b/software/app/lib/l10n/app_localizations.dart index 0c263fd..0293c5e 100644 --- a/software/app/lib/l10n/app_localizations.dart +++ b/software/app/lib/l10n/app_localizations.dart @@ -115,7 +115,7 @@ abstract class AppLocalizations { /// No description provided for @listTypeLeader. /// /// In de, this message translates to: - /// **'Gameleadergeräte'** + /// **'Game LEader'** String get listTypeLeader; /// No description provided for @typeWeapon. @@ -139,7 +139,7 @@ abstract class AppLocalizations { /// No description provided for @listTypeEquipment. /// /// In de, this message translates to: - /// **'Ausrüstungsgeräte'** + /// **'Sonstiges'** String get listTypeEquipment; } diff --git a/software/app/lib/l10n/app_localizations_de.dart b/software/app/lib/l10n/app_localizations_de.dart index 823cb2e..bb3be58 100644 --- a/software/app/lib/l10n/app_localizations_de.dart +++ b/software/app/lib/l10n/app_localizations_de.dart @@ -18,7 +18,7 @@ class AppLocalizationsDe extends AppLocalizations { String get searching => 'Suche nach Knoten...'; @override - String get listTypeLeader => 'Gameleadergeräte'; + String get listTypeLeader => 'Game LEader'; @override String get typeWeapon => 'Waffe'; @@ -30,5 +30,5 @@ class AppLocalizationsDe extends AppLocalizations { String get typeBeacon => 'Beacon'; @override - String get listTypeEquipment => 'Ausrüstungsgeräte'; + String get listTypeEquipment => 'Sonstiges'; } diff --git a/software/app/lib/main.dart b/software/app/lib/main.dart index f1c4bc5..d836bbe 100644 --- a/software/app/lib/main.dart +++ b/software/app/lib/main.dart @@ -1,14 +1,13 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; // Neu import 'package:dynamic_color/dynamic_color.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter/material.dart'; import 'package:lasertag_app/l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; + +import 'providers/device_provider.dart'; import 'ui/screens/device_selection_screen.dart'; -import 'providers/device_provider.dart'; // Neu void main() { runApp( - // Den Provider hier für die ganze App bereitstellen ChangeNotifierProvider( create: (context) => DeviceProvider(), child: const LasertagApp(), diff --git a/software/app/lib/models/device_model.dart b/software/app/lib/models/device_model.dart index 96b5a40..c3022db 100644 --- a/software/app/lib/models/device_model.dart +++ b/software/app/lib/models/device_model.dart @@ -1,4 +1,5 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart'; + import '../../constants.dart'; class LasertagDevice { diff --git a/software/app/lib/providers/device_provider.dart b/software/app/lib/providers/device_provider.dart index 4ac0ac8..ec95de0 100644 --- a/software/app/lib/providers/device_provider.dart +++ b/software/app/lib/providers/device_provider.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'dart:convert'; -import '../models/device_model.dart'; + import '../constants.dart'; +import '../models/device_model.dart'; class DeviceProvider extends ChangeNotifier { final List _discoveredLeaders = []; @@ -13,69 +15,7 @@ class DeviceProvider extends ChangeNotifier { List get leaders => _discoveredLeaders; List get peripherals => _discoveredPeripherals; - // 1. HARDWARE LESEN - Future readDeviceNameFromHardware(LasertagDevice ltDevice) async { - try { - if (!ltDevice.btDevice.isConnected) { - await ltDevice.btDevice.connect( - license: License.free, - timeout: const Duration(seconds: 5), - ); - } - - List services = await ltDevice.btDevice.discoverServices(); - var service = services.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService)); - var characteristic = service.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provNameChar)); - - List value = await characteristic.read(); - String hardwareName = utf8.decode(value); - - // Den Namen lokal speichern und als verifiziert markieren (für das Styling) - updateDeviceName(ltDevice.id, hardwareName, verified: true); - - return hardwareName; - } catch (e) { - debugPrint("Fehler beim Lesen der Hardware: $e"); - rethrow; - } - } - - // 2. HARDWARE SCHREIBEN - Future updateDeviceNameOnHardware(LasertagDevice ltDevice, String newName) async { - try { - await ltDevice.btDevice.connect( - license: License.free, - autoConnect: false, - timeout: const Duration(seconds: 10), - ); - - await Future.delayed(const Duration(milliseconds: 500)); - - List services = await ltDevice.btDevice.discoverServices(); - var service = services.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService)); - var characteristic = service.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provNameChar)); - - await characteristic.write(utf8.encode(newName)); - - // Lokal aktualisieren und als verifiziert markieren - updateDeviceName(ltDevice.id, newName, verified: true); - - await ltDevice.btDevice.disconnect(); - } catch (e) { - debugPrint("Hardware-Fehler: $e"); - rethrow; - } - } - - // 3. LOKALE LISTE AKTUALISIEREN (Hilfsmethode konsolidiert) - void updateDeviceName(String id, String newName, {bool verified = false}) { - _updateDeviceInLists( - id, - (old) => old.copyWith(name: newName, isNameVerified: verified) - ); - } - - // 4. BLUETOOTH SCAN + // Öffentlicher API-Entry: Scan starten void startScan() async { _discoveredLeaders.clear(); _discoveredPeripherals.clear(); @@ -117,6 +57,63 @@ class DeviceProvider extends ChangeNotifier { } } + // Öffentlicher API-Entry: Namen von der Hardware lesen + Future readDeviceNameFromHardware(LasertagDevice ltDevice) async { + try { + if (!ltDevice.btDevice.isConnected) { + await ltDevice.btDevice.connect( + license: License.free, + timeout: const Duration(seconds: 5), + ); + } + + List services = await ltDevice.btDevice.discoverServices(); + var service = services.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService)); + var characteristic = service.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provNameChar)); + + List value = await characteristic.read(); + String hardwareName = utf8.decode(value); + + updateDeviceName(ltDevice.id, hardwareName, verified: true); + + return hardwareName; + } catch (e) { + debugPrint("Fehler beim Lesen der Hardware: $e"); + rethrow; + } + } + + // Öffentlicher API-Entry: Namen auf Hardware schreiben + Future updateDeviceNameOnHardware(LasertagDevice ltDevice, String newName) async { + try { + await ltDevice.btDevice.connect( + license: License.free, + autoConnect: false, + timeout: const Duration(seconds: 10), + ); + + await Future.delayed(const Duration(milliseconds: 500)); + + List services = await ltDevice.btDevice.discoverServices(); + var service = services.firstWhere((s) => s.uuid == Guid(LasertagUUIDs.provService)); + var characteristic = service.characteristics.firstWhere((c) => c.uuid == Guid(LasertagUUIDs.provNameChar)); + + await characteristic.write(utf8.encode(newName)); + + updateDeviceName(ltDevice.id, newName, verified: true); + + await ltDevice.btDevice.disconnect(); + } catch (e) { + debugPrint("Hardware-Fehler: $e"); + rethrow; + } + } + + // Öffentlicher API-Entry: Lokale Liste aktualisieren + void updateDeviceName(String id, String newName, {bool verified = false}) { + _updateDeviceInLists(id, (old) => old.copyWith(name: newName, isNameVerified: verified)); + } + void _addDeviceToLists(LasertagDevice device) { List list = device.isLeader ? _discoveredLeaders : _discoveredPeripherals; diff --git a/software/app/lib/ui/screens/device_selection_screen.dart b/software/app/lib/ui/screens/device_selection_screen.dart index 1297e04..0b5197a 100644 --- a/software/app/lib/ui/screens/device_selection_screen.dart +++ b/software/app/lib/ui/screens/device_selection_screen.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:lasertag_app/l10n/app_localizations.dart'; + +import '../../constants.dart'; import '../../models/device_model.dart'; import '../../providers/device_provider.dart'; -import '../../constants.dart'; import '../widgets/rename_dialog.dart'; -import 'package:lasertag_app/l10n/app_localizations.dart'; class DeviceSelectionScreen extends StatefulWidget { const DeviceSelectionScreen({super.key}); @@ -84,12 +85,12 @@ class _DeviceSelectionScreenState extends State { device.name, style: TextStyle( fontWeight: FontWeight.bold, - fontStyle: device.isNameVerified - ? FontStyle.normal - : FontStyle.italic, // Kursiv, wenn nicht verifiziert + // fontStyle: device.isNameVerified + // ? FontStyle.normal + // : FontStyle.italic, // Kursiv, wenn nicht verifiziert color: device.isNameVerified ? Theme.of(context).colorScheme.onSurface - : Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + : Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), ), subtitle: Text("BTLE-ID: ${device.id}"), diff --git a/software/app/lib/ui/widgets/rename_dialog.dart b/software/app/lib/ui/widgets/rename_dialog.dart index 4e68b0e..7986177 100644 --- a/software/app/lib/ui/widgets/rename_dialog.dart +++ b/software/app/lib/ui/widgets/rename_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; + import '../../models/device_model.dart'; import '../../providers/device_provider.dart'; diff --git a/software/app/test/widget_test.dart b/software/app/test/widget_test.dart deleted file mode 100644 index f8e18ee..0000000 --- a/software/app/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:lasertag_app/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}