Scenario:
I am building an Android app using Xamarin.Forms that will be deployed to a group of devices. All but one of the devices will be doing some data collection, and the remaining device will be the "hub" to aggregate all of the data and do some reporting. I am using Bluetooth for the device-to-device communication. The 'hub', labelled the master, acts as the client, and all of the collectors act as the server. I have a prototype working with a single server and client...almost.
Occasionally the client/master will be unable to read from the server/collector. I am struggling to find the reason for why this is and would appreciate any help.
Symptoms:
The client's call to .Read() from the InputStream will occasionally block indefinitely, even though the server has written to the output stream. I've added a timeout to this call to prevent the app from getting stuck entirely.
This happens intermittently, but I've found some pattern to when it works and when it doesn't
It seems to be related to the 'server' app, and not the client. The client can remain open, running, and initiate the request to connect to the server as often as needed.
It always works the first time the 'server' app is launched and connected to. It ususally works the second time. By the third connection, .Read() will consistently block/timeout. Closing and reopening the app on the server "cleans the slate" so to speak and it will work again.
Once it starts failing, it seems to be 'stuck' in a failed state.
Removing the app from the foreground (but not closing/killing it) seems to correct the faulted state, and the connection/read will happen successfully as long as the app/UI remains in the background. Once restored to the foreground, it starts failing again.
Code:
All of the bluetooth handling is done by a single class/service that I'm injecting using Xamarin.Forms DependencyService. All of the devices will, on startup (via the constructor of this class), loop indefinitely on a background thread, waiting for connections and repeating. Much of this bluetooth code is based on the Bluetooth Chat example, as well as some other online resources I've found (some android native/java, some Xamarin/C#)
The master will, on demand (triggered by press of a button in the UI), attempt to connect to any collectors (via bonded bluetooth devices) and read data from them. There is also a simple UI component which essentially serves as a console log.
Here is the service class in its entirety.
public class GameDataSyncService : IGameDataSyncService
{
private const string UUID = "8e99f5f1-4a07-4268-9686-3a288326e0a2";
private static Task acceptLoopTask;
private static Task syncDataTask;
private static readonly object locker = new object();
private static bool running = false;
public event EventHandler<DataSyncMessage> MessageBroadcast;
public GameDataSyncService()
{
// Every device will listen and accept incoming connections. The master will make the connections.
lock (locker)
{
if (acceptLoopTask == null)
{
acceptLoopTask = Task.Factory.StartNew(AcceptLoopWorker, TaskCreationOptions.LongRunning);
}
}
}
public void SyncData()
{
lock (locker)
{
if (running)
{
BroadcastMessage("Previous data sync is still running.", DataSyncMessageType.Warning);
return;
}
else
{
running = true;
syncDataTask = Task.Factory.StartNew(SyncDataWorker);
}
}
}
private void BroadcastMessage(string message, DataSyncMessageType type = DataSyncMessageType.Info)
{
MessageBroadcast?.Invoke(this, new DataSyncMessage { Text = message, Type = type });
}
private async Task AcceptLoopWorker()
{
int count = 0;
while (true)
{
BluetoothServerSocket serverSocket = null;
BluetoothSocket clientSocket = null;
try
{
BroadcastMessage($"Listening for incoming connection...", DataSyncMessageType.Debug);
serverSocket = BluetoothAdapter.DefaultAdapter.ListenUsingRfcommWithServiceRecord(nameof(GameDataSyncService), Java.Util.UUID.FromString(UUID));
clientSocket = serverSocket.Accept(); // This call blocks until a connection is established.
BroadcastMessage($"Connection received from {clientSocket.RemoteDevice.Name}. Sending data...", DataSyncMessageType.Info);
var bytes = Encoding.UTF8.GetBytes($"Hello World - {string.Join(" ", Enumerable.Repeat(Guid.NewGuid(), ++count))}");
await clientSocket.OutputStream.WriteAsync(bytes, 0, bytes.Length);
clientSocket.OutputStream.Flush();
// Give the master some time to close the connection from their end
await Task.Delay(1000*3);
}
catch (Exception ex)
{
BroadcastMessage($"{ex.GetType().FullName}: {ex.Message}", DataSyncMessageType.Debug);
}
finally
{
try { clientSocket?.InputStream?.Close(); } catch { }
try { clientSocket?.InputStream?.Dispose(); } catch { }
try { clientSocket?.OutputStream?.Close(); } catch { }
try { clientSocket?.OutputStream?.Dispose(); } catch { }
try { clientSocket?.Close(); } catch { }
try { clientSocket?.Dispose(); } catch { }
try { serverSocket?.Close(); } catch { }
try { serverSocket?.Dispose(); } catch { }
BroadcastMessage($"Connection closed.", DataSyncMessageType.Debug);
}
}
}
private async Task SyncDataWorker()
{
BroadcastMessage($"Beginning data sync...");
foreach (var bondedDevice in BluetoothAdapter.DefaultAdapter.BondedDevices.OrderBy(d => d.Name))
{
BluetoothSocket clientSocket = null;
try
{
clientSocket = bondedDevice.CreateRfcommSocketToServiceRecord(Java.Util.UUID.FromString(UUID));
BroadcastMessage($"Connecting to {bondedDevice.Name}...");
try
{
clientSocket.Connect();
}
catch
{
BroadcastMessage($"Connection to {bondedDevice.Name} failed.", DataSyncMessageType.Error);
}
while (clientSocket.IsConnected)
{
byte[] buffer = new byte[1024];
var readTask = clientSocket.InputStream.ReadAsync(buffer, 0, buffer.Length);
if (await Task.WhenAny(readTask, Task.Delay(1000)) != readTask)
{
BroadcastMessage($"Read timeout...", DataSyncMessageType.Error);
break;
}
int bytes = readTask.Result;
BroadcastMessage($"Read {bytes} bytes.", DataSyncMessageType.Success);
if (bytes > 0)
{
var text = Encoding.UTF8.GetString(buffer.Take(bytes).ToArray());
BroadcastMessage(text, DataSyncMessageType.Success);
break;
}
}
}
catch (Exception ex)
{
BroadcastMessage($"{ex.GetType().FullName}: {ex.Message}", DataSyncMessageType.Debug);
}
finally
{
try { clientSocket?.InputStream?.Close(); } catch { }
try { clientSocket?.InputStream?.Dispose(); } catch { }
try { clientSocket?.OutputStream?.Close(); } catch { }
try { clientSocket?.OutputStream?.Dispose(); } catch { }
try { clientSocket?.Close(); } catch { }
try { clientSocket?.Dispose(); } catch { }
}
}
await Task.Delay(1000 * 3);
BroadcastMessage($"Data sync complete!");
lock (locker)
{
running = false;
}
}
}
What I've tried (nothing below has had any effect):
Most of these were from 'solutions' from other stackoverflow posts.
Adding arbitrary delays into the mix
Making sure to explicitly close/dispose everything, in order, including the streams
Tried replacing the socket handling with their 'Insecure' counterparts.
Adjusting my read timeout to something arbitrarily long, in case a second wasn't enough.
Disabling/Re-enabling bluetooth on the server/collector before .Accept() ing a new connection (resorted to trying random stuff by this point)
Video:
I took a video of this happening.
The tablet in the back is the collector/server The tablet in the foreground is the master/client. When the video starts, the client is displaying some previous attempts, and the server app is in the background (but running). I demonstrate that the .Read works when the collector/server app is in the background, but not the foreground. Each request to begin data sync has a corresponding entry to the "console" (or a warning if I pressed it too soon)
https://youtu.be/NGuGa7upCU4
Summary:
To the best of my knowledge, my code is correct. I have no idea what else to change/fix to get this working more reliably. The actual connection seems like it is successful (based on logs from the server/collector, unfortunately not shown in the video), but the issue lies somewhere in the .Write (or .Read). ANy help, suggestions, or insight would be awesome.
Try the following, changed all to using:
private async Task AcceptLoopWorker()
{
int count = 0;
while (true)
{
try
{
BroadcastMessage("Listening for incoming connection...", DataSyncMessageType.Debug);
using (var serverSocket = BluetoothAdapter.DefaultAdapter.ListenUsingRfcommWithServiceRecord(nameof(GameDataSyncService), Java.Util.UUID.FromString(UUID)))
using (var clientSocket = serverSocket.Accept()) // This call blocks until a connection is established.
{
BroadcastMessage(string.Format("Connection received from {0}. Sending data...", clientSocket.RemoteDevice.Name), DataSyncMessageType.Info);
var bytes = System.Text.Encoding.UTF8.GetBytes(string.Format("Hello World - {0}", string.Join(" ", Enumerable.Repeat(Guid.NewGuid(), ++count))));
await clientSocket.OutputStream.WriteAsync(bytes, 0, bytes.Length);
}
await Task.Delay(1000 * 3); // Give the master some time to close the connection from their end
}
catch (Java.IO.IOException ex)
{
BroadcastMessage(string.Format("IOException {0}: {1}", ex.GetType().FullName, ex.Message), DataSyncMessageType.Debug);
}
catch (Java.Lang.Exception ex)
{
BroadcastMessage(string.Format("Exception {0}: {1}", ex.GetType().FullName, ex.Message), DataSyncMessageType.Debug);
}
}
}
private async Task SyncDataWorker()
{
BroadcastMessage("Beginning data sync...");
foreach (var bondedDevice in BluetoothAdapter.DefaultAdapter.BondedDevices.OrderBy(d => d.Name))
{
try
{
using (var clientSocket = bondedDevice.CreateRfcommSocketToServiceRecord(Java.Util.UUID.FromString(UUID)))
{
BroadcastMessage(string.Format("Connecting to {0}...", bondedDevice.Name));
if (!clientSocket.IsConnected)
{
clientSocket.Connect();
}
if (clientSocket.IsConnected)
{
byte[] buffer = new byte[1024];
var readTask = clientSocket.InputStream.ReadAsync(buffer, 0, buffer.Length);
if (await Task.WhenAny(readTask, Task.Delay(1000)) != readTask)
{
BroadcastMessage("Read timeout...", DataSyncMessageType.Error);
break;
}
int bytes = readTask.Result;
BroadcastMessage(string.Format("Read {0} bytes.", bytes), DataSyncMessageType.Success);
if (bytes > 0)
{
var text = System.Text.Encoding.UTF8.GetString(buffer.Take(bytes).ToArray());
BroadcastMessage(text, DataSyncMessageType.Success);
break;
}
}
else
{
BroadcastMessage("Not Connected...", DataSyncMessageType.Error);
}
}
}
catch (Java.IO.IOException ex)
{
BroadcastMessage(string.Format("IOException {0}: {1}", ex.GetType().FullName, ex.Message), DataSyncMessageType.Debug);
}
catch (Java.Lang.Exception ex)
{
BroadcastMessage(string.Format("Exception {0}: {1}", ex.GetType().FullName, ex.Message), DataSyncMessageType.Debug);
}
}
await Task.Delay(1000 * 3);
BroadcastMessage("Data sync complete!");
lock (locker)
{
running = false;
}
}
I am trying to listen at incoming Bluetooth connection thanks to this code based on this documentation:
public void WaitForConnection()
{
BluetoothServerSocket serverSocket = null;
BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter;
if (adapter == null)
throw new Exception("No Bluetooth adapter found");
if (!adapter.IsEnabled)
throw new Exception("Bluetooth adapter is not enabled");
// Create a new listening server socket
try
{
serverSocket = adapter.ListenUsingRfcommWithServiceRecord("MindCam", UUID.FromString(Ev3UUID));
}
catch (Java.IO.IOException e)
{
Console.WriteLine("Error listening connection: " + e.Message);
}
Task t = Task.Factory.StartNew(() =>
{
while (true)
{
try
{
_socket = serverSocket.Accept();
}
catch (IOException e)
{
Console.WriteLine("Socket's accept() method failed");
break;
}
if (_socket != null)
{
// A connection was accepted.
serverSocket.Close();
Console.WriteLine("Connection accepted");
break;
}
}
});
}
I call this method at the beginning of an activity. I debug that, and it seems that the code is executed until "serverSocket.Accept()", and even if it is not awaited, the code is no more executed. Afterwards, even when I attempt to make a connection from a Bluetooth device (a Mindstorms brick) to the Android device, it is not detected and this thread doesn't continue...
Why doesn't this work ?
Thanks in advance.
In my application I'm using a 9600 baud rate serial connection and I want to use a 115200 baud rate connection for data transfer.
I've disconnected from the old connection and set it to be null value, and set my serial connection to new connection with different baud rate.
The connection is unstable and I sometimes get a System.ObjectDisposedException - what did I miss?
The connection code
public string startConnection()
{
if (serial != null)
{
serial.Dispose();
}
foreach (string portname in SerialPort.GetPortNames())
{
serial = new SerialPort(portname, 9600, Parity.None, 8, StopBits.One);
serial.ReadTimeout = 5000;
serial.WriteTimeout = 5000;
serial.Handshake = System.IO.Ports.Handshake.None;
serial.NewLine = "\n";
string received = "";
try
{
serial.Open();
serial.DiscardInBuffer();
serial.Write(":09;BATTERY;");
Thread.Sleep(500);
received = serial.ReadLine();
if (received.Contains(";BATTERY;V="))
{
status = SERIAL_CONNECTED;
return portname;
}
}
catch (Exception err)
{
try
{
serial.Close();
status = DISCONNECTED;
}
catch (Exception)
{
// throw;
}
}
}
throw new Exception("couldn't connect to coms");
//return "couldn't connect to coms";
//this.Close();
}
Disconnect function:
public void disconnect ()
{
if (serial == null || serial.IsOpen==false ||status == DISCONNECTED)
return;
status = DISCONNECTED;
serial.Close();
serial = null;
}
The main program is:
private async void BurnOFP_click(object sender, RoutedEventArgs e)
{
startConnection();
some actions.............
disconnect();
var t = new Task(() =>
{
try
{
myUswm.startModemConnection(); // same but with different baud rate
}
catch (Exception e2) { MessageBox.Show(e2.Message); }
});
t.Start();
t.Wait();
modem = new XMODEM_FullDotNET(myUswm.getSerialPort(), XMODEM_FullDotNET.Variants.XModemCRC);
buff = File.ReadAllBytes(softwareFilePath_Text.Text);
if (buff.Length < 1)
{
MessageBox.Show("ERROR : wrong OFP file");
return;
}
if (myUswm.prepareOFPBurning()) // sends u to start transfer
{
if (isBurning == false)
{
isBurning = true;
modem._ProgressSent = 0;
myProgBar = new myProgressBar(modem);
myProgBar.StartTransfer(modem, buff.Length);
myProgBar.Show(); // show window
// got the Exception here!!!!!!!!!!
var t3 = new Task(() =>
{
modem.Send(buff);
});
............
}
else
MessageBox.Show("burning in progress..");
}
}
catch (Exception e1)
{
MessageBox.Show(e1.Message);
}
}
Thanks for any help
RESOLVED
my problem was A bad timing caused by closing and reopen the same port.
I've found the information in MSDN Serial class:
The best practice for any application is to wait for some amount of time after calling the Close method before attempting to call the Open method, as the port may not be closed instantly.
my solution was keeping the connection alive and change the baud rate and update the connection status in my application manually.
I have written a C# Windows-forms program that reads a weight-value from an RS-232 port.
Here is my program:
try { Brate = Convert.ToInt32(MyParam._BOUD1); }
catch { Brate = 9600; }
port = new SerialPort(MyParam._COM1, Brate, Parity.None, 8, StopBits.One);
port.DtrEnable = true;
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
if (port.IsOpen == false)
{
try
{
port.Open();
port.DiscardOutBuffer();
}
catch (Exception oex)
{
MessageBox.Show(oex.ToString());
}
}
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
this.Invoke(new EventHandler(DoUpdate));
}
catch (Exception ex)
{
}
}
private void DoUpdate(object s, EventArgs e)
{
try
{
lblMSG2.Text = port.ReadLine().ToString();
port.DiscardInBuffer();
}
catch (Exception ex)
{
lblMSG2.Text = ex.ToString();
}
}
This program is working excellently, except for when I try and close the form in order to open another form.
Here is how I close the serial-port:
if (port.IsOpen == true)
{
port.DataReceived -= new SerialDataReceivedEventHandler(port_DataReceived);
try
{
port.Close();
port.DiscardOutBuffer();
}
catch (Exception oex)
{
MessageBox.Show(oex.ToString());
}
}
My program hangs, and I can't do anything except for quit the program and restart it.
What am I doing wrong?
EDIT
I tried everything suggested here
if (port.IsOpen == true)
{
//port.DataReceived -= new SerialDataReceivedEventHandler(port_DataReceived);
try
{
//port.Dispose();
// port.DiscardOutBuffer();
// port = null;
port.Close();
}
catch (Exception oex)
{
MessageBox.Show(oex.ToString());
}
}
but still program hangs, and I can't do anything except for quit the program and restart it.
its stuck in this line: port.Close();
Your problem is that you're declaring a new event handler at the same time that you're trying to un-subscribe your existing handler.
Remove the code where it says:
port.DataReceived -= new SerialDataReceivedEventHandler(port_DataReceived);
This is most likely what's causing your hang-up. You don't need to have that code in there at all, because once you close the port you won't receive any new data anyways. Additionally, you should simplify what you're doing when you try to close the port. Change the code where you close the port to:
try
{
if(port.IsOpen)
{
port.Close();
}
}
catch (Exception oex)
{
MessageBox.Show(oex.ToString());
}
Given that all you're doing is using a simple event handler to receive some data over the port, the above code-block is all you really need to do to close out the port.
I have an serial port read running on a background thread, but everytime I try to close it, I get an exception. Usually an IO-exception.
It is as if the read continues on eventhough I close the thread.
This is my current code:
EDIT: I changed the code, removed the checks on threatstate.
public bool Connect(string portName)
{
try
{
sp = new SerialPort(portName, BaudRate);
sp.Open();
sp.DtrEnable = true;
cf = SingletonFormProvider.GetInstance<ConnectionForm>(null);
_continue = true;
readThread = new Thread(Read);
readThread.Start();
return true;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
}
public void Disconnect()
{
if (IsConnectionOpen)
{
_continue = false;
readThread.Abort();
while (readThread.ThreadState == ThreadState.AbortRequested)
{ }
sp.Close();
readThread.Join();
}
}
private void Read()
{
while (_continue)
{
try
{
string message = sp.ReadLine();
cf.WriteLog(message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
_continue = false;
readThread.Join();
sp.Close();
}
}
}
You do know that what thread.Abort() does is throw a ThreadAbortException in the thread in question, right? In the thread you catch all exceptions that inherited from Exception. I believe that includes the abort exception. If you really must call Abort, you may want to close the serial port first since I believe that will cause any pending calls to return.
Join your read thread before you close the serial port:
public void Disconnect()
{
if (IsConnectionOpen)
{
_continue = false;
readThread.Join();
sp.Close();
}
}