Giter Club home page Giter Club logo

wmipnp's Introduction

WMI for Plug-and-Play Devices

WMI = Windows Management Interface.

The primary goal of the project is to get battery level of WH-1000XM4 headphones. Perhaps Xm4Battery might also works with similar models of headphones such as WH-1000XM3, WF-1000XM3 or WF-1000XM4.

emoji_flash_bullet_battery_level_v23-5-2

Xm4Battery Application

The Windows Forms, trayiconed and window-less application at once.
Ready to run app is available under the Latest Release section.

System requirements: Windows 10 x64, .NET Desktop Runtime 6.0.

Headphones Win 10 Pro Win 11
WH-1000_XM4 Yes Expected
WH-1000_XM3 Yes Unknown

User Interface

  • F - 100% or fully charged
  • 9..4 - 90..40%
  • 3 yellow - 30%
  • 2 orange - 20%
  • 1 red - 10%
  • X - headphones disconnected, gray background. Tooltip shows the last known battery level and the last connected date/time.

Right Mouse Button opens a context menu:

  • Connect - tries connect already paired headphones. ⚠
  • Disconnect - tries disconnect headphones (not unpair, just disconnect). ⚠
  • About - leads to this page.
  • Quit - closes and unloads application at all.

Connect / Disconnect items appear if the app is run as an administrator.
⚠ These functions may cause system artefacts or unusual behavior of Volume Control, Sound Mixer, Bluetooth Device Manager, etc.
⚠ Especially the Disconnect item. Connect is a law-abiding one.

Tray Icon Mods

Background color is defined in the CreateIconForLevel method:

var brush =
    level switch {
        > 0 and <= 10 => Brushes.Red,
        > 0 and <= 20 => Brushes.Orange,
        > 0 and <= 30 => Brushes.Yellow,
        <= 0 => Brushes.Gray,
        _ => Brushes.White
    };

Font for notification icon text (battery level or headphones status):

static readonly Font _notifyIconFont
    = new ( "Segoe UI", 16, FontStyle.Regular );

Xm4Poller

Automatically updates status of headphones.

Start-Stop Polling

var xm4result = Xm4Entity.Create();
if ( xm4result.IsFailed ) return 1;

Xm4Entity xm4 = xm4result.Value;

Xm4Poller statePoll = new ( xm4 );
statePoll.ConnectionChanged += Xm4state_ConnectionChanged;
statePoll.BatteryLevelChanged += Xm4state_BatteryLevelChanged;
statePoll.Start();

// starts main loop of window-less WinForms app
Application.Run();

// application was closed, quit
statePoll.Stop();

Connection Changed

private static void Xm4state_ConnectionChanged(
    object? sender,
    bool connected )
{
    var xm4 = sender as Xm4Entity;
    ...

Battery Level Changed

private static void Xm4state_BatteryLevelChanged
    object? sender,
    int batteryLevel )
{
    var xm4 = sender as Xm4Entity;
    ...

Xm4Entity

Create XM4 Instance

var xm4result = Xm4Entity.Create();
if ( xm4result.IsFailed ) return; // headphones did not found at all

Xm4Entity _xm4 = xm4result.Value;

Is Connected or Not?

...
bool connected = _xm4.IsConnected;

What Is The Last Connected Time?

We don't know how to get the last connected time if headphones is online and already connected. This property is valid only if headphones are DISconnected.

Result<DateTime> dt = _xm4.LastConnectedTime;
bool disconnected = !_xm4.IsConnected;
if ( disconnected )
    Console.WriteLine( $"Last connected time: {_xm4.LastConnectedTime.Value}.\n" );
else
    var it_is_true = _xm4.LastConnectedTime.IsFailed; // can not get the last connected time

Headphones Battery Level

It can get the actual battery level if headphones are connected. Otherwise, headphones are DISconnected, it returns the last known level.

int level = _xm4.BatteryLevel;

Re/Connect Already Paired

When headphones are used with multiple sources (laptop, pc, smartphone, etc) you have to reconnect headphones from time to time. So headphones are already paired but disconnected. In this case WmiPnp has experimental Xm4Entity.TryConnect() and very unstable Xm4Entity.TryDisconnect(). Both want the application run as administrator. Otherwise these functions are ignored.

If you are curious to find out about turn off bluetooth at all, see topic about Windows Radio.

PnpEntity

First, we should know a name or device id of the device we are working with or at least a part of the device name.

  • ByFriendlyName - exact a friendly name.
  • ByDeviceId - exact a device id, like {GUID} pid.
  • LikeFriendlyName - a part of a friendly name. Returns a list of founded devices IEnumerable<PnpEntity> or empty list otherwise.
  • EntityOrNone - a where part of WQL request to retrieve exact a single device only.
  • EntitiesOrNone - a where part of WQL request to retrieve zero, one or several devices at once.

All of methods produce instances of PnpEntity or Result.Fail if the given device was not found.

How To Find PNP Device?

Result<PnpEntity> result =
    PnpEntity.ByFriendlyName( "The Bluetooth Device #42" );

if ( result.IsSuccess ) { // device found
    PnpEntity btDevice = result.Value;
    ...
}

Get / Update Specific Device Property

...
PnpEntity btDevice = result.Value;

Result<DeviceProperty> propertyResult =
    btDevice.GetDeviceProperty( PnpEntity.DeviceProperty_IsConnected );

if ( propertyResult.IsSuccess ) {
    DeviceProperty dp = propertyResult.Value;

    while ( !Console.KeyAvailable ) {
        bool connected = (bool)(dp.Data ?? false);

        Console.WriteLine(
            $"{btDevice.Name} is {(connected ? "connected" : "disconnected")}" );

        btDevice.UpdateProperty( dp );
    }    
}

Enumerate Device Properties

...
PnpEntity btDevice = result.Value;

IEnumerable<DeviceProperty> properties = btDevice.UpdateProperties();

foreach( var p in properties ) {
    Console.WriteLine( $"{p.KeyName}: {p.Data}" );
    ...
}

Same thing but with a cached list of the last updated properties

_ = btDevice.UpdateProperties();

foreach( var p in btDevice.Properties ) {
    ...

Enable-Disable Device

Some devices could be enabled or disabled.

...
PnpEntity btDevice = result.Value;

btDevice.Disable();
btDevice.Enable();

Device Specific Properties

Key = {GUID} pid

Battery Level

  • Key = {104EA319-6EE2-4701-BD47-8DDBF425BBE5} 2
  • Type = 3 (Byte)

Data is in percents

Is Connected

  • Key = {83DA6326-97A6-4088-9453-A1923F573B29} 15
  • Type = 17 (Boolean)

Data = False → device is disconnected

Last Arrival Date

  • Key = {83DA6326-97A6-4088-9453-A1923F573B29} 102
  • KeyName = DEVPKEY_Device_LastArrivalDate
  • Type = 16 (FileTime)

Data = 20230131090906.098359+180 → 2023 Jan 31, 9:09:06 GMT+3

Last Removal Date

Key = {83da6326-97a6-4088-9453-a1923f573b29} 103

XM4 Related Properties

  • WH-1000XM4 Hands-Free AG - exact name for PnpEntity to get a BATTERY LEVEL only.
  • WH-1000XM4 - exact name for PnpEntity to get a STATE of the xm4.

Actually, the app utilize templates like W_-1000XM_ to generalize model of headphones (WH-1000XM3, WF-1000XM4, etc.)

DEVPKEY_Device_DevNodeStatus

Instead of this bit flags, we can use Is Connected property to retrieve a connection status of xm4.

  • Key = {4340A6C5-93FA-4706-972C-7B648008A5A7} 2

  • KeyName = DEVPKEY_Device_DevNodeStatus

  • Type = 7 (Uint32)

  • Connected = 25190410 (fall bit#25): value & 0x20000 == 0

  • Disconnected = 58744842 (set bit#25): value & 0x20000 == 0x20000

DEVPKEY_Bluetooth_LastConnectedTime

This is only property to retrieve the last connection date-time of headphones. This property appears only when headphones are DISconnected.

  • Key = {2BD67D8B-8BEB-48D5-87E0-6CDA3428040A} 11
  • KeyName = DEVPKEY_Bluetooth_LastConnectedTime
  • Type = 16 (FileTime)

For ex.: Data = 20230131090906.098359+180 → 2023 Jan 31, 9:09:06, GMT+3

?Last Connected Time

Contains the same data as the DEVPKEY_Bluetooth_LastConnectedTime property. Same behavior.

  • Key = {2BD67D8B-8BEB-48D5-87E0-6CDA3428040A} 5
  • Type = 16 (FileTime)

Windows Radio

Preparation

There is a way to use UWP functions from desktop application. Just setup a TargetFramework in YourProject.csproj to use specific version of dotNet-framework-windows-only like: netX.x-windows10.0.xxxxx.x. For example:

<PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
    ...

Switch system bluetooth on and off

Now we can use Windows.Devices.Radios namespace:

using Windows.Devices.Radios;

⚠ Be aware, this one could switch off system bluetooth radio at all (not only enable or disable).
⚠ Use at your own risk!

public static async Task OsEnableBluetooth()
    => InternalBluetoothState( enable: true );

public static async Task OsDisableBluetooth()
    => InternalBluetoothState( enable: false );

private async Task InternalBluetoothState( bool enable )
{
    var result = await Radio.RequestAccessAsync();
    if (result != RadioAccessStatus.Allowed) return;

    var bluetooth =
        (await Radio.GetRadiosAsync())
        .FirstOrDefault(
            radio => radio.Kind == RadioKind.Bluetooth );

    await bluetooth?.SetStateAsync(
        enable ? RadioState.On
        : RadioState.Off
        );
}

We can also use Windows.Devices.Bluetooth namespace or even Windows.Devices.*** for other peripheral devices.

References

wmipnp's People

Contributors

nikvoronin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

beatcracker

wmipnp's Issues

The program doesn't respond at all at windows11 OS

I downloaded the latest code and successfully compiled it, but when it ran, there was no reaction, and there was no useful information in the process. I downloaded your distribution version, and the situation is the same. May I ask what caused it?

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.