{"id":5404,"date":"2024-11-04T15:12:05","date_gmt":"2024-11-04T14:12:05","guid":{"rendered":"https:\/\/pmortensen.eu\/world2\/?p=5404"},"modified":"2026-01-31T03:54:24","modified_gmt":"2026-01-31T02:54:24","slug":"the-battery-state-of-a-keychron-qmk-based-keyboard-can-be-displayed-in-the-operating-system","status":"publish","type":"post","link":"https:\/\/pmortensen.eu\/world2\/2024\/11\/04\/the-battery-state-of-a-keychron-qmk-based-keyboard-can-be-displayed-in-the-operating-system\/","title":{"rendered":"The battery state of a wireless Keychron QMK-based keyboard can be displayed in the operating system"},"content":{"rendered":"<p>The short version: A wireless <a href=\"https:\/\/docs.qmk.fm\/#\/faq_general?id=what-is-qmk\">QMK<\/a>-based <a href=\"https:\/\/deskthority.net\/wiki\/Keychron\">Keychron<\/a> keyboard can provide the information about the battery state, e.g. &#8220;81%&#8221;, to the operating system, just like, for example, a wireless computer mouse. Though the operating system may have to be configured for it to work, and it may not work for all Keychron keyboard models.<\/p>\n<p>An example is a Keychron <a href=\"https:\/\/www.keychron.com\/products\/keychron-v6-max-qmk-via-wireless-custom-mechanical-keyboard-iso-layout-collection\">V6 Max<\/a> on <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ubuntu_version_history#Ubuntu_22.04_LTS_(Jammy_Jellyfish)\">Ubuntu&nbsp;22.04<\/a> (Jammy Jellyfish), after making some configuration changes in the operating system (it isn&#8217;t necessary to install any additional software). The battery state <a href=\"https:\/\/pmortensen.eu\/world2\/wp-content\/uploads\/2024\/11\/Keychron_V6_Max_battery_84_percent_Ubuntu_panel_Settings_Power.png\">is shown<\/a> in <em>&#8220;Settings&#8221;<\/em> &rarr; <em>&#8220;Power&#8221;<\/em>, along with other wireless devices that provide the information, e.g., mice and headsets.<\/p>\n<h2>Introduction<\/h2>\n<p>On the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Reddit#Subreddits\">subreddit<\/a> <a href=\"https:\/\/www.reddit.com\/r\/Keychron\">r\/Keychron<\/a>, questions about displaying the battery state in the operating system for Keychron keyboards, like for other wireless devices, say mice and headphones, is an <a href=\"https:\/\/en.wikipedia.org\/wiki\/FAQ\">FAQ<\/a>. But not much real information is usually provided.<\/p>\n<p>Thus I decided to dig deeper. I couldn&#8217;t get it to work at all on <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ubuntu_version_history#Ubuntu_20.04_LTS_(Focal_Fossa)\">Ubuntu&nbsp;20.04<\/a> (Focal Fossa) using <a href=\"https:\/\/askubuntu.com\/questions\/1117563\/check-bluetooth-headphones-battery-status-in-linux\/1420501#1420501\">a prescribed way<\/a>, including for mice and headsets for which it was expected to work. The keyboard didn&#8217;t provide the &#8220;Battery Percentage&#8221; line in the output from &#8220;info&#8221; in &#8216;bluetoothctl&#8217;.<\/p>\n<p>But using a newer Ubuntu version (Ubuntu 22.04) and the described configuration change, I could get the battery to show in the GUI for both a &#8216;2.4 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Hertz#SI_multiples\">&nbsp;GHz<\/a>&#8216; mouse, two Bluetooth headsets, and a Keychron <a href=\"https:\/\/www.keychron.com\/products\/keychron-v6-max-qmk-via-wireless-custom-mechanical-keyboard-iso-layout-collection\">V6 Max<\/a> keyboard.<\/p>\n<p>The charge state can be displayed on the Keychron keyboard itself by <strong><em>Fn<\/em><\/strong> + <strong><em>B<\/em><\/strong>, but this is about the information being transferred to the computer and being shown in the GUI.<\/p>\n<h2 id=\"ConfigurationChange\">The configuration change in Linux<\/h2>\n<p>It is essentially as <a href=\"https:\/\/askubuntu.com\/questions\/1117563\/check-bluetooth-headphones-battery-status-in-linux\/1420501#1420501\">in here<\/a>. The value-add here is to <strong><em>test it<\/em><\/strong> on particular versions of Ubuntu, and for particular Keychron keyboards to find out if and where it actually works (or not). Also, caveats and testing steps are provided to be confident it will work (instead of only doing blind configuration changes).<\/p>\n<p>It can be done as (from the <a href=\"https:\/\/en.wiktionary.org\/wiki\/command_line#Noun\">command line<\/a>):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nsudo vi \/etc\/bluetooth\/main.conf\r\n<\/pre>\n<p>In file <em>main.conf<\/em>, add a line with this content:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nExperimental = true\r\n<\/pre>\n<p><strong><em>Notes<\/em><\/strong>: <\/p>\n<ol>\n<li>The line should be in the <em>&#8220;[General]&#8221;<\/em> section, not, for example, the <em>&#8220;[Policy]&#8221;<\/em> section.<\/li>\n<li>Proficiency in using <a href=\"https:\/\/en.wikipedia.org\/wiki\/Vi\">vi<\/a> or <a href=\"https:\/\/en.wikipedia.org\/wiki\/Vim_%28text_editor%29\">Vim<\/a> is presumed (including <a href=\"https:\/\/stackoverflow.com\/questions\/11828270\/\">quitting it<\/a>&#8230;). If not, use some other editor, like <a href=\"https:\/\/pmortensen.eu\/world2\/2020\/03\/29\/using-geany\/\">Geany<\/a>. But it must be run with administrator privileges (in order to make changes to file <em>main.conf<\/em>), and this may change an application&#8217;s normal behaviour&#8230; (because it is run as another user)<\/li>\n<\/ol>\n<p>Then, from the command line, restart the Bluetooth service in order to apply the change (alternatively, restart the computer):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nsudo systemctl restart bluetooth\r\n<\/pre>\n<p>Check that the line with &#8220;Experimental&#8221; is accepted:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nsystemctl status bluetooth\r\n<\/pre>\n<p>The output should <strong><em>not<\/em><\/strong> contain &#8220;Unknown key Experimental for group General&#8221;.<\/p>\n<p>Note that, despite <a href=\"https:\/\/stackoverflow.com\/questions\/49078659\/check-battery-level-of-connected-bluetooth-device-on-linux\/55008142#55008142\">this 2019-05-03 Stack Overflow answer<\/a>, it actually does work on Ubuntu 22.04 (or at least the &#8216;dbus-send&#8217; line didn&#8217;t work at all (<em>&#8220;Error org.freedesktop.DBus.Error.InvalidArgs: No such interface &#8216;org.bluez.Battery1&#8242;&#8221;<\/em>)).<\/p>\n<h3 id=\"Test_the_battery_status_information\">Test that the battery status information is provided by the device<\/h3>\n<p>First power on and pair the Bluetooth devices. And test that they work in normal operation.<\/p>\n<p><strong><em>A gotcha<\/em><\/strong>: Only connect one Bluetooth headset at a time&#8230; For testing each headset in turn, power the other headsets off.<\/p>\n<p>From the command line, identify the devices (and get their MAC addresses for use in subsequent steps):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nbluetoothctl devices\r\n<\/pre>\n<p>Sample output:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nDevice 20:74:CF:51:62:CA Titanium by AfterShokz\r\nDevice 00:02:3C:9C:29:CF Zen Hybrid (SX:&lt;C@FrxG'fK)\r\nDevice E5:EF:C8:61:40:DF Keychron V6 Max\r\nDevice 6C:93:08:65:8E:E6 Keychron K5 Pro\r\n<\/pre>\n<p>Note that this output also contains information about devices connected in the past, so a device&#8217;s presence in this list does <em>not<\/em> guarantee it will work (the response may be something like <em>&#8220;Device E5:EF:C8:61:40:DF not available&#8221;<\/em>). Also note that the MAC addresses may not be stable.<\/p>\n<p>Test a device, for example &#8220;Keychron V6 Max&#8221;, for the presence of the battery state information:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\necho -e &quot;connect E5:EF:C8:61:40:DF\\ninfo\\n&quot; | bluetoothctl\r\n<\/pre>\n<p>It can also be done interactively:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nbluetoothctl\r\nconnect E5:EF:C8:61:40:DF\r\ninfo\r\n<\/pre>\n<p>Sample output:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nDevice E5:EF:C8:61:40:DF (random)\r\n   \tName: Keychron V6 Max\r\n   \tAlias: Keychron V6 Max\r\n   \tAppearance: 0x03c1\r\n   \tIcon: input-keyboard\r\n   \tPaired: yes\r\n   \tTrusted: yes\r\n   \tBlocked: no\r\n   \tConnected: yes\r\n   \tWakeAllowed: no\r\n   \tLegacyPairing: no\r\n   \tUUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)\r\n   \tUUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)\r\n   \tUUID: Device Information        (0000180a-0000-1000-8000-00805f9b34fb)\r\n   \tUUID: Battery Service           (0000180f-0000-1000-8000-00805f9b34fb)\r\n   \tUUID: Human Interface Device    (00001812-0000-1000-8000-00805f9b34fb)\r\n   \tUUID: Vendor specific           (00010203-0405-0607-0809-0a0b0c0d1912)\r\n   \tModalias: usb:v3434p0961d0113\r\n   \tManufacturerData Key: 0x0006\r\n   \tManufacturerData Value:\r\n     03 00 80 4b 65 79 63 68 72 6f 6e 20 4b 42        ...Keychron KB\r\n   \tAdvertisingFlags:\r\n     05                                               .\r\n   \tBattery Percentage: 0x56 (86)\r\n<\/pre>\n<p>In this case, the output contains a line with &#8220;Battery Percentage&#8221; (the very last line):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\n    Battery Percentage: 0x56 (86)\r\n<\/pre>\n<p>Which is interpreted as 86%.<\/p>\n<p>The output also contains a line with <em>&#8220;UUID: Battery Service  (0000180F-0000-1000-8000-00805F9B34FB)&#8221;<\/em>.<\/p>\n<p>To extract just the battery state information (for MAC address E5:EF:C8:61:40:DF in this example):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\necho -e &quot;connect E5:EF:C8:61:40:DF\\ninfo\\n&quot; | bluetoothctl | grep &quot;Battery Percentage&quot; \r\n<\/pre>\n<p>And with a timestamp (for MAC address E5:EF:C8:61:40:DF in this example):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nclear ; echo -n &quot;Time: $(date +%FT%T_%N_ns)&quot; ; echo -e &quot;connect E5:EF:C8:61:40:DF\\ninfo\\n&quot; | bluetoothctl | grep &quot;Battery Percentage&quot; \r\n<\/pre>\n<h3 id=\"NotForKeychronProSeries\">Ifs and buts<\/h3>\n<p>In the test, it worked on Ubuntu&nbsp;22.04, but not on Ubuntu&nbsp;20.04. A reason could be a different version of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Bluetooth_stack#BlueZ\">BlueZ<\/a> (use &#8220;bluetoothd -v&#8221; or &#8220;bluetoothctl -v&#8221;):<\/p>\n<ul>\n<li>Ubuntu 20.04: 5.53<\/li>\n<li>Ubuntu 22.04: 5.64<\/li>\n<li>Ubuntu 24.04: 5.72<\/li>\n<\/ul>\n<p>On Ubuntu 20.04, &#8220;<strong><em>systemctl status bluetooth<\/em><\/strong>&#8221; on the command line reports this error after the configuration change:<\/p>\n<p><em>&#8220;bluetoothd[1432]: Unknown key Experimental for group General in \/etc\/bluetooth\/main.conf&#8221;<\/em><\/p>\n<p>In the test, it also <strong><em>only worked for a Keychron V Max series keyboard<\/em><\/strong> (<a href=\"https:\/\/www.keychron.com\/products\/keychron-v6-max-qmk-via-wireless-custom-mechanical-keyboard-iso-layout-collection\">V6&nbsp;Max<\/a>), but not for a K&nbsp;Pro series keyboard (<a href=\"https:\/\/www.keychron.com\/products\/keychron-k5-pro-qmk-via-wireless-custom-mechanical-keyboard-iso-layout-collection\">K5&nbsp;Pro&nbsp;ISO<\/a>), which is probably <a href=\"https:\/\/github.com\/Keychron\/qmk_firmware\/issues\/338#issuecomment-2564070299\">not that surprising<\/a> (test with, for example, &#8216;<em>sudo btmgmt con<\/em>&#8216;). Both keyboards&#8217; firmware was the newest (compiled from the newest source code (October&nbsp;2024)).<\/p>\n<h4 id=\"GNOME_is_the_culprit\"><a href=\"https:\/\/en.wikipedia.org\/wiki\/GNOME\">GNOME<\/a> may be the deciding factor<\/h3>\n<p>On a Ubuntu 20.04 system (with the same version of BlueZ, 5.53), but with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Cinnamon_(desktop_environment)\">Cinnamon<\/a> as the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Desktop_environment#Desktop_environments_for_the_X_Window_System\">desktop environment<\/a>, the battery for the V6 Max showed up without any required configuration changes.<\/p>\n<p><a href=\"https:\/\/stackoverflow.com\/questions\/49078659\/check-battery-level-of-connected-bluetooth-device-on-linux\/55008142#55008142\">It could be due<\/a> to <a href=\"https:\/\/en.wikipedia.org\/wiki\/D-Bus\">D-Bus<\/a> used in GNOME:<\/p>\n<p><em>&#8220;&#8230;this specific <a href=\"https:\/\/en.wikipedia.org\/wiki\/List_of_Bluetooth_profiles#Attribute_Profile_(ATT)\">GATT<\/a> characteristic was moved into the D-Bus <strong>org.bluez.Battery1<\/strong> interface.&#8221;<\/em><\/p>\n<p>Or in other words, GNOME broke the battery indication. It seems to have been fixed in Ubuntu&nbsp;24.04 (Noble Numbat); the battery indication worked out of the box, without requiring the configuration change as in Ubuntu&nbsp;22.04 (Jammy Jellyfish).<\/p>\n<p>On <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linux_Mint#LMDE\">LMDE<\/a> with Cinnamon, it worked out of the box. BlueZ version: <\/p>\n<ul>\n<li>LMDE 6 and Cinnamon 6.4.6: 5.66<\/li>\n<\/ul>\n<p>Cinnamon version: <em>cinnamon &#8211;version<\/em> from the command line (two single dashes before <em>version<\/em> (it will not work if pasted directly from here)).<\/p>\n<h2>Test conditions<\/h2>\n<p>Keyboard: Keychron V6 Max<\/p>\n<p>Keyboard firmware: Compiled from close to the latest source code at the time (2024-10)<\/p>\n<p>Keyboard Bluetooth firmware (for the Bluetooth module): <a href=\"https:\/\/www.reddit.com\/r\/Keychron\/comments\/1f47p4b\/comment\/llk3cf4\/\">version<\/a> 0.1.13 (2024-01-08)<\/p>\n<h2 id=\"it_works_in_Ubuntu_22_04\">Results<\/h2>\n<p>The display in Ubuntu 22.04:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/pmortensen.eu\/world2\/wp-content\/uploads\/2024\/11\/Keychron_V6_Max_battery_84_percent_Ubuntu_panel_Settings_Power.png\" alt=\"\"   \/><\/p>\n<p><!--  width=\"816\" height=\"1056\"  class=\"aligncenter size-full wp-image-5429\"  --><br \/>\n<!--  width=\"408\" height=\"528\"  --> <\/p>\n<p>It may or may not be required to restart the computer before it takes effect.<\/p>\n<p>Though the reading is not entirely consistent with the <strong><em>Fn<\/em><\/strong> + <strong><em>B<\/em><\/strong> indicator: At 85%, the indicator still shows 10\/10 (100%). Only at 84% does it change to 9\/10 (90%).<\/p>\n<h2>References<\/h2>\n<ul>\n<li><a href=\"https:\/\/www.bluetooth.org\/docman\/handlers\/downloaddoc.ashx?doc_id=245138\">Bluetooth battery service specification<\/a><\/li>\n<li><a href=\"https:\/\/www.bluetooth.com\/wp-content\/uploads\/Files\/Specification\/HTML\/Assigned_Numbers\/out\/en\/Assigned_Numbers.pdf?v=1733417984269#2.1.3\">An overview of different Bluetooth versions<\/a>. E.g., &#8220;EDR&#8221; for Bluetooth 2 (the battery service is not available)<\/li>\n<li><a href=\"https:\/\/wiki.archlinux.org\/title\/Bluetooth\">Bluetooth<\/a>. Arch Linux documentation. Includes troubleshooting information<\/li>\n<li><a href=\"https:\/\/github.com\/Keychron\/qmk_firmware\/tree\/wireless_playground\/keyboards\/keychron\/v6_max\">V6 Max source code<\/a>. Note: In <a href=\"https:\/\/github.com\/Keychron\/qmk_firmware\/tree\/wireless_playground\/keyboards\/keychron\">Keychron&#8217;s fork<\/a> and in that fork, in <a href=\"https:\/\/en.wikipedia.org\/wiki\/Git\">Git<\/a>&nbsp;branch <strong><em>&#8220;wireless_playground&#8221;<\/em><\/strong> (not the default&nbsp;branch). No matter the Git&nbsp;branch, for example, <strong><em>&#8220;wireless_playground&#8221;<\/em><\/strong>, it requires <a href=\"https:\/\/www.reddit.com\/r\/Keychron\/comments\/1cxnqtu\/comment\/l55cx8r\/\">special setup of QMK<\/a> (the standard QMK instructions and many other guides will <strong><em>not<\/em><\/strong> work (because they implicitly assume the main QMK&nbsp;repository and a particular Git branch)). <a href=\"https:\/\/github.com\/Keychron\/qmk_firmware\/commits\/wireless_playground\/\">Source&nbsp;code&nbsp;commits<\/a> (<a href=\"https:\/\/github.com\/Keychron\/qmk_firmware\/commits\/wireless_playground.atom\">RSS&nbsp;feed<\/a>. Latest:&nbsp;2024-10-15).<\/li>\n<li><a href=\"https:\/\/github.com\/bluez\/bluez\">BlueZ source code<\/a>. Note: The supposed official site for BlueZ, www.bluez.org, appears to be completely dead (empty pages).<\/li>\n<\/ul>\n<p><!-- Referenced in:\n\n    https:\/\/www.reddit.com\/r\/Keychron\/comments\/1ghsh5k\/comment\/lvddiv8\/ \n--><\/p>\n<p><!--     --><\/p>\n<p><!- Marker for editing in WordPress: pppp --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The short version: A wireless QMK-based Keychron keyboard can provide the information about the battery state, e.g. &#8220;81%&#8221;, to the operating system, just like, for example, a wireless computer mouse. Though the operating system may have to be configured for &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"more-link\" href=\"https:\/\/pmortensen.eu\/world2\/2024\/11\/04\/the-battery-state-of-a-keychron-qmk-based-keyboard-can-be-displayed-in-the-operating-system\/\"> <span class=\"screen-reader-text\">The battery state of a wireless Keychron QMK-based keyboard can be displayed in the operating system<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[44,4,23,26,35,16,24],"tags":[],"_links":{"self":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/5404"}],"collection":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/comments?post=5404"}],"version-history":[{"count":131,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/5404\/revisions"}],"predecessor-version":[{"id":6160,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/5404\/revisions\/6160"}],"wp:attachment":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/media?parent=5404"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/categories?post=5404"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/tags?post=5404"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}