簡體   English   中英

Xamarin-Android和UWP之間的藍牙連接

[英]Bluetooth connection between Xamarin-Android and UWP

在相當長的時間內,我一直在努力尋找一種可行的解決方案,以通過IP連接或藍牙連接android設備和UWP應用程序(在PC上)。 主要問題是找到一套簡單易行但可以保證正常工作的代碼或示例(這樣我的工作就不會徒勞,現在已經有一個多星期了)。

清楚的是,“代碼對”是不可能的(從客戶端到服務器),因為所使用的庫和代碼結構的構建方式必須有很大的不同。 另一個問題是藍牙似乎不允許環回連接,這會導致更多的測試問題。 另一個問題可能是過時的示例項目。 另外,很難找到xamarin / c#解決方案,而且我不想進入Android Studio和Java(我的項目是UWP項目,android部分僅用於測試)。 這些對我來說簡直是太多困難。

目前的目標(因此也是我尋求幫助的問題 )是一項基本操作:

  • 從Xamarin-Android(作為客戶端)向UWP(作為服務器)發送純文本消息或數據流,並通過藍牙進行接收。

現在讓我們忽略設備搜索(如果可能),我們直接使用IP / MAC地址。 從那里開始,一切都應該放到位。 設置所有必要的功能/聲明並配對設備。

我將非常感謝您的幫助。

我自己找到了解決方案,所以它是這樣的:

首先,請記住為藍牙定義所有必要的聲明和功能。 這將明確地集中在代碼部分。

對於Xamarin / Android客戶端部分。 真正有用的網站就是這個 還可以嘗試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的轉換,因為字節順序字節順序在UWP特定方法中通常有所不同。 如果您的單詞長度不是預期的長度(而不是大約23個類似3000000+的單詞),那就是個問題。 即使使用try { } catch { }子句,讀取尚不存在的字節也可能意味着異常,甚至意味着無情的崩潰。

下面的方法以上述格式發送消息。 如前所述,這是執行此操作的最簡單方法之一,因此我不會提及如何更好地完成工作。

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 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. 要發送消息,請使用其SendMessage方法。

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

它應該包含完成我所要求的一切……在hintsight中,我發現不可能有簡單的答案。 從這里開始, 一切都會得到改善,因為這是在UWP和Xamarin / Android之間進行藍牙通信的最基本方法。

如果您對此有疑問,請隨時在評論部分提問。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM