{"id":20526549,"url":"https://github.com/nikvoronin/xm4battery","last_synced_at":"2025-04-14T04:20:39.038Z","repository":{"id":144197571,"uuid":"603537114","full_name":"nikvoronin/Xm4Battery","owner":"nikvoronin","description":"Battery level of WH-1000XM4 headphones. Based on WMI wrapper for Plug-n-Play devices","archived":false,"fork":false,"pushed_at":"2025-03-23T21:28:37.000Z","size":156,"stargazers_count":11,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-23T22:31:20.206Z","etag":null,"topics":["battery-level","battery-monitor","bluetooth","csharp","dotnet","plug-and-play","plug-n-play","wf-1000xm4","wh-1000xm3","wh-1000xm4","windows","winforms-desktop-application","wmi","wmi-query"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nikvoronin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-18T20:12:00.000Z","updated_at":"2025-03-23T21:28:40.000Z","dependencies_parsed_at":"2025-03-23T22:24:25.910Z","dependency_job_id":"a1809c55-6c2a-4d34-9760-068808d09404","html_url":"https://github.com/nikvoronin/Xm4Battery","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikvoronin%2FXm4Battery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikvoronin%2FXm4Battery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikvoronin%2FXm4Battery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikvoronin%2FXm4Battery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nikvoronin","download_url":"https://codeload.github.com/nikvoronin/Xm4Battery/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248819634,"owners_count":21166512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["battery-level","battery-monitor","bluetooth","csharp","dotnet","plug-and-play","plug-n-play","wf-1000xm4","wh-1000xm3","wh-1000xm4","windows","winforms-desktop-application","wmi","wmi-query"],"created_at":"2024-11-15T23:14:42.658Z","updated_at":"2025-04-14T04:20:39.027Z","avatar_url":"https://github.com/nikvoronin.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Xm4Battery\n\n--over WMI for plug-and-play devices\n\n\u003e WMI = Windows Management Interface.\n\nThe 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.\n\n![emoji_flash_bullet_battery_level_v23-5-2](https://user-images.githubusercontent.com/11328666/235766399-44585bee-0e8f-4d21-b96a-81b58b9e83d2.jpg)\n\n- [Desktop Application](#desktop-application)\n    - [User interface](#user-interface)\n    - [Tray icon mods](#tray-icon-mods)\n- [Xm4Poller](#xm4poller)\n    - [Start and stop device polling](#start-and-stop-device-polling)\n    - [Xm4State](#xm4state)\n- [Xm4Entity](#xm4entity)\n    - [Create XM4 instance](#create-xm4-instance)\n    - [Is connected or not?](#is-connected-or-not)\n    - [What was the last connected time?](#what-was-the-last-connected-time)\n    - [Headphones battery level](#headphones-battery-level)\n    - [Re/Connect already paired](#reconnect-already-paired)\n- [PnpEntity](#pnpentity)\n    - [How To find PNP-device?](#how-to-find-pnp-device)\n    - [Get and update a specific property of a device](#get-and-update-a-specific-property-of-a-device)\n    - [Enumerate all properties of device](#enumerate-all-properties-of-device)\n    - [Enable or disable device](#enable-or-disable-device)\n- [Device specific properties](#device-specific-properties)\n- [XM4 related properties](#xm4-related-properties)\n- [Windows Radio](#windows-radio)\n- [References](#references)\n\n## Desktop Application\n\nThe Windows Forms, trayiconed and window-less application at once.\\\nReady to run app is available under the [Latest Release](https://github.com/nikvoronin/WmiPnp/releases/latest) section.\n\n__System requirements:__ Windows 10 x64, [.NET Desktop Runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)\n\n| Headphones  | Win 10 | Win 11  |\n| ----------- | ------ | ------- |\n| WH-1000_XM4 | Yes    | Yes     |\n| WH-1000_XM3 | Yes    | Unknown |\n\n### User interface\n\n- __F__ - 100% fully charged\n- __4..9__ - 40..90%\n- __3__ yellow - 30%\n- __2__ orange - 20%\n- __!__ red - 10%\n- __X__ - headphones disconnected.\n- __%__ - headphones disconnected, the last known battery level was low (30% or lower).\n\nWhen headphones are disconnected, a tooltip displays the last known battery level and the last known date/time of the headphone connection.\n\n`Right Mouse Button` opens a context menu:\n\n- Connect - tries connect already paired headphones. ⚠\n- Disconnect - tries disconnect headphones (not unpair, just disconnect). ⚠\n- About - leads to this page.\n- Quit - closes and unloads application at all.\n\n\u003e⚠ __Connect / Disconnect__ items appear if the app is run as an administrator.\\\n\u003e⚠ These functions may cause system artefacts or unusual behavior of Volume Control, Sound Mixer, Bluetooth Device Manager, etc.\\\n\u003e⚠ Especially the Disconnect item. Connect is a law-abiding one.\n\n### Tray icon mods\n\nThe real icon size is 256x256 pixels. It is automatically scaled by system depend on display scaling factor.\n\n\u003eThe app icon is currently adjusted to 125% display scale. Other scale factors may lead to uglifying tray icon.\n\nIcon text color and background are defined in the `CreateXmIcon` method:\n\n```csharp\n// icon background color\nvar iconBackgroundBrush =\n    uiBatteryLevel switch {\n        \u003c= DisconnectedLevel =\u003e Brushes.Transparent,\n        \u003c= CriticalPowerLevel =\u003e Brushes.Red,\n        \u003c= LowPowerLevel =\u003e Brushes.Orange,\n        \u003c= WarningPowerLevel =\u003e Brushes.Yellow,\n        _ =\u003e Brushes.White // 40..100(F)\n    };\n\n// icon text color\nvar iconTextBrush =\n    uiBatteryLevel switch {\n        \u003c= DisconnectedLevel =\u003e Brushes.WhiteSmoke,\n        \u003c= CriticalPowerLevel =\u003e Brushes.White,\n        //\u003c= LowPowerLevel =\u003e Brushes.Magenta,\n        //\u003c= WarningLevel =\u003e Brushes.Cyan,\n        _ =\u003e Brushes.Black\n    };\n```\n\nFont of the notification icon text (battery level or headphones status):\n\n```csharp\nstatic readonly Font _notifyIconFont =\n    new( \"Segoe UI\", 124, FontStyle.Regular );\n```\n\n## Xm4Poller\n\nAutomatically updates status of headphones.\n\n### Start and stop device polling\n\n```csharp\nvar xm4result = Xm4Entity.Create();\nif ( xm4result.IsFailed ) return 1;\n\nXm4Entity xm4 = xm4result.Value;\n\nvar statePoller = new Xm4Poller ( \n    xm4,\n    ( previousState, newState ) =\u003e {\n        // this handler is called when xm4 state changed:\n        // connection status or/and battery charge level.\n        // previousState \u003c\u003e newState - always unequal!\n        UpdateUi_ForExample(newState);\n    } );\n\nstatePoller.Start();\n\n// starts main loop of window-less WinForms app\nApplication.Run();\n\n// application was closed, quit\nstatePoller.Stop();\n```\n\n### Xm4State\n\n```csharp\nnamespace WmiPnp.Xm4;\n\npublic record Xm4State\n{\n    public bool Connected   // true if connected, false - otherwise.\n    public int BatteryLevel // battery charge level\n```\n\n## Xm4Entity\n\n### Create XM4 instance\n\n```csharp\nvar xm4result = Xm4Entity.CreateDefault();\nif ( xm4result.IsFailed ) return; // headphones did not found at all\n\nXm4Entity _xm4 = xm4result.Value;\n```\n\n### Is connected or not?\n\n```csharp\n...\nbool connected = _xm4.IsConnected;\n```\n\n### What was the last connected time?\n\nWe 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.\n\n```csharp\nResult\u003cDateTime\u003e dt = _xm4.LastConnectedTime;\n```\n\n```csharp\nbool disconnected = !_xm4.IsConnected;\nif ( disconnected )\n    Console.WriteLine( $\"Last connected time: {_xm4.LastConnectedTime.Value}.\\n\" );\nelse\n    var it_is_true = _xm4.LastConnectedTime.IsFailed; // can not get the last connected time\n```\n\n### Headphones battery level\n\nIt can get the actual battery level if headphones are connected. Otherwise, headphones are DISconnected, it returns the last known level.\n\n```csharp\nint level = _xm4.BatteryLevel;\n```\n\n### Re/Connect already paired\n\nWhen 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.\n\nIf you are curious to find out about turn off bluetooth at all, see topic about [Windows Radio](#windows-radio).\n\n## PnpEntity\n\nFirst, we should know a `name` or `device id` of the device we are working with or at least a part of the device name.\n\n- ByFriendlyName - exact a friendly name.\n- ByDeviceId - exact a device id, like `{GUID} pid`.\n- FindByFriendlyName - a part of a friendly name. Returns a list of founded devices `IEnumerable\u003cPnpEntity\u003e` or empty list otherwise.\n- FindByNameForExactClass - same as `FindByFriendlyName` but with exact class name equality.\n- EntityOrNone - a `where` part of WQL request to retrieve exact a single device only.\n- EntitiesOrNone - a `where` part of WQL request to retrieve zero, one or several devices at once.\n\nAll of methods produce instances of `PnpEntity` or `Result.Fail` if the given device was not found.\n\n### How To find PNP-device?\n\n```csharp\nResult\u003cPnpEntity\u003e result =\n    PnpEntity.ByFriendlyName( \"The Bluetooth Device #42\" );\n\nif ( result.IsSuccess ) { // device found\n    PnpEntity btDevice = result.Value;\n    ...\n}\n```\n\n### Get and update a specific property of a device\n\n```csharp\n...\nPnpEntity btDevice = result.Value;\n\nwhile ( !Console.KeyAvailable ) {\n    Result\u003cDeviceProperty\u003e propertyResult =\n        btDevice.GetDeviceProperty(\n            Xm4Entity.DeviceProperty_IsConnected );\n\n    if ( propertyResult.IsSuccess ) {\n        DeviceProperty dp = propertyResult.Value;\n        bool connected = (bool)(dp.Data ?? false);\n\n        Console.WriteLine(\n            $\"{btDevice.Name} is {(connected ? \"connected\" : \"disconnected\")}\" );\n    }\n\n    // wait a little before the next attempt\n    Thread.Sleep( TimeSpan.FromSeconds( 1 ) );\n}\n```\n\n### Enumerate all properties of device\n\n```csharp\n...\nPnpEntity btDevice = result.Value;\n\nIEnumerable\u003cDeviceProperty\u003e properties = btDevice.GetProperties();\n\nforeach( var p in properties ) {\n    Console.WriteLine( $\"{p.KeyName}: {p.Data}\" );\n    ...\n}\n```\n\n### Enable or disable device\n\nSome devices could be enabled or disabled.\n\n```csharp\n...\nPnpEntity btDevice = result.Value;\n\nbtDevice.Disable();\nbtDevice.Enable();\n```\n\n## Device specific properties\n\n\u003e Key = {GUID} pid\n\n\u003c!-- omit in toc --\u003e\n### Battery level\n\n- Key = `{104EA319-6EE2-4701-BD47-8DDBF425BBE5} 2`\n- Type = 3 (Byte)\n\n`Data` is in percents\n\n\u003c!-- omit in toc --\u003e\n### Is connected or not\n\n- Key = `{83DA6326-97A6-4088-9453-A1923F573B29} 15`\n- Type = 17 (Boolean)\n\nData = False → device is disconnected\n\n\u003c!-- omit in toc --\u003e\n### Last arrival date\n\n- Key = `{83DA6326-97A6-4088-9453-A1923F573B29} 102`\n- KeyName = DEVPKEY_Device_LastArrivalDate\n- Type = 16 (FileTime)\n\nData = 20230131090906.098359+180 → 2023 Jan 31, 9:09:06 GMT+3\n\n\u003c!-- omit in toc --\u003e\n### Last removal date\n\nKey = {83da6326-97a6-4088-9453-a1923f573b29} 103\n\n## XM4 related properties\n\n- `WH-1000XM4 Hands-Free AG` - exact name for PnpEntity to get a __BATTERY LEVEL__ only.\n- `WH-1000XM4` - exact name for PnpEntity to get a __STATE__ of the xm4.\n\n\u003e Actually, the app utilize templates like `W_-1000XM_` to generalize model of headphones (WH-1000XM3, WF-1000XM4, etc.)\n\n\u003c!-- omit in toc --\u003e\n### DEVPKEY_Device_DevNodeStatus\n\n\u003e Instead of this bit flags, we can use [Is Connected](#is-connected) property to retrieve a connection status of xm4.\n\n- Key = `{4340A6C5-93FA-4706-972C-7B648008A5A7} 2`\n- KeyName = DEVPKEY_Device_DevNodeStatus\n- Type = 7 (Uint32)\n\n- Connected = 25190410 (fall bit#25): value \u0026 0x20000 == 0\n- Disconnected = 58744842 (set bit#25): value \u0026 0x20000 == 0x20000\n\n\u003c!-- omit in toc --\u003e\n### DEVPKEY_Bluetooth_LastConnectedTime\n\nThis is only property to retrieve the last connection date-time of headphones. This property appears only when headphones are DISconnected.\n\n- Key = `{2BD67D8B-8BEB-48D5-87E0-6CDA3428040A} 11`\n- KeyName = DEVPKEY_Bluetooth_LastConnectedTime\n- Type = 16 (FileTime)\n\nFor ex.: Data = 20230131090906.098359+180 → 2023 Jan 31, 9:09:06, GMT+3\n\n\u003c!-- omit in toc --\u003e\n### ?Last connected time\n\nContains the same data as the [DEVPKEY_Bluetooth_LastConnectedTime](#devpkey_bluetooth_lastconnectedtime) property. Same behavior.\n\n- Key = `{2BD67D8B-8BEB-48D5-87E0-6CDA3428040A} 5`\n- Type = 16 (FileTime)\n\n## Windows Radio\n\n\u003c!-- omit in toc --\u003e\n### Preparation\n\nThere 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:\n\n```xml\n\u003cPropertyGroup\u003e\n    \u003cOutputType\u003eWinExe\u003c/OutputType\u003e\n    \u003cTargetFramework\u003enet6.0-windows10.0.17763.0\u003c/TargetFramework\u003e\n    ...\n```\n\n\u003c!-- omit in toc --\u003e\n### Switch system bluetooth on and off\n\nNow we can use `Windows.Devices.Radios` namespace:\n\n```csharp\nusing Windows.Devices.Radios;\n```\n\n\u003e⚠ Be aware, this one could switch off system bluetooth radio __at all__ (not only enable or disable).\\\n\u003e⚠ Use at your own risk!\n\n```csharp\npublic static async Task OsEnableBluetooth() =\u003e\n    InternalBluetoothState( enable: true );\n\npublic static async Task OsDisableBluetooth() =\u003e\n    InternalBluetoothState( enable: false );\n\nprivate async Task InternalBluetoothState( bool enable )\n{\n    var result = await Radio.RequestAccessAsync();\n    if (result != RadioAccessStatus.Allowed) return;\n\n    var bluetooth =\n        (await Radio.GetRadiosAsync())\n        .FirstOrDefault(\n            radio =\u003e radio.Kind == RadioKind.Bluetooth );\n\n    await bluetooth?.SetStateAsync(\n        enable ? RadioState.On\n        : RadioState.Off );\n}\n```\n\n\u003e We can also use `Windows.Devices.Bluetooth` namespace or even `Windows.Devices.***` for other peripheral devices.\n\n## References\n\n- [Enumerating windows device](https://www.codeproject.com/articles/14412/enumerating-windows-device). Enumerating the device using the SetupDi* API provided with WinXP. CodeProject // 17 Jun 2006\n- [How to get the details for each enumerated device?](https://social.msdn.microsoft.com/Forums/en-US/65086709-cee8-4efa-a794-b32979abb0ea/how-to-get-the-details-for-each-enumerated-device?forum=vbgeneral) MSDN, Archived Forums 421-440. `Visual Basic`\n- [Query battery level for WH-1000XM4 wireless headphones](https://gist.github.com/nikvoronin/e8fc8a1631dd0e851f1ab821d0e3cf01). GitHub gist. `PowerShell`\n- [Enable/disable already paired bluetooth devices](https://stackoverflow.com/questions/62502414/how-to-connect-to-a-paired-audio-bluetooth-device-using-windows-uwp-api/71539568#71539568). StackOverflow. How to connect to a paired audio Bluetooth device using Windows UWP API? `PowerShell`\n- [Talking to robots (or other devices) using Bluetooth from a Windows Runtime app](https://blogs.windows.com/windowsdeveloper/2014/05/07/talking-to-robots-or-other-devices-using-bluetooth-from-a-windows-runtime-app/). `Windows.Devices.Bluetooth.Rfcomm` namespace // May 7, 2014\n- [My Bluetooth headset can now be switched on and off from the command line](https://superuser.com/a/1815325). `PowerShell`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikvoronin%2Fxm4battery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnikvoronin%2Fxm4battery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikvoronin%2Fxm4battery/lists"}