Sie sind auf Seite 1von 9

Bluetooth Simulation in C# with Serial Ports

By Florin Badea, 10 Jun 2008


4.84 (16 votes)

Sign Up to vote 1 2 3 4 5 4.84/5 - 16 votes 1 removed 4.68, a 1.08 [?] Tweet

vote 1vote 2vote 3vote 4vote 5

V ote!

Download source code - 19.1 KB

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# doesnt 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 didnt 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:
Collapse | Copy Code

SerialPort inPort = new SerialPort("COM1",9600);

And you open the port like this:


Collapse | Copy Code

inPort.Open();

To read a line from the serial port, you use the following code:
Collapse | Copy Code

string mess=inPort.ReadLine();

To write a line to the serial port, you can use the following code:
Collapse | Copy Code

string message="Hello"; inPort.WriteLine(message);

To find out the available COM ports on your device, you can use:
Collapse | Copy Code

string[] ports=SerialPort.GetPortNames();

After you are done, you can close the connection like this:
Collapse | Copy Code

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:
Collapse | Copy Code

public delegate void UpdateTextBox(string text);

Then, you create the method in which you update the TextBox:
Collapse | Copy Code

private void UpdateText(string text) { TextBox1.Text=text; }

To update the text box, you can call the following line on the secondary thread:
Collapse | Copy Code

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:
Collapse | Copy Code

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:
Collapse | Copy 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:
Collapse | Copy Code

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 arent 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:
Collapse | Copy Code

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 arent, 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:
Collapse | Copy Code

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:
Collapse | Copy Code

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 doesnt 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.
Collapse | Copy Code

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.
Collapse | Copy Code

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:
Collapse | Copy Code

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.


Collapse | Copy Code

if(line.CompareTo("quit$$$") == 0) { disconnectRequested = true; continue; }

Thats 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.

Das könnte Ihnen auch gefallen