代码之家  ›  专栏  ›  技术社区  ›  Battle

xamarin android和uwp之间的蓝牙连接

  •  0
  • Battle  · 技术社区  · 7 年前

    在相当长的一段时间里,我一直在努力寻找一个有效的解决方案,用IP连接或蓝牙连接Android设备和UWP应用程序(在PC上)。主要的问题是找到一组代码或示例,这些代码或示例足够简单,可以进入,但可以保证工作(这样我的努力就不会白费,而现在已经有一个多星期了)。

    很明显,“代码对”(如客户机-服务器)是不可能的,因为使用的库和构建代码结构的方式必须大不相同。另一个问题是蓝牙似乎不允许环回连接,这会导致更多的测试问题。另一个问题可能是过时的示例项目。此外,很难找到XAMARIN/C的解决方案,我不想进入Android Studio和Java(我的项目是UWP一个,Android部分只是为了测试)。对我来说,这些困难太多了。

    现在的目标 问题 寻求帮助)是一个基本操作:

    • 从xamarin android(作为客户端)向uwp(作为服务器)发送一条简单的消息或数据流,并对其进行响应-通过蓝牙。

    我们现在忽略设备搜索(如果可能的话),直接使用IP/MAC地址。从那里开始,一切都应该到位。设置所有必要的功能/声明,并对设备进行配对。

    如果有任何帮助,我将不胜感激。

    1 回复  |  直到 7 年前
        1
  •  1
  •   Battle    7 年前

    我自己找到了解决方案,下面是它的过程:

    首先,记住为蓝牙定义所有必要的声明和功能。这将显式地集中在代码部分。

    用于Xamarin/Android客户端部分。这个网站真的很有帮助 is this one 。还可以试试相当有名的 chat sample 为XAMARIN。 CreateMessage 是在可以显示的本地设备上创建调试消息的方法。我保持了它非常简单,因为我的项目主要是关于uwp部分。所有这些都被封闭在 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.");
        }
    }
    

    现在我们输入带有字节和疼痛的部分。我使用这种格式发送消息: [4 bytes of uint for message length] [1 byte per character] 。重要的是使用相同的字节到uint转换,因为 order of bytes 或者说,在UWP的具体方法上,它是如何发展的。如果你的单词长度不是它应该的长度(而不是大约23个,比如3000000以上),那就是个问题。读取还不存在的字节可能意味着异常,甚至是无情的崩溃,尽管使用 试试抓 条款。

    以下方法以上述格式发送消息。如前所述,这是最简单的方法之一,所以我不会提及如何做事情。 更好的 .

    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);
    }
    

    用法:先运行方法1,然后运行方法2。您还可以在方法1的末尾(当它已经连接时)执行sendmessage。

    现在我们来谈谈倾听信息/响应的部分。在第一个方法中,您将看到这个方法是通过一个任务运行的,这样它就不会阻塞启动它的方法。也许有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.");
    }
    

    这只是工作的一半。对于uwp服务器部分,我将简单地发布 现在的 代码,这就更干净了,不需要为此进行编辑。

    using System;
    using System.Text;
    using System.Threading.Tasks;
    using Windows.Devices.Bluetooth.Rfcomm;
    using Windows.Networking.Sockets;
    using DictaNet;
    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.");
            }
        }
    }
    

    用法如下:

    1. 创建BlueToothConnectionHandler的实例。
    2. 设置messageoutput和/或logoutput(阅读代码中与此相关的注释)。
    3. 运行其StartServer方法。
    4. 要发送消息,请使用其send message方法。

    这应该包含我所要求的一切…在内心深处,我看不出有什么简单的答案。从这里开始 一切 可以改进,因为这可能是UWP和Xamarin/Android之间进行蓝牙通信的最基本方式。

    如果您对此有任何疑问,请随时在评论部分提问。