Reputation: 146
I would be okay with a C/C++ answer as I can bridge the gap between them.
I can get the LMP Subversion in C++ (the 8207), but how do I get the LMP version (the 11)?
I need it to be programmatic so I can tell the user if certain features (e.g. BLE supported at version 4.0+).
I have spent the better part of a day on google and made a program in C to get the LMP subversion but have not found a single reference to reading the LMP value other than to visually read it off the dialog in Device Panel.
C# Answer Based on comment by @Mike Petrichenko and Answer by @Iłya Bursov
Simple code to list off supported features and make other details available:
namespace BluetoothInfoReader
{ internal class Program { static void Main(string[] args) { var features = Api.GetSupportedFeatures();
foreach(HostFeature feature in Enum.GetValues(typeof(HostFeature)))
{
if (feature != HostFeature.None)
{
Console.WriteLine($"{feature}: {((features & feature) == feature)}");
}
}
var info = Api.GetRadioInfo();
// Any more info required can be gotten from "info"
Console.ReadLine();
}
}
}
Supporting structs and enums:
namespace Bluetooth
{ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct FindRadioParams { public FindRadioParams() { dwSize = (uint)Marshal.SizeOf(); }
public uint dwSize;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DeviceInfo
{
public uint flags;
public ulong address;
public uint classOfDevice;
// 248 is the value of BTH_MAX_NAME_SIZE
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 248)]
public string name;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct RadioInfo
{
public ulong lmpSupportedFeatures;
public ushort mfg;
public ushort lmpSubversion;
public byte lmpVersion;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct LocalRadioInfo
{
public DeviceInfo localInfo;
public uint flags;
public ushort hciRevision;
public byte hciVersion;
public RadioInfo radioInfo;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Overlapped
{
public ulong Internal;
public ulong InternalHigh;
public IntPtr Pointer;
public IntPtr hEvent;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct HostFeatureMask
{
public HostFeature Mask;
public ulong Reserved1;
public ulong Reserved2;
}
public enum HostFeature : ulong
{
None = 0,
EnhancedRetransmissionMode = (0x0000000000000001),
StreamingMode = (0x0000000000000002),
LowEnergy = (0x0000000000000004),
ScoHci = (0x0000000000000008),
ScoHciBypass = (0x0000000000000010)
}
public static class IoControl
{
const int FILE_DEVICE_BLUETOOTH = 0x00000041;
const int METHOD_BUFFERED = 0;
const int FILE_ANY_ACCESS = 0;
const int BTH_IOCTL_BASE = 0;
private static uint CtlCodeCreator(ushort deviceType, ushort function, byte method, byte access)
{
uint result = (uint)((deviceType << 16) | (access << 14) | (function << 2) | method);
return result;
}
private static uint BthCtlCodeCreator(ushort id)
{
return CtlCodeCreator((ushort)FILE_DEVICE_BLUETOOTH, id, (byte)METHOD_BUFFERED, (byte)FILE_ANY_ACCESS);
}
public static readonly uint GetLocalInfo = BthCtlCodeCreator(BTH_IOCTL_BASE + 0x00);
public static readonly uint GetHostSupportedFeatures = BthCtlCodeCreator(BTH_IOCTL_BASE + 0x88);
}
public static class Api
{
[DllImport("bthprops.cpl")]
public static extern IntPtr BluetoothFindFirstRadio([In] ref FindRadioParams findRadioParams, [Out] out IntPtr hRadio);
[DllImport("bthprops.cpl")]
public static extern bool BluetoothFindNextRadio([In] IntPtr hFindHandle, [Out] out IntPtr hRadio);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr handle);
[DllImport("kernel32.dll")]
public static extern bool DeviceIoControl([In] IntPtr hDevice, uint dwIoControlCode, [In] IntPtr inBuffer, uint nInBufferSize, [Out] IntPtr outBuffer, uint nOutBufferSize, [Out] out uint bytesReturned, [In, Out] ref Bluetooth.Overlapped overlapped);
public static bool DeviceIoControl<T>([In] IntPtr hDevice, uint dwIoControlCode, [In] IntPtr inBuffer, uint nInBufferSize, [Out] out T outBuffer) where T : struct
{
int size = Marshal.SizeOf<T>();
IntPtr buffer = Marshal.AllocHGlobal(size);
Overlapped overlapped = new Overlapped();
var result = DeviceIoControl(hDevice, dwIoControlCode, inBuffer, nInBufferSize, buffer, (uint)size, out uint bytesReturned, ref overlapped);
if (bytesReturned != size)
{
// Houston we have a problem.
}
outBuffer = Marshal.PtrToStructure<T>(buffer);
return result;
}
public static HostFeature GetSupportedFeatures()
{
HostFeature result = HostFeature.None;
FindRadioParams findParams = new FindRadioParams();
var findHandle = BluetoothFindFirstRadio(ref findParams, out IntPtr radioHandle);
if (findHandle != IntPtr.Zero)
{
do
{
if (DeviceIoControl(radioHandle, IoControl.GetHostSupportedFeatures, IntPtr.Zero, 0, out Bluetooth.HostFeatureMask features))
{
result = features.Mask;
break;
}
} while (BluetoothFindNextRadio(findHandle, out radioHandle));
CloseHandle(radioHandle);
}
return result;
}
public static LocalRadioInfo GetRadioInfo()
{
LocalRadioInfo result = new LocalRadioInfo();
FindRadioParams findParams = new FindRadioParams();
var findHandle = BluetoothFindFirstRadio(ref findParams, out IntPtr radioHandle);
if (findHandle != IntPtr.Zero)
{
do
{
if (DeviceIoControl(radioHandle, IoControl.GetLocalInfo, IntPtr.Zero, 0, out result))
{
break;
}
} while (BluetoothFindNextRadio(findHandle, out radioHandle));
CloseHandle(radioHandle);
}
return result;
}
}
}
Upvotes: 1
Views: 204
Reputation: 24156
as @Mike Petrichenko pointed, DeviceIoControl returns this information, but I'm not sure that you actually want version, probably it is better to query feature support instead:
#include <iostream>
#include <windows.h>
#include <bluetoothapis.h>
#include <bthioctl.h>
int main() {
BLUETOOTH_FIND_RADIO_PARAMS params;
params.dwSize = sizeof(params);
HANDLE hRadio;
HBLUETOOTH_RADIO_FIND hFind = ::BluetoothFindFirstRadio(¶ms, &hRadio);
if (hFind) {
do {
{
BTH_LOCAL_RADIO_INFO output;
DWORD returned;
if (::DeviceIoControl(hRadio, IOCTL_BTH_GET_LOCAL_INFO, NULL, 0, &output, sizeof(output), &returned, NULL)) {
std::cout << "HCI: " << (unsigned)output.hciVersion << "." << output.hciRevision << std::endl;
std::cout << "LMP: " << (unsigned)output.radioInfo.lmpVersion << "." << output.radioInfo.lmpSubversion << std::endl;
std::cout << "LMP_LE_SUPPORTED: " << (LMP_LE_SUPPORTED(output.radioInfo.lmpSupportedFeatures) ? "YES" : "NO") << std::endl;
}
}
{
BTH_HOST_FEATURE_MASK output;
DWORD returned;
if (::DeviceIoControl(hRadio, IOCTL_BTH_GET_HOST_SUPPORTED_FEATURES, NULL, 0, &output, sizeof(output), &returned, NULL)) {
std::cout << "BTH_HOST_FEATURE_LOW_ENERGY: " << ((output.Mask & BTH_HOST_FEATURE_LOW_ENERGY) ? "YES" : "NO") << std::endl;
}
}
::CloseHandle(hRadio);
} while (::BluetoothFindNextRadio(hFind, &hRadio));
::BluetoothFindRadioClose(hFind);
}
}
Upvotes: 2