带有Unity的BLE设备 [英] BLE device with Unity

查看:152
本文介绍了带有Unity的BLE设备的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在Unity3D中创建一个游戏,该游戏连接到支持心率服务并收集HR数据的蓝牙低功耗设备.我创建了一个WPF库,该库已在控制台应用程序上经过测试,可以连接到BLE设备并读取可以正常工作的数据.但是,我不知道如何在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. 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. However, I do not know how to use this in Unity.

我将库转移到Unity上,并且不得不对编辑器禁用它,因为代码使用了编辑器中不支持的Windows名称空间.我现在的问题是如何在运行游戏时在Unity中调试代码以检查库代码是否正常工作.我尝试在#if NETFX_CORE,#if ENABLE_WINMD_SUPPORT,#if WINDOWS_UWP等中包装用于调用库名称空间和库中函数的代码,但从未得到我编写的任何调试日志.

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. 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. 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.

对此有任何可能的解决方案吗?任何帮助,将不胜感激,谢谢!

Is there any possible solution to this? Any help would be appreciated, thank you!

这是蓝牙LE连接库的代码:

This is the code for the bluetooth LE connection library:

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中尝试的代码:

And here is the code I am trying in 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
    }
}

推荐答案

在Unity中使用UWP/.Net-Core-only功能进行调试对我来说似乎是不可行的工作流程,因为其编辑-编译运行周期长.Unity Editor与.Net-Framework一起运行,因此无法直接访问.Net-Core功能.对于非Unity项目,可以通过Windows提供的包装程序从.Net-Framework访问UWP-API.但这由于某些特定于Unity的 1 而不适用​​于Unity.

Debugging with UWP/.Net-Core-only features in Unity seems like an infeasible workflow to me because of the long edit-compile-run cycles. The Unity Editor runs with .Net-Framework and thus can't directly access .Net-Core features. For non-Unity projects there is the possibility to access UWP-API from .Net-Framework via wrappers provided by Windows. But that doesn't work for Unity because of some Unity-specifics1.

但是有可能将UWP调用包装在C ++ winrt dll中并将该dll复制到Unity.这样,UWP-API也可以在Unity编辑器中使用(例如,按下播放按钮时).我的用例也需要BLE,我将包装器上传到此存储库中.您可以根据需要随意使用它作为起点.

BUT there is the possibility to wrap the UWP-calls in a C++ winrt dll and copy the dll to Unity. This way the UWP-API can be also used inside the Unity Editor (e.g. when pushing the play button). My use case also requires BLE and I uploaded my wrapper in this repo. You're free to use it as a starting point if you want.

最后一件事:随着也宣布为Unity推出的.Net版本5,我认为随着版本5被合并为.Net-Framework和.Net-Core,这应该已经过时了.

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.

[1]此答案的很多信息来自

[1] Much information for this answer came from this blog-post by Mike Taulty.

这篇关于带有Unity的BLE设备的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆