Reputation: 73
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:
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()
}
}
}
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:
0x1001
next to that feature, but I am not sure what it could mean, maybe it's some Logitech's identifier for this feature.0x10
, long - 20B - 0x11
)0xFF
is for receiver, yet to find which is meant for device connected via a wire)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
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
hidpp20_batteryvoltage_get_battery_voltage
will give you an idea how to execute and process response of 0x1001Upvotes: 0