Transferring files and monitoring bluetooth ports in C#

Author: Josip Zohil, Koper, Slovenija, Josip.Zohil1@guest.arnes.si

Sometimes there are problems transferring data between two bluetooth devices. We can't transfer some type of files, we can't discover the service (protocols) for data transfer, for example the OBEX or FTP protocol, which are above the RFCOMM layers of the Bluetooth protocol stack. But the devices can communicate on the lower RFCOMM layer. If we have control over the two device, for example PC or Pocket PC, we can create C# programs for file transfer. We will show you, how to transfer files with bluetooth technology without the use of additional software and how to monitor the serial port bytes traffic in C#. You can reuse the code in this article to create yours application to transfer data also in environment when you can't do that with standard bluetooth application.

Creating Bluetooth application in C#

Creating Bluetooth server and client applications in C# is similar to creating server and client applications for the most known TCP communication. With the installation of the bluetooth driver (stack), its generates virtual comm ports which provides RS-232 serial cable emulation for Bluetooth devices as a serial cable link. In the configuration or settings menu of the device we create a connection between the client and server, for example the virtual port com42 on the device (C) is connected to the virtual port com7 on device (S). We can look at this configuration in Start->Settings->ControlPanel->Bluetooth menu of each device. We transfer data from client to server over the bluetooth connection between the two virtual ports. As mentioned earlier the driver (stack) create the basis for this connection.
A client (in bluetooth terminology a client service) creates an outbound RFCOMM connection to a server virtual serial port. A Bluetooth server uses a virtual comm port (server service) to communicate with the client. When we send data to the client serial port (for example virtual port com42), the stack software make a wireless connection to the server (for example virtual port com7). Once the client and server connect to each other, they exchange data until the client or server terminates the connection or until the connection is lost.
If we know the names of both virtual serial port (client and server), we can create a C# program to send data (messages and files of all type) from the client to the server on the RFCOMM layer. This transfer is similar to socket bytes transfer. Because we have control over both devices, we can create our own protocol for file transfer. The simplest method of file transfer is when on the client side we send bytes to the comm port and we accept the incoming bytes on the server device virtual comm port. In more elaborate protocol we first send the file length to the server, and than the file bytes. In this way on the server side we can monitor the progress of the transfer. In some cases we add also a file name in the start portion of the bytes stream. So, on the server side we now the name and location of the file to save.
In the OBEX protocol the transfer is organized in packets. The client send a connection packet to the server and wait for the OK response. Than it sends a put packet with file length and name. When the server response with continue packet, the client send the next packet. It sends the last bytes of the file in the put packet with the final bit (0x82). In this article we shell show you how to create OBEX packet in C#, how to send and receive them and how to monitor port bytes traffic when developing and testing the application. In Microsoft Visual Studio 2005 we will create four projects: three file transfer clients and the file transfer server. Firstly we present a simple client project which send a connect OBEX packet and receive an OK packet. In the second project we shall extend the project functions to the client able of sending small files and finally the client for sending files of all types. The sources of all projects are downloadable.

Creating an OBEX packet in C#

               

The OBEX packet is an array of bytes composed of headers and data. Same headers byte are in Listing 1.

               

private byte obex_conn = (byte)0x80;   

private byte obex_disconn = (byte)0x81;

private byte obex_put = (byte)0x02;

private byte obex_put_final_bit = (byte)0x82;

private byte obex_ok = (byte)0xA0;

private byte obex_continue = (byte)0x90;

private byte obex_body = (byte)0x48;

private byte obex_end_body = (byte)0x49;

private byte obex_version = (byte)0x10;

private byte obex_conn_flags = (byte)0x00;

private byte obex_name = (byte)0x01;

private byte obex_length = (byte)0xC3;

               

Listing 1. Headers bytes used in OBEX protocol

               

               

//mPacketSize is the maximum packet size allowed

public byte[] ConnectPacket(Int16 mPacketSize)

        {

            MemoryStream ms = new MemoryStream();

            byte[] bufxx = new byte[] { obex_conn };  //first byte identify the packet

            ms.Write(bufxx, 0, 1);

            bufxx = BitConverter.GetBytes((UInt16)(7));  //the length of the packet

            ms.Write(bufxx, 0, 2);

               

            bufxx = new byte[] { obex_version };

            ms.Write(bufxx, 0, 1);

            bufxx = new byte[] { obex_conn_flags };

            ms.Write(bufxx, 0, 1);

               

            bufxx = BitConverter.GetBytes((Int16)mPacketSize);  // The number Int16 is

            ms.Write(bufxx, 0, 2);                                                    // converted in two bytes.

            ms.Position = 0;

            return ms.ToArray();

        }

               

Listing 2.  The method that generate an array of bytes – the connect packet

 

 

 

In  Listing 2 is an example of the program which create a connect packet (byte array).  We create a memory stream (MemoryStream ms = new MemoryStream();) and a one dimensional byte array (            byte[] bufxx = new byte[] { obex_conn };  ) in which we store the byte 0x80 (obex connect header). We write the bufxx byte array to the memory stream (            ms.Write(bufxx, 0, 1);). The packet length is 7 bytes. The command ((Int16)(7)) parse this integer to Int16, so the function  (BitConverter.GetBytes((Int16)(7))) give us a two byte array bufxx. We write it to the memory stream. The same we do with the maximum packet length. Two one byte arrays with the obex version (0x10) and connection flags are also written to the memory stream. The method return a byte array so the memory stream is converted to it with the  function (ms.ToArray()).

               

public byte[] SuccessConnectPacket(Int16 mPacketSize)

        {

            MemoryStream ms = new MemoryStream();

            byte[] bufxx = new byte[] { obex_ok };

            ms.Write(bufxx, 0, 1);

            bufxx = BitConverter.GetBytes((Int16)(7));

            ms.Write(bufxx, 0, 2);

               

            bufxx = new byte[] { 0x10 };

            ms.Write(bufxx, 0, 1);

            bufxx = new byte[] { obex_conn_flags };

            ms.Write(bufxx, 0, 1);

               

            bufxx = BitConverter.GetBytes((Int16)mPacketSize);

            ms.Write(bufxx, 0, 2);

            ms.Position = 0;

            return ms.ToArray();

        }

               

Listing 3. SuccessConnectPacket that the server send as a response of the connect packet

               

Listing 3 show the program that create the packet that the server send as a response to the connect packet.

               

               

//bufferx is a byte array, in it we read  the received bytes. numR is the number of bytes received (read).

public int DecodeReceivedA0(byte[] bufferx, int numR)

        {

            int maxPacketSize = 0;

            try

            {

                //receive connect packet  A0     //   if (bufferx[0] == (byte)0xA0)

                byte[] buffer1 = new byte[2];

                int    iix = 4;

                while (iix < 6)

                {

                    buffer1.SetValue(bufferx.GetValue(iix+1 ), iix-4);

                    iix += 1;

                }

                maxPacketSize = (int)BitConverter.ToInt16(buffer1, 0);

            }

            catch (Exception ex)

            {

                this.error="Error in response packet." + ex.Message.ToString();

                return 0;

            }

            return maxPpacketSize;

        }

Listing 4.  The method for decoding the received packet (byte array).

               

When the server or the client receive a packet, they must decode it: read the information in it. Listing 4. show a method for decoding the packet. We read the received bytes in byte array named bufferx. The number of received bytes is numR (numR is less or equal bufferx length). After reading the bytes in the byte array, we extract the first byte. If its value is 0xA0, we know, that this is a response to a connect packet and with the DecodeReceivedA0 method we extract the data from it: packet length, OBEX version; maximum packet size. Decoding the packet we obtain two information: the packet first byte 0xA0, that tell the client that it is connected to the server and the maximum packet size allowed in the transfer process.

               

Those and other methods for creating OBEX packet are in the TransferSerial class of the project.

Sending and receiving packets

               

Let us see how we send and receive OBEX packet in C#. For example, we send a connect packet and receive a response (ok) packet.

               

private void sendobex_Click(object sender, EventArgs e)

        {

            this.monitor.Text = "";

         

            this.lblMessage.Text = "Connecting...";

            this.tra = new TransferSerial();

            if (!tra.Connect(this.serialPort1)) return;

            this.com.Text = this.serialPort1.PortName.ToString();

            this.serialPort1.Write(tra.conn, 0, tra.conn.Length); //write the Connect packet

            this.Invoke(new LabelWriteByte(this.LogByte), tra.conn, tra.connLength);

            if (!tra.ConnectResponse(this.serialPort1))

            {

                this.lblMessage.Text = tra.ErrorMes();

                return; //didn't receive ok packet from server

            }

            this.Invoke(new LabelWriteByte(this.LogByte), tra.conn, tra.connLength);

            this.lblMessage.Text = "Connected.";

            this.disconnected = false;

            //response connect ok

            this.confirm = true;

             }

      

Listing 5. A Click event send a connect packet and receive an OK from the server

               

In Listing 5. is called a Connect method to a serial port (!tra.Connect(this.serialPort1)) that generate a byte array conn in which we store the put packet created with the method in Listing 6. The command this.serialPort1.Write(tra.conn, 0, tra.conn.Length)

write the array (packet) to the selected comm port serialPort1 stream and the bluetooth stack software transfer this bytes to the server port. The command

tra.ConnectResponse(this.serialPort1)

says the system to listen on port serialPort1 for the response from server. The function decode the received packet and  return true if the server response with a OK response packet (Listing 7).

               

public bool Connect(System.IO.Ports.SerialPort serialport1)

        {

            if (serialport1.PortName == "COM0")

            {

                MessageBox.Show("Select comm port!");

                return false;

            }

            if (!this.OpenSerialPort(serialport1)) return false; //open a serial port

            try

            {

                this._conn = this.ConnectPacket(); //create a connect packet

                this._connLength = this._conn.Length;

            }

            catch (Exception ex)

            {

                MessageBox.Show("Can not connect to remote service. (Remote server not listen?)." + this.ErrorMes() + ex.Message.ToString());

                return false;

            }

            return true;

        }.

Listing 6. Connect method

               

The Connect method (Listing 6) open a selected comm port and call the ConnectPacket metod (Listing 2.), that generate the put packet and store its bytes in a byte array. It returns also a put packet length (connLength).

               

//read response first 6 byte from connect packet

        public bool ConnectResponse(System.IO.Ports.SerialPort serialport1)

        {

            if (!this.ReadResponsePacket6(serialport1))  //read response from server

            {

                MessageBox.Show("Did not received response Ok from server.");

                return false;

            }

            return true;

        }

Listing 7. ConnectResponse method that wait on  port serialport1 the response from the server     

               

The ConnectResponse method (Listing 7) wait on  port serialport1 the response from the server.

               

private bool ReadResponsePacket6(System.IO.Ports.SerialPort serialport1)

        {

          while (serialport1.BytesToRead < 3) Thread.Sleep(10);

            byte[] ComBuffer1 = new byte[7];

            int lenRead = serialport1.Read(ComBuffer1, 0, 7);

           //extract the packet length (byte 2 and 3 of the readed bytes)

           byte[] bufferPacket = new byte[2];

            int iix = 0;

            while (iix < 2)

            {

                bufferPacket.SetValue(ComBuffer1.GetValue(iix + 1), iix);

                iix += 1;

            }

            int packetLenToRead  = ((int)BitConverter.ToInt16(bufferPacket, 0)) ;

            this._connLength = lenRead; //return the packet length to the calling procedure

            if (packetLenToRead  == 3 && ComBuffer1[0] == obex_ok)  // ok without max packet length

              {

                 this._conn = ComBuffer1;

                 return true;

              }

            if (lenRead < 6)

              {

                    lenRead = serialport1.Read(ComBuffer1, 3, 4);

                    this._connLength = 3 + lenRead; //return to the calling procedure

                }

                else

                {

                    this._connLength = lenRead;

                }

            if (ComBuffer1[0] == obex_ok)

            {

                //client listen response from server

                int pr = this.DecodeReceivedA0(ComBuffer1, 6);

                if (pr > 0)  //max packet length >0

                {

                    this.mPacketSize = Convert.ToInt16(pr);  //max packet size

          //read the remaining bytes of the packet

                    if (packetLenToRead > 7)

                    {

                        lenRead = serialport1.Read(ComBuffer1, 7, packetLenToRead - 6);

                        this._connLength += lenRead;

                    }

                    this._conn = ComBuffer1; //return to the calling procedure

                    return true;

                }

                else

                {

                    MessageBox.Show("Not connected. Error in response packet.Try again.");

                    return false;

                }

            }

            else

            {

                return false;

            }

        }

 Listing 8. A method for reading and decoding the received packet.

               

The ReadResponsePacket6 method read the port stream and decode its first 3 (6) bytes (Listing 8).   The command   bufferPacket.SetValue(ComBuffer1.GetValue(iix + 1), iix);

write the byte with index iix+1 of the byte array  ComBuffer1 to the byte with the index iix of the byte array  bufferPacket and the command ((int)BitConverter.ToInt16(bufferPacket, 0)) convert the two byte array to an integer – the packet length. If the lenRead is less than 6 and packet length is grater than 3, we read the remaining bytes (lenRead = serialport1.Read(ComBuffer1, 3, 4);). In the line int pr = this.DecodeReceivedA0(ComBuffer1, 6); we decode the first 6 bytes and from this we extract the maximum packet length.

Later, we shall explain the delegate LabelWriteByte.

The log files and the port monitor

In the development and test phase of the project we register the process steps in the log file. We will also write the bytes traffic (sent and received) on comm port to the file logByte and monitor this traffic in the text box named 'monitor' on the form.

               

// logByteFileName is the name of the log byte file, for example C:\myByteLog.txt

private void LogByte(byte[] bWrite, int byteLen)

        {

  if (fLogByte == null) fLogByte = new FileStream(logByteFileName, FileMode.Create, FileAccess.Write);

            fLogByte.Write(bWrite, 0, byteLen);

            //Only in the test phase. Write the byte array in hexadecimal format

            string hexString = newLine + newLine; //separete with a blank line

            int jj = 0;

            while (jj < byteLen)

            {

                hexString += String.Format("{0:X2}", bWrite[jj]);  //write the bytes in string format

                if (hexString.Length > 80)      //the nunmer of columns don't excede 80.

                {

                    this.monitor.Text += hexString;

                    hexString = "";

                }

                jj += 1;

            }

            if (hexString.Length > 0)

            {

                this.monitor.Text += hexString;   //write the string to the monitor textbox.

            }

        }

Listing 9. A method for writing a byte array to the file and textbox on the form.

               

On every read or write of bytes on the comm port, we write the bytes to the logByte file and monitor text box. This operation is executed with the form invoke method  and the delegate LabelWriteByte (Listing 9). For example the command

this.Invoke(new LabelWriteByte(this.LogByte), ComBuffer1, lenRead)

use the delegate LabelWriteByte to call the logByte method to write the first lenRead bytes of the ComBuffer1 byte array to the file and textbox.

Let us look at the command in the logByte method:

                hexString += String.Format("{0:X2}", bWrite[jj]);  //write the bytes in string format

The string  hexString show us a byte array  in hexdecimal format (as we see the bytes in the file binary editor). 

 

Figure 1. Bytes traffic on comm port sending a file of small size.

In the simple project SimpleTranConnect we use the methods described till now. It  can be downloaded. In the Figure 1. are presented the visual controls used in the project. We add to it also the serial port component that is not visible. In the textbox we see the hex representation of the bytes traffic on the comm port. The first sequence of bytes (80070010000004) is the connect packet sent to the server. Its length is 7 bytes. The response packet  is the second sequence (A0070010000004) etc. The last sequence of bytes (810300) is an disconnect packet with the length of 3 bytes. If you would like to test it, you have to install the FileTranServer  (see the download).

Sending files of small size

When the file is small enough to put it in one packet, we send the file in one put packet. The body header of this packet is 0x82. Normally the body header is byte 0x02, only the body header of  the last packet is 0x82. When the server receive the put packet with body header 0x02 it respond with the continue packet and  when it receives body header 0x82, it respond with OK packet with the header 0xA0.

               

private void sendobex_Click(object sender, EventArgs e)

        {

            this.monitor.Text = "";

            this.progressBar1.Value = 0;

            this.lblMessage.Text = "Connecting...";

            this.tra = new TransferSerial();

            if (!tra.Connect(this.serialPort1)) return;

            this.com.Text = this.serialPort1.PortName.ToString();

            this.serialPort1.Write(tra.conn, 0, tra.conn.Length); //write the Connect packet

            this.Invoke(new LabelWriteByte(this.LogByte), tra.conn, tra.connLength);

            if (!tra.ConnectResponse(this.serialPort1))

            {

                this.lblMessage.Text = tra.ErrorMes();

                return; //didn't receive ok packet from server

            }

            this.Invoke(new LabelWriteByte(this.LogByte), tra.conn, tra.connLength);

            this.lblMessage.Text = "Connected.";

            file = tra.FileStreamToSend(this.destination.Text.Trim());

            if (file == null) return;

            this.fileName = tra.fileName;

            this.destName = this.destination.Text.Trim();

            //response connect ok

            try

            {

                //receive connect packet  A0  and send put    //   if (bufferx[0] == (byte)0xA0)

                tra.CreatePutPacketAndSend(serialPort1, this.destName, this.fileName, this.file, this.streamRead);

                this.serialPort1.Write(tra.conn, 0, tra.connLength);

                streamRead = streamRead + tra.chunkReadFile;

                if (file.Length - streamRead > 0) this.transfering = true;

            }

            catch (Exception ex)

            {

                MessageBox.Show("Error:" + ex.Message.ToString());

            }

               

               

Listing  10. The button click event for sending small files.

               

               

In Listing 10  we try to connect to the server with the click on the button. If the connection (tra.Connect(this.serialPort1) is successful, we create the file stream (file) of the file we wish to send (  file=this.FileStreamToSend()). The method  tra.CreatePutPacketAndSend  create the put packet.  We send it with                this.serialPort1.Write(tra.conn, 0, tra.connLength);

 If the file length in bytes is smaller or equal to bytes read, we call the DisconetService method, otherwise the control of the program execution is passed to the serial port datareceived event.

               

public byte[] PutPacket(string destName, string fileName, FileStream file, int streamRead)

        {

            // int chunk = 1024;

            int chunk = this.packetSize-nameF.Length-16;

//calculate the chunk of file bytes to read

            if (file.Length < this.packetSize -nameF.Length-16) chunk = (int)file.Length;

            if (file.Length - streamRead < chunk) chunk = (int)file.Length - (int)streamRead;

            //read from file

            byte[] fiReadChunk = new byte[chunk];

            int      chunkR= file.Read(fiReadChunk, 0, chunk);

            byte[] nameF = new UnicodeEncoding().GetBytes(destName);

//in the byte array nameF we store the destination file name

            //sum of the header length (in revers order). Look at bufxx.Write!

            int packetLength = (1 + chunkR + 2 + 1) + (4 + 1) + (2 + nameF.Length + 2 + 1) + 2;

// chunkR+ nameF.Length+16

            MemoryStream ms = new MemoryStream();

            byte[] bufxx;

            if (file.Length - streamRead - chunkR <= 0)

            {

                bufxx = new byte[] { obex_put_final_bit };

            }

            else

            {

                bufxx = new byte[] { obex_put };

            }

            ms.Write(bufxx, 0, 1);

            //packet length       

            bufxx = BitConverter.GetBytes((UInt16)(packetLength));

            ms.Write(bufxx, 0, 2);

            bufxx = new byte[] { obex_version };

            ms.Write(bufxx, 0, 1);

            //name header

            bufxx = new byte[] { obex_name };

            ms.Write(bufxx, 0, 1);

            bufxx = BitConverter.GetBytes((UInt16)(nameF.Length + 3 + 2));

            ms.Write(bufxx, 0, 2);

            ms.Write(nameF, 0, nameF.Length);

            //null terminated

            bufxx = new byte[] { 0x00, 0x00 };

            ms.Write(bufxx, 0, 2);

            //  object length header

            bufxx = new byte[] { obex_length };

            ms.Write(bufxx, 0, 1);

            bufxx = BitConverter.GetBytes((file.Length));

            ms.Write(bufxx, 0, 4);

            //body header

            if (file.Length - streamRead - chunkR == 0)

            {

                bufxx = new byte[] { this.obex_end_body };

            }

            else

            {

                bufxx = new byte[] { obex_body };

            }

            ms.Write(bufxx, 0, 1);

           // bufxx = BitConverter.GetBytes((UInt16)(fiReadChunk.Length + 3));

            bufxx = BitConverter.GetBytes((UInt16)(chunkR + 3));

            ms.Write(bufxx, 0, 2);

            //  bufxx = fiReadChunk;

            ms.Write(fiReadChunk, 0, chunkR);

            //END of Body

            ms.Position = 0;

           this._chunkReadFile = chunkR;

            return ms.ToArray();

        }

       

Listing11. The C# program that create the put packet

               

The central  part of the method CreatePutPacketAndSend is the method PutPacket (Listing 11)  of the class TransferSerial. This method create a put packet with header 0x02 or 0x82 for the last packet. With the put packet we send the file data and other information.  It contains also the headers and the data for the file length, destination file name and the packet size. In the last phase of the procedure (Listing 11) we create  the body header and the file data that we read from the file stream. With the variable streamRead we control the portion of the read file stream. Usually the last put packet is of smaller size. In it we put the remaining bytes to send. We send the packet bytes stream (conn) to the comm port with the command  serialPort2.Write(conn, 0, conn.Length).

In Figure 1. we see the sent and received bytes on the virtual serial port in hexadecimal format. There are the results of the connect, transfer and disconnect process sending the file with contents »ABCDANDABCD«. Its hexadecimal representation is »41424344414E4441424344«. The destination file name is c:\jumar.txt. The code for this project is in the downloadable file SimpleTranFile.zip.

Sending files of larger size - multiple put packet

When the file is of larger size, we send it in a sequence of put packets. How we send the first packet is described in the previsious  section.

               

private serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)

        {

            if (this.transfering)

            {

                //Receiving packet from server and sending put packet

                bool cont =tra.Receive(this.serialPort1 );

                if (this.disconnected)

                {

                    this.Invoke(new LabelWrite(this.LabelMes), ">>Client received success 0xA0 when at end of file and disconnected. File length:" + this.file.Length.ToString() + " read:" + this.streamRead.ToString());

                }

                else

                {

                    this.Invoke(new LabelWrite(this.LabelMes), ">>Client received success 0xA0 when at end of file. ");

                }

                 this.Invoke(new LabelWriteByte(this.LogByte), tra.conn , tra.connLength);

                if (!cont) 

                {

                    this.Invoke(new LabelWrite(this.LabelMes), "End file transfer." );

                  this.DisconnectService();

                    return; //didn't receive a continue packet

                }

                 //1) received confirm of connection 80 or put packet send

      //          this.confirm = true;

                this.Invoke(new LabelWrite(this.LabelMes), ">>Client recceive continue (packet 0x90) and send new put packet." + this.file.Length.ToString() + " read:" + this.streamRead.ToString());

                tra.CreatePutPacketAndSend(this.serialPort1 ,this.destName, this.fileName, this.file, this.streamRead);

                this.serialPort1.Write(tra.conn, 0, tra.connLength);

                streamRead = streamRead + tra.chunkReadFile;

                this.Invoke(new LabelWriteByte(this.LogByte), tra.conn, tra.connLength);

            }

        }

               

Listing 12. The serial port DataReceived event

               

The second and next packets traffic  are controlled by the event  serialPort1_DataReceived (Listing 12). We read the bytes received on the comm port with the method Receive(), which return a boolean value cont (inue). If the value is true (we receive at the comm port the packet continue), we send the next packet, otherwise we terminate the transfer.

The method CreatePutPacketAndSend create a put packet and store it in a byte array conn. We write this array to the comm port (we send it to the server). The event serialPort1_DataReceived wait for the next received byte. (With the modification of the presented programs in this method you can control also the connect and first put packets).

Conclusion

               

To improve performance in the production code of your file transfer project, you should exclude the monitor part of the presented programs. You should build the client and the server function in the same project to have the possibility to send and receive files on booth devices. We separate the functions for pedagogical reason.

We don't comment the server side project FileTranServer. In it we use the same technique as in the client project.

It is possible to hide the complexity of encoding,  decoding and transferring packet over bluetooth serial ports in helper class and we can transfer and receive files with only a few line of code as is shown in the events  sendobex_Click  and serialPort1_DataReceived.

   

Downloads     

 

 

 SimpleTranConnect.zip   ,  SimpleTranFile.zip ,     FileTranClient.zip ,  FileTranServer.zip

 

Internet Links

OBEX. Transfer files between a Pocket PC and a Palm or a Symbian device

By Viraj Chatterjee, July 24, 2002.

IrDA INTEROPERABILITY

Elements of the OBEX protocol.

               

A Simple Guide To Mobile Phone File Transferring
By hesicong

Reading binary data in C#

An Introduction to Bluetooth programming in GNU/Linux

Albert Huang

SerialPort (RS-232 Serial COM Port) in C# .NET

Noah Coad