How can I cancel LoadAsync() without closing the underlying socket? - c#

I have an app that communicates to some hardware over rfcomm on bluetooth. My app works on Android and am in the process of getting things working on UWP. Here's how I set up the stream reader/writers in the UWP code:
var btDevice = await BluetoothDevice.FromIdAsync(devId);
var services = await btDevice.GetRfcommServicesAsync();
if (services.Services.Count > 0)
{
// We only have one service so use the first one...
var service = services.Services[0];
// Create a stream...
_bluetoothStream = new StreamSocket();
await _bluetoothStream.ConnectAsync(service.ConnectionHostName, service.ConnectionServiceName);
_dataReader = new DataReader(_bluetoothStream.InputStream);
_dataWriter = new DataWriter(_bluetoothStream.OutputStream);
_dataReader.InputStreamOptions = InputStreamOptions.Partial;
My hardware only sends data to my app after the app sends it data so I've set up a send/receive mechanism. Everything works great except for a specific use case where my device is restarting (but bluetooth connection is still active) and is unable to send a response. In this case my upper level code is setup to attempt a retry, however the bluetooth connection gets closed when the receive times out.
_dataWriter.WriteBytes(comm.TransmitData);
Task<UInt32> writeAysncTask = _dataWriter.StoreAsync().AsTask();
UInt32 bytesWritten = await writeAysncTask;
:
try
{
using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(comm.TimeoutMs))) // _receiveTimeoutMs)))
{
// When this times out, exception gets thrown and socket is closed
// How do I prevent the socket from closing so I can do a retry???
var loadTask = _dataReader.LoadAsync(comm.ReceiveCount).AsTask(cts.Token);
bytesRead = await loadTask;
if (bytesRead > 0)
{
rxData = new byte[bytesRead];
_dataReader.ReadBytes(rxData);
}
else
{
System.Diagnostics.Debug.WriteLine("Received 0!");
}
}
}
catch (Exception ex)
{
// The bluetooth connection is closed automatically if the
// caancellationToken fires...In my case, I need the connection
// to stay open...How do I achieve this???
// Update: When this code is executed with _dataReader/Writer
// that was created with SerialDevice class (see below), the
// timeout exception does not cause the Serial connection to
// close so my calling code can then issue a retry.
System.Diagnostics.Debug.WriteLine(ex.Message) ;
}
UPDATE: It should be noted that when I use the exact same code with streams created from a SerialDevice everything works as I would expect...When the receive times out the socket is NOT closed. Seems like maybe I'm up against something in the Bluetooth Implementation in UWP. Ugh. Here's how I create the _dataReader/_dataWriter with the SerialDevice class:
_serialDevice = await SerialDevice.FromIdAsync(devId);
// Configure the port
_serialDevice.BaudRate = _baudrate;
_serialDevice.Parity = SerialParity.None;
_serialDevice.DataBits = 8;
_serialDevice.StopBits = SerialStopBitCount.One;
_dataReader = new DataReader(_serialDevice.InputStream);
_dataWriter = new DataWriter(_serialDevice.OutputStream);

I've come up with a work around to the problem I was facing. Unfortunately, I can't use the same code for SerialDevice and BluetoothDevice. I have to say, it really stinks that the bluetooth socket gets closed when a cancellation token times out. The code would be so much cleaner if it didn't close! Shouldn't it be up to me to decide if the socket should be closed? Now I'm stuck with this:
using (var cts = new CancellationTokenSource())
{
Task.Run(async () =>
{
try
{
await Task.Delay((int)comm.TimeoutMs, cts.Token);
System.Diagnostics.Debug.WriteLine("Canceling async read");
// If we make it this far, then the read as failed...cancel the async io
// which will cause the bytesRead below to be 0.
await _bluetoothStream.CancelIOAsync();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}, cts.Token);
var loadTask = _dataReader.LoadAsync(comm.ReceiveCount).AsTask();
bytesRead = await loadTask;
if (bytesRead > 0)
{
// SIgnal the delay task to cancel...
cts.Cancel(true);
if (bytesRead > comm.ReceiveCount)
System.Diagnostics.Debug.WriteLine("Received too much!!");
rxData = new byte[bytesRead];
_dataReader.ReadBytes(rxData);
}
else
{
System.Diagnostics.Debug.WriteLine("Received 0!");
}
}
After implementing this, I did notice that after I've paired my BT device, Windows returns it as a SerialDevice in the following query:
string aqs = SerialDevice.GetDeviceSelector();
DeviceInformationCollection devices = await DeviceInformation.FindAllAsync(aqs);
// My bluetooth device is included in the 'devices' collection
So if I connect to it as a serial device, I guess I won't need the work around after all. Oh well, hopefully this post will help someone else.

Related

UWP C# UART RS485 DetachBuffer

I have previously posted regarding the issue but haven't really found the problem.
Recently, I found this post regarding detachbuffer and wonder if this could be the reason i encounter the problem.
I have my UART for RS485 using a FTDI USB to 485 cable connected to Raspberry Pi on Windows IoT Core.
I set a dispatchertimer at every 1second to transmit polling to respective field devices.
I am able to tx and rx the 485 data with no problem.
However, after the polling loop to about 20 times it just crash and exited the debug mode.
I used try & catch to trap the exception but could not get it. However, i manage to read the error message at the debug output pane - The program [0xBB0] PwD.exe' has exited with code -1073741811 (0xc000000d).
I wonder if i repeatedly transmit polling, dataWriteObject.DetachBuffer(); would cause the problem?
Thanks.
snippets of my code are as follow;
private void PollTimer_Tick(object sender, object e)
{
if (pollCounter <= maxDevice)
{
var a = pollCounter | 0x80;
pollCounter++;
TxAdr = Convert.ToByte(a);
TxCmd = TxPoll;
TxPollCard();
}
else pollCounter = 0;
}
private async void TxPollCard()
{
if (serialPort != null)
{
List<byte> data = new List<byte>();
data.Add(TxHeader);
data.Add(TxAdr);
data.Add(TxCmd);
TxChkSum = 0;
foreach (byte a in data)
{
TxChkSum += a;
}
TxChkSum = (byte)(TxChkSum - 0x80);
data.Add(TxChkSum);
try
{
// Create the DataWriter object and attach to OutputStream
dataWriteObject = new DataWriter(serialPort.OutputStream);
dataWriteObject.WriteBytes(data.ToArray());
Task<UInt32> storeAsyncTask;
// Launch an async task to complete the write operation
storeAsyncTask = dataWriteObject.StoreAsync().AsTask();
UInt32 bytesWritten = await storeAsyncTask;
dataWriteObject.DetachBuffer();
}
catch (Exception ex)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
MainStatusDisplay.Text = ex.Message;
});
}
}
else
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
MainStatusDisplay.Text = "No UART port found";
});
}
}
Update:
Additional test i did which i disconnect the devices & keep transmitting without expecting response, it didn't crash.
On the other hand, i stop transmit and only listen to the 485 bus, it didn't crash either.

UWP SerialDevice DataReader Exception: 0x800710DD. The operation identifier is not valid

I'm working on porting functionality from an example Windows Forms App to a Xamarin.Forms UWP app where it should write to & read from a bluetooth device on a COM port. I have it working fine most of the time, but intermittently the UWP app will get itself into a state where any call to dataReader.LoadAsync will trigger the exception:
Exception thrown at 0x74AF1A62 (KernelBase.dll) in MyApp.UWP.exe: WinRT originate error - 0x800710DD : 'The operation identifier is not valid.'.
Exception thrown: 'System.Runtime.InteropServices.COMException' in MyApp.UWP.exe
WinRT information: The operation identifier is not valid.
Restarting the app or Visual Studio does not help, the issue persists.
The last time it happened it did not appear to impact my dataWriter writing to the device, only the subsequent read.
All of the code is in the UWP project.
private DataReader _dataReader;
private DataWriter _dataWriter;
private SerialDevice _currentSerialDevice;
private async Task ReadAsync(SerialDevice serialDevice)
{
const uint ReadBufferLength = 1024;
if (_dataReader == null)
{
_dataReader = new DataReader(_currentSerialDevice.InputStream) { InputStreamOptions = InputStreamOptions.Partial };
}
uint bytesRead = await _dataReader.LoadAsync(ReadBufferLength); // <- exception here
if (bytesRead > 0)
{
var vals = new byte[bytesRead];
_dataReader.ReadBytes(vals);
DoStuffWithBytes(vals);
}
}
The serial device is chosen from a list in the application.
// Get serial devices
DeviceInformationCollection serialDeviceCollection = await DeviceInformation.FindAllAsync(SerialDevice.GetDeviceSelector());
// Load serial device from user choosing a device from serialDeviceCollection
public async void ConnectToSerialDevice(DeviceInformation device)
{
_currentSerialDevice = await SerialDevice.FromIdAsync(device.Id);
_currentSerialDevice.BaudRate = 115200;
_currentSerialDevice.Parity = SerialParity.None;
_currentSerialDevice.DataBits = 8;
_currentSerialDevice.StopBits = SerialStopBitCount.One;
_currentSerialDevice.Handshake = SerialHandshake.RequestToSend;
}
Code for writing to the device, which works even when it gets in the odd state:
private async Task WriteToDevice(byte[] outBuffer)
{
if (_currentSerialDevice != null)
{
if (_dataWriter == null)
{
_dataWriter = new DataWriter(_currentSerialDevice.OutputStream);
}
_dataWriter.WriteBytes(outBuffer);
await _dataWriter.StoreAsync();
}
}
I've tried things like flushing the data writer, recreating the datawriter & datareaders each time, but I get the same error nonetheless and cannot read anything from the device. In normal operation I am able successfully read the bytes I'm expecting (even when there are no bytes to be read, it "reads" 0 bytes) and can output this result with no exception.
The curious thing about it all is that not only does the original Windows Forms app work fine (with the same bluetooth device) even after it gets in this state, but just opening the port and reading from the device (in the old app) actually fixes the issue in the UWP app for a time, allowing me to read from the device again.
This may be related to asynchronous methods. You can try this:
var task = await _dataReader.LoadAsync(ReadBufferLength);
task.AsTask().Wait();
uint bytesRead = task.GetResults();
For asynchronous methods (such as DataReader.LoadAsync), events occur on the UI thread and can only be triggered once, and can only continue to be triggered after the previous asynchronous method is completed. Your question may be related to this.
In the end it turns out that the cause of the problem was the LoadAsync method hanging while waiting to fill the entire buffer (1024 bytes) despite the InputStreamOptions being set to Partial. The exception I was getting was somewhat unrelated and was to do with the asynchronous method not working properly (the method was being called again when the first task had not completed).
The fix was a combination of adding a ReadTimeout to the SerialDevice:
_currentSerialDevice.ReadTimeout = TimeSpan.FromMilliseconds(500);
and also wrapping the LoadAsync task itself in a timed cancellation token:
using (var cts = new CancellationTokenSource(500))
{
var task = _dataReader.LoadAsync(ReadBufferLength);
var readTask = task.AsTask(cts.Token);
uint bytesRead = await readTask;
}
This allowed the LoadAsync method to complete both when the device had less than 1024 bytes to consume (handled by the SerialDevice.ReadTimeout) and also when the device had 0 bytes to consume (handled by the CancellationToken).
I'm still not sure why running the win forms app fixed the issue for a time, possibly it was setting the ReadTimeout (while my UWP app was not) and this was persisting on the serial port in some way.

C# NetworkStream detect all kind of disconnections and exceptions

I have this code to connect to streaming server. the server disconnect from time to time and I want to detect it and restart the connection when neede.
How can I detect in this code any kind of exceptions?
Because now I get disconnected and can't catch it.
this.ns = new NetworkStream(server);
while (true)
{
// Incoming message may be larger than the buffer size.
do
{
byte[] myReadBuffer = new byte[10240 * 5];
await this.ns.ReadAsync(myReadBuffer, 0, myReadBuffer.Length).ContinueWith((numberOfBytesRead) =>
{
string message = Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead.Result);
p.Invoke(message);
});
}
while (this.ns.DataAvailable);
}
Your variable numberOfBytesRead is actually the previous task that has finished from where you can check whether it is completed or failed.
if(numberOfBytesRead.IsFaulted)
{
var aggregatedEx = numberOfBytesRead.Exception;
//do something
}

BluetoothListener.AcceptBluetooth client blocks in 32Feet windows 7

We are creating application to communicat external device with windows PC (here we are using windows 7), in PC we are using bluetooth dongle.
when we tried to discover and pair device it was successfull in windows PC.
But in code side we are trying to connect the device it was not successfull and here we are using 32feet.net to connect the device.
below code i am trying to connect the device.
////_serviceClassId = new Guid("9bde4762-89a6-418e-bacf-fcd82f1e0677");
Guid serviceClass = BluetoothService.RFCommProtocol;
int selectedIndex = device_list.SelectedIndex;
BluetoothDeviceInfo selectedDevice = this.array[selectedIndex];
var lsnr = new BluetoothListener(serviceClass);
lsnr.Start();
Task.Run(() => Listener(lsnr));
and the Listener method is
private void Listener(BluetoothListener lsnr)
{
try
{
while (true)
{
using (var client = lsnr.AcceptBluetoothClient())
{
using (var streamReader = new StreamReader(client.GetStream()))
{
try
{
var content = streamReader.ReadToEnd();
if (!string.IsNullOrEmpty(content))
{
////_responseAction(content);
}
}
catch (IOException)
{
client.Close();
break;
}
}
}
}
}
catch (Exception exception)
{
// todo handle the exception
// for the sample it will be ignored
}
}
If i run the application it will blocked in the lsnr.AcceptBluetoothClient() Can any one help what wrong in this?
Note : Bluetooth device created two comports one is incoming and anther one is outgoing port, when we connect through PC.
It is because it wait untill it have a client connected. You have to run it in a thread to work simultaneously!

Bluetooth Communication from Xamarin to C# Console App

I am developing an Xamarin application (currently focused on Android but will eventually support iOS) that communicates via bluetooth to a nearby C# console application. They both act as a client/server as they need to send messages back and forth.
The read code for the Android implementation is as follows:
byte[] buffer = new byte[1024];
int bytes;
// Listen to the input stream while connected
while (true)
{
try
{
// Read from the InputStream
bytes = this.InStream.Read(buffer, 0, buffer.Length);
var message = new Java.Lang.String(buffer, 0, bytes);
// TODO: Notify the UI of the message
}
catch (Java.IO.IOException e)
{
// TODO: Show some error
}
}
The problem with the above code is that since I am only reading a buffer size of 1024 I am only getting partial messages for anything over 1024 bytes. Using a DataWriter (as shown below in the console application) would be ideal for me, but it is not available for an Android implementation using Xamarin.
The write code for the Android application is as follows:
try
{
this.OutStream.Write(buffer, 0, buffer.Length);
// TODO: Notify the UI
}
catch (Java.IO.IOException e)
{
// TODO: Show some error
}
Now, for the console application the read code is as follows:
// socket here is a Windows.Networking.Sockets.StreamSocket
var reader = new DataReader(socket.InputStream);
while (true)
{
try
{
uint readLength = await reader.LoadAsync(sizeof(uint));
uint currentLength = reader.ReadUInt32();
await reader.LoadAsync(currentLength);
string message = reader.ReadString(readLength);
Console.WriteLine("Message received: " + message);
}
catch (Exception e)
{
// TODO: Show some error
}
}
The problem here, is the the await reader.LoadAsync(currentLength) is never resolved. I am guessing that is because the Android app is only sending the message and not the length, then the message. I can get rid of the second await and use a buffer size of 1024 as I am in the Android application, but I will again be getting partial messages. Simply increasing the buffer size to get a bigger message is another option, but that sounds incorrect to me.
And the write code for the console application:
// socket here is a Windows.Networking.Sockets.StreamSocket
var writer = new DataWriter(socket.OutputStream)
writer.WriteUInt32((uint)message.Length);
writer.WriteString(message);
try
{
await writer.StoreAsync();
}
catch (Exception exception)
{
// TODO: Show some error
}
I am unsure of the changes I need to make to be able to send and receive complete messages back and forth on both applications. As I stated before, the implementation of the console application will need to support iOS bluetooth communication as well.

Categories

Resources