16

I am using Xcode to develop a MacOS app, based on Cocoa & AppKit, written in Swift.

I am using IOBluetoothDevice objects throughout my app, and I want to be able to display the devices' battery levels, if accessible.

I expect that devices which battery levels are visible on the OS's Bluetooth settings (see image below), to be also accessible programmatically (e.g., AirPods, Magic Keyboard, etc.). However, I could not find this anywhere.

I have also thought about executing a terminal command and found this thread, but it did also not work.

Thanks

Bluetooth device battery level available to the OS

Ori
  • 1,680
  • 21
  • 24
  • don't see anyone else looking for this, but that what I found as private api https://github.com/w0lfschild/macOS_headers/blob/86427703608aa25521e57d8082f00a2db39af39b/macOS/Frameworks/IOBluetooth/6.0.2f2/IOBluetoothDevice.h#L306 – Oleg Sep 25 '19 at 11:20

1 Answers1

11

You can get the battery level of Bluetooth devices from the IORegistry with IOKit.

This is a simple example to get the battery level for the Magic Trackpad 2

import IOKit

var serialPortIterator = io_iterator_t()
var object : io_object_t
let port: mach_port_t
if #available(macOS 12.0, *) {
    port = kIOMainPortDefault // New name in macOS 12 and higher
} else {
    port = kIOMasterPortDefault // Old name in macOS 11 and lower
}
let matchingDict : CFDictionary = IOServiceMatching("AppleDeviceManagementHIDEventService")
let kernResult = IOServiceGetMatchingServices(port, matchingDict, &serialPortIterator)

if KERN_SUCCESS == kernResult {
    repeat {
        object = IOIteratorNext(serialPortIterator)
        if object != 0, let percent = IORegistryEntryCreateCFProperty(object, "BatteryPercent" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Int {
            print(percent)
            break
        }
    } while object != 0
    IOObjectRelease(object)
}    
IOObjectRelease(serialPortIterator)

For other devices you have to replace AppleDeviceManagementHIDEventService and Trackpad2 with the appropriate values. You can display the entire IORegistry in Terminal.app with ioreg -l.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thanks! Is the list of all possible device names is listed somewhere? Or how to tell if I have for example two gamepads of the same model connected (say they support battery reporting) that BatteryPercent relates to this particular gamepad? – meneroush Jan 14 '20 at 14:31
  • 1
    No, it’s a lot of investigation and trial&error. For example in Terminal.app filter the lines containing *BatteryPercent* with `grep` or `awk` and find the tree node and the class of the enclosing object. And to distinguish the devices there are other properties like vendor or maybe a serial number. – vadian Jan 14 '20 at 14:41
  • I'm surprised that there's no better API tho, so you can subscribe to the batteryPercentChanged or something. I doubt that Bluetooth menu bar widget just checks BatteryPercent inside of the for loop every 5 seconds. – meneroush Jan 14 '20 at 17:28
  • I'm sure there is an API to observe properties in IOReg. Pretty low-level C-callbacks – vadian Jan 14 '20 at 17:37
  • @meneroush I mean, the menu probably just queries the value when the submenu pops open. Why would it need to observe the value? – TheNextman Jan 15 '20 at 03:56
  • 2022: I get an error `Unexpectedly found nil while implicitly unwrapping an Optional value` where `let percent` is defined. – ixany Jul 07 '22 at 13:18
  • @ixany This could be fix by adding a `?` after `IORegistryEntryCreateCFProperty(_:_:_:)` to unwrap optional safely. – ix4n33 Jul 12 '22 at 14:16
  • `let percent…` is optional bound, actually it cannot crash. – vadian Jul 12 '22 at 16:38
  • What if I want to get battery level on iOS devices? Can I still using the same code? – Tony Chen Oct 03 '22 at 14:22
  • @TonyChen Certainly not, the question is about Bluetooth devices. – vadian Oct 03 '22 at 14:27
  • I mean getting the battery level of classic bluetooth accessories (not BLE) on an iOS app. Is that possible? – Tony Chen Oct 03 '22 at 14:38
  • I don't know. Try it. – vadian Oct 03 '22 at 14:39