简体   繁体   English

Xamarin-Android和UWP之间的蓝牙连接

[英]Bluetooth connection between Xamarin-Android and UWP

For quite a lot of time I struggled with finding a working solution to connect an android device and an UWP application (on PC) with either an IP connection or bluetooth. 在相当长的时间内,我一直在努力寻找一种可行的解决方案,以通过IP连接或蓝牙连接android设备和UWP应用程序(在PC上)。 The main issue is to find a set of code or example which is simple enough to get into but is guaranteed to work (so that my efforts are not futile, which was the case for more than a week now). 主要问题是找到一套简单易行但可以保证正常工作的代码或示例(这样我的工作就不会徒劳,现在已经有一个多星期了)。

What is clear is that there is no possibility for a "code pair" (as of client-server), as the libraries used and the way the code structures are built must be vastly different. 清楚的是,“代码对”是不可能的(从客户端到服务器),因为所使用的库和代码结构的构建方式必须有很大的不同。 A further problem is that bluetooth does not seem to allow loopback connections, which causes even more issues with testing. 另一个问题是蓝牙似乎不允许环回连接,这会导致更多的测试问题。 Another issue is possibly outdated example projects. 另一个问题可能是过时的示例项目。 Also it's hard to find xamarin/c# solutions, and I didn't want to get into Android Studio and Java (my project is the UWP one, the android part is just for testing). 另外,很难找到xamarin / c#解决方案,而且我不想进入Android Studio和Java(我的项目是UWP项目,android部分仅用于测试)。 These are simply too many layers of difficulties to get into for me. 这些对我来说简直是太多困难。

The goal for now (and thus also my question asking for help) is a basic operation: 目前的目标(因此也是我寻求帮助的问题 )是一项基本操作:

  • Send a plain message or datastream from Xamarin-Android (as client) to UWP (as server) and have a response for receiving it - via bluetooth. 从Xamarin-Android(作为客户端)向UWP(作为服务器)发送纯文本消息或数据流,并通过蓝牙进行接收。

Let's ignore device searching now (if possible), let's just use IP/MAC address directly. 现在让我们忽略设备搜索(如果可能),我们直接使用IP / MAC地址。 From there on everything should fall into place. 从那里开始,一切都应该放到位。 All necessary capabilities/declarations are set and devices are paired. 设置所有必要的功能/声明并配对设备。

I would be really grateful for any help. 我将非常感谢您的帮助。

I found out the solution myself, so here is how it went: 我自己找到了解决方案,所以它是这样的:

First of all, remember to define all necessary declarations and capabilities for bluetooth. 首先,请记住为蓝牙定义所有必要的声明和功能。 This will explicitly focus on the code part. 这将明确地集中在代码部分。

For the Xamarin/Android client part. 对于Xamarin / Android客户端部分。 The website which was really helpful is this one . 真正有用的网站就是这个 Also try out the quite well known chat sample for Xamarin. 还可以尝试Xamarin的著名聊天示例 CreateMessage is a method to create debug messages on the local device which can be displayed. CreateMessage是一种在本地设备上创建可以显示的调试消息的方法。 I kept it very simple, because my project is primarily about the UWP part. 我使它非常简单,因为我的项目主要是关于UWP部分的。 All of this was enclosed in a try { } catch { } clause, but I leave it out now due to having even more indentations and brackets. 所有这些都包含在try { } catch { }子句中,但是由于具有更多的缩进和括号,我现在将其省略。

using Java.Util;
using System.Text;
using System.IO;
using Android.Runtime;
using System.Threading.Tasks;

TestClass
{
    // The UUIDs will be displayed down below if not known.
    const string TARGET_UUID = "00001105-0000-1000-8000-00805f9b34fb";
    BluetoothSocket socket = null;
    OutputStreamInvoker outStream = null;
    InputStreamInvoker inStream = null;

    void Connect ()
    {
        BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter;
        if (adapter == null) CreateMessage ("No Bluetooth adapter found.");
        else if (!adapter.IsEnabled) CreateMessage ("Bluetooth adapter is not enabled.");

        List<BluetoothDevice> L = new List<BluetoothDevice> ();
        foreach (BluetoothDevice d in adapter.BondedDevices)
        {
            CreateMessage ("D: " + d.Name + " " + d.Address + " " + d.BondState.ToString ());
            L.Add (d);
        }

        BluetoothDevice device = null;
        device = L.Find (j => j.Name == "PC-NAME");

        if (device == null) CreateMessage ("Named device not found.");
        else
        {
            CreateMessage ("Device has been found: " + device.Name + " " + device.Address + " " + device.BondState.ToString ());
        }

        socket = device.CreateRfcommSocketToServiceRecord (UUID.FromString (TARGET_UUID));
        await socket.ConnectAsync ();

        if (socket != null && socket.IsConnected) CreateMessage ("Connection successful!");
        else CreateMessage ("Connection failed!");

        inStream = (InputStreamInvoker) socket.InputStream;
        outStream = (OutputStreamInvoker) socket.OutputStream;

        if (socket != null && socket.IsConnected)
        {
            Task t = new Task (() => Listen (inStream));
            t.Start ();
        }
        else throw new Exception ("Socket not existing or not connected.");
    }
}

Now we enter the part with the bytes and pain. 现在,我们输入带有字节和痛苦的部分。 I used this format to transmit messages: [4 bytes of uint for message length] [1 byte per character] . 我使用这种格式来传输消息: [4 bytes of uint for message length] [1 byte per character] What is important is that you use the same byte to uint conversion, because the order of bytes or how it went in general had differences in the UWP specific methods. 重要的是,您使用相同的字节到uint的转换,因为字节顺序字节顺序在UWP特定方法中通常有所不同。 If your word length is not what it is supposed to be (instead of ~23 something like 3000000+), that is a problem. 如果您的单词长度不是预期的长度(而不是大约23个类似3000000+的单词),那就是个问题。 Reading bytes which do not exist (yet) can mean exceptions or even merciless crashes despite using try { } catch { } clauses. 即使使用try { } catch { }子句,读取尚不存在的字节也可能意味着异常,甚至意味着无情的崩溃。

The following method sends the message in the format mentioned above. 下面的方法以上述格式发送消息。 As said, it is among the most simple ways to do this, so I won't mention how things can be done better . 如前所述,这是执行此操作的最简单方法之一,因此我不会提及如何更好地完成工作。

private async void SendMessage (string message)
{
    uint messageLength = (uint) message.Length;
    byte[] countBuffer = BitConverter.GetBytes (messageLength);
    byte[] buffer = Encoding.UTF8.GetBytes (message);

    await outStream.WriteAsync (countBuffer, 0, countBuffer.Length);
    await outStream.WriteAsync (buffer, 0, buffer.Length);
}

Usage: Run method 1, and then method 2. You can also do a SendMessage within method 1 at the end (when it is already connected). 用法:先运行方法1,然后运行方法2。您也可以在方法1的最后(已连接时)执行SendMessage。

Now to the part about listening for messages/responses. 现在到有关侦听消息/响应的部分。 In the first method you will see this one was run via a Task, so that it does not block the method it is started it. 在第一种方法中,您将看到此方法是通过任务运行的,因此它不会阻止启动它的方法。 Maybe there are Xamarin/Android specific ways to solve that, but it does not matter to me, so I simply circumvented that. 也许有Xamarin / Android特定的方法可以解决此问题,但这对我来说并不重要,所以我只是绕过了这一点。

private async void Listen (Stream inStream)
{
    bool Listening = true;
    CreateMessage ("Listening has been started.");
    byte[] uintBuffer = new byte[sizeof (uint)]; // This reads the first 4 bytes which form an uint that indicates the length of the string message.
    byte[] textBuffer; // This will contain the string message.

    // Keep listening to the InputStream while connected.
    while (Listening)
    {
        try
        {
            // This one blocks until it gets 4 bytes.
            await inStream.ReadAsync (uintBuffer, 0, uintBuffer.Length);
            uint readLength = BitConverter.ToUInt32 (uintBuffer, 0);

            textBuffer = new byte[readLength];
            // Here we know for how many bytes we are looking for.
            await inStream.ReadAsync (textBuffer, 0, (int) readLength);

            string s = Encoding.UTF8.GetString (textBuffer);
            CreateMessage ("Received: " + s);
        }
        catch (Java.IO.IOException e)
        {
            CreateMessage ("Error: " + e.Message);
            Listening = false;
            break;
        }
    }
    CreateMessage ("Listening has ended.");
}

This was only half the work. 这只是工作的一半。 For the UWP server part, I will simply post my current code, which is way more clean and requires no editing for this. 对于UWP服务器部分,我将简单地发布当前代码,这更简洁,并且不需要对此进行编辑。

using System;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth.Rfcomm;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace BT
{
    public sealed class BluetoothConnectionHandler
    {
        RfcommServiceProvider provider;
        bool isAdvertising = false;
        StreamSocket socket;
        StreamSocketListener socketListener;
        DataWriter writer;
        DataReader reader;
        Task listeningTask;

        public bool Listening { get; private set; }
        // I use Actions for transmitting the output and debug output. These are custom classes I created to pack them more conveniently and to be able to just "Trigger" them without checking anything. Replace this with regular Actions and use their invoke methods.
        public ActionSingle<string> MessageOutput { get; private set; } = new ActionSingle<string> ();
        public ActionSingle<string> LogOutput { get; private set; } = new ActionSingle<string> ();

        // These were in the samples.
        const uint SERVICE_VERSION_ATTRIBUTE_ID = 0x0300;
        const byte SERVICE_VERSION_ATTRIBUTE_TYPE = 0x0a; // UINT32
        const uint SERVICE_VERSION = 200;

        const bool DO_RESPONSE = true;

        public async void StartServer ()
        {
            // Initialize the provider for the hosted RFCOMM service.
            provider = await RfcommServiceProvider.CreateAsync (RfcommServiceId.ObexObjectPush);

            // Create a listener for this service and start listening.
            socketListener = new StreamSocketListener ();
            socketListener.ConnectionReceived += OnConnectionReceived;
            await socketListener.BindServiceNameAsync (provider.ServiceId.AsString (), SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);

            // Set the SDP attributes and start advertising.
            InitializeServiceSdpAttributes (provider);
            provider.StartAdvertising (socketListener);
            isAdvertising = true;
        }

        public void Disconnect ()
        {
            Listening = false;
            if (provider != null) { if (isAdvertising) provider.StopAdvertising (); provider = null; } // StopAdvertising relentlessly causes a crash if not advertising.
            if (socketListener != null) { socketListener.Dispose (); socketListener = null; }
            if (writer != null) { writer.DetachStream (); writer.Dispose (); writer = null; }
            if (reader != null) { reader.DetachStream (); reader.Dispose (); reader = null; }
            if (socket != null) { socket.Dispose (); socket = null; }
            if (listeningTask != null) { listeningTask = null; }
        }

        public async void SendMessage (string message)
        {
            // There's no need to send a zero length message.
            if (string.IsNullOrEmpty (message)) return;

            // Make sure that the connection is still up and there is a message to send.
            if (socket == null || writer == null) { LogOutput.Trigger ("Cannot send message: No clients connected."); return; } // "No clients connected, please wait for a client to connect before attempting to send a message."

            uint messageLength = (uint) message.Length;
            byte[] countBuffer = BitConverter.GetBytes (messageLength);
            byte[] buffer = Encoding.UTF8.GetBytes (message);

            LogOutput.Trigger ("Sending: " + message);

            writer.WriteBytes (countBuffer);
            writer.WriteBytes (buffer);

            await writer.StoreAsync ();
        }



        private void InitializeServiceSdpAttributes (RfcommServiceProvider provider)
        {
            DataWriter w = new DataWriter ();

            // First write the attribute type.
            w.WriteByte (SERVICE_VERSION_ATTRIBUTE_TYPE);

            // Then write the data.
            w.WriteUInt32 (SERVICE_VERSION);

            IBuffer data = w.DetachBuffer ();
            provider.SdpRawAttributes.Add (SERVICE_VERSION_ATTRIBUTE_ID, data);
        }

        private void OnConnectionReceived (StreamSocketListener listener, StreamSocketListenerConnectionReceivedEventArgs args)
        {
            provider.StopAdvertising ();
            isAdvertising = false;
            provider = null;
            listener.Dispose ();
            socket = args.Socket;
            writer = new DataWriter (socket.OutputStream);
            reader = new DataReader (socket.InputStream);
            writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
            reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
            //StartListening ();
            LogOutput.Trigger ("Connection established.");
            listeningTask = new Task (() => StartListening ());
            listeningTask.Start ();
            // Notify connection received.
        }

        private async void StartListening ()
        {
            LogOutput.Trigger ("Starting to listen for input.");
            Listening = true;
            while (Listening)
            {
                try
                {
                    // Based on the protocol we've defined, the first uint is the size of the message. [UInt (4)] + [Message (1*n)] - The UInt describes the length of the message.
                    uint readLength = await reader.LoadAsync (sizeof (uint));

                    // Check if the size of the data is expected (otherwise the remote has already terminated the connection).
                    if (!Listening) break;
                    if (readLength < sizeof (uint))
                    {
                        Listening = false;
                        Disconnect ();
                        LogOutput.Trigger ("The connection has been terminated.");
                        break;
                    }

                    uint messageLength = reader.RReadUint (); // 

                    LogOutput.Trigger ("messageLength: " + messageLength.ToString ());

                    // Load the rest of the message since you already know the length of the data expected.
                    readLength = await reader.LoadAsync (messageLength);

                    // Check if the size of the data is expected (otherwise the remote has already terminated the connection).
                    if (!Listening) break;
                    if (readLength < messageLength)
                    {
                        Listening = false;
                        Disconnect ();
                        LogOutput.Trigger ("The connection has been terminated.");
                        break;
                    }

                    string message = reader.ReadString (messageLength);
                    MessageOutput.Trigger ("Received message: " + message);
                    if (DO_RESPONSE) SendMessage ("abcdefghij");
                }
                catch (Exception e)
                {
                    // If this is an unknown status it means that the error is fatal and retry will likely fail.
                    if (SocketError.GetStatus (e.HResult) == SocketErrorStatus.Unknown)
                    {
                        Listening = false;
                        Disconnect ();
                        LogOutput.Trigger ("Fatal unknown error occurred.");
                        break;
                    }
                }
            }
            LogOutput.Trigger ("Stopped to listen for input.");
        }
    }
}

Usage is the following: 用法如下:

  1. Create an instance of BluetoothConnectionHandler. 创建BluetoothConnectionHandler的实例。
  2. Set up the MessageOutput and/or LogOutput (read the comment in the code regarding this). 设置MessageOutput和/或LogOutput(阅读与此相关的代码中的注释)。
  3. Run its StartServer method. 运行其StartServer方法。
  4. To send a message, use its SendMessage method. 要发送消息,请使用其SendMessage方法。

The extension method for the RReadUint: RReadUint的扩展方法:

public static uint RReadUint (this DataReader reader)
{
    uint a = 0;
    byte[] x = new byte[sizeof (uint)];
    reader.ReadBytes (x);
    a = BitConverter.ToUInt32 (x, 0);
    return a;
}

This should contain everything needed to do what I asked for... in hintsight I see there was no simple answer possible. 它应该包含完成我所要求的一切……在hintsight中,我发现不可能有简单的答案。 From here on everything can get improved, as it is meant as the possibly most basic way to have bluetooth communication between UWP and Xamarin/Android. 从这里开始, 一切都会得到改善,因为这是在UWP和Xamarin / Android之间进行蓝牙通信的最基本方法。

In case you have questions about this, feel free to ask in the comment sections. 如果您对此有疑问,请随时在评论部分提问。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM