Skip to content

Added various features for WH-1000XM4 #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -530,3 +530,7 @@ Client/imgui.ini

# linux build
Client/linux/sonyheadphonesclient.build/

# build folders
build-win/
build-linux/
59 changes: 52 additions & 7 deletions Client/BluetoothWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,25 @@ 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 dtype)
{
int bytesSent;
std::lock_guard guard(this->_connectorMtx);
auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber++);
auto bytesSent = this->_connector->send(data.data(), data.size());
auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber);
bytesSent = this->_connector->send(data.data(), data.size());

this->_waitForAck();
if (dtype != DATA_TYPE::ACK)
this->_waitForAck();

return bytesSent;
}

void BluetoothWrapper::sendAck(unsigned int seqNumber)
{
auto data = CommandSerializer::packageDataForBt({}, DATA_TYPE::ACK, seqNumber ^ 0x01);
this->_connector->send(data.data(), data.size());
}

bool BluetoothWrapper::isConnected() noexcept
{
return this->_connector->isConnected();
Expand All @@ -51,13 +59,29 @@ void BluetoothWrapper::disconnect() noexcept
this->_connector->disconnect();
}


std::vector<BluetoothDevice> BluetoothWrapper::getConnectedDevices()
{
return this->_connector->getConnectedDevices();
}

void BluetoothWrapper::_waitForAck()
{
std::unique_lock<std::mutex> guard(this->_dataMtx);

while (!(this->_ackBuffer > 0)){
this->_ack.wait(guard);
}

this->_ackBuffer--;
}

void BluetoothWrapper::postAck()
{
std::lock_guard guard(this->_dataMtx);
this->_ackBuffer++;
}

Buffer BluetoothWrapper::readReplies()
{
bool ongoingMessage = false;
bool messageFinished = false;
Expand All @@ -66,11 +90,25 @@ void BluetoothWrapper::_waitForAck()

do
{
auto numRecvd = this->_connector->recv(buf, sizeof(buf));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the whole change here is unclear - now two different pieces of logic are parsing the messages. Delete one of them and use the constants for both END and START markers.

int i = 0;
while(!messageFinished)
{
this->_connector->recv(buf+i, 1);
i++;
if (buf[i-1] == 0x3c)
messageFinished = true;
// break;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// break;

}
auto numRecvd = i;
size_t messageStart = 0;
size_t messageEnd = numRecvd;
// for (int i = 0; i < numRecvd; i++)
// {
// std::cout << std::hex << (0xff & (unsigned int)buf[i]) << " ";
// }
// std::cout << std::endl;
Comment on lines +105 to +109
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// for (int i = 0; i < numRecvd; i++)
// {
// std::cout << std::hex << (0xff & (unsigned int)buf[i]) << " ";
// }
// std::cout << std::endl;


for (size_t i = 0; i < numRecvd; i++)
for (int i = 0; i < numRecvd; i++)
{
if (buf[i] == START_MARKER)
{
Expand All @@ -92,7 +130,14 @@ void BluetoothWrapper::_waitForAck()
msgBytes.insert(msgBytes.end(), buf + messageStart, buf + messageEnd);
} while (!messageFinished);

return msgBytes;

auto msg = CommandSerializer::unpackBtMessage(msgBytes);
this->_seqNumber = msg.seqNumber;
Comment on lines +134 to 136
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto msg = CommandSerializer::unpackBtMessage(msgBytes);
this->_seqNumber = msg.seqNumber;

}

void BluetoothWrapper::setSeqNumber(unsigned char seqNumber)
{
std::lock_guard guard(this->_dataMtx);
this->_seqNumber = seqNumber;
}
26 changes: 22 additions & 4 deletions Client/BluetoothWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
#include <vector>
#include <string>
#include <mutex>

#include <future>
#include <iostream>

//Thread-safety: This class is thread-safe.
class BluetoothWrapper
Expand All @@ -21,19 +22,36 @@ class BluetoothWrapper
BluetoothWrapper(BluetoothWrapper&& other) noexcept;
BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept;

int sendCommand(const std::vector<char>& bytes);
int sendCommand(const std::vector<char>& bytes, DATA_TYPE dtype = DATA_TYPE::DATA_MDR);
void sendAck(unsigned int seqNumber);

Buffer readReplies();

bool isConnected() noexcept;
//Try to connect to the headphones
void connect(const std::string& addr);
void disconnect() noexcept;

std::vector<BluetoothDevice> getConnectedDevices();
void setSeqNumber(unsigned char seqNumber);
void postAck();

private:
void _waitForAck();

std::unique_ptr<IBluetoothConnector> _connector;
std::mutex _connectorMtx;
unsigned int _seqNumber = 0;
};
std::mutex _dataMtx;

/*
seqNumber logic:
every command that client sends has the inverse seqNumber of the last ACK packet sent by the headphones
every ACK packet sent by the client has the inverse seqNumber as the response being ACK'd (this is passed as a parameter to sendACK)
both seqNumbers are independent of each other
*/
unsigned char _seqNumber = 0;
unsigned int _ackBuffer = 0;

public:
std::condition_variable _ack;
};
3 changes: 2 additions & 1 deletion Client/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
project(SonyHeadphonesClient)
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(SonyHeadphonesClient)

set(CMAKE_CXX_STANDARD 20)

Expand All @@ -14,6 +14,7 @@ target_sources(SonyHeadphonesClient
ByteMagic.cpp
CommandSerializer.cpp
TimedMessageQueue.cpp
Listener.cpp
CrossPlatformGUI.cpp "Headphones.cpp")

target_include_directories (SonyHeadphonesClient
Expand Down
75 changes: 65 additions & 10 deletions Client/CommandSerializer.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
#include "CommandSerializer.h"

constexpr unsigned char ESCAPED_BYTE_SENTRY = 61;
constexpr unsigned char ESCAPED_60 = 44;
constexpr unsigned char ESCAPED_61 = 45;
constexpr unsigned char ESCAPED_62 = 46;
/*
* Because
* 0x3E represents beginning of packet
* 0x3C represents end of packet
* we need to escape these in the packet payload
*/
constexpr unsigned char ESCAPED_BYTE_SENTRY = 61; // 0x3D
constexpr unsigned char ESCAPED_60 = 44; // 0x2C
constexpr unsigned char ESCAPED_61 = 45; // 0x2D
constexpr unsigned char ESCAPED_62 = 46; // 0x2E


constexpr int MAX_STEPS_WH_1000_XM3 = 19;

namespace CommandSerializer
Expand Down Expand Up @@ -133,23 +141,25 @@ namespace CommandSerializer
return ret;
}

Message unpackBtMessage(const Buffer& src)
BtMessage unpackBtMessage(const Buffer& src)
{
//Message data format: ESCAPE_SPECIALS(<DATA_TYPE><SEQ_NUMBER><BIG ENDIAN 4 BYTE SIZE OF UNESCAPED DATA><DATA><1 BYTE CHECKSUM>)
auto unescaped = _unescapeSpecials(src);

if (src.size() < 7)
if (unescaped.size() < 7)
{
throw std::runtime_error("Invalid message: Smaller than the minimum message size");
}

Message ret;
ret.dataType = static_cast<DATA_TYPE>(src[0]);
ret.seqNumber = src[1];
if ((unsigned char)src[src.size() - 1] != _sumChecksum(src.data(), src.size() - 1))
BtMessage ret;
ret.dataType = static_cast<DATA_TYPE>(unescaped[0]);
ret.seqNumber = unescaped[1];
if ((unsigned char)unescaped[unescaped.size() - 1] != _sumChecksum(unescaped.data(), unescaped.size() - 1))
{
throw RecoverableException("Invalid checksum!", true);
}
unsigned char numMsgBytes = static_cast<unsigned char>(unescaped[5]);
ret.messageBytes.insert(ret.messageBytes.end(), unescaped.begin() + 6, unescaped.begin() + 6 + numMsgBytes);
return ret;
}

Expand All @@ -171,6 +181,16 @@ namespace CommandSerializer
return val;
}

Buffer serializeXM4OptimizeCommand(OPTIMIZER_STATE state)
{
Buffer ret;
ret.push_back(static_cast<unsigned char>(COMMAND_TYPE::XM4_OPTIMIZER_PARAM));
ret.push_back(static_cast<unsigned char>(0x01));
ret.push_back(static_cast<unsigned char>(0x00));
ret.push_back(static_cast<unsigned char>(state));
return ret;
}

Buffer serializeNcAndAsmSetting(NC_ASM_EFFECT ncAsmEffect, NC_ASM_SETTING_TYPE ncAsmSettingType, ASM_SETTING_TYPE asmSettingType, ASM_ID asmId, char asmLevel)
{
Buffer ret;
Expand All @@ -195,5 +215,40 @@ namespace CommandSerializer
return ret;
}

Buffer serializeXM4SpeakToChat(S2C_TOGGLE s2cState)
{
Buffer ret;
ret.push_back(static_cast<unsigned char>(COMMAND_TYPE::XM4_S2C_TOGGLE_PARAM));
ret.push_back(static_cast<unsigned char>(0x05));
ret.push_back(static_cast<unsigned char>(0x01));
ret.push_back(static_cast<unsigned char>(s2cState));
return ret;
}

Buffer serializeXM4_S2C_Options(unsigned char sensitivity, unsigned char voice, unsigned char offTime)
{
Buffer ret;
ret.push_back(static_cast<unsigned char>(COMMAND_TYPE::XM4_S2C_OPTIONS_PARAM));
ret.push_back(static_cast<unsigned char>(0x05));
ret.push_back(static_cast<unsigned char>(0x00));
ret.push_back(static_cast<unsigned char>(sensitivity));
ret.push_back(static_cast<unsigned char>(voice));
ret.push_back(static_cast<unsigned char>(offTime));
return ret;
}

Buffer serializeMultiPointCommand(MULTI_POINT_COMMANDS cmd, std::string macAddr)
{
Buffer ret;
ret.push_back(static_cast<unsigned char>(COMMAND_TYPE::MULTI_POINT_PARAM));
ret.push_back(static_cast<unsigned char>(0x01));
ret.push_back(static_cast<unsigned char>(0x00));
ret.push_back(static_cast<unsigned char>(cmd));
for (unsigned char c: macAddr)
{
ret.push_back(c);
}
return ret;
}
}

23 changes: 14 additions & 9 deletions Client/CommandSerializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@
constexpr int MINIMUM_VOICE_FOCUS_STEP = 2;
constexpr unsigned int ASM_LEVEL_DISABLED = -1;

namespace CommandSerializer
struct BtMessage
{
struct Message
{
DATA_TYPE dataType;
unsigned char seqNumber;
//Not really needed for now
//Buffer messageBytes;
};
DATA_TYPE dataType;
unsigned char seqNumber;
//Not really needed for now
Buffer messageBytes;
};


namespace CommandSerializer
{
//escape special chars

Buffer _escapeSpecials(const Buffer& src);
Expand All @@ -36,10 +37,14 @@ namespace CommandSerializer
*/
Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber);

Message unpackBtMessage(const Buffer& src);
BtMessage unpackBtMessage(const Buffer& src);

NC_DUAL_SINGLE_VALUE getDualSingleForAsmLevel(char asmLevel);
Buffer serializeXM4OptimizeCommand(OPTIMIZER_STATE state);
Buffer serializeNcAndAsmSetting(NC_ASM_EFFECT ncAsmEffect, NC_ASM_SETTING_TYPE ncAsmSettingType, ASM_SETTING_TYPE asmSettingType, ASM_ID asmId, char asmLevel);
Buffer serializeVPTSetting(VPT_INQUIRED_TYPE type, unsigned char preset);
Buffer serializeXM4SpeakToChat(S2C_TOGGLE s2cState);
Buffer serializeXM4_S2C_Options(unsigned char sensitivity, unsigned char voice, unsigned char offTime);
Buffer serializeMultiPointCommand(MULTI_POINT_COMMANDS cmd, std::string macAddr);
}

54 changes: 53 additions & 1 deletion Client/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,21 @@ enum class NC_DUAL_SINGLE_VALUE : signed char
enum class COMMAND_TYPE : signed char
{
VPT_SET_PARAM = 72,
NCASM_SET_PARAM = 104
NCASM_SET_PARAM = 104,
XM4_OPTIMIZER_PARAM = (signed char) 0x84,
XM4_S2C_TOGGLE_PARAM = (signed char) 0xf8,
XM4_S2C_OPTIONS_PARAM = (signed char) 0xfc,
MULTI_POINT_PARAM = (signed char) 0x3c,

XM4_OPTIMIZER_RESPONSE = (signed char) 0x85,
DEVICES_QUERY_RESPONSE = (signed char) 0x37,
DEVICES_STATE_RESPONSE = (signed char) 0x39,
CAPABILITY_QUERY_RESPONSE = (signed char) 0x07,

CAPABILITY_QUERY = (signed char) 0x06,
MULTI_POINT_DEVICES_QUERY = (signed char) 0x36,
S2C_QUERY = (signed char) 0xf6,
S2C_OPTIONS_QUERY = (signed char) 0xfa
};

enum class VPT_PRESET_ID : signed char
Expand Down Expand Up @@ -128,3 +142,41 @@ enum class VPT_INQUIRED_TYPE : signed char
SOUND_POSITION = 2,
OUT_OF_RANGE = -1
};

enum class OPTIMIZER_STATE : signed char
{
IDLE = 0,
OPTIMIZING = 1
};

enum class S2C_TOGGLE : signed char
{
ACTIVE = 1,
INACTIVE = 0
};

enum class MULTI_POINT_COMMANDS : signed char
{
CONNECT = (signed char) 0x01,
DISCONNECT = (signed char) 0x00,
UNPAIR = (signed char) 0x02
};

enum DEVICE_CAPABILITIES
{
NC_ASM = 0x01 << 0,
VPT = 0x01 << 1,
MULTI_POINT = 0x01 << 2,
OPTIMIZER = 0x01 << 3,
SPEAK_TO_CHAT = 0x01 << 4,

};

enum class FUNCTION_TYPE : signed char
{
DEVICE_MANAGEMENT = 56,
VPT = 65,
OPTIMIZER = -127,
NC_ASM = 98,
SMART_TALKING_MODE = -11
};
Loading