[英]BLE device with Unity
I am trying to create a game in Unity3D which connects to bluetooth low energy device that supports the heart rate service and collects the HR data.我正在尝试在 Unity3D 中创建一个游戏,该游戏连接到支持心率服务并收集 HR 数据的蓝牙低功耗设备。 I have created a WPF library that has been tested on a console application to connect to a BLE device and read the data which works perfectly.
我创建了一个 WPF 库,该库已在控制台应用程序上进行了测试,以连接到 BLE 设备并读取完美运行的数据。 However, I do not know how to use this in Unity.
但是,我不知道如何在 Unity 中使用它。
I transferred the library to Unity and had to disable it for the editor as the code uses Windows namespaces which are not supported in the editor.我将库转移到 Unity 并且不得不为编辑器禁用它,因为代码使用了编辑器不支持的 Windows 命名空间。 My problem now is how do I debug the code in Unity to check if the library code is working when I run the game.
我现在的问题是如何在 Unity 中调试代码以检查运行游戏时库代码是否正常工作。 I tried wrapping the code for calling the library namespace and the functions from the library in #if NETFX_CORE, #if ENABLE_WINMD_SUPPORT, #if WINDOWS_UWP and many more but, never got any of the debug logs that I wrote.
我尝试在#if NETFX_CORE、#if ENABLE_WINMD_SUPPORT、#if WINDOWS_UWP 等中包装用于调用库命名空间和库中函数的代码,但从未获得我编写的任何调试日志。
Is there any possible solution to this?有没有可能的解决方案? Any help would be appreciated, thank you!
任何帮助将不胜感激,谢谢!
This is the code for the bluetooth LE connection library:这是蓝牙LE连接库的代码:
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
}
}
And here is the code I am trying in Unity:这是我在 Unity 中尝试的代码:
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
}
}
Debugging with UWP/.Net-Core-only features in Unity seems like an infeasible workflow to me because of the long edit-compile-run cycles.由于编辑-编译-运行周期很长,在 Unity 中使用 UWP/.Net-Core-only 功能进行调试对我来说似乎是一个不可行的工作流程。 The Unity Editor runs with .Net-Framework and thus can't directly access .Net-Core features.
Unity 编辑器与 .Net-Framework 一起运行,因此无法直接访问 .Net-Core 功能。 For non-Unity projects there is the possibility to access UWP-API from .Net-Framework via wrappers provided by Windows.
对于非 Unity 项目,可以通过 Windows 提供的包装器从 .Net-Framework 访问 UWP-API。 But that doesn't work for Unity because of some Unity-specifics 1 .
但这对 Unity 不起作用,因为某些 Unity 特定的1 。
BUT there is the possibility to wrap the UWP-calls in a C++ winrt dll and copy the dll to Unity.但是有可能将 UWP 调用包装在 C++ winrt dll 中并将该 dll 复制到 Unity。 This way the UWP-API can be also used inside the Unity Editor (eg when pushing the play button).
这样,UWP-API 也可以在 Unity 编辑器中使用(例如,按下播放按钮时)。 My use case also requires BLE and I uploaded my wrapper in this repo .
我的用例也需要 BLE,我在这个 repo 中上传了我的包装器。 You're free to use it as a starting point if you want.
如果您愿意,您可以自由地将其用作起点。
One last thing: With .Net version 5 which is also announced for Unity, I guess this should become obsolete as version 5 is said to merge .Net-Framework and .Net-Core.最后一件事:随着 .Net 第 5 版也为 Unity 发布,我想这应该会过时,因为据说第 5 版合并了 .Net-Framework 和 .Net-Core。
[1] Much information for this answer came from this blog-post by Mike Taulty . [1] 这个答案的很多信息来自Mike Taulty 的这篇博文。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.