C# 统一可调装置

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

我正在尝试在Unity3D中创建一个游戏,该游戏连接到支持心率服务的蓝牙低能设备,并收集HR数据。我已经创建了一个WPF库,它已经在控制台应用程序上进行了测试,可以连接到一个BLE设备并读取工作正常的数据。然而,我不知道如何在统一中使用它

我将库转移到Unity,并不得不为编辑器禁用它,因为代码使用编辑器不支持的Windows命名空间。我现在的问题是如何在Unity中调试代码,以便在运行游戏时检查库代码是否正常工作。我尝试在#if NETFX_CORE、#if ENABLE_WINMD_SUPPORT、#if WINDOWS_UWP和其他许多文件中包装调用库名称空间和库中函数的代码,但从未获得我编写的任何调试日志

有什么可能的解决办法吗? 任何帮助都将不胜感激,谢谢

这是蓝牙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
}
}

在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
    }
}