HoloLens 无法通过 BT 和 TCP 发送或接收数据
HoloLens unable to send or receive data via BT and TCP
我正在使用 HoloLens (Unity-UWP) 并尝试与 PC (UWP) 或 Android phone 工作 (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 有什么原因导致了这种情况——可能是一个糟糕的设置?我必须使用异步方法还是只使用同步方法? Tasks/Threads 和 Unity 是否存在不兼容问题? this 可能是问题所在(无法同意权限)吗?
另一件需要注意的事情是,即使 IP 正确,我也无法使用 cmd 通过其 IP ping HoloLens。
如果有任何建议、回答或猜测,我将不胜感激。如果需要,我可以提供更多信息(另请参阅下面的评论)。我建议关注 TCP 连接,因为它似乎工作得更好并且似乎更多 "basic." 这是代码:
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
巧合的是,我刚刚在 HoloLens 和 UWP 应用程序之间实现了 BT 连接。我在 https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/BluetoothRfcommChat.
关注了示例
作为能力,我设置了"Bluetooth"(当然),"Internet (Client & Server)"和"Private Networks (Client & Server)"。服务器端的步骤是:
为您自己的或现有的(例如 OBEX 对象推送)服务 ID 创建一个 RfcommServiceProvider
。
创建一个 StreamSocketListener
并连接其 ConnectionReceived
事件。
在监听器上绑定服务名称:listener.BindServiceNameAsync(provider.ServiceId.AsString(), SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);
如果您有自定义服务 ID,请设置其名称以及您可能想要配置的其他属性。请参阅上面链接的示例。我认为,这主要是可选的。
开始宣传 BT 服务:provider.StartAdvertising(listener, true);
客户端连接后,StreamSocketListenerConnectionReceivedEventArgs
中有一个 StreamSocket
,您可以像在任何其他流上一样使用它来创建 DataReader
和 DataWriter
。如果您只想允许一个客户,您也可以立即停止投放广告。
在客户端,你会:
显示DevicePicker
并让用户select成为对端设备。不要忘记设置像 picker.Filter.SupportedDeviceSelectors.Add(BluetoothDevice.GetDeviceSelectorFromPairingState(true));
这样的过滤器你也可以允许未配对的设备,但你需要先调用 PairAsync
才能继续第 2 步。另外,我认为没有办法绕过用户同意在这种情况下进行对话,所以我建议先配对。老实说,我没有检查未配对的东西是否适用于 HoloLens。
你从选择器中得到了一个DeviceInformation
实例,你可以用它来获得BT设备,比如await BluetoothDevice.FromIdAsync(info.Id);
从您感兴趣的 device.GetRfcommServicesAsync(BluetoothCacheMode.Uncached);
和 select 设备获取服务。请注意,我发现内置过滤没有按预期运行,所以我只是枚举结果并手动比较 UUID。我相信 UWP 实现在某些时候会执行区分大小写的字符串比较,这可能会导致请求的服务不显示,尽管它存在。
一旦你找到你的服务,从现在开始我将其称为 s
,创建一个 StreamSocket
并使用 socket.ConnectAsync(s.ConnectionHostName, s.ConnectionServiceName, SocketProtectionLevel.PlainSocket);
[ 连接=68=]
同样,您不能像在服务器端那样创建流读取器和写入器。
答案是线程。
对于可能遇到类似问题的人,我找到了解决方案。这是由于 Unity 本身,而不是 HoloLens。我的问题是我在自己的 class 中单独编写了我的代码,而不是将它与 UI 代码混合在一起,这会使它 1. 不可读和 2. 不可模块化使用。所以我尝试了一种更好的编码方法(在我看来)。每个人都可以下载它并轻松集成它,并拥有用于文本消息传递的基本代码。虽然这对 Xamarin 和 UWP 来说不是问题,但对 Unity 本身来说是个问题(还有 Unity-UWP 解决方案)。
Bluetooth和TCP的接收端好像是创建了自己的线程(也有可能是别的,不过我没主动做),在Unity中无法在主线程上写, 仅处理 GameObjects(如输出日志)。因此,当我在 HoloLens 上测试它时,我得到了奇怪的日志输出。
我使用 TcpClient/TcpListener 创建了适用于 Unity 但不适用于 Unity-UWP 解决方案的新 TCP 代码,以便尝试使用 TCP 连接的另一个版本。幸运的是,当我 运行 在编辑器中它最终指出了问题本身:无法访问主线程,这将写入 UI - 日志输出的文本框。为了解决这个问题,我只需要使用 Unity 的 Update() 方法来将文本设置为输出。仍然可以访问变量本身,但不能访问游戏对象。
我正在使用 HoloLens (Unity-UWP) 并尝试与 PC (UWP) 或 Android phone 工作 (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 有什么原因导致了这种情况——可能是一个糟糕的设置?我必须使用异步方法还是只使用同步方法? Tasks/Threads 和 Unity 是否存在不兼容问题? this 可能是问题所在(无法同意权限)吗?
另一件需要注意的事情是,即使 IP 正确,我也无法使用 cmd 通过其 IP ping HoloLens。
如果有任何建议、回答或猜测,我将不胜感激。如果需要,我可以提供更多信息(另请参阅下面的评论)。我建议关注 TCP 连接,因为它似乎工作得更好并且似乎更多 "basic." 这是代码:
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
巧合的是,我刚刚在 HoloLens 和 UWP 应用程序之间实现了 BT 连接。我在 https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/BluetoothRfcommChat.
关注了示例作为能力,我设置了"Bluetooth"(当然),"Internet (Client & Server)"和"Private Networks (Client & Server)"。服务器端的步骤是:
为您自己的或现有的(例如 OBEX 对象推送)服务 ID 创建一个
RfcommServiceProvider
。创建一个
StreamSocketListener
并连接其ConnectionReceived
事件。在监听器上绑定服务名称:
listener.BindServiceNameAsync(provider.ServiceId.AsString(), SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);
如果您有自定义服务 ID,请设置其名称以及您可能想要配置的其他属性。请参阅上面链接的示例。我认为,这主要是可选的。
开始宣传 BT 服务:
provider.StartAdvertising(listener, true);
客户端连接后,StreamSocketListenerConnectionReceivedEventArgs
中有一个 StreamSocket
,您可以像在任何其他流上一样使用它来创建 DataReader
和 DataWriter
。如果您只想允许一个客户,您也可以立即停止投放广告。
在客户端,你会:
显示
DevicePicker
并让用户select成为对端设备。不要忘记设置像picker.Filter.SupportedDeviceSelectors.Add(BluetoothDevice.GetDeviceSelectorFromPairingState(true));
这样的过滤器你也可以允许未配对的设备,但你需要先调用PairAsync
才能继续第 2 步。另外,我认为没有办法绕过用户同意在这种情况下进行对话,所以我建议先配对。老实说,我没有检查未配对的东西是否适用于 HoloLens。你从选择器中得到了一个
DeviceInformation
实例,你可以用它来获得BT设备,比如await BluetoothDevice.FromIdAsync(info.Id);
从您感兴趣的
device.GetRfcommServicesAsync(BluetoothCacheMode.Uncached);
和 select 设备获取服务。请注意,我发现内置过滤没有按预期运行,所以我只是枚举结果并手动比较 UUID。我相信 UWP 实现在某些时候会执行区分大小写的字符串比较,这可能会导致请求的服务不显示,尽管它存在。一旦你找到你的服务,从现在开始我将其称为
[ 连接=68=]s
,创建一个StreamSocket
并使用socket.ConnectAsync(s.ConnectionHostName, s.ConnectionServiceName, SocketProtectionLevel.PlainSocket);
同样,您不能像在服务器端那样创建流读取器和写入器。
答案是线程。
对于可能遇到类似问题的人,我找到了解决方案。这是由于 Unity 本身,而不是 HoloLens。我的问题是我在自己的 class 中单独编写了我的代码,而不是将它与 UI 代码混合在一起,这会使它 1. 不可读和 2. 不可模块化使用。所以我尝试了一种更好的编码方法(在我看来)。每个人都可以下载它并轻松集成它,并拥有用于文本消息传递的基本代码。虽然这对 Xamarin 和 UWP 来说不是问题,但对 Unity 本身来说是个问题(还有 Unity-UWP 解决方案)。
Bluetooth和TCP的接收端好像是创建了自己的线程(也有可能是别的,不过我没主动做),在Unity中无法在主线程上写, 仅处理 GameObjects(如输出日志)。因此,当我在 HoloLens 上测试它时,我得到了奇怪的日志输出。
我使用 TcpClient/TcpListener 创建了适用于 Unity 但不适用于 Unity-UWP 解决方案的新 TCP 代码,以便尝试使用 TCP 连接的另一个版本。幸运的是,当我 运行 在编辑器中它最终指出了问题本身:无法访问主线程,这将写入 UI - 日志输出的文本框。为了解决这个问题,我只需要使用 Unity 的 Update() 方法来将文本设置为输出。仍然可以访问变量本身,但不能访问游戏对象。