7
\$\begingroup\$

I've recently started a new job and been drafted on to a project that's running late. My task was initially to add Bluetooth connectivity to a C# app, but at the request of a more senior colleague I've had to write a standalone executable to open up two Bluetooth connections and forward any data sent/received from the Bluetooth devices over two TCP connections. The Code also listens to the TCP connections and forwards messages on to the Bluetooth device.

When the Network connection is closed by the client the Bluetooth connection also dies, this was requested behaviour.

The code works as it's required to from the tests that I've done, but I don't have much experience with C# or Bluetooth. The implementation I have written spawns a few threads which I would ideally reduce, and I'm looking for ways to possibly improve reliability - although it's worked in testing I'd like to make this a bullet-proof as possible as it seems like it's going to form the crux of the product we're making.

I should add that the code uses the 32feet.Net library to find and connect to Bluetooth devices.

Main Method

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace BluetoothManager
{
class Program
{
    static void Main(string[] args)
    {

        BTManager rover_btm = new BTManager();
        BTManager base_btm = new BTManager();
        base_btm.Port = 0xba5e;
        rover_btm.Port = 17825;
        base_btm.Name = "Base";
        rover_btm.Name = "Rover";

        base_btm.match = (args.Length >= 1 && args[0] != "") ? args[0] : "baseBT";
        rover_btm.match = (args.Length >= 2 && args[1] != "") ? args[1] : "roverBT";

        if (rover_btm.match == base_btm.match)
        {
            Console.WriteLine("WARNING: The rover and base names are the same, this can be problematic");
        }

        Console.WriteLine("Base Station match: " + base_btm.match);
        Console.WriteLine("Rover match: " + rover_btm.match);
        Thread Base = new Thread(new ThreadStart(base_btm.HandleThread));
        Thread Rover = new Thread(new ThreadStart(rover_btm.HandleThread));

        Base.Start();
        Rover.Start();

        Base.Join();
        Rover.Join();

        Console.Read();

    }
}
}   

Bluetooth Manager

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Ports;
using InTheHand.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using Microsoft.Win32;
using System.IO;


namespace BluetoothManager
{
class BTManager
{
    private static BluetoothDeviceInfo[] peers;
    private BluetoothClient client;
    private bool _isConnected = false;
    private string _match;
    private const string defpin = "0000";
    private TcpListener tcpListener;
    private int _port;
    private string _name = "Not Named";
    private NetworkStream networkStream;
    private Stream bluetoothStream;
    private byte[] fromNetwork = new byte[1024];
    private byte[] fromBluetooth = new byte[1024];
    private ManualResetEvent bluetoothRecievedEvent = new ManualResetEvent(false);
    private TcpClient TcpClient;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public int Port
    {
        get { return _port; }
        set { _port = value; }
    }

    public bool IsConnected
    {
        get { return _isConnected; }
        private set { _isConnected = value; }
    }

    public string match
    {
        get { return _match; }

        set { _match = value; }
    }

    public BTManager()
    {

    }

    private void nullEverything()
    {
        if (client != null)
        {
            client = null;
        }

        if (TcpClient != null)
        {
            TcpClient.Close();
            TcpClient = null;
        }

        if (tcpListener != null)
        {
            tcpListener.Stop();
            tcpListener = null;
        }

        if (networkStream != null)
        {
            networkStream.Flush();
            networkStream.Close();
            networkStream = null;
        }

        if (bluetoothStream != null)
        {
            bluetoothStream.Flush();
            bluetoothStream.Close();
            bluetoothStream = null;
        }
    }

    public void HandleThread()
    {
        nullEverything();
        client = new BluetoothClient();
        //client.Close();
        BluetoothDeviceInfo device;
        while (!this.findDevice(out device)) ;

        Console.WriteLine("About to pair");
        int count = 0;
        int max = 5;
        while ((!(BluetoothSecurity.PairRequest(device.DeviceAddress, defpin))) && count < max)
        {
            Console.WriteLine("Pairing Failed, retrying");
            count++;
            Thread.Sleep(100);
        }

        if (count == max)
        {
            HandleThread();
        }
        else
        {
            Console.WriteLine("Paired..Beginning connect");
            client.BeginConnect(device.DeviceAddress, BluetoothService.SerialPort, this.callback, client);
        }
    }

    private void callback(IAsyncResult result)
    {
        client.EndConnect(result);

        this.tcpListener = new TcpListener(IPAddress.Loopback, _port);
        this.tcpListener.Start();
        this.TcpClient = this.tcpListener.AcceptTcpClient();
        this.networkStream = TcpClient.GetStream();
        this.bluetoothStream = client.GetStream();
        this.bluetoothStream.ReadTimeout = 10000;

        while (client.Connected && TcpClient.Connected)
        {

            try
            {
                if (networkStream.CanRead)
                {
                    Array.Clear(this.fromNetwork, 0, 1024);
                    if (networkStream.DataAvailable)
                    {
                        networkStream.Read(this.fromNetwork, 0, 1024);
                        Console.WriteLine(Encoding.ASCII.GetString(fromNetwork));
                    }

                    bluetoothStream.Write(fromNetwork, 0, 1024);
                    bluetoothStream.Flush();
                    Array.Clear(fromBluetooth, 0, 1024);
                    bluetoothRecievedEvent.Reset();
                    this.bluetoothStream.BeginRead(this.fromBluetooth, 0, 1024, this.bluetoothReceived, this.bluetoothStream);
                    if (!bluetoothRecievedEvent.WaitOne(new TimeSpan(0, 0, 0, 10)))
                    {
                        Console.WriteLine("Bluetooth Timeout Reached");
                        break;
                    }
                }


            }
            catch (Exception e)
            {
                Console.WriteLine("Either Network Connection cancelled or Bluetooth failed");
                Console.WriteLine(e.Message);
            }

        }
        client.Close();
        TcpClient.Close();
        tcpListener.Stop();
        this.HandleThread();
    }


    private void bluetoothReceived(IAsyncResult result)
    {
        try
        {
            networkStream.Write(fromBluetooth, 0, 1024);
            networkStream.Flush();
            Array.Clear(fromBluetooth, 0, 1024);
            bluetoothRecievedEvent.Set();
        }
        catch (Exception e)
        {
            Console.WriteLine("Network write operation failed");
            Console.WriteLine(e.Message);
        }
    }

    private bool findDevice(out BluetoothDeviceInfo device)
    {
        peers = client.DiscoverDevicesInRange();
        device = Array.Find(peers, element => element.DeviceName == match);

        foreach (BluetoothDeviceInfo btdi in peers)
        {
            Console.WriteLine(btdi.DeviceName);
        }


        if (device == null)
        {
            Console.WriteLine(Name +": Not Found");
            return false;
        }
        else
        {
            Console.WriteLine(Name +": Found");
            return true;
        }

    }
}
}
\$\endgroup\$
4
\$\begingroup\$

In C#, you have auto-properties, which means instead of :

private string _name;
public string Name
{
    get { return _name; }
    set { _name = value; }
}

you can use only

public string Name{get;set;}

It reduces noise in the code, I think you should use it in your BTManager class since you don't do anything particular in your getters/setters.

Also, you don't need to specify the parameterless constructor. If there is no constructor specified in C#, it will assume there is a public parameterless one.

The C# naming convention implies that method names should be in PascalCase, which means nullEverything should be NullEverything (same for all the other methods). Try to be consistent in your private field naming, some of them have underscores at the beginning, some don't. There's no official documentation specifying you should use the underscore, so pick the way you want and stick with it.

Finally, I don't know if it is because you had trouble with formatting your code here, but pay caution to the white spacing of your code!

\$\endgroup\$
3
\$\begingroup\$

The number 1024 shows up in the code a lot. Particularly in the callback method. I consider it to be a magic number. I had to scroll up and down and up again before I found this.

private byte[] fromNetwork = new byte[1024];
private byte[] fromBluetooth = new byte[1024];

This really needs to be replaced with a constant. A meaningful name will go a long way here. Consider using something like maxBytes. Whatever you choose, do not use kilobyte. It's tempting, but then you'd be encoding the value into the identifier.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.