HoloLens无法通过BT和TCP发送或接收数据

时间:2018-09-11 13:28:52

标签: c# unity3d uwp virtual-reality

我正在研究HoloLens( Unity-UWP ),并尝试与PC( UWP )或Android手机( Xamarin >)。到目前为止,我已经尝试在Android和UWP上同时使用蓝牙和TCP(甚至两个版本具有不同的库)的客户端和主机。我将代码与用户界面完全分开,以便更易于使用,理解和模块化。 Action<string>用于输出结果(错误日志和已发送的消息)。

HoloLens上所有不是的东西都可以正常工作(即使代码完全相同)。从PC(UWP)到Android,客户端和主机均已切换。但是它甚至在HoloLens和PC(UWP)之间也不起作用。行为范围从崩溃(主要是蓝牙)到即时断开。一旦将要接收字节,最后的测试导致断开连接。它甚至可以读取前4个字节(在后续的UTF-8消息的长度中为uint),但随后断开了连接。其他设备似乎工作正常。

我所知道的是:功能已设置,代码有效,问题可能与联网和HoloLens的所有操作有关。

所以问题是,Unity或HoloLens与我使用的东西不兼容吗?我所使用的值得一提的是:StreamSocket,BinaryWriter,BinaryReader,任务(异步,等待)。还是HoloLens主动阻止与其他设备上的应用程序通信?我知道它可以通过蓝牙连接到设备,并且可以通过TCP连接,并且看起来人们成功地使它能够工作。有已知问题吗?还是Unity导致此问题-可能是错误的设置?我必须使用异步方法还是仅同步?任务/线程和Unity是否存在不兼容问题? this可能是问题(无法同意许可)吗?

要注意的另一件事是,即使IP地址正确,我也无法使用cmd通过其IP ping HoloLens。

我将不胜感激任何建议,答案或猜测。如果需要,我可以提供更多信息(另请参见下面的评论)。我建议将重点放在 TCP连接上,因为它似乎工作得更好,并且看起来更“基本”。这是代码:

using System;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Windows.Networking;
using Windows.Networking.Sockets;

#region Common
public abstract class TcpCore
{
    protected StreamSocket Socket;
    protected BinaryWriter BWriter;
    protected BinaryReader BReader;
    protected Task ReadingTask;

    public bool DetailedInfos { get; set; } = false;
    public bool Listening { get; protected set; }

    public ActionSingle<string> MessageOutput { get; protected set; } = new ActionSingle<string> (); // Used for message and debug output. They wrap an Action and allow safer use.
    public ActionSingle<string> LogOutput { get; protected set; } = new ActionSingle<string> ();

    protected const string USED_PORT = "1337";
    protected readonly Encoding USED_ENCODING = Encoding.UTF8;

    public abstract void Disconnect ();

    protected void StartCommunication ()
    {
        Stream streamOut = Socket.OutputStream.AsStreamForWrite ();
        Stream streamIn = Socket.InputStream.AsStreamForRead ();
        BWriter = new BinaryWriter (streamOut); //{ AutoFlush = true };
        BReader = new BinaryReader (streamIn);

        LogOutput.Trigger ("Connection established.");
        ReadingTask = new Task (() => StartReading ());
        ReadingTask.Start ();
    }

    public 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 || BWriter == null) { LogOutput.Trigger ("Cannot send message: No clients connected."); return; }

        uint length = (uint) message.Length;
        byte[] countBuffer = BitConverter.GetBytes (length);
        byte[] buffer = USED_ENCODING.GetBytes (message);

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

        BWriter.Write (countBuffer);
        BWriter.Write (buffer);
        BWriter.Flush ();
    }

    protected void StartReading ()
    {
        if (DetailedInfos) LogOutput.Trigger ("Starting to listen for input.");
        Listening = true;
        while (Listening)
        {
            try
            {
                if (DetailedInfos) LogOutput.Trigger ("Starting a listen iteration.");

                // 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 (=n).
                uint length = BReader.ReadUInt32 ();

                if (DetailedInfos) LogOutput.Trigger ("ReadLength: " + length.ToString ());

                MessageOutput.Trigger ("A");
                byte[] messageBuffer = BReader.ReadBytes ((int) length);
                MessageOutput.Trigger ("B");
                string message = USED_ENCODING.GetString (messageBuffer);
                MessageOutput.Trigger ("Received Message: " + message);
            }
            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)
                {
                    // Seems to occur on disconnects. Let's not throw().
                    Listening = false;
                    Disconnect ();
                    LogOutput.Trigger ("Unknown error occurred: " + e.Message);
                    break;
                }
                else
                {
                    Listening = false;
                    Disconnect ();
                    break;
                }
            }
        }
        LogOutput.Trigger ("Stopped to listen for input.");
    }
}
#endregion

#region Client
public class GTcpClient : TcpCore
{
    public async void Connect (string target, string port = USED_PORT) // Target is IP address.
    {
        try
        {
            Socket = new StreamSocket ();
            HostName serverHost = new HostName (target);
            await Socket.ConnectAsync (serverHost, port);

            LogOutput.Trigger ("Connection successful to: " + target + ":" + port);
            StartCommunication ();
        }
        catch (Exception e)
        {
            LogOutput.Trigger ("Connection error: " + e.Message);
        }
    }

    public override void Disconnect ()
    {
        Listening = false;
        if (BWriter != null) { BWriter.Dispose (); BWriter.Dispose (); BWriter = null; }
        if (BReader != null) { BReader.Dispose (); BReader.Dispose (); BReader = null; }
        if (Socket != null) { Socket.Dispose (); Socket = null; }
        if (ReadingTask != null) { ReadingTask = null; }
    }
}
#endregion

#region Server
public class GTcpServer : TcpCore
{
    private StreamSocketListener socketListener;

    public bool AutoResponse { get; set; } = false;

    public async void StartServer ()
    {
        try
        {
            //Create a StreamSocketListener to start listening for TCP connections.
            socketListener = new StreamSocketListener ();

            //Hook up an event handler to call when connections are received.
            socketListener.ConnectionReceived += ConnectionReceived;

            //Start listening for incoming TCP connections on the specified port. You can specify any port that's not currently in use.
            await socketListener.BindServiceNameAsync (USED_PORT);
        }
        catch (Exception e)
        {
            LogOutput.Trigger ("Connection error: " + e.Message);
        }
    }

    private void ConnectionReceived (StreamSocketListener listener, StreamSocketListenerConnectionReceivedEventArgs args)
    {
        try
        {
            listener.Dispose ();
            Socket = args.Socket;
            if (DetailedInfos) LogOutput.Trigger ("Connection received from: " + Socket.Information.RemoteAddress + ":" + Socket.Information.RemotePort);
            StartCommunication ();
        }
        catch (Exception e)
        {
            LogOutput.Trigger ("Connection Received error: " + e.Message);
        }
    }

    public override void Disconnect ()
    {
        Listening = false;
        if (socketListener != null) { socketListener.Dispose (); socketListener = null; }
        if (BWriter != null) { BWriter.Dispose (); BWriter.Dispose (); BWriter = null; }
        if (BReader != null) { BReader.Dispose (); BReader.Dispose (); BReader = null; }
        if (Socket != null) { Socket.Dispose (); Socket = null; }
        if (ReadingTask != null) { ReadingTask = null; }
    }
}
#endregion

2 个答案:

答案 0 :(得分:1)

巧合的是,我刚刚在HoloLens和UWP应用之间实现了BT连接。我在https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/BluetoothRfcommChat跟踪了示例。

作为功能,我设置了“蓝牙”(当然),“ Internet(客户端和服务器)”和“专用网络(客户端和服务器)”。服务器端的步骤如下:

  1. 为您自己的或现有的(例如OBEX对象推送)服务ID创建一个RfcommServiceProvider

  2. 创建一个StreamSocketListener并关联其ConnectionReceived事件。

  3. 在侦听器上绑定服务名称:listener.BindServiceNameAsync(provider.ServiceId.AsString(), SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);

  4. 如果您有自定义服务ID,请设置其名称以及您可能要配置的其他属性。请参阅上面链接的示例。我认为这主要是可选的。

  5. 开始宣传BT服务:provider.StartAdvertising(listener, true);

一旦客户端连接,StreamSocket中就会有一个StreamSocketListenerConnectionReceivedEventArgs,您可以像其他任何流一样使用它来创建DataReaderDataWriter。如果您只想允许一个客户,也可以立即停止投放广告。

在客户端,您将:

  1. 显示DevicePicker,然后让用户选择对等设备。不要忘记设置picker.Filter.SupportedDeviceSelectors.Add(BluetoothDevice.GetDeviceSelectorFromPairingState(true));之类的过滤器。您还可以允许未配对的设备,但是您需要先致电PairAsync才能继续执行步骤2。此外,我认为无法绕开用户的同意在这种情况下进行对话,因此建议您先配对。老实说,我没有检查未配对的东西是否可以在HoloLens上使用。

  2. 您从选择器中获得了一个DeviceInformation实例,您可以使用它来获取一个像await BluetoothDevice.FromIdAsync(info.Id);的BT设备

  3. device.GetRfcommServicesAsync(BluetoothCacheMode.Uncached);之类的设备中获取服务,然后选择您感兴趣的服务。请注意,我发现内置过滤的行为不符合预期,因此我只列举了结果并手动比较了UUID。我相信UWP实现在某些时候会执行区分大小写的字符串比较,这可能会导致所请求的服务尽管存在,但仍不显示。

  4. 一旦找到您的服务(从现在起我称为s),创建一个StreamSocket并使用socket.ConnectAsync(s.ConnectionHostName, s.ConnectionServiceName, SocketProtectionLevel.PlainSocket);

同样,您不能像在服务器端那样创建流读取器和写入器。

答案 1 :(得分:1)

答案是线程。

对于可能遇到类似问题的人,我找到了解决方案。这是由于Unity本身,而不是HoloLens。我的问题是,我在自己的类中单独编写了代码,而不是将其与UI代码混合在一起,这会使它1.难以阅读,并且2.无法模块化使用。因此,我尝试了一种更好的编码方法(我认为)。每个人都可以下载并轻松集成它,并拥有用于文本消息传递的基本代码。虽然这对于Xamarin和UWP而言不是问题,但对于Unity本身(以及Unity-UWP解决方案)来说都是一个问题。

Bluetooth和TCP的接收端似乎创建了一个自己的线程(甚至可能是其他线程,但是我没有积极地进行操作),无法在Unity的主线程中写, GameObjects (类似于输出日志)。因此,当我在HoloLens上对其进行测试时,我得到了奇怪的日志输出。

我使用TcpClient / TcpListener创建了一个新的TCP代码,该代码适用于Unity,但不适用于Unity-UWP解决方案,以便尝试使用TCP连接的另一个版本。幸运的是,当我在编辑器中运行该程序时,它最终指出了问题本身:无法访问主线程,该线程将被写入UI-日志输出的文本框。为了解决这个问题,我只需要使用Unity的Update()方法即可将文本设置为输出。变量本身仍然可以访问,但GameObjects不能访问。