BLE Handling

This commit is contained in:
2026-01-12 15:10:54 +01:00
parent c13b6d73c9
commit 395d577b78
5 changed files with 110 additions and 96 deletions

View File

@@ -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)

View File

@@ -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,
);
}
}

View File

@@ -13,6 +13,7 @@ class DeviceProvider extends ChangeNotifier {
List<LasertagDevice> get leaders => _discoveredLeaders;
List<LasertagDevice> get peripherals => _discoveredPeripherals;
// 1. HARDWARE LESEN
Future<String> readDeviceNameFromHardware(LasertagDevice ltDevice) async {
try {
if (!ltDevice.btDevice.isConnected) {
@@ -22,21 +23,15 @@ class DeviceProvider extends ChangeNotifier {
);
}
List<BluetoothService> 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<BluetoothService> 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<int> 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<void> updateDeviceNameOnHardware(
LasertagDevice ltDevice,
String newName,
) async {
// 2. HARDWARE SCHREIBEN
Future<void> 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<BluetoothService> services = await ltDevice.btDevice
.discoverServices();
List<BluetoothService> 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<LasertagDevice> 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<void> _verifyNameInBackground(LasertagDevice device) async {
if (device.isNameVerified) return;
try {
await device.btDevice.connect(license: License.free, timeout: const Duration(seconds: 5));
List<BluetoothService> 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<int> 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();
}
}
}

View File

@@ -29,7 +29,15 @@ class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.appTitle)),
appBar: AppBar(
title: Text(l10n.appTitle),
actions: [
IconButton(
onPressed: () => context.read<DeviceProvider>().startScan(),
icon: Icon(Icons.refresh),
),
],
),
body: CustomScrollView(
slivers: [
_buildHeader(context, l10n.listTypeLeader),
@@ -74,16 +82,24 @@ class _DeviceSelectionScreenState extends State<DeviceSelectionScreen> {
),
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),
);

View File

@@ -20,7 +20,12 @@ class _RenameDialogState extends State<RenameDialog> {
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<void> _fetchHardwareName() async {