Oschły
Oschły

Reputation: 73

Get the battery voltage of Logitech Lightspeed device with IOKit?

I'd like to write a simple tool in Swift to read battery status of my mouse (Logitech G Pro Wireless). Since it's my first dive into both IOKit and managing HID devices, I am struggling with getting it done.

Here's my current approach:

  1. I used this library to skip for now messing with Obj-C https://github.com/Arti3DPlayer/USBDeviceSwift
  2. On launch of the app I create HID Device with hardcoded ids and start listening for its presence:
struct MyApp: App {
   let rfDeviceMonitor = HIDDeviceMonitor([
       HIDMonitorData(vendorId: 0x046d, productId: 0xC539)//0xc088)
       ], reportSize: 64)

   var body: some Scene {
       Window()
           .onAppear {
                  let rfDeviceDaemon = Thread(target: self.rfDeviceMonitor, selector:#selector(self.rfDeviceMonitor.start), object: nil)
                  rfDeviceDaemon.start()
           }
   }

}
  1. Another class is listening for connection and device's data.
    func configure() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.usbConnected), name: .HIDDeviceConnected, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.hidReadData), name: .HIDDeviceDataReceived, object: nil)
    }
    
    @objc func usbConnected(notification: NSNotification) {
        guard let nobj = notification.object as? NSDictionary else {
            return
        }
        
        guard let deviceInfo: HIDDevice = nobj["device"] as? HIDDevice else {
            return
        }
        
        self.deviceInfo = deviceInfo
        write()
    }
    
    @objc func hidReadData(notification: Notification) {
        let obj = notification.object as! NSDictionary
        let data = obj["data"] as! Data
        print([UInt8](data))
    }
    
    func write() {
        let payload: [UInt8] = [
            0x10, // short message
            0xff, // receiver's index
            0x06, // feature index - battery voltage for g pro wireless
            0x00,
            0x00,
            0x00
        ]
        
        var correctData = Data(payload)
        var count = correctData.count
        
        let ret: Int32 = IOHIDDeviceGetReport(
            self.deviceInfo.device,
            kIOHIDReportTypeFeature,
            CFIndex(0x10),
            &correctData,
            &count
        )
        
        print(ret) // 0xe0005000
        print([UInt8](correctData)) // [16, 255, 6, 0, 0, 0]
    }

The issue is that IOKit always returns a value (0xe0005000) after calling IOHIDDeviceGetReport that is not a success. I have no idea what this means, since kIOReturn header doesn't mention this value at all.

Links that I found useful:

The most important informations I got from these sites:

There's a line in documentation that describes bytes meaning, says: "The device index, feature index, function identifier, and software identifier are always returned unchanged.", so I suppose I should use getReport instead of setReport, but again, my knowledge is very limited here, so I tried setReport as well, but with no luck. Same error has been thrown.

Upvotes: 3

Views: 335

Answers (1)

Egor
Egor

Reputation: 69

Important You will need to make sure you are "speaking" to the proper HID device, receiver usually provides several. I presume your usbConnected() functions needs to check HIDDevice has reportSize == 20 and only do IOHIDDeviceRegisterInputReportCallback(...) for that single device.

Then you can use this code to send your reports while using USBDeviceSwift lib:

func write(_ data: Data) {
    let reportID:UInt8 = 16 // important, try 1 6 7 8 17 if still getting 0xe0005000
    var reportLength = 20 // important, replace with 7 if your mouse is old hid++ 1.0 device
    var report = [UInt8](repeating: 0, count: reportLength)
    data.copyBytes(to: &report, count: data.count)
    let result = IOHIDDeviceSetReport(self.deviceInfo.device, kIOHIDReportTypeOutput, CFIndex(reportID), report, reportLength)
    print(result)
}

Then you need to find actual address for your function 0x1001 by calling

write(Data([0x10, 0x01, 0x00, 0x00, 0x10, 0x01]))

(Second byte for single device connected to receiver is 0x01 as in "1st device". Gets more difficult if there's several devices connected.)

Response should be something like

11 01 00 00 06 00

response[4] is your address you will use as request[2] in your next request, e.g. if response[4] of response is 0x06:

write(Data([0x10, 0x01, 0x06, 0x00]))

response[4] and response[5] should be your big-endian 16bit int voltage.

You can refer to this: https://github.com/libratbag/libratbag/blob/8444ceb638b19c3fbeb073a5cd29f17c6d34dd07/src/hidpp20.c#L427

  • function hidpp20_batteryvoltage_get_battery_voltage will give you an idea how to execute and process response of 0x1001

Upvotes: 0

Related Questions