Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ A cross-platform tool to control USB gaming headsets on **Linux**, **macOS**, an
| ROCCAT Elo 7.1 USB | All | | | | x | | | | | | | | | | | | |
| Audeze Maxwell | All | x | x | | | x | x | x | | x | | | | | x | | |
| Lenovo Wireless VoIP Headset | All | x | x | | | x | | x | x | x | | | | | x | | |
| Sony INZONE Buds | All | | x | | | | | | | | | | | | | | |
| HeadsetControl Test device | All | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x |

**Platform:** All = Linux, macOS, Windows | L/M = Linux and macOS only
Expand Down
6 changes: 6 additions & 0 deletions lib/device_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
// Lenovo devices
#include "devices/lenovo_wireless_voip.hpp"

// Sony devices
#include "devices/sony_inzone_buds.hpp"

// Test device
#include "devices/headsetcontrol_test.hpp"

Expand Down Expand Up @@ -129,6 +132,9 @@ void DeviceRegistry::initialize()
// Lenovo devices
registerDevice(std::make_unique<LenovoWirelessVoip>());

// Sony devices
registerDevice(std::make_unique<SonyINZONEBuds>());

// Test device
registerDevice(std::make_unique<HeadsetControlTest>());
});
Expand Down
80 changes: 80 additions & 0 deletions lib/devices/sony_inzone_buds.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#pragma once

#include "../result_types.hpp"
#include "hid_device.hpp"
#include <array>
#include <string_view>

using namespace std::string_view_literals;

namespace headsetcontrol {

/**
* @brief Sony INZONE Buds (WF-G700N) wireless gaming earbuds
*
* Communicates via USB dongle (VID 054c PID 0ec2).
* The dongle sends unsolicited HID reports; battery data is in the report
* identified by byte[1]=0x12 and byte[2]=0x04 (report ID byte[0]=0x02).
*
* Battery bytes (0-100, direct percentage):
* byte[14] = right earbud
* byte[16] = left earbud
* byte[18] = charging case
*
* Checksum: byte[19] = (byte[14] + byte[16] + 117) mod 256
*/
class SonyINZONEBuds : public HIDDevice {
public:
static constexpr uint16_t VENDOR_SONY = 0x054c;
static constexpr std::array<uint16_t, 1> PRODUCT_IDS { 0x0ec2 };

static constexpr int REPORT_SIZE = 64;
static constexpr uint8_t BATTERY_TYPE = 0x12;
static constexpr uint8_t BATTERY_SUBTYPE = 0x04;
static constexpr int MAX_READ_ATTEMPTS = 45;

static constexpr int BYTE_RIGHT_EARBUD = 14;
static constexpr int BYTE_LEFT_EARBUD = 16;
static constexpr int BYTE_CASE = 18;

constexpr uint16_t getVendorId() const override { return VENDOR_SONY; }

std::vector<uint16_t> getProductIds() const override
{
return { PRODUCT_IDS.begin(), PRODUCT_IDS.end() };
}

std::string_view getDeviceName() const override { return "Sony INZONE Buds"sv; }

constexpr int getCapabilities() const override { return B(CAP_BATTERY_STATUS); }

Result<BatteryResult> getBattery(hid_device* device_handle) override
{
std::array<uint8_t, REPORT_SIZE> response {};

for (int attempt = 0; attempt < MAX_READ_ATTEMPTS; ++attempt) {
auto rd = readHIDTimeout(device_handle, response, hsc_device_timeout);
if (!rd) {
if (rd.error().code == DeviceError::Code::Timeout)
continue;
return rd.error();
}

if (response[1] == BATTERY_TYPE && response[2] == BATTERY_SUBTYPE) {
uint8_t right = response[BYTE_RIGHT_EARBUD];
uint8_t left = response[BYTE_LEFT_EARBUD];

return BatteryResult {
.level_percent = std::min(left, right),
.status = BATTERY_AVAILABLE,
.raw_data
= std::vector<uint8_t>(response.begin(), response.end()),
};
}
}

return DeviceError::timeout("No battery report received within attempt limit");
}
};

} // namespace headsetcontrol
Loading