Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
R
Robobin
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Deploy
Releases
Package Registry
Model registry
Operate
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
plw1g21
Robobin
Commits
a052b055
Commit
a052b055
authored
5 months ago
by
Paul-Winpenny
Browse files
Options
Downloads
Patches
Plain Diff
Bluetooth works! (BLE)
parent
cb5cf7d9
No related branches found
Branches containing commit
No related tags found
1 merge request
!1
App now has a basic structure and BLE support
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
+208
-29
208 additions, 29 deletions
App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
App/RobobinApp/Views/ConnectionPage.xaml
+32
-4
32 additions, 4 deletions
App/RobobinApp/Views/ConnectionPage.xaml
with
240 additions
and
33 deletions
App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
+
208
−
29
View file @
a052b055
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
}
This diff is collapsed.
Click to expand it.
App/RobobinApp/Views/ConnectionPage.xaml
+
32
−
4
View file @
a052b055
...
...
@@ -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>
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment