From a052b05535d0b037a6e1e8fb907c1d1f5db30897 Mon Sep 17 00:00:00 2001
From: Paul-Winpenny <92634321+Paul-Winpenny@users.noreply.github.com>
Date: Thu, 17 Oct 2024 19:07:39 +0100
Subject: [PATCH] Bluetooth works! (BLE)

---
 .../ViewModels/ConnectionPageViewModel.cs     | 237 +++++++++++++++---
 App/RobobinApp/Views/ConnectionPage.xaml      |  36 ++-
 2 files changed, 240 insertions(+), 33 deletions(-)

diff --git a/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs b/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
index 3bea0065..8c08c32b 100644
--- a/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
+++ b/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
@@ -1,5 +1,5 @@
 using System.Collections.ObjectModel;
-using System.Diagnostics; // Add this for Debug.WriteLine
+using System.Diagnostics;
 using System.Windows.Input;
 using Microsoft.Maui.Controls;
 using RobobinApp.Views;
@@ -7,38 +7,125 @@ using Plugin.BLE;
 using Plugin.BLE.Abstractions.Contracts;
 using Plugin.BLE.Abstractions.Exceptions;
 using Plugin.BLE.Abstractions.EventArgs;
-
+using System.Threading.Tasks;
+using Plugin.BLE.Abstractions;
 
 namespace RobobinApp.ViewModels
 {
     public class ConnectionPageViewModel : BaseViewModel
     {
         private BluetoothDevice _selectedDevice;
+        private IDevice _connectedDevice;  // Track the connected device
         public ICommand GoHomeCommand { get; }
         public ObservableCollection<BluetoothDevice> BluetoothDevices { get; }
         public ICommand ConnectCommand { get; }
+        public ICommand SendWifiInfoCommand { get;  }
+        public ICommand DisconnectCommand { get; }  // Command for disconnecting
+        private bool _isConnected;  // Track connection state
+        private string ssid;
+        private string password;
+        public bool IsConnected
+        {
+            get => _isConnected;
+            set
+            {
+                _isConnected = value;
+                OnPropertyChanged();
+                OnPropertyChanged(nameof(ConnectButtonText));  // Update button text when connection state changes
+            }
+        }
+
+        public string SSID
+        {
+            get => ssid;
+            set
+            {
+                ssid = value;
+                OnPropertyChanged(nameof(SSID));
+            }
+        }
+
+        public string Password
+        {
+            get => password;
+            set
+            {
+                password = value;
+                OnPropertyChanged(nameof(Password));
+            }
+        }
 
-        // BLE plugin fields
         private readonly IAdapter _adapter;
         private readonly IBluetoothLE _bluetoothLE;
+        public string ConnectButtonText => IsConnected ? "Disconnect" : "Connect";
 
         public ConnectionPageViewModel()
         {
             BluetoothDevices = new ObservableCollection<BluetoothDevice>();
             ConnectCommand = new Command(OnConnect);
+            DisconnectCommand = new Command(OnDisconnect);  // Initialize disconnect command
+            SendWifiInfoCommand = new Command(OnSendWifiInfo);
             GoHomeCommand = new Command(async () => await OnGoHome());
-
-            // Initialize BLE manager and adapter
+            ConnectCommand = new Command(OnToggleConnection);
             _bluetoothLE = CrossBluetoothLE.Current;
             _adapter = _bluetoothLE.Adapter;
-
-            // Subscribe to the DeviceDiscovered event
             _adapter.DeviceDiscovered += OnDeviceDiscovered;
 
-            // Check and request Bluetooth permissions before scanning
             Debug.WriteLine("Checking and requesting Bluetooth permissions.");
             CheckAndRequestBluetoothPermissions();
         }
+        private async void OnSendWifiInfo(object obj)
+        {
+            if (!_isConnected || _connectedDevice == null)
+            {
+                Debug.WriteLine("Cannot send WiFi information: No device is connected.");
+                await Application.Current.MainPage.DisplayAlert("Error", "No device is connected.", "OK");
+                return;
+            }
+
+            try
+            {
+                Debug.WriteLine($"Sending WiFi information to {_connectedDevice.Name}...");
+
+                // Retrieve the UART service
+                var services = await _connectedDevice.GetServicesAsync();
+                var uartService = services.FirstOrDefault(s => s.Id == Guid.Parse("6e400001-b5a3-f393-e0a9-e50e24dcca9e"));
+
+                if (uartService == null)
+                {
+                    Debug.WriteLine("UART service not found.");
+                    await Application.Current.MainPage.DisplayAlert("Error", "UART service not found on the device.", "OK");
+                    return;
+                }
+
+                // Get the RX characteristic for writing data
+                var characteristics = await uartService.GetCharacteristicsAsync();
+                var rxCharacteristic = characteristics.FirstOrDefault(c => c.Id == Guid.Parse("6e400002-b5a3-f393-e0a9-e50e24dcca9e"));
+
+                if (rxCharacteristic == null)
+                {
+                    Debug.WriteLine("RX characteristic not found.");
+                    await Application.Current.MainPage.DisplayAlert("Error", "RX characteristic not found on the device.", "OK");
+                    return;
+                }
+
+                // Prepare the data to send
+                string wifiInfo = $"{SSID},{Password}"; // Format as SSID,Password
+                byte[] data = System.Text.Encoding.UTF8.GetBytes(wifiInfo);
+
+                // Write the data to the characteristic
+                await rxCharacteristic.WriteAsync(data);
+
+                Debug.WriteLine("WiFi information sent successfully.");
+                await Application.Current.MainPage.DisplayAlert("Success", "WiFi information sent successfully.", "OK");
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine($"Error sending WiFi information: {ex.Message}");
+                await Application.Current.MainPage.DisplayAlert("Error", $"Failed to send WiFi information: {ex.Message}", "OK");
+            }
+        }
+
 
         public BluetoothDevice SelectedDevice
         {
@@ -49,28 +136,34 @@ namespace RobobinApp.ViewModels
                 OnPropertyChanged();
             }
         }
-
+        private async void OnToggleConnection()
+        {
+            if (IsConnected)
+            {
+                OnDisconnect();  // Call the existing disconnect method
+            }
+            else
+            {
+                OnConnect();  // Call the existing connect method
+            }
+        }
         private async void CheckAndRequestBluetoothPermissions()
         {
-            // Check if the Bluetooth scan permission is granted
             Debug.WriteLine("Checking Bluetooth permissions.");
             var status = await Permissions.CheckStatusAsync<Permissions.Bluetooth>();
             if (status != PermissionStatus.Granted)
             {
                 Debug.WriteLine("Bluetooth permission not granted, requesting permission.");
-                // Request permission
                 status = await Permissions.RequestAsync<Permissions.Bluetooth>();
             }
 
             if (status == PermissionStatus.Granted)
             {
                 Debug.WriteLine("Bluetooth permission granted, proceeding to scan devices.");
-                // Permission granted, proceed with scanning devices
                 ScanDevices();
             }
             else
             {
-                // Permission denied, handle accordingly
                 Debug.WriteLine("Bluetooth permission denied.");
                 await Application.Current.MainPage.DisplayAlert("Permissions", "Bluetooth scan permission is required to discover devices.", "OK");
             }
@@ -84,7 +177,6 @@ namespace RobobinApp.ViewModels
 
         private async void ScanDevices()
         {
-            // Clear the current list of devices before scanning
             BluetoothDevices.Clear();
             Debug.WriteLine("Cleared existing Bluetooth devices. Starting scan...");
 
@@ -104,7 +196,6 @@ namespace RobobinApp.ViewModels
                     return;
                 }
 
-                // Start scanning for devices
                 Debug.WriteLine("Starting scanning for devices...");
                 await _adapter.StartScanningForDevicesAsync();
             }
@@ -115,11 +206,16 @@ namespace RobobinApp.ViewModels
             }
         }
 
-        // New method to handle discovered devices
         private void OnDeviceDiscovered(object sender, DeviceEventArgs e)
         {
             Debug.WriteLine($"Discovered device: ID={e.Device.Id}, Name={e.Device.Name}, State={e.Device.State}, RSSI={e.Device.Rssi}");
 
+            // Filter out devices that are not in a ready state
+            //if (e.Device.State != DeviceState.Connected && e.Device.State != DeviceState.Disconnected)
+            //{
+            //    Debug.WriteLine($"Device {e.Device.Name} is not in a fully ready state (State: {e.Device.State}). Skipping.");
+            //    return;
+            //}
 
             var bluetoothDevice = new BluetoothDevice
             {
@@ -137,8 +233,29 @@ namespace RobobinApp.ViewModels
             }
         }
 
-
-
+        private async Task<IDevice> TryConnectAsync(Guid deviceId, int retryCount = 3)
+        {
+            IDevice device = null;
+            for (int attempt = 1; attempt <= retryCount; attempt++)
+            {
+                try
+                {
+                    Debug.WriteLine($"Attempt {attempt}: Connecting to device {deviceId}");
+                    var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));  // Create a cancellation token with a timeout
+                    device = await _adapter.ConnectToKnownDeviceAsync(deviceId, new ConnectParameters(false, true), cancellationTokenSource.Token);  // Pass the cancellation token
+                    return device;
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine($"Attempt {attempt} failed: {ex.Message}");
+                    if (attempt == retryCount)
+                    {
+                        throw;  // Rethrow the exception if we've exhausted the retries
+                    }
+                }
+            }
+            return device;
+        }
 
         private async void OnConnect()
         {
@@ -148,35 +265,97 @@ namespace RobobinApp.ViewModels
                 return;
             }
 
-            // Logic to connect to the selected Bluetooth device
-            Debug.WriteLine($"Attempting to connect to {SelectedDevice.Name}...");
+            Debug.WriteLine($"Attempting to connect to {SelectedDevice.Name}, MAC Address {SelectedDevice.MacAddress}");
             await Application.Current.MainPage.DisplayAlert("Connection", $"Connecting to {SelectedDevice.Name}...", "OK");
 
             try
             {
-                // Stop scanning before attempting connection
-                await _adapter.StopScanningForDevicesAsync();
+                await _adapter.StopScanningForDevicesAsync(); // Ensure scanning is stopped.
                 Debug.WriteLine("Stopped scanning for devices.");
+                IsConnected = true;
+                _connectedDevice = await TryConnectAsync(Guid.Parse(SelectedDevice.MacAddress));  // Use retry logic for connection
 
-                // Find the device using the MAC address
-                var device = await _adapter.ConnectToKnownDeviceAsync(Guid.Parse(SelectedDevice.MacAddress));
-
-                // Display successful connection
                 Debug.WriteLine($"Successfully connected to {SelectedDevice.Name}.");
-                await Application.Current.MainPage.DisplayAlert("Connected", $"Connected to {SelectedDevice.Name}.", "OK");
+                var services = await _connectedDevice.GetServicesAsync();
+
+                foreach (var service in services)
+                {
+                    Debug.WriteLine($"Service: ID={service.Id}, Name={service.Name}");
+
+                    // Check if the service is the UART service
+                    if (service.Id == Guid.Parse("6e400001-b5a3-f393-e0a9-e50e24dcca9e"))
+                    {
+                        Debug.WriteLine("Found UART service!");
+
+                        // Get the characteristics of the UART service
+                        var characteristics = await service.GetCharacteristicsAsync();
+                        foreach (var characteristic in characteristics)
+                        {
+                            Debug.WriteLine($"Characteristic: ID={characteristic.Id}, Properties={characteristic.Properties}");
+
+                            if (characteristic.Id == Guid.Parse("6e400002-b5a3-f393-e0a9-e50e24dcca9e"))
+                            {
+                                Debug.WriteLine("Found RX characteristic (for writing data)");
+                                // Convert the string to a byte array
+                                byte[] data = System.Text.Encoding.UTF8.GetBytes("Hello World");
+                                // Write to the characteristic
+                                await characteristic.WriteAsync(data);
+                            }
+                            else if (characteristic.Id == Guid.Parse("6e400003-b5a3-f393-e0a9-e50e24dcca9e"))
+                            {
+                                Debug.WriteLine("Found TX characteristic (for notifications)");
+                            }
+                        }
+                    }
+                }
             }
             catch (DeviceConnectionException ex)
             {
-                // Handle connection failure
                 Debug.WriteLine($"Connection error: {ex.Message}");
                 await Application.Current.MainPage.DisplayAlert("Connection Error", $"Failed to connect: {ex.Message}", "OK");
             }
+            catch (TaskCanceledException ex)
+            {
+                Debug.WriteLine($"TaskCanceledException: {ex.Message}");
+                await Application.Current.MainPage.DisplayAlert("Connection Error", "Connection timed out.", "OK");
+                _connectedDevice = null;
+                IsConnected = false;
+            }
             catch (Exception ex)
             {
                 Debug.WriteLine($"General connection error: {ex.Message}");
                 await Application.Current.MainPage.DisplayAlert("Connection Error", $"An error occurred: {ex.Message}", "OK");
             }
         }
+
+
+        private async void OnDisconnect()
+        {
+            if (_connectedDevice != null)
+            {
+                try
+                {
+                    Debug.WriteLine($"Attempting to disconnect from {_connectedDevice.Name}");
+                    await _adapter.DisconnectDeviceAsync(_connectedDevice);
+                    Debug.WriteLine("Disconnected successfully.");
+
+                    // Dispose of the device reference
+                    _connectedDevice.Dispose();
+                    _connectedDevice = null;
+                    IsConnected = false;
+                    await Application.Current.MainPage.DisplayAlert("Disconnected", "Device disconnected successfully.", "OK");
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine($"Error during disconnection: {ex.Message}");
+                    await Application.Current.MainPage.DisplayAlert("Disconnection Error", $"Failed to disconnect: {ex.Message}", "OK");
+                }
+            }
+            else
+            {
+                Debug.WriteLine("No device to disconnect.");
+            }
+        }
     }
 
     public class BluetoothDevice
@@ -184,4 +363,4 @@ namespace RobobinApp.ViewModels
         public string Name { get; set; }
         public string MacAddress { get; set; }
     }
-}
\ No newline at end of file
+}
diff --git a/App/RobobinApp/Views/ConnectionPage.xaml b/App/RobobinApp/Views/ConnectionPage.xaml
index cf108b67..7d7ae169 100644
--- a/App/RobobinApp/Views/ConnectionPage.xaml
+++ b/App/RobobinApp/Views/ConnectionPage.xaml
@@ -27,12 +27,40 @@
             </ListView.ItemTemplate>
         </ListView>
 
-        <!-- Connect Button -->
-        <Button Text="Connect" 
-                Command="{Binding ConnectCommand}"
+        <Button Text="{Binding ConnectButtonText}" 
+                Command="{Binding ConnectCommand}" 
                 IsEnabled="{Binding SelectedDevice, Converter={StaticResource NullToBooleanConverter}}" 
                 HorizontalOptions="Center" 
                 VerticalOptions="End" />
+
+        <!-- Section for SSID and Password - Initially Hidden -->
+        <StackLayout IsVisible="{Binding IsConnected}" 
+                     Padding="10" 
+                     BackgroundColor="LightGray"
+                     HorizontalOptions="FillAndExpand" 
+                     VerticalOptions="Start">
+            <Label Text="SSID:" 
+                   FontSize="Medium" 
+                   HorizontalOptions="Start" />
+            <Entry x:Name="SSIDEntry" 
+                   Placeholder="Enter SSID" 
+                   Text="{Binding SSID}" 
+                   HorizontalOptions="Fill" />
+
+            <Label Text="Password:" 
+                   FontSize="Medium" 
+                   HorizontalOptions="Start" />
+            <Entry x:Name="PasswordEntry" 
+                   Placeholder="Enter Password" 
+                   Text="{Binding Password}" 
+                   IsPassword="True" 
+                   HorizontalOptions="Fill" />
+
+            <Button Text="Send WiFi Information" 
+                    Command="{Binding SendWifiInfoCommand}" 
+                    HorizontalOptions="Center" 
+                    VerticalOptions="End" />
+        </StackLayout>
+
     </StackLayout>
-   
 </ContentPage>
-- 
GitLab