【问题标题】:HoloLens unable to send or receive data via BT and TCPHoloLens 无法通过 BT 和 TCP 发送或接收数据
【发布时间】:2018-09-11 13:28:52
【问题描述】:

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

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

我所知道的:功能已设置,代码有效,问题可能是与网络和 HoloLens 相关的所有事情的常见问题。

所以问题是,Unity 或 HoloLens 是否与我正在使用的东西不兼容?我使用的值得一提的是:StreamSocket、BinaryWriter、BinaryReader、Task(async、await)。还是 HoloLens 主动阻止与其他设备上的应用程序通信?我知道它可以通过蓝牙连接到设备,并且可以通过 TCP 连接,看起来人们成功地让它工作了。是否存在已知问题?或者 Unity 是否存在导致这种情况的原因 - 可能是一个糟糕的设置?我必须使用异步方法还是只同步?任务/线程和 Unity 是否存在不兼容问题? this 可能是问题(无法同意权限)吗?

另外需要注意的是,即使 IP 正确,我也无法使用 cmd 通过其 IP ping HoloLens。

如果有任何建议、回答或猜测,我将不胜感激。如果需要,我可以提供更多信息(另请参阅下面的 cmets)。我建议关注 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

【问题讨论】:

  • 您好,您的服务器在我假设的 PC 中运行,而客户端在 HoloLens 中运行?您是否确定了两者中哪一个具有奇怪的行为?是服务器首先挂起,因此客户端断开连接或客户端崩溃?同样在 StartReading 中,我会在 if 之外添加一个带有 (e.Message) 的日志,以确保我记录任何可能发生的一般错误。
  • @S.Fragkos - 使用 PC(服务器) HoloLens 我可以向 PC 发送消息,但是当我尝试发送到 HL 时,它会因未知错误而断开连接。日志中没有任何内容出现在 HL 端。使用 HoloLens (Server) PC 它可以连接,但 HL 日志中没有任何内容...直到我尝试从那里发送消息,然后我收到它连接到 [IP]:52643 的消息,即它从Socket.Information.RemotePort 获得的端口,而我一直使用 1337。HoloLens 端只是拒绝接收任何消息,并且难以处理传入的连接。
  • @S.Fragkos - 我在 HL(服务器) PC 测试中收到此错误消息:“get_isActiveAndEnabled 只能从主线程调用。构造函数和字段初始化程序将从加载场景时的加载线程。不要在构造函数或字段初始化程序中使用此函数。而是将初始化代码移动到 Awake 或 State 函数。 - 这显然是 Unity 特定的错误消息。 Unity & Threading 是我的问题吗?我没有积极使用多线程,所以我想知道是什么原因造成的,如果这就是问题所在。
  • @S.Fragkos - 我调整它以添加异常消息并通过 PC(服务器)收到此消息 HL:“发生未知错误:Unable to read beyond the end of the stream.” - 当我尝试向 HL 发送消息,然后断开连接。
  • 几天前我刚刚完成了 BT 的工作,但我使用了 RFCOMM 类和DataReader 和DataWriter。我刚刚关注了github.com/Microsoft/Windows-universal-samples/tree/master/… 的“蓝牙 RFCOMM 聊天示例”。我唯一遇到的一些问题是正确设置双方的服务 ID。

标签: c# unity3d uwp virtual-reality


【解决方案1】:

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

作为功能,我设置了“蓝牙”(当然)、“互联网(客户端和服务器)”和“私人网络(客户端和服务器)”。那么服务器端的步骤是:

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

  2. 创建一个StreamSocketListener 并连接它的ConnectionReceived 事件。

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

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

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

一旦客户端连接,StreamSocketListenerConnectionReceivedEventArgs 中有一个StreamSocket,您可以使用它来创建DataReader 和DataWriter,就像在任何其他流上一样。如果您只想允许一个客户,您也可以现在停止广告。

在客户端,您会:

  1. 显示DevicePicker 并让用户选择对等设备。不要忘记设置像picker.Filter.SupportedDeviceSelectors.Add(BluetoothDevice.GetDeviceSelectorFromPairingState(true));这样的过滤器您也可以允许未配对的设备,但您需要调用PairAsync才能继续步骤2。另外,我认为在这种情况下没有办法绕过用户同意对话,所以我建议之前配对。老实说,我没有检查未配对的东西是否适用于 HoloLens。

  2. 你从选择器中得到一个DeviceInformation实例,你可以用它来获取一个BT设备,比如await BluetoothDevice.FromIdAsync(info.Id);

  3. 从设备中获取服务,如device.GetRfcommServicesAsync(BluetoothCacheMode.Uncached);,然后选择您感兴趣的服务。注意,我发现内置过滤器没有按预期运行,所以我只是列举了结果并比较了手动 UUID。我相信 UWP 实现会在某些时候执行区分大小写的字符串比较,这可能会导致请求的服务没有显示,尽管它在那里。

  4. 一旦你找到了你的服务,从现在开始我将称之为s,创建一个StreamSocket并使用socket.ConnectAsync(s.ConnectionHostName, s.ConnectionServiceName, SocketProtectionLevel.PlainSocket);进行连接

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

【讨论】:

  • 感谢您的回答,但是我可以建立蓝牙和 TCP 连接,我可以在 PC(UWP 应用程序)上以两种方式(服务器/客户端)测试到 Android(Xamarin 应用程序)。它工作得很好。我目前的怀疑是它与 Unity 和线程有关......并且 HoloLens 上的接收端创建了自己的线程,该线程无法访问主线程并导致未显示的异常(直到昨天,模糊)。跨度>
  • 很高兴你解决了它。结合图形和网络时,这始终是一个问题。必须假定所有内置侦听器和所有等待的调用都在任意线程上下文中运行。在某些情况下,您可以使用延续上下文来控制线程,但我通常有一个队列用于网络引起的更改,该队列在每个 Frame 之前在渲染线程中处理。
  • 这是一个残酷的教训,但仍然很有价值(花费了大量的时间和精力)。如果我考虑一下……在 Xamarin/Android 应用程序中,由于懒惰,我偶然在不知道的情况下解决了这个问题(我让一切尽可能简约……我使用了一个带有弹出文本的按钮,该按钮是在创建项目)。在 UWP 中,我也遇到了问题,但很早就解决了。只是我在 HoloLens 上测试时没有编辑器可供使用,而 Unity 使用了 Unity-UWP 无法使用的 TCP 库。
【解决方案2】:

答案是线程。

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

蓝牙和TCP的接收端似乎创建了一个自己的线程(甚至可能是别的,但我没有主动做),无法在Unity的主线程上写,只处理游戏对象(如输出日志)。因此,当我在 HoloLens 上测试它时,我得到了奇怪的日志输出。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-10
    • 2014-09-06
    • 2020-02-29
    • 2015-10-07
    • 2014-07-25
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    相关资源
    最近更新 更多