0

我找到了调整 BLE 扫描窗口大小的解决方案:BLE Scan Interval Windows 10

但是,它不起作用。扫描时错过了许多信标。

using System;
using System.Linq;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Storage.Streams;

namespace BeaconExample
{
    using System;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Threading.Tasks;

    class BetterScanner
    {
        static Task task;
        /// <summary>
        /// The BLUETOOTH_FIND_RADIO_PARAMS structure facilitates enumerating installed Bluetooth radios.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        private struct BLUETOOTH_FIND_RADIO_PARAM
        {
            internal UInt32 dwSize;
            internal void Initialize()
            {
                this.dwSize = (UInt32)Marshal.SizeOf(typeof(BLUETOOTH_FIND_RADIO_PARAM));
            }
        }

        /// <summary>
        /// Closes an open object handle.
        /// </summary>
        /// <param name="handle">[In] A valid handle to an open object.</param>
        /// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError.</returns>
        [DllImport("Kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr handle);

        /// <summary>
        /// Finds the first bluetooth radio present in device manager
        /// </summary>
        /// <param name="pbtfrp">Pointer to a BLUETOOTH_FIND_RADIO_PARAMS structure</param>
        /// <param name="phRadio">Pointer to where the first enumerated radio handle will be returned. When no longer needed, this handle must be closed via CloseHandle.</param>
        /// <returns>In addition to the handle indicated by phRadio, calling this function will also create a HBLUETOOTH_RADIO_FIND handle for use with the BluetoothFindNextRadio function.
        /// When this handle is no longer needed, it must be closed via the BluetoothFindRadioClose.
        /// Returns NULL upon failure. Call the GetLastError function for more information on the error. The following table describe common errors:</returns>
        [DllImport("irprops.cpl", SetLastError = true)]
        static extern IntPtr BluetoothFindFirstRadio(ref BLUETOOTH_FIND_RADIO_PARAM pbtfrp, out IntPtr phRadio);

        [StructLayout(LayoutKind.Sequential)]
        /*
        private struct LE_SCAN_REQUEST
        {
            internal UInt32 unknown1;
            internal UInt32 scanType;
            internal UInt32 unknown2;
            internal UInt16 scanInterval;
            internal UInt16 scanWindow;
            internal UInt32[] unknown3;
        }
        */
        
        private struct LE_SCAN_REQUEST
        {
            internal int scanType;
            internal ushort scanInterval;
            internal ushort scanWindow;
        };
        

        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
        ref LE_SCAN_REQUEST lpInBuffer, uint nInBufferSize,
        IntPtr lpOutBuffer, uint nOutBufferSize,
        out uint lpBytesReturned, IntPtr lpOverlapped);

        /// <summary>
        /// Starts scanning for LE devices.
        /// Example: BetterScanner.StartScanner(0, 29, 29)
        /// </summary>
        /// <param name="scanType">0 = Passive, 1 = Active</param>
        /// <param name="scanInterval">Interval in 0.625 ms units</param>
        /// <param name="scanWindow">Window in 0.625 ms units</param>
        //public static void StartScanner(UInt32 scanType, UInt16 scanInterval, UInt16 scanWindow)
        public static void StartScanner(int scanType, ushort scanInterval, ushort scanWindow)
        {
            Action<object> action = (object obj) => {
                BLUETOOTH_FIND_RADIO_PARAM param = new BLUETOOTH_FIND_RADIO_PARAM();
                param.Initialize();
                IntPtr handle;
                BluetoothFindFirstRadio(ref param, out handle);
                uint outsize;

                /*
                LE_SCAN_REQUEST req = new LE_SCAN_REQUEST
                {
                    unknown1 = 0,
                    scanType = scanType,
                    unknown2 = 0,
                    scanInterval = scanInterval,
                    scanWindow = scanWindow,
                    unknown3 = new UInt32[2] {0, 0}
                };
                */
                 
                LE_SCAN_REQUEST req = new LE_SCAN_REQUEST
                {
                    scanType = scanType,
                    scanInterval = scanInterval,
                    scanWindow = scanWindow,
                };
                 
                bool ret = DeviceIoControl(handle, 0x41118c, ref req, 8, IntPtr.Zero, 0, out outsize, IntPtr.Zero);
                Console.WriteLine("DeviceControl ret=" + ret);
                if(ret == false)
                {
                    var error = Marshal.GetLastWin32Error();
                    Console.WriteLine("The last Win32 Error was: " + error);
                }
            };
            task = new Task(action, "nothing");
            task.Start();
        }

        public static void StopScanner()
        {
           Console.WriteLine("BetterScanner Stop");
            task.Wait();
        }
    }

    class Program
    {
        const String UUID = "44790ba4-7eb3-4095-9e14-4b43ae67512b";
        const ushort MAJOR = 40033;
        const short RSSI = -40;

        private class BeaconData
        {
            public Guid Uuid { get; set; }
            public ushort Major { get; set; }
            public ushort Minor { get; set; }
            public byte Battery { get; set; }
            public static BeaconData FromBytes(byte[] bytes)
            {
                //if (bytes[0] != 0x02) { throw new ArgumentException("First byte in array was exptected to be 0x02", "bytes"); }
                //if (bytes[1] != 0x15) { throw new ArgumentException("Second byte in array was expected to be 0x15", "bytes"); }
                //if (bytes.Length != 23) { throw new ArgumentException("Byte array length was expected to be 23", "bytes"); }
                return new BeaconData
                {
                    Uuid = new Guid(
                            BitConverter.ToInt32(bytes.Skip(2).Take(4).Reverse().ToArray(), 0),
                            BitConverter.ToInt16(bytes.Skip(6).Take(2).Reverse().ToArray(), 0),
                            BitConverter.ToInt16(bytes.Skip(8).Take(2).Reverse().ToArray(), 0),
                            bytes.Skip(10).Take(8).ToArray()),
                    Major = BitConverter.ToUInt16(bytes.Skip(18).Take(2).Reverse().ToArray(), 0),
                    Minor = BitConverter.ToUInt16(bytes.Skip(20).Take(2).Reverse().ToArray(), 0),
                    Battery = (byte)((byte)bytes[23] & (byte)0x7F)
                };
            }
            public static BeaconData FromBuffer(IBuffer buffer)
            {
                var bytes = new byte[buffer.Length];
                if(bytes.Length !=24)
                {
                    return null;
                }
                using (var reader = DataReader.FromBuffer(buffer))
                {
                    reader.ReadBytes(bytes);
                }
                return BeaconData.FromBytes(bytes);
            }
        }

        static void Main(string[] args)
        {
            var watcher = new BluetoothLEAdvertisementWatcher();
            watcher.Received += Watcher_Received;
            //watcher.SignalStrengthFilter.InRangeThresholdInDBm = RSSI;
            //watcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = RSSI-10;
            watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(100);
            watcher.ScanningMode = BluetoothLEScanningMode.Passive;
            BetterScanner.StartScanner(0, 29, 29);
            watcher.Start();

            Console.WriteLine("Bluetooth LE Advertisement Watcher Started (Press ESC to exit)");
            while (Console.ReadKey().Key != ConsoleKey.Escape)
            {
            }
            watcher.Stop();
            BetterScanner.StopScanner();
            Console.WriteLine("Bluetooth LE Advertisement Watcher Stopped");
        }

        private static void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
        {
            const ushort AppleCompanyId = 0x004C;
            Int16 rssi = args.RawSignalStrengthInDBm;
            foreach (var adv in args.Advertisement.ManufacturerData.Where(x => x.CompanyId == AppleCompanyId))
            {
                var beaconData = BeaconData.FromBuffer(adv.Data);
                if(beaconData == null)
                {
                    continue;
                }
                if (beaconData.Uuid.ToString() == UUID && beaconData.Major == MAJOR)
                {
                    if (rssi < RSSI)
                    {
                        Console.WriteLine("RSSI=" + rssi+ ", "+beaconData.Uuid+"_"+ beaconData.Major + "_" + beaconData.Minor);
                        continue;
                    }
                    Console.WriteLine(
                        "UUID={0}, Major={1}, Minor={2}, Battery={3}, Rssi={4}",
                        beaconData.Uuid,
                        beaconData.Major,
                        beaconData.Minor,
                        beaconData.Battery,
                        args.RawSignalStrengthInDBm);
                }
            }
        }
    }
}

DeviceControl 返回 false 但最后一个错误代码为 0 并且仍然进行延迟扫描。

DeviceControl ret=False
Bluetooth LE Advertisement Watcher Started (Press ESC to exit)
The last Win32 Error was: 0
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-51, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-73, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_9
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-70, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_18
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-74, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_9
RSSI=-48, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-74, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_18
RSSI=-50, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-51, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_6
RSSI=-64, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_18
RSSI=-75, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_9
RSSI=-76, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_9
RSSI=-67, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_18
RSSI=-75, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_9
RSSI=-78, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_9
RSSI=-87, 44790ba4-7eb3-4095-9e14-4b43ae67512b_40033_9

有 40033_1、3、6、9、18 个信标,但有些信标在某些时间段内没有被扫描。

广告间隔为 700 毫秒。

我应该检查什么?

4

0 回答 0