Introduction
I wanted to write a Bluetooth application in C# for mobile devices that sends messages from one instance to another (a chatting application). After doing a little research, I found out that C# doesn’t natively support Bluetooth. If I wanted to build such an application, I would have to buy a Bluetooth library. Franson Bluetools is a very good Bluetooth library for C#, and it costs only $100. Seeing that I didn’t want to pay anything, I had to start writing my application from scratch.
The simplest method I found was to simulate the Bluetooth connection using serial ports.
Background
This application uses the .NET Compact Framework. To understand the code, you require a minimum knowledge in using the SerialPort
class, and also, you need to know how to update the UI of your application from a secondary thread that is different from that on which the application is running.
The SerialPort
class, as its name suggests, is used in C# for serial port communication. The class is found in the System.IO.Ports
namespace. You instantiate an object of this type like this:
SerialPort inPort = new SerialPort("COM1",9600);
And you open the port like this:
inPort.Open();
To read a line from the serial port, you use the following code:
string mess=inPort.ReadLine();
To write a line to the serial port, you can use the following code:
string message="Hello";
inPort.WriteLine(message);
To find out the available COM ports on your device, you can use:
string[] ports=SerialPort.GetPortNames();
After you are done, you can close the connection like this:
inPort.Close();
The other thing you need to know before we get to the next section is how to update the user interface from a secondary thread. In the .NET world, a UI can be updated only from the thread that created it. If you try to update the UI from a different thread, a cross threaded operation exception is cast. To update the UI from a different thread, you need to use the invoke()
method. This method uses a delegate to call a function on the same thread as the object that contains the invoke()
method that was called. As an example, suppose you wanted to update a TextBox
on a form from a secondary thread. To do this, you first create a delegate that has the same signature as the function you want to call:
public delegate void UpdateTextBox(string text);
Then, you create the method in which you update the TextBox
:
private void UpdateText(string text)
{
TextBox1.Text=text;
}
To update the text box, you can call the following line on the secondary thread:
TextBox1.invoke(new UpdateTextBox(UpdateText),textToWrite);
Using the code
Seeing that my application connects to another mobile device through a serial port, and the serial ports used for Bluetooth communications differ from one device to another, I used the SettingsForm
form class in order to set the COM ports for the communication (you can find those by going to Settings->Connections->Bluetooth->Services, then you select the Serial Port service and click Advanced). The Settings form looks like this:
The Settings form initializes in its constructor two combo boxes with all the available COM ports for that device, like this:
string[] ports = SerialPort.GetPortNames();
comboBox1.Items.Add("NO PORT SELECTED");
for (int i = 0; i < ports.Length; i++)
comboBox1.Items.Add(ports[i]);
comboBox2.Items.Add("NO PORT SELECTED");
for (int i = 0; i < ports.Length; i++)
comboBox2.Items.Add(ports[i]);
When the form is shown, the user selects the inbound and outbound ports and clicks OK. The COM settings can be retrieved using the following code:
public string GetInboundPort()
{
return (string)comboBox1.SelectedItem;
}
public string GetOutboundPort()
{
return (string)comboBox2.SelectedItem;
}
We can only attempt a connection after we have set the COM ports using the Settings form.
The main form of the application is represented by the Form1
class, and looks like in the picture below:
As you can see, the form has a very simple interface that consists of two text boxes (one for writing the messages to send, and the other for displaying the history of the sent and received messages), a button to send the message, a label to show some current status information, and a menu. From the menu, you can set the COM ports, you can connect to another device, and you can disconnect from another device.
Like I said before, to set the COM ports, you click the Settings menu option. The code for this event handler is:
SettingsForm form = new SettingsForm(inboundPort, outboundPort);
if(form.ShowDialog() == DialogResult.OK)
{
if(form.GetInboundPort().CompareTo("NO PORT SELECTED") == 0 ||
form.GetOutboundPort().CompareTo("NO PORT SELECTED") == 0)
{
MessageBox.Show("The ports were not set properly.");
lblStatus.Text = "Ports not set.";
mnuConnect.Enabled = false;
}
else
{
inboundPort = form.GetInboundPort();
outboundPort = form.GetOutboundPort();
mnuConnect.Enabled = true;
lblStatus.Text = "Ports set.\r\nin:" + inboundPort +
"\r\nout:" + outboundPort +
"\r\nWaiting to press Connect...";
}
}
As you can see from this code, I first create an instance of the SettingsForm
class and show it. If the ports are set properly, I set the inboundPort
and outboundPort
variables and enable the connect menu option, or I show a message box indicating that the ports aren’t set.
After you set the COM ports, you can connect to a device by clicking the Connect Menu option. The code for this event handler is presented below:
serialIn = new SerialPort(inboundPort);
serialOut = new SerialPort(outboundPort);
serialIn.ReadTimeout = 1000;
serialOut.ReadTimeout = 1000;
disconnectRequested = false;
try
{
if(!serialIn.IsOpen)
{
lblStatus.Text = "Input port closed. Opening input port...";
serialIn.Open();
}
if(!serialOut.IsOpen)
{
lblStatus.Text = "Output port closed. Opening output port...";
serialOut.Open();
}
lblStatus.Text = "Ports opened. Starting the listener thread...";
rcvThread = new Thread(new ThreadStart(ReceiveData));
numThreads++;
rcvThread.Start();
lblStatus.Text = "Listener thread started.";
btnSend.Enabled = true;
lblStatus.Text="Connected.\r\nInbound:" + inboundPort +
"\r\nOutbound:" + outboundPort;
MessageBox.Show("Connected.");
mnuConnect.Enabled = false;
mnuSettings.Enabled = false;
mnuDisconnect.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
lblStatus.Text = "error.\r\nPorts not set.";
mnuConnect.Enabled = false;
}
First, I instantiate the two variables that represent the serial ports (you need two: one for incoming and one for ongoing). I also set timeout values so that the application can exit gracefully when required. I will talk about this later.
Next, I verify if the ports are opened, and if they aren’t, I open them. On the PDAs I tested the application on, at this moment, you are presented with a list of all nearby devices. You can now select the device with which you want to communicate and the ports open.
After the ports are opened, a secondary thread is started. This thread is used to receive the messages from the remote device and show them in the history textbox. After the thread is started, the status of the application is updated and the Disconnect menu item is enabled.
You can now write messages and send them. The code for the Send button event handler is presented below:
try
{
serialOut.WriteLine(txtMess.Text);
txtLog.Text += "you:" + txtMess.Text + "\r\n";
txtMess.Text = "";
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
The code uses the WriteLine()
function of the SerialPort
class to send the message.
Like I said a few lines above, the messages are received on a secondary thread. This thread executes the ReceiveData()
function. This function is implemented in the code below:
private void ReceiveData()
{
while(!closeRequested && !disconnectRequested)
{
try
{
string line = serialIn.ReadLine();
if(line.CompareTo("quit$$$") == 0)
{
disconnectRequested = true;
continue;
}
txtLog.Invoke(new updateText(UpdateText),line);
}
catch
{}
}
if(closeRequested)
closeMe();
if(disconnectRequested)
this.Invoke(new disconnectDel(onDisconnect));
}
The function uses a while
loop, and as long as the user doesn’t exit or disconnect, the loop continues to read messages using the SerialPort.ReadLine()
function. After the message is read, the UI is updated by calling the UpdateText()
function on the UI thread using Invoke()
.
Now is the time to explain why I set the timeout values for the serial port variables. The ReadLine()
method of the SerialPort
class blocks until a message is retrieved. In order for the application to process the disconnect and close requests, it needs to execute the lines of code after ReadLine()
. By setting the timeout values to 1000ms, after every second of waiting without receiving a message, the function throws an exception and a new iteration of the while
loop is started. This gives the application the chance to check the two variables and possibly exit the loop.
If the user clicks the Close button, the application will call the closeMe()
function. This function decrements the number of secondary threads opened, and calls the Form.Close()
function to close the application. The Form.Close()
function must also be called using Invoke()
because it needs to be called on the same thread as the UI.
private void closeMe()
{
numThreads--;
this.Invoke(new closeDel(this.Close));
}
If the user clicks Disconnect, the “quit$$$” message to announce the remote device that it intends to disconnect is displayed and the disconnectRequired
flag is set.
try
{
disconnectRequested = true;
serialOut.WriteLine("quit$$$");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
If the disconnectRequired
flag is set to true
, the while
loop in the secondary thread terminates, and the onDisconnect()
method is executed. The method has the following implementation:
private void onDisconnect()
{
serialIn.Close();
serialOut.Close();
mnuDisconnect.Enabled = false;
mnuConnect.Enabled = false;
mnuSettings.Enabled = true;
lblStatus.Text = "Disconected.\r\nPorts not set.";
numThreads--;
}
This function closes the ports, enables the Settings button, and decrements the number of secondary threads running.
Also, if the application receives the “quit$$$” message, it disconnects.
if(line.CompareTo("quit$$$") == 0)
{
disconnectRequested = true;
continue;
}
That’s about it. As you have seen from this code, it is a relatively simple matter to simulate a Bluetooth connection using serial ports on mobile devices. You only need to check what ports are used, and then start the application. I hope you liked this article, and also that you will send some feedback.