Skip to content
Snippets Groups Projects
Commit a052b055 authored by Paul-Winpenny's avatar Paul-Winpenny
Browse files

Bluetooth works! (BLE)

parent cb5cf7d9
No related branches found
No related tags found
1 merge request!1App now has a basic structure and BLE support
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
}
......@@ -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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment