Skip to content
Snippets Groups Projects

App now has a basic structure and BLE support

Merged plw1g21 requested to merge app-bluetooth-connection into main
2 files
+ 240
33
Compare changes
  • Side-by-side
  • Inline
Files
2
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using Microsoft.Maui.Controls;
using RobobinApp.Views;
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;
using RobobinApp.Models;
using System.Net.NetworkInformation;
using System.Collections.Generic;
using Windows.Networking.Connectivity;
using Windows.Devices.WiFi;
using System.Reflection.PortableExecutable;
using Plugin.BLE.Windows;
using System.Text;
using System.Windows.Documents;
using Robobin.Interfaces;
namespace RobobinApp.ViewModels
{
public class ConnectionPageViewModel : BaseViewModel
{
private BluetoothDevice _selectedDevice;
private IDevice _connectedDevice;
public ICommand GoHomeCommand { get; }
public ObservableCollection<BluetoothDevice> BluetoothDevices { get; }
public ObservableCollection<WifiNetwork> WifiNetworks { get; }
public ICommand ConnectCommand { get; }
public ICommand SendWifiInfoCommand { get; }
public ICommand DisconnectCommand { get; }
public ICommand TestReadOperation { get; }
public ICommand TestWriteOperation { get; }
private bool _isConnected;
private string ssid;
private string password;
public bool IsBluetoothDeviceSelectionVisible => !IsWifiNetworkSelectionVisible;
public const string SendReceiveServiceUUID = "00000001-710e-4a5b-8d75-3e5b444bc3cf";
public const string rxUUID = "00000002-710e-4a5b-8d75-3e5b444bc3cf";
public const string wxUUID = "00000003-710e-4a5b-8d75-3e5b444bc3cf";
public const string readCharacteristicName = "SSID List";
public const string writeCharacteristicName = "SSID name and password";
public string tempUnit = "C";
public bool IsConnected
{
get => _isConnected;
set
{
_isConnected = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ConnectButtonText));
}
}
private ICharacteristic _readCharacteristic;
private ICharacteristic _writeCharacteristic;
public ICharacteristic ReadCharacteristic
{
get => _readCharacteristic;
set
{
_readCharacteristic = value;
OnPropertyChanged();
}
}
public ICharacteristic WriteCharacteristic
{
get => _writeCharacteristic;
set
{
_writeCharacteristic = value;
OnPropertyChanged();
}
}
public string Password
{
get => password;
set
{
password = value;
OnPropertyChanged(nameof(Password));
}
}
private readonly IAdapter _adapter;
private readonly IBluetoothLE _bluetoothLE;
public string ConnectButtonText => IsConnected ? "Disconnect" : "Connect";
private bool _isWifiNetworkSelectionVisible;
private WifiNetwork _selectedWifiNetwork;
public bool IsWifiNetworkSelectionVisible
{
get => _isWifiNetworkSelectionVisible;
set
{
_isWifiNetworkSelectionVisible = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsBluetoothDeviceSelectionVisible));
}
}
public ConnectionPageViewModel()
{
_bluetoothLE = CrossBluetoothLE.Current;
_adapter = CrossBluetoothLE.Current.Adapter;
BluetoothDevices = new ObservableCollection<BluetoothDevice>();
WifiNetworks = new ObservableCollection<WifiNetwork>();
ConnectCommand = new Command(OnConnect);
DisconnectCommand = new Command(OnDisconnect);
GoHomeCommand = new Command(async () => await OnGoHome());
ConnectCommand = new Command(OnToggleConnection);
TestReadOperation = new Command(async () => await ReadOperationAsync());
SendWifiInfoCommand = new Command(OnSendWifiInfo);
TestWriteOperation = new Command(async() => await PingPiASync());
_adapter.DeviceDiscovered += OnDeviceDiscovered;
IsWifiNetworkSelectionVisible = false;
Debug.WriteLine("Checking and requesting Bluetooth permissions.");
CheckAndRequestBluetoothPermissions();
}
private void OnSendWifiInfo(object obj)
{
var SSID = SelectedWifiNetwork.SSID;
var result = WriteOperationAsync("CONNECT", SSID + "," + password);
Debug.WriteLine($"Result: {result}");
}
private async Task PingPiASync()
{
if (tempUnit.Equals("C"))
{
tempUnit = "F";
} else
{
tempUnit = "C";
}
WriteOperationAsync(tempUnit);
}
private async void ScanForWifiNetworks()
{
WifiNetworks.Clear();
Debug.WriteLine("Retrieving Wifi networks from readCharacteristic");
//Delay 5 seconds
await Task.Delay(2500);
var networks = await ReadOperationAsync();
//var networks = "Network1\nNetwork2\nNetwork3"; // For testing
Debug.WriteLine(networks);
var delimiter = '\n'; //Test to see if actual SSID has \n, see if it breaks things
var networkList = networks.Split(delimiter);
var addedSsids = new HashSet<string>();
foreach (var network in networkList)
{
Debug.Write("This network exists: " + network);
if (addedSsids.Add(network))
{
var wifiNetwork = new WifiNetwork
{
SSID = network,
SignalStrength = 0.0
};
Microsoft.Maui.Controls.Device.BeginInvokeOnMainThread(() => WifiNetworks.Add(wifiNetwork));
Debug.WriteLine($"Found Wi-Fi network: {wifiNetwork.SSID}, Signal Strength: {wifiNetwork.SignalStrength} dBm");
}
}
}
public BluetoothDevice SelectedDevice
{
get => _selectedDevice;
set
{
_selectedDevice = value;
OnPropertyChanged();
}
}
public WifiNetwork SelectedWifiNetwork
{
get => _selectedWifiNetwork;
set
{
_selectedWifiNetwork = value;
OnPropertyChanged();
}
}
private async void OnToggleConnection()
{
if (IsConnected)
{
OnDisconnect();
}
else
{
OnConnect();
}
}
private async void CheckAndRequestBluetoothPermissions()
{
Debug.WriteLine("Checking Bluetooth permissions.");
var status = await Permissions.CheckStatusAsync<Permissions.Bluetooth>();
if (status != PermissionStatus.Granted)
{
Debug.WriteLine("Bluetooth permission not granted, requesting permission.");
status = await Permissions.RequestAsync<Permissions.Bluetooth>();
}
if (status == PermissionStatus.Granted)
{
Debug.WriteLine("Bluetooth permission granted, proceeding to scan devices.");
ScanDevices();
}
else
{
Debug.WriteLine("Bluetooth permission denied.");
await Application.Current.MainPage.DisplayAlert("Permissions", "Bluetooth scan permission is required to discover devices.", "OK");
}
}
public async Task OnGoHome()
{
Debug.WriteLine("Navigating to home page.");
await Application.Current.MainPage.Navigation.PushAsync(new MainPage());
}
private async void ScanDevices()
{
BluetoothDevices.Clear();
Debug.WriteLine("Cleared existing Bluetooth devices. Starting scan...");
try
{
if (!_bluetoothLE.IsAvailable)
{
Debug.WriteLine("Bluetooth is not available.");
await Application.Current.MainPage.DisplayAlert("Bluetooth", "Bluetooth is not available.", "OK");
return;
}
if (!_bluetoothLE.IsOn)
{
Debug.WriteLine("Bluetooth is turned off.");
await Application.Current.MainPage.DisplayAlert("Bluetooth", "Please turn on Bluetooth.", "OK");
return;
}
Debug.WriteLine("Starting scanning for devices...");
await _adapter.StartScanningForDevicesAsync();
}
catch (Exception ex)
{
Debug.WriteLine($"Error during scanning: {ex.Message}");
await Application.Current.MainPage.DisplayAlert("Error", $"Failed to scan for devices: {ex.Message}", "OK");
}
}
private void OnDeviceDiscovered(object sender, DeviceEventArgs e)
{
Debug.WriteLine($"Discovered device: {e.Device.Name ?? e.Device.Id.ToString()}");
var bluetoothDevice = new BluetoothDevice(
string.IsNullOrWhiteSpace(e.Device.Name) ? e.Device.Id.ToString() : e.Device.Name,
e.Device.Id.ToString()
);
// Check if the device is already in the list
if (!BluetoothDevices.Any(d => d.MacAddress == bluetoothDevice.MacAddress))
{
// Device is new, add it
Microsoft.Maui.Controls.Device.BeginInvokeOnMainThread(() => BluetoothDevices.Add(bluetoothDevice));
}
}
private void RemoveUnavailableDevices(IEnumerable<IDevice> availableDevices)
{
var currentDevices = BluetoothDevices.ToList(); // Copy current list
foreach (var device in currentDevices)
{
if (!availableDevices.Any(d => d.Id.ToString() == device.MacAddress))
{
// Device is no longer available, remove it
Microsoft.Maui.Controls.Device.BeginInvokeOnMainThread(() => BluetoothDevices.Remove(device));
Debug.WriteLine($"Removed unavailable device: {device.MacAddress}");
}
}
}
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(); // No timeout specified
device = await _adapter.ConnectToKnownDeviceAsync(deviceId, new ConnectParameters(false, true), cancellationTokenSource.Token);
return device;
}
catch (Exception ex)
{
Debug.WriteLine($"Attempt {attempt} failed: {ex.Message}");
if (attempt == retryCount)
{
throw;
}
}
}
return device;
}
private async void OnConnect()
{
if (SelectedDevice == null)
{
Debug.WriteLine("No device selected for connection.");
return;
}
Debug.WriteLine($"Attempting to connect to {SelectedDevice.Name}, MAC Address {SelectedDevice.MacAddress}");
try
{
await _adapter.StopScanningForDevicesAsync();
IsConnected = true;
_connectedDevice = await TryConnectAsync(Guid.Parse(SelectedDevice.MacAddress));
if (_connectedDevice != null)
{
Debug.WriteLine($"Successfully connected to {SelectedDevice.Name}.");
//
var services = await _connectedDevice.GetServicesAsync();
Debug.WriteLine("Services gotten");
IsWifiNetworkSelectionVisible = true;
// Iterate through the discovered services
//var service = await _connectedDevice.GetServiceAsync(Guid.Parse("00000001-710e-4a5b-8d75-3e5b444bc3cf"));
foreach (var service in services)
{
Debug.WriteLine($"Service: ID={service.Id}, Name={service.Name}");
if (service.Id.Equals(Guid.Parse("00000001-710e-4a5b-8d75-3e5b444bc3cf")) )
{
Debug.WriteLine("Found RW service");
var characteristics = await service.GetCharacteristicsAsync();
foreach (var characteristic in characteristics)
{
var descriptors = await characteristic.GetDescriptorsAsync();
foreach (var descriptor in descriptors)
{
var descriptorValue = await descriptor.ReadAsync();
var descriptorValueString = Encoding.UTF8.GetString(descriptorValue);
Debug.WriteLine($"Descriptor: {descriptor.Id} | Value: {descriptorValueString}");
}
Debug.WriteLine($"{characteristic.Uuid} |{characteristic.Name}");
if (characteristic.Name.Equals(readCharacteristicName))
{
ReadCharacteristic = characteristic;
var result = await ReadOperationAsync();
}
else if (characteristic.Name.Equals(writeCharacteristicName))
{
WriteCharacteristic = characteristic;
const string command = "SCAN";
//await WriteOperationAsync(command);
ScanForWifiNetworks();
}
}
}
}
var transmissionService = services.FirstOrDefault(s => s.Id == Guid.Parse(SendReceiveServiceUUID));
var readCharacteristic = await transmissionService.GetCharacteristicAsync(Guid.Parse(rxUUID));
var result2 = await ReadOperationAsync();
}
else
{
Debug.WriteLine("Failed to connect to the device.");
IsConnected = false;
}
}
catch (DeviceConnectionException ex)
{
Debug.WriteLine($"Connection error: {ex.Message}");
await Application.Current.MainPage.DisplayAlert("Connection Error", $"Failed to connect: {ex.Message}", "OK");
}
catch (Exception ex)
{
Debug.WriteLine($"General connection error: {ex.Message}");
await Application.Current.MainPage.DisplayAlert("Connection Error", $"An error occurred: {ex.Message}", "OK");
}
finally
{
// Ensure we reset connection state
if (_connectedDevice == null)
{
IsConnected = false;
// Consider resetting any other related UI state
}
}
}
private async Task<string> ReadOperationAsync(int retryCount = 3)
{
if (ReadCharacteristic == null)
{
Debug.WriteLine("Read characteristic is not set.");
return "Failure";
}
Debug.WriteLine($"READ COMMAND : {ReadCharacteristic.Uuid}");
for (int attempt = 1; attempt <= retryCount; attempt++)
{
try
{
Debug.WriteLine($"Attempt {attempt}: Reading from characteristic {ReadCharacteristic.Uuid}");
var temperatureData = await ReadCharacteristic.ReadAsync();
var temperatureValue = System.Text.Encoding.UTF8.GetString(temperatureData.data);
Debug.WriteLine($"Temperature Value: {temperatureValue}");
return temperatureValue;
}
catch (Exception ex)
{
Debug.WriteLine($"Read attempt {attempt} failed: {ex.Message}");
if (attempt == retryCount)
{
Debug.WriteLine("Max retry attempts reached for reading.");
throw;
} else
{
await Task.Delay(1000);
}
}
}
return "Failure";
}
private async Task<string> WriteOperationAsync(string command, string arguments="", int retryCount = 3)
{
if (WriteCharacteristic == null)
{
Debug.WriteLine("Write characteristic is not set.");
return "Failure";
}
string joinedData = command + ":" + arguments;
Debug.WriteLine($"WRITE COMMAND {joinedData}, UUID: {WriteCharacteristic}");
byte[] data = System.Text.Encoding.UTF8.GetBytes(joinedData);
for (int attempt = 1; attempt <= retryCount; attempt++)
{
try
{
await WriteCharacteristic.WriteAsync(data);
Debug.WriteLine("Write operation succeeded.");
return "Success";
}
catch (Exception ex)
{
Debug.WriteLine($"Write attempt {attempt} failed: {ex.Message}");
if (attempt == retryCount)
{
Debug.WriteLine("Max retry attempts reached for writing.");
throw;
} else
{
await Task.Delay(1000);
}
}
}
return "Failure";
}
private async void OnDisconnect()
{
if (_connectedDevice != null)
{
try
{
Debug.WriteLine($"Attempting to disconnect from {_connectedDevice.Name}");
await _adapter.DisconnectDeviceAsync(_connectedDevice);
Debug.WriteLine("Disconnected successfully.");
IsWifiNetworkSelectionVisible = false;
// Dispose of the device reference
if (_connectedDevice != null)
// _connectedDevice.Dispose();
_connectedDevice = null;
IsConnected = false;
ScanDevices();
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.");
}
}
}
}
Loading