K.Lawrence
K.Lawrence

Reputation: 146

In C# how do I get the LMP version number?

I would be okay with a C/C++ answer as I can bridge the gap between them.

Device Manager Properties Page

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

Answers (1)

Iłya Bursov
Iłya Bursov

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(&params, &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

Related Questions