diff --git a/firmware/apps/vest/src/main.c b/firmware/apps/vest/src/main.c index 1533ccb..b0d75ce 100644 --- a/firmware/apps/vest/src/main.c +++ b/firmware/apps/vest/src/main.c @@ -33,7 +33,7 @@ int main(void) if (rc) { LOG_ERR("Thread initialization failed (err %d)", rc); } else { - LOG_INF("Leader Application successfully started with Thread Mesh."); + LOG_INF("Vest Application successfully started with Thread Mesh."); return rc; } diff --git a/firmware/libs/ble_mgmt/src/ble_mgmt.c b/firmware/libs/ble_mgmt/src/ble_mgmt.c index 320b9fb..b32a349 100644 --- a/firmware/libs/ble_mgmt/src/ble_mgmt.c +++ b/firmware/libs/ble_mgmt/src/ble_mgmt.c @@ -45,6 +45,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 /* --- GATT Callbacks --- */ @@ -104,6 +105,12 @@ 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(); + } } else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0) { @@ -248,6 +255,7 @@ int ble_mgmt_adv_start(void) if (!err) { LOG_INF("Advertising started as: %s, type: %d", name, device_role); + adv_enabled = 1; } return err; } @@ -258,6 +266,7 @@ int ble_mgmt_adv_stop(void) if (!err) { LOG_INF("Advertising stopped"); + adv_enabled = 0; } return err; } \ No newline at end of file diff --git a/firmware/libs/lasertag_utils/src/lasertag_utils.c b/firmware/libs/lasertag_utils/src/lasertag_utils.c index 1a0c1ed..5720345 100644 --- a/firmware/libs/lasertag_utils/src/lasertag_utils.c +++ b/firmware/libs/lasertag_utils/src/lasertag_utils.c @@ -19,40 +19,55 @@ static uint8_t thread_channel = 15; static uint8_t thread_ext_pan_id[8] = {0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe}; static uint8_t thread_network_key[16] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff -}; + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; /* --- Settings Handler --- */ static int lasertag_settings_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { const char *next; - if (settings_name_steq(name, "name", &next) && !next) { - if (len > sizeof(device_name) - 1) return -EINVAL; + if (settings_name_steq(name, "name", &next) && !next) + { + if (len > sizeof(device_name) - 1) + return -EINVAL; ssize_t rc = read_cb(cb_arg, device_name, len); - if (rc >= 0) { device_name[rc] = '\0'; return 0; } + if (rc >= 0) + { + device_name[rc] = '\0'; + return 0; + } } - if (settings_name_steq(name, "pan_id", &next) && !next) { + if (settings_name_steq(name, "pan_id", &next) && !next) + { return read_cb(cb_arg, &thread_pan_id, sizeof(thread_pan_id)) >= 0 ? 0 : -EIO; } - if (settings_name_steq(name, "net_name", &next) && !next) { - if (len > sizeof(thread_network_name) - 1) return -EINVAL; + if (settings_name_steq(name, "net_name", &next) && !next) + { + if (len > sizeof(thread_network_name) - 1) + return -EINVAL; ssize_t rc = read_cb(cb_arg, thread_network_name, len); - if (rc >= 0) { thread_network_name[rc] = '\0'; return 0; } + if (rc >= 0) + { + thread_network_name[rc] = '\0'; + return 0; + } } - if (settings_name_steq(name, "channel", &next) && !next) { + if (settings_name_steq(name, "channel", &next) && !next) + { return read_cb(cb_arg, &thread_channel, sizeof(thread_channel)) >= 0 ? 0 : -EIO; } - if (settings_name_steq(name, "ext_pan_id", &next) && !next) { + if (settings_name_steq(name, "ext_pan_id", &next) && !next) + { return read_cb(cb_arg, thread_ext_pan_id, sizeof(thread_ext_pan_id)) >= 0 ? 0 : -EIO; } - if (settings_name_steq(name, "net_key", &next) && !next) { + if (settings_name_steq(name, "net_key", &next) && !next) + { return read_cb(cb_arg, thread_network_key, sizeof(thread_network_key)) >= 0 ? 0 : -EIO; } return -ENOENT; } -struct settings_handler lasertag_conf = { .name = "lasertag", .h_set = lasertag_settings_set }; +struct settings_handler lasertag_conf = {.name = "lasertag", .h_set = lasertag_settings_set}; void lasertag_utils_init(void) { @@ -71,39 +86,47 @@ void lasertag_utils_init(void) } /* Getters */ -const char* lasertag_get_device_name(void) { return device_name; } +const char *lasertag_get_device_name(void) { return device_name; } uint16_t lasertag_get_thread_pan_id(void) { return thread_pan_id; } -const char* lasertag_get_thread_network_name(void) { return thread_network_name; } +const char *lasertag_get_thread_network_name(void) { return thread_network_name; } uint8_t lasertag_get_thread_channel(void) { return thread_channel; } -const uint8_t* lasertag_get_thread_ext_pan_id(void) { return thread_ext_pan_id; } -const uint8_t* lasertag_get_thread_network_key(void) { return thread_network_key; } +const uint8_t *lasertag_get_thread_ext_pan_id(void) { return thread_ext_pan_id; } +const uint8_t *lasertag_get_thread_network_key(void) { return thread_network_key; } /* Setters */ -int lasertag_set_device_name(const char *name, size_t len) { - if (len >= sizeof(device_name)) len = sizeof(device_name) - 1; +int lasertag_set_device_name(const char *name, size_t len) +{ + if (len >= sizeof(device_name)) + len = sizeof(device_name) - 1; memcpy(device_name, name, len); device_name[len] = '\0'; return settings_save_one("lasertag/name", device_name, len); } -int lasertag_set_thread_pan_id(uint16_t pan_id) { +int lasertag_set_thread_pan_id(uint16_t pan_id) +{ thread_pan_id = pan_id; return settings_save_one("lasertag/pan_id", &thread_pan_id, sizeof(thread_pan_id)); } -int lasertag_set_thread_network_name(const char *name, size_t len) { - if (len >= sizeof(thread_network_name)) len = sizeof(thread_network_name) - 1; +int lasertag_set_thread_network_name(const char *name, size_t len) +{ + if (len >= sizeof(thread_network_name)) + len = sizeof(thread_network_name) - 1; memcpy(thread_network_name, name, len); thread_network_name[len] = '\0'; return settings_save_one("lasertag/net_name", thread_network_name, len); } -int lasertag_set_thread_channel(uint8_t channel) { +int lasertag_set_thread_channel(uint8_t channel) +{ thread_channel = channel; return settings_save_one("lasertag/channel", &thread_channel, sizeof(thread_channel)); } -int lasertag_set_thread_ext_pan_id(const uint8_t *ext_id) { +int lasertag_set_thread_ext_pan_id(const uint8_t *ext_id) +{ memcpy(thread_ext_pan_id, ext_id, 8); return settings_save_one("lasertag/ext_pan_id", thread_ext_pan_id, 8); } -int lasertag_set_thread_network_key(const uint8_t *key) { +int lasertag_set_thread_network_key(const uint8_t *key) +{ memcpy(thread_network_key, key, 16); return settings_save_one("lasertag/net_key", thread_network_key, 16); } @@ -112,71 +135,77 @@ int lasertag_set_thread_network_key(const uint8_t *key) { #if CONFIG_LASERTAG_SHELL -static int lasertag_hex2bin(const char *hex, uint8_t *bin, size_t bin_len) { - for (size_t i = 0; i < bin_len; i++) { - char buf[3] = { hex[i*2], hex[i*2+1], '\0' }; +static int lasertag_hex2bin(const char *hex, uint8_t *bin, size_t bin_len) +{ + for (size_t i = 0; i < bin_len; i++) + { + char buf[3] = {hex[i * 2], hex[i * 2 + 1], '\0'}; bin[i] = (uint8_t)strtoul(buf, NULL, 16); } return 0; } -static int cmd_reboot(const struct shell *sh, size_t argc, char **argv) { +static int cmd_reboot(const struct shell *sh, size_t argc, char **argv) +{ shell_print(sh, "Rebooting..."); sys_reboot(SYS_REBOOT_COLD); return 0; } -static int cmd_name_set(const struct shell *sh, size_t argc, char **argv) { +static int cmd_name_set(const struct shell *sh, size_t argc, char **argv) +{ lasertag_set_device_name(argv[1], strlen(argv[1])); shell_print(sh, "Name gespeichert."); return 0; } -static int cmd_thread_ping(const struct shell *sh, size_t argc, char **argv) { +static int cmd_thread_ping(const struct shell *sh, size_t argc, char **argv) +{ char msg[64]; snprintf(msg, sizeof(msg), "Ping von %s", device_name); shell_print(sh, "Sende Multicast-Ping an ff03::1..."); - int rc = thread_mgmt_send_udp("ff03::1", (uint8_t*)msg, strlen(msg)); - if (rc) shell_error(sh, "Ping fehlgeschlagen (%d)", rc); + int rc = thread_mgmt_send_udp("ff03::1", (uint8_t *)msg, strlen(msg)); + if (rc) + shell_error(sh, "Ping fehlgeschlagen (%d)", rc); return 0; } /* Subcommands definitions omitted for brevity, but they should include 'ping' */ -static int cmd_thread_set_panid(const struct shell *sh, size_t argc, char **argv) { +static int cmd_thread_set_panid(const struct shell *sh, size_t argc, char **argv) +{ uint16_t pan = (uint16_t)strtoul(argv[1], NULL, 0); lasertag_set_thread_pan_id(pan); return 0; } -static int cmd_thread_set_chan(const struct shell *sh, size_t argc, char **argv) { +static int cmd_thread_set_chan(const struct shell *sh, size_t argc, char **argv) +{ uint8_t chan = (uint8_t)strtoul(argv[1], NULL, 10); lasertag_set_thread_channel(chan); return 0; } SHELL_STATIC_SUBCMD_SET_CREATE(sub_thread, - SHELL_CMD_ARG(panid, NULL, "Set PAN ID", cmd_thread_set_panid, 2, 0), - SHELL_CMD_ARG(chan, NULL, "Set channel", cmd_thread_set_chan, 2, 0), - SHELL_CMD(ping, NULL, "Send multicast ping", cmd_thread_ping), - SHELL_SUBCMD_SET_END -); + SHELL_CMD_ARG(panid, NULL, "Set PAN ID", cmd_thread_set_panid, 2, 0), + SHELL_CMD_ARG(chan, NULL, "Set channel", cmd_thread_set_chan, 2, 0), + SHELL_CMD(ping, NULL, "Send multicast ping", cmd_thread_ping), + SHELL_SUBCMD_SET_END); -static int cmd_ble_start(const struct shell *sh, size_t argc, char **argv) { +static int cmd_ble_start(const struct shell *sh, size_t argc, char **argv) +{ return ble_mgmt_adv_start(); } SHELL_STATIC_SUBCMD_SET_CREATE(sub_ble, - SHELL_CMD(start, NULL, "Start BLE", cmd_ble_start), - SHELL_SUBCMD_SET_END -); + SHELL_CMD(start, NULL, "Start BLE", cmd_ble_start), + SHELL_SUBCMD_SET_END); SHELL_STATIC_SUBCMD_SET_CREATE(sub_lasertag, - SHELL_CMD_ARG(name, NULL, "Set name", cmd_name_set, 2, 0), - SHELL_CMD(thread, &sub_thread, "Thread configuration", NULL), - SHELL_CMD(ble, &sub_ble, "BLE Management", NULL), - SHELL_CMD(reboot, NULL, "Reboot", cmd_reboot), - SHELL_SUBCMD_SET_END -); + SHELL_CMD_ARG(name, NULL, "Set name", cmd_name_set, 2, 0), + SHELL_CMD(thread, &sub_thread, "Thread configuration", NULL), + SHELL_CMD(ble, &sub_ble, "BLE Management", NULL), + SHELL_CMD(reboot, NULL, "Reboot", cmd_reboot), + SHELL_SUBCMD_SET_END); SHELL_CMD_REGISTER(lasertag, &sub_lasertag, "Lasertag Befehle", NULL); diff --git a/software/app/lib/providers/device_provider.dart b/software/app/lib/providers/device_provider.dart index bb69cc8..3bf0af1 100644 --- a/software/app/lib/providers/device_provider.dart +++ b/software/app/lib/providers/device_provider.dart @@ -13,12 +13,51 @@ class DeviceProvider extends ChangeNotifier { List get leaders => _discoveredLeaders; List get peripherals => _discoveredPeripherals; + 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), + ); + + // 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); + + return hardwareName; + } catch (e) { + debugPrint("Fehler beim Lesen der Hardware: $e"); + rethrow; + } + } + Future updateDeviceNameOnHardware( LasertagDevice ltDevice, String newName, ) async { try { - await ltDevice.btDevice.connect(); + 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(); @@ -88,6 +127,11 @@ class DeviceProvider extends ChangeNotifier { _scanSubscription?.cancel(); _scanSubscription = FlutterBluePlus.scanResults.listen((results) { for (ScanResult r in results) { + bool hasService = r.advertisementData.serviceUuids.contains( + Guid(LasertagUUIDs.provService), + ); + if (!hasService) continue; + final mfgData = r.advertisementData.manufacturerData[65535]; // Unsere ID 0xFFFF @@ -109,12 +153,9 @@ class DeviceProvider extends ChangeNotifier { } }); - // 3. Scan starten (gefiltert nach unserem Service) + // 3. Bluetooth-Scan starten try { - await FlutterBluePlus.startScan( - withServices: [Guid(LasertagUUIDs.provService)], // - timeout: const Duration(seconds: 15), - ); + await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15)); } catch (e) { debugPrint("Scan-Fehler: $e"); } diff --git a/software/app/lib/ui/screens/device_selection_screen.dart b/software/app/lib/ui/screens/device_selection_screen.dart index 49424c2..ba0cb8b 100644 --- a/software/app/lib/ui/screens/device_selection_screen.dart +++ b/software/app/lib/ui/screens/device_selection_screen.dart @@ -103,55 +103,4 @@ class _DeviceSelectionScreenState extends State { return Icons.device_unknown; } } - - void _showNameEditDialog(BuildContext context, LasertagDevice device) { - final TextEditingController nameController = TextEditingController( - text: device.name, - ); - - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Row( - children: [ - Icon( - _getIconForType(device.type), - color: Theme.of(context).colorScheme.primary, - ), - const SizedBox(width: 10), - const Text("Gerät umbenennen"), - ], - ), - content: TextField( - controller: nameController, - decoration: const InputDecoration( - labelText: "Anzeigename", - border: OutlineInputBorder(), - ), - autofocus: true, - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text("Abbrechen"), - ), - ElevatedButton( - onPressed: () { - if (nameController.text.trim().isNotEmpty) { - // Provider aktualisieren - context.read().updateDeviceName( - device.id, - nameController.text.trim(), - ); - Navigator.pop(context); - } - }, - child: const Text("Speichern"), - ), - ], - ); - }, - ); - } } diff --git a/software/app/lib/ui/widgets/rename_dialog.dart b/software/app/lib/ui/widgets/rename_dialog.dart index 82f48e4..2ec7442 100644 --- a/software/app/lib/ui/widgets/rename_dialog.dart +++ b/software/app/lib/ui/widgets/rename_dialog.dart @@ -14,32 +14,70 @@ class RenameDialog extends StatefulWidget { class _RenameDialogState extends State { late TextEditingController _controller; bool _isSaving = false; + bool _isReading = true; // Neu: Startet im Lade-Modus @override void initState() { super.initState(); _controller = TextEditingController(text: widget.device.name); + _fetchHardwareName(); // Namen beim Öffnen abfragen + } + + Future _fetchHardwareName() async { + try { + String hName = await context + .read() + .readDeviceNameFromHardware(widget.device); + if (mounted) { + setState(() { + _controller.text = hName; + _isReading = false; + }); + } + } catch (e) { + if (mounted) setState(() => _isReading = false); + } } @override Widget build(BuildContext context) { return AlertDialog( - title: Text("${widget.device.name} umbenennen"), - content: TextField( - controller: _controller, - decoration: const InputDecoration(labelText: "Neuer Name"), - enabled: !_isSaving, + title: Text("${widget.device.name} umbenennen"), // Titel bleibt gleich + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (_isReading) + const Padding( + padding: EdgeInsets.only(bottom: 10), + child: + LinearProgressIndicator(), // Zeigt, dass wir die Hardware abfragen + ), + TextField( + controller: _controller, + decoration: InputDecoration( + labelText: _isReading ? "Lese Hardware..." : "Neuer Name", + border: const OutlineInputBorder(), + ), + enabled: !_isSaving && !_isReading, + ), + ], ), actions: [ TextButton( - onPressed: _isSaving ? null : () => Navigator.pop(context), + onPressed: (_isSaving || _isReading) + ? null + : () => Navigator.pop(context), child: const Text("Abbrechen"), ), ElevatedButton( - onPressed: _isSaving ? null : _save, - child: _isSaving - ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)) - : const Text("Speichern"), + onPressed: (_isSaving || _isReading) ? null : _save, + child: _isSaving + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text("Speichern"), ), ], ); @@ -49,8 +87,8 @@ class _RenameDialogState extends State { setState(() => _isSaving = true); try { await context.read().updateDeviceNameOnHardware( - widget.device, - _controller.text.trim() + widget.device, + _controller.text.trim(), ); if (mounted) Navigator.pop(context); } catch (e) { @@ -63,4 +101,4 @@ class _RenameDialogState extends State { if (mounted) setState(() => _isSaving = false); } } -} \ No newline at end of file +}