From 395d577b7892d4c152c695f6390fcefcaf603bd8 Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Mon, 12 Jan 2026 15:10:54 +0100 Subject: [PATCH] BLE Handling --- firmware/libs/ble_mgmt/src/ble_mgmt.c | 7 +- software/app/lib/models/device_model.dart | 14 +- .../app/lib/providers/device_provider.dart | 148 ++++++++---------- .../ui/screens/device_selection_screen.dart | 30 +++- .../app/lib/ui/widgets/rename_dialog.dart | 7 +- 5 files changed, 110 insertions(+), 96 deletions(-) diff --git a/firmware/libs/ble_mgmt/src/ble_mgmt.c b/firmware/libs/ble_mgmt/src/ble_mgmt.c index b32a349..1e24347 100644 --- a/firmware/libs/ble_mgmt/src/ble_mgmt.c +++ b/firmware/libs/ble_mgmt/src/ble_mgmt.c @@ -105,11 +105,8 @@ static ssize_t write_lasertag_val(struct bt_conn *conn, const struct bt_gatt_att if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NAME_CHAR) == 0) { rc = lasertag_set_device_name(buf, len); - if (rc == 0 && adv_enabled) { - LOG_INF("Stopping advertising to update device name"); - ble_mgmt_adv_stop(); - LOG_INF("Restarting advertising with new device name"); - ble_mgmt_adv_start(); + if (rc == 0 ) { + bt_set_name(lasertag_get_device_name()); } } else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0) diff --git a/software/app/lib/models/device_model.dart b/software/app/lib/models/device_model.dart index 297c7be..96b5a40 100644 --- a/software/app/lib/models/device_model.dart +++ b/software/app/lib/models/device_model.dart @@ -7,6 +7,7 @@ class LasertagDevice { final int type; final bool isConnected; final BluetoothDevice btDevice; + final bool isNameVerified; LasertagDevice({ required this.id, @@ -14,8 +15,19 @@ class LasertagDevice { required this.type, required this.btDevice, this.isConnected = false, + this.isNameVerified = false, }); - // Hilfsmethode: Ist es ein Leader? bool get isLeader => type == LasertagUUIDs.typeLeader; + + LasertagDevice copyWith({String? name, bool? isNameVerified}) { + return LasertagDevice( + id: id, + type: type, + btDevice: btDevice, + name: name ?? this.name, + isNameVerified: isNameVerified ?? this.isNameVerified, + isConnected: isConnected, + ); + } } \ No newline at end of file diff --git a/software/app/lib/providers/device_provider.dart b/software/app/lib/providers/device_provider.dart index 3bf0af1..4ac0ac8 100644 --- a/software/app/lib/providers/device_provider.dart +++ b/software/app/lib/providers/device_provider.dart @@ -13,6 +13,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) { @@ -22,21 +23,15 @@ class DeviceProvider extends ChangeNotifier { ); } - 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 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)); - // Namen von der Hardware lesen List value = await characteristic.read(); String hardwareName = utf8.decode(value); - // Lokalen Cache in der App ebenfalls aktualisieren, um Caching zu umgehen - updateDeviceName(ltDevice.id, hardwareName); + // Den Namen lokal speichern und als verifiziert markieren (für das Styling) + updateDeviceName(ltDevice.id, hardwareName, verified: true); return hardwareName; } catch (e) { @@ -45,10 +40,8 @@ class DeviceProvider extends ChangeNotifier { } } - Future updateDeviceNameOnHardware( - LasertagDevice ltDevice, - String newName, - ) async { + // 2. HARDWARE SCHREIBEN + Future updateDeviceNameOnHardware(LasertagDevice ltDevice, String newName) async { try { await ltDevice.btDevice.connect( license: License.free, @@ -58,21 +51,14 @@ class DeviceProvider extends ChangeNotifier { await Future.delayed(const Duration(milliseconds: 500)); - List services = await ltDevice.btDevice - .discoverServices(); + 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)); - var service = services.firstWhere( - (s) => s.uuid == Guid(LasertagUUIDs.provService), - ); - var characteristic = service.characteristics.firstWhere( - (c) => c.uuid == Guid(LasertagUUIDs.provNameChar), - ); - - // 1. Auf Hardware schreiben (UTF-8) await characteristic.write(utf8.encode(newName)); - // 2. Lokal in der App-Liste aktualisieren - updateDeviceName(ltDevice.id, newName); + // Lokal aktualisieren und als verifiziert markieren + updateDeviceName(ltDevice.id, newName, verified: true); await ltDevice.btDevice.disconnect(); } catch (e) { @@ -81,71 +67,42 @@ class DeviceProvider extends ChangeNotifier { } } - void updateDeviceName(String id, String newName) { - // In den Leadern suchen - int leaderIndex = _discoveredLeaders.indexWhere((d) => d.id == id); - if (leaderIndex != -1) { - final old = _discoveredLeaders[leaderIndex]; - _discoveredLeaders[leaderIndex] = LasertagDevice( - id: old.id, - name: newName, - type: old.type, - btDevice: old.btDevice, - isConnected: old.isConnected, - ); - notifyListeners(); - return; - } - - // In der Ausrüstung suchen - int peripheralIndex = _discoveredPeripherals.indexWhere((d) => d.id == id); - if (peripheralIndex != -1) { - final old = _discoveredPeripherals[peripheralIndex]; - _discoveredPeripherals[peripheralIndex] = LasertagDevice( - id: old.id, - name: newName, - type: old.type, - btDevice: old.btDevice, - isConnected: old.isConnected, - ); - notifyListeners(); - } + // 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 void startScan() async { - // Listen leeren für neuen Scan _discoveredLeaders.clear(); _discoveredPeripherals.clear(); notifyListeners(); - // 1. Bluetooth-Status prüfen + await FlutterBluePlus.stopScan(); // Scan sauber stoppen vor Neustart + await FlutterBluePlus.adapterState .where((s) => s == BluetoothAdapterState.on) .first; - // 2. Scan-Ergebnisse verarbeiten _scanSubscription?.cancel(); _scanSubscription = FlutterBluePlus.scanResults.listen((results) { for (ScanResult r in results) { - bool hasService = r.advertisementData.serviceUuids.contains( - Guid(LasertagUUIDs.provService), - ); + bool hasService = r.advertisementData.serviceUuids.contains(Guid(LasertagUUIDs.provService)); if (!hasService) continue; - final mfgData = - r.advertisementData.manufacturerData[65535]; // Unsere ID 0xFFFF + final mfgData = r.advertisementData.manufacturerData[65535]; if (mfgData != null && mfgData.isNotEmpty) { - int type = mfgData[0]; // Typ-Byte vom nRF52 - final device = LasertagDevice( id: r.device.remoteId.toString(), - name: r.device.platformName.isEmpty - ? "Unbekannt" - : r.device.platformName, - type: type, + name: r.device.platformName.isEmpty ? "Unbekannt" : r.device.platformName, + type: mfgData[0], btDevice: r.device, isConnected: false, + isNameVerified: false, // Initial immer unversichert (Kursiv) ); _addDeviceToLists(device); @@ -153,7 +110,6 @@ class DeviceProvider extends ChangeNotifier { } }); - // 3. Bluetooth-Scan starten try { await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15)); } catch (e) { @@ -162,17 +118,45 @@ class DeviceProvider extends ChangeNotifier { } void _addDeviceToLists(LasertagDevice device) { - if (device.isLeader) { - if (!_discoveredLeaders.any((d) => d.id == device.id)) { - _discoveredLeaders.add(device); - _sortList(_discoveredLeaders); - notifyListeners(); - } - } else { - if (!_discoveredPeripherals.any((d) => d.id == device.id)) { - _discoveredPeripherals.add(device); - _sortList(_discoveredPeripherals); + List list = device.isLeader ? _discoveredLeaders : _discoveredPeripherals; + + if (!list.any((d) => d.id == device.id)) { + list.add(device); + _sortList(list); + notifyListeners(); + _verifyNameInBackground(device); // Hintergrund-Check anstoßen + } + } + + Future _verifyNameInBackground(LasertagDevice device) async { + if (device.isNameVerified) return; + + try { + await device.btDevice.connect(license: License.free, timeout: const Duration(seconds: 5)); + + List services = await device.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 realName = utf8.decode(value); + + updateDeviceName(device.id, realName, verified: true); + + await device.btDevice.disconnect(); + } catch (e) { + debugPrint("Background Sync fehlgeschlagen für ${device.id}: $e"); + } + } + + void _updateDeviceInLists(String id, LasertagDevice Function(LasertagDevice) updateFn) { + for (var list in [_discoveredLeaders, _discoveredPeripherals]) { + int index = list.indexWhere((d) => d.id == id); + if (index != -1) { + list[index] = updateFn(list[index]); + _sortList(list); notifyListeners(); + return; } } } @@ -190,4 +174,4 @@ class DeviceProvider extends ChangeNotifier { _scanSubscription?.cancel(); super.dispose(); } -} +} \ No newline at end of file diff --git a/software/app/lib/ui/screens/device_selection_screen.dart b/software/app/lib/ui/screens/device_selection_screen.dart index ba0cb8b..1297e04 100644 --- a/software/app/lib/ui/screens/device_selection_screen.dart +++ b/software/app/lib/ui/screens/device_selection_screen.dart @@ -29,7 +29,15 @@ class _DeviceSelectionScreenState extends State { final l10n = AppLocalizations.of(context)!; return Scaffold( - appBar: AppBar(title: Text(l10n.appTitle)), + appBar: AppBar( + title: Text(l10n.appTitle), + actions: [ + IconButton( + onPressed: () => context.read().startScan(), + icon: Icon(Icons.refresh), + ), + ], + ), body: CustomScrollView( slivers: [ _buildHeader(context, l10n.listTypeLeader), @@ -74,16 +82,24 @@ class _DeviceSelectionScreenState extends State { ), title: Text( device.name, - style: const TextStyle(fontWeight: FontWeight.bold), + style: TextStyle( + fontWeight: FontWeight.bold, + 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), + ), ), subtitle: Text("BTLE-ID: ${device.id}"), trailing: const Icon(Icons.chevron_right), onTap: () { - showDialog( - context: context, - builder: (context) => RenameDialog(device: device), - ); -}, + showDialog( + context: context, + builder: (context) => RenameDialog(device: device), + ); + }, ); }, childCount: devices.length), ); diff --git a/software/app/lib/ui/widgets/rename_dialog.dart b/software/app/lib/ui/widgets/rename_dialog.dart index 2ec7442..4e68b0e 100644 --- a/software/app/lib/ui/widgets/rename_dialog.dart +++ b/software/app/lib/ui/widgets/rename_dialog.dart @@ -20,7 +20,12 @@ class _RenameDialogState extends State { void initState() { super.initState(); _controller = TextEditingController(text: widget.device.name); - _fetchHardwareName(); // Namen beim Öffnen abfragen + if (!widget.device.isNameVerified) { + // Wenn der Name bereits verifiziert ist, nicht von der Hardware lesen + _isReading = false; + } else { + _fetchHardwareName(); // Namen beim Öffnen abfragen + } } Future _fetchHardwareName() async {