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
18 changes: 10 additions & 8 deletions Client/BluetoothWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept
return *this;
}

int BluetoothWrapper::sendCommand(const std::vector<char>& bytes)
int BluetoothWrapper::sendCommand(const std::vector<char>& bytes, DATA_TYPE dataType)
{
std::lock_guard guard(this->_connectorMtx);
auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber++);
auto data = CommandSerializer::packageDataForBt(bytes, dataType, this->_seqNumber++);
auto bytesSent = this->_connector->send(data.data(), data.size());

this->_waitForAck();
Expand Down Expand Up @@ -65,10 +65,11 @@ SonyProtocolVersion BluetoothWrapper::getProtocolVersion() noexcept
return this->_connector->getProtocolVersion();
}

Buffer BluetoothWrapper::sendCommandAndReadResponse(const std::vector<char>& bytes, unsigned char retCommandId, int retSubType)
Buffer BluetoothWrapper::sendCommandAndReadResponse(const std::vector<char>& bytes, unsigned char retCommandId, int retSubType,
DATA_TYPE dataType)
{
std::lock_guard guard(this->_connectorMtx);
auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber++);
auto data = CommandSerializer::packageDataForBt(bytes, dataType, this->_seqNumber++);
this->_connector->send(data.data(), data.size());

// The device replies with an ACK and then the RET frame; unrelated notifications may interleave.
Expand All @@ -80,7 +81,7 @@ Buffer BluetoothWrapper::sendCommandAndReadResponse(const std::vector<char>& byt
this->_seqNumber = msg.seqNumber;
continue;
}
if (msg.dataType == DATA_TYPE::DATA_MDR && !msg.payload.empty()
if (msg.dataType == dataType && !msg.payload.empty()
&& (unsigned char)msg.payload[0] == retCommandId
&& (retSubType < 0 || (msg.payload.size() >= 2 && (unsigned char)msg.payload[1] == retSubType)))
{
Expand Down Expand Up @@ -150,9 +151,10 @@ CommandSerializer::Message BluetoothWrapper::_readMessage()

auto msg = CommandSerializer::unpackBtMessage(msgBytes);

// The device retransmits any DATA_MDR frame it sends until the host ACKs it. The ack carries the
// toggled 1-bit sequence number (1 - deviceSeq), matching the device's own ack-to-us behaviour.
if (msg.dataType == DATA_TYPE::DATA_MDR)
// The device retransmits any DATA_MDR / DATA_MDR_NO2 frame it sends until the host ACKs it. The ack
// carries the toggled 1-bit sequence number (1 - deviceSeq), matching the device's own ack-to-us behaviour.
// Both tables share the one sequence counter.
if (msg.dataType == DATA_TYPE::DATA_MDR || msg.dataType == DATA_TYPE::DATA_MDR_NO2)
{
auto ack = CommandSerializer::packageDataForBt({}, DATA_TYPE::ACK, (unsigned char)(1 - msg.seqNumber));
this->_connector->send(ack.data(), ack.size());
Expand Down
15 changes: 9 additions & 6 deletions Client/BluetoothWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ class BluetoothWrapper
BluetoothWrapper(BluetoothWrapper&& other) noexcept;
BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept;

int sendCommand(const std::vector<char>& bytes);

// Sends an inquiry command and returns the payload of the first DATA_MDR response whose command id
// (payload[0]) matches retCommandId, skipping the ACK and any unrelated notifications. When retSubType
// is >= 0, payload[1] must also match it (for multiplexed command families that share one opcode).
Buffer sendCommandAndReadResponse(const std::vector<char>& bytes, unsigned char retCommandId, int retSubType = -1);
// dataType selects the command table: DATA_MDR (0x0c, table 1) or DATA_MDR_NO2 (0x0e, table 2).
// The two tables are separate command spaces - the same opcode means different things in each.
int sendCommand(const std::vector<char>& bytes, DATA_TYPE dataType = DATA_TYPE::DATA_MDR);

// Sends an inquiry command and returns the payload of the first response of the same dataType whose
// command id (payload[0]) matches retCommandId, skipping the ACK and any unrelated notifications. When
// retSubType is >= 0, payload[1] must also match it (for multiplexed command families that share one opcode).
Buffer sendCommandAndReadResponse(const std::vector<char>& bytes, unsigned char retCommandId, int retSubType = -1,
DATA_TYPE dataType = DATA_TYPE::DATA_MDR);

bool isConnected() noexcept;
//Try to connect to the headphones
Expand Down
49 changes: 49 additions & 0 deletions Client/Constants.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <cstddef>
#include <vector>

inline constexpr auto MAX_BLUETOOTH_MESSAGE_SIZE = 2048;
Expand Down Expand Up @@ -55,6 +56,54 @@ namespace V2Command
inline constexpr unsigned char BTNMODE_SET = 0xf8; // SET f8 <sub> <value...>
inline constexpr unsigned char SUB_ADAPTIVE_VOLUME = 0x0a;
inline constexpr unsigned char SUB_SPEAK_TO_CHAT = 0x0c;
// INIT_REPLY layout: 01 <type> <protocol version, 4 bytes BE> <table1: 00=yes> <table2: 00=yes>
inline constexpr size_t INIT_REPLY_TABLE2_INDEX = 7;
// General settings: up to four device-defined boolean/list settings in slots D1..D4. Which slot holds
// what is per-model, so the slot is found by name via GS_GET_CAPABILITY. Values: 00=ON, 01=OFF.
inline constexpr unsigned char GS_GET_CAPABILITY = 0xd0; // d0 <slot> <display language 01=English>
inline constexpr unsigned char GS_RET_CAPABILITY = 0xd1; // d1 <slot> <type> <fmt> <len>name <len>summary
inline constexpr unsigned char GS_GET_PARAM = 0xd6; // d6 <slot>
inline constexpr unsigned char GS_RET_PARAM = 0xd7; // d7 <slot> <type 00=boolean> <value>
inline constexpr unsigned char GS_SLOT_FIRST = 0xd1;
inline constexpr unsigned char GS_SLOT_LAST = 0xd4;
inline constexpr unsigned char GS_DISPLAY_LANGUAGE_ENGLISH = 0x01;
}

// Second command table (frame DATA_TYPE::DATA_MDR_NO2 = 0x0e) on v2 devices: multipoint / paired-device
// management. Layouts from mos9527/SonyHeadphonesClient (ProtocolV2T2.hpp, generated from Sound Connect)
// and WF-1000XM5 / WH-1000XM6 captures.
// NEVER send these with DATA_MDR (0x0c): in table 1, 0x30-0x3f are the firmware-update (UPDT) commands.
namespace V2Table2Command
{
inline constexpr unsigned char SUPPORT_FUNCTION_GET = 0x06; // 06 00
inline constexpr unsigned char SUPPORT_FUNCTION_RET = 0x07; // 07 00 <n> (<function> <priority>) x n
inline constexpr unsigned char PERI_GET_PARAM = 0x36; // 36 <inquired type>
inline constexpr unsigned char PERI_RET_PARAM = 0x37;
inline constexpr unsigned char PERI_SET_PARAM = 0x38; // 38 01 <00=fix playback, 01=free>
inline constexpr unsigned char PERI_NTFY_PARAM = 0x39; // 39 01 <value> <result>
inline constexpr unsigned char PERI_SET_EXTENDED_PARAM = 0x3c; // 3c 01 <target address, 17 ASCII>
inline constexpr unsigned char PERI_NTFY_EXTENDED_PARAM = 0x3d; // 3d 01 <result> <target address>

// Source-switch results (switch / fix writes). NO_CONFIRMATION is ours: no notify within the timeout.
inline constexpr int RESULT_SUCCESS = 0x00;
inline constexpr int RESULT_FAIL = 0x01;
inline constexpr int RESULT_FAIL_CALLING = 0x02;
inline constexpr int RESULT_FAIL_A2DP_NOT_CONNECTED = 0x03;
inline constexpr int RESULT_FAIL_VOICE_ASSISTANT = 0x04;
inline constexpr int RESULT_NO_CONFIRMATION = -1;

// Inquired types (payload[1] of the PERI_* commands).
inline constexpr unsigned char PERI_DEVICES_CLASSIC = 0x00; // device list without class of device
inline constexpr unsigned char PERI_SOURCE_SWITCH = 0x01; // 37 01 <1=switching free, 0=playback fixed>
inline constexpr unsigned char PERI_DEVICES_WITH_COD = 0x02; // device list with class of device

// Function ids in the SUPPORT_FUNCTION_RET list.
inline constexpr unsigned char FN_DEVICES_CLASSIC = 0x30;
inline constexpr unsigned char FN_SOURCE_SWITCH = 0x31;
inline constexpr unsigned char FN_DEVICES_WITH_COD = 0x32;
inline constexpr unsigned char FN_DEVICES_WITH_COD_LE = 0x33;

inline constexpr size_t BT_ADDRESS_LENGTH = 17; // ASCII "XX:XX:XX:XX:XX:XX", no terminator
}

// v2 equalizer preset ids (byte value sent/received at EQ payload[2]).
Expand Down
Loading