C# 统一可调装置
我正在尝试在Unity3D中创建一个游戏,该游戏连接到支持心率服务的蓝牙低能设备,并收集HR数据。我已经创建了一个WPF库,它已经在控制台应用程序上进行了测试,可以连接到一个BLE设备并读取工作正常的数据。然而,我不知道如何在统一中使用它 我将库转移到Unity,并不得不为编辑器禁用它,因为代码使用编辑器不支持的Windows命名空间。我现在的问题是如何在Unity中调试代码,以便在运行游戏时检查库代码是否正常工作。我尝试在#if NETFX_CORE、#if ENABLE_WINMD_SUPPORT、#if WINDOWS_UWP和其他许多文件中包装调用库名称空间和库中函数的代码,但从未获得我编写的任何调试日志 有什么可能的解决办法吗? 任何帮助都将不胜感激,谢谢 这是蓝牙LE连接库的代码:C# 统一可调装置,c#,windows,unity3d,bluetooth-lowenergy,C#,Windows,Unity3d,Bluetooth Lowenergy,我正在尝试在Unity3D中创建一个游戏,该游戏连接到支持心率服务的蓝牙低能设备,并收集HR数据。我已经创建了一个WPF库,它已经在控制台应用程序上进行了测试,可以连接到一个BLE设备并读取工作正常的数据。然而,我不知道如何在统一中使用它 我将库转移到Unity,并不得不为编辑器禁用它,因为代码使用编辑器不支持的Windows命名空间。我现在的问题是如何在Unity中调试代码,以便在运行游戏时检查库代码是否正常工作。我尝试在#if NETFX_CORE、#if ENABLE_WINMD_SUPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Storage.Streams;
namespace BLEHR
{
/// <summary>
/// Wraps and makes use if the <see cref="BluetoothLeAdvertisementWatcher"/>
/// for easier consumption
///
/// </summary>
public class BLEAdvertisementWatcher
{
#region Private Members
/// <summary>
/// The underlying bluetooth watcher class
/// </summary>
private readonly BluetoothLEAdvertisementWatcher mWatcher;
/// <summary>
/// a list of discovered devices
/// </summary>
private readonly Dictionary<string, BLEDevice> mDiscoveredDevices = new Dictionary<string, BLEDevice>();
/// <summary>
/// The details about Gatt services
/// </summary>
private readonly GattServiceIDs mGattServiceIds;
/// <summary>
/// a thread lock object for this class
/// </summary>
private readonly object mThreadLock = new object();
#endregion
#region Public Properties
/// <summary>
/// indicates is this watcher is listening for advertisements
/// </summary>
public bool Listening => mWatcher.Status == BluetoothLEAdvertisementWatcherStatus.Started;
/// <summary>
/// a list of discovered devices
/// </summary>
public IReadOnlyCollection<BLEDevice> DiscoveredDevices
{
get
{
//Clena up any Timeouts
CleanupTimeouts();
//Practice Thread safety
lock (mThreadLock)
{
//Convert to readonly list
return mDiscoveredDevices.Values.ToList().AsReadOnly();
}
}
}
/// <summary>
/// The timeout in seconds that a device is removed from the <see cref="DiscoveredDevices"/>
/// list if it is not re-advertised within this time
/// </summary>
public int TimeoutRemoval { get; set; } = 20;
public int HRValue { get; set; }
#endregion
#region Constructor
/// <summary>
/// The default constructor
/// </summary>
public BLEAdvertisementWatcher(GattServiceIDs gattIds)
{
//Null guard
mGattServiceIds = gattIds ?? throw new ArgumentNullException(nameof(gattIds));
//Create bluetooth listener
mWatcher = new BluetoothLEAdvertisementWatcher
{
ScanningMode = BluetoothLEScanningMode.Active
};
//Listen out for new advertisements
mWatcher.Received += WatcherAdvertisementReceivedAsync;
//Listen out for when the watcher stops listening
mWatcher.Stopped += (watcher, e) =>
{
//Inform listeners
StoppedListening();
};
}
#endregion
#region Private Methods
/// <summary>
/// Listens out for watcher advertisements
/// </summary>
/// <param name="sender"> The Watcher </param>
/// <param name="args">The Arguments </param>
private async void WatcherAdvertisementReceivedAsync(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
//cleanup timeouts
CleanupTimeouts();
//Get BLE device info
var device = await GetBLEDeviceAsync(args.BluetoothAddress, args.Timestamp, args.RawSignalStrengthInDBm);
//Null guard
if(device == null)
{
return;
}
//is new discovery?
var newDiscovery = false;
var existingName = default(string);
//Lock your doors
lock (mThreadLock)
{
//Check if this is a new discovery
newDiscovery= !mDiscoveredDevices.ContainsKey(device.DeviceID);
//If this is not new...
if (!newDiscovery)
{
//Store the old name
existingName = mDiscoveredDevices[device.DeviceID].Name;
}
}
//Name changed?
var nameChanged =
//if it already exists
!newDiscovery &&
//And is not a blank name
!string.IsNullOrEmpty(device.Name) &&
//And the name is different
existingName != device.Name;
lock (mThreadLock)
{
//add/update the device in the dictionary
mDiscoveredDevices[device.DeviceID] = device;
}
//Inform listeners
DeviceDiscovered(device);
//if new discovery...
if (newDiscovery)
{
//Inform listeners
NewDeviceDiscovered(device);
}
}
/// <summary>
/// Connects to the BLE device and extracts more information from the
/// <seealso cref="https://docs.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothledevice"/>
/// </summary>
/// <param name="address">The BT address of the device to connect to</param>
/// <param name="broadcastTime">The time the broadcast message was received</param>
/// <param name="rssi">The signal strength in dB</param>
/// <returns></returns>
private async Task<BLEDevice> GetBLEDeviceAsync(ulong address, DateTimeOffset broadcastTime, short rssi)
{
//Get bluetooth device info
var device = await BluetoothLEDevice.FromBluetoothAddressAsync(address).AsTask();
//Null guard
if(device == null)
{
return null;
}
//Get GATT services that are available
var gatt = await device.GetGattServicesAsync().AsTask();
//if we have any services..
if(gatt.Status == GattCommunicationStatus.Success)
{
//loop each gatt service
foreach(var service in gatt.Services)
{
//This ID contains the GATT profile assigned number we want!
//TODO: Get more info and connect
var gattProfileId = service.Uuid;
}
}
//Return the new device information
return new BLEDevice
(
//Device ID
deviceID: device.DeviceId,
//Bluetooth address
address: device.BluetoothAddress,
//Device name
name: device.Name,
//Broadcast time
broadcastTime: broadcastTime,
//Signal strength
rssi: rssi,
//Is connected
connected: device.ConnectionStatus== BluetoothConnectionStatus.Connected,
//Can Pair?
canPair: device.DeviceInformation.Pairing.CanPair,
//Is Paired?
isPaired: device.DeviceInformation.Pairing.IsPaired
);
}
/// <summary>
/// Prune any timed out devices that we have not heard off
/// </summary>
private void CleanupTimeouts()
{
lock (mThreadLock)
{
//The date in time that if less than means a device has timed out
var threshold = DateTime.UtcNow - TimeSpan.FromSeconds(TimeoutRemoval);
//any devices that have not sent a new broadcast within the time
mDiscoveredDevices.Where(f => f.Value.BroadcastTime < threshold).ToList().ForEach(device =>
{
//remove device
mDiscoveredDevices.Remove(device.Key);
//Inform listeners
DeviceTimeout(device.Value);
});
}
}
#endregion
#region Public events
/// <summary>
/// Fired when the bluetooth watcher stops listening
/// </summary>
public event Action StoppedListening = () => { };
/// <summary>
/// Fired when the bluetooth watcher start listening
/// </summary>
public event Action StartedListening = () => { };
/// <summary>
/// fired when a new device is discovered
/// </summary>
public event Action<BLEDevice> NewDeviceDiscovered = (device) => {};
/// <summary>
/// fired when a device is discovered
/// </summary>
public event Action<BLEDevice> DeviceDiscovered = (device) => { };
/// <summary>
/// Fired when a device is removed for timing out
/// </summary>
public event Action<BLEDevice> DeviceTimeout = (device) => { };
#endregion
#region Public Methods
/// <summary>
/// Starts listening for advertisements
/// </summary>
public void StartListening()
{
lock (mThreadLock)
{
//if already listening...
if (Listening)
{
//DO nothing more
return;
}
//Start the underlying watcher
mWatcher.Start();
}
//inform listeners
StartedListening();
}
/// <summary>
/// Stops listening for advertisements
/// </summary>
public void StopListening()
{
lock (mThreadLock)
{
//if we are not currently listening
if (!Listening)
{
//Do nothing more
return;
}
//Stop listening
mWatcher.Stop();
//clear any devices
mDiscoveredDevices.Clear();
}
}
/// <summary>
/// Attempts to pair to a BLE device, by ID
/// </summary>
/// <param name="deviceID"> The BLE device ID</param>
/// <returns></returns>
public async Task PairToDeviceAsync(string deviceID)
{
//Get bluetooth device info
var device = await BluetoothLEDevice.FromIdAsync(deviceID).AsTask();
//Null guard
if (device == null)
{
//TODO: localize
throw new ArgumentNullException("");
}
//if we are already paired...
if (device.DeviceInformation.Pairing.IsPaired)
{
//un-pair the device
await device.DeviceInformation.Pairing.UnpairAsync().AsTask();
return;
}
//Try and pair to the device
device.DeviceInformation.Pairing.Custom.PairingRequested += (sender, args) =>
{
//Accept all attempts
args.Accept(); // <-- could enter a pin in here to accept
};
var result = await device.DeviceInformation.Pairing.Custom.PairAsync(DevicePairingKinds.ConfirmOnly).AsTask();
//Get GATT services that are available
var gatt = await device.GetGattServicesAsync().AsTask();
GattDeviceService serviceReq = null;
GattCharacteristic characteristicReq = null;
//if we have any services..
if (gatt.Status == GattCommunicationStatus.Success)
{
//loop each gatt service
foreach (var service in gatt.Services)
{
if (service.Uuid == GattServiceUuids.HeartRate)
{
serviceReq = service;
}
//This ID contains the GATT profile assigned number we want!
//TODO: Get more info and connect
var gattProfileId = service.Uuid;
}
var charResults = await serviceReq.GetCharacteristicsAsync().AsTask();
if(charResults.Status == GattCommunicationStatus.Success)
{
foreach (var chars in charResults.Characteristics)
{
if(chars.Uuid == GattCharacteristicUuids.HeartRateMeasurement)
{
characteristicReq = chars;
}
}
GattCharacteristicProperties properties = characteristicReq.CharacteristicProperties;
if (properties.HasFlag(GattCharacteristicProperties.Read))
{
GattReadResult readVal = await characteristicReq.ReadValueAsync().AsTask();
if(readVal.Status == GattCommunicationStatus.Success)
{
var reader = DataReader.FromBuffer(readVal.Value);
byte[] input = new byte[reader.UnconsumedBufferLength];
reader.ReadBytes(input);
HRValue = BitConverter.ToInt32(input, 0);
}
}
}
}
////Log the result
//if(result.Status == DevicePairingResultStatus.Paired)
//{
// Console.WriteLine("Pairing successful");
//}
//else
//{
// Console.WriteLine($"Pairing failed: {result.Status}");
//}
}
#endregion
}
}
在Unity中使用UWP/.Net仅核心功能进行调试对我来说似乎是不可行的工作流,因为编辑编译运行周期很长。Unity编辑器使用.Net Framework运行,因此无法直接访问.Net核心功能。对于非Unity项目,可以通过Windows提供的包装器从.Net Framework访问UWP-API。但这对团结不起作用,因为有些团结的具体情况1 <>但是,有可能在C++ WRRT-DLL中封装UWP调用,并将DLL复制到Unity。这样,UWP-API也可以在Unity编辑器中使用(例如,按下播放按钮时)。我的用例也需要BLE,我上传了我的包装器。如果你愿意,你可以自由地把它作为起点 最后一件事:随着.Net版本5也宣布推出Unity,我想这应该已经过时了,因为据说版本5合并了.Net Framework和.Net Core
[1] 这个答案的许多信息来自于。如果不使用代码,就很难说出代码。不过,你可以试试这个,节省你的时间:谢谢你的建议,我尝试了你提供的这个链接,但是,所有的代码都给了我编译错误,没有运行。DLL,这样你就可以打开Unity项目:(将DLL放入资产文件夹)。谢谢,有关于这个的文档吗?我已经将我的设备连接到unity项目,现在我不知道如何检索特征值(服务是heart 0X180D,特征是心率测量)。谢谢大家!@MikePetrichenko我已经完成了所有工作,但是当我尝试读取支持的心率测量值时,我看到UUID显示在调试日志中,我不断地得到结果值“331808”,心率值没有返回。我应该做些什么来获得价值?非常感谢。这是一个伟大的回购,做得好。只有两个问题:当你说“要尝试它,你可以将文件复制到可调试文件夹”时,你引用的是另一个repo吗?我在你的可调试文件夹中没有看到unity场景,我在你的存储库中有测试它所需的一切吗?问题2:debugable/BleWinrtDll.dll是否已经有我可以使用的预编译文件,或者您是否建议按照您的说明自己编译一个?嗨,乔,谢谢!问题1:不,这是相同的回购协议。DebugBle是一个控制台应用程序,您可以在其中试用它。一切都包括在内。但是你也应该能够在Unity中使用完全相同的C代码。我只是还没有在回购协议中添加统一场景。问题2:是的,debugable/BleWinrtDll.dll是一个预编译版本,因此您可以直接启动debugable。你也应该能够统一使用它。如果dll失败或出于安全原因,您也可以编译自己的dll(以确保我或github黑客没有篡改dll来监视您;)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if NETFX_CORE
using BLEHR
#endif
public class HRTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
#if NETFX_CORE
var watcher = new BLEAdvertisementWatcher(new GattServiceIDs());
Debug.Log("Working?");
#endif
}
// Update is called once per frame
void Update()
{
#if WINDOWS_UWP
Debug.Log("Connecting");
#endif
}
}